rafcode 1.0.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 (370) hide show
  1. package/.claude/settings.local.json +32 -0
  2. package/CLAUDE.md +187 -0
  3. package/LICENSE +21 -0
  4. package/RAF/001-raf-task-improvements/input.md +9 -0
  5. package/RAF/001-raf-task-improvements/outcomes/001-add-decisions-folder.md +21 -0
  6. package/RAF/001-raf-task-improvements/outcomes/002-fix-write-error-on-shutdown.md +22 -0
  7. package/RAF/001-raf-task-improvements/outcomes/003-stash-changes-on-failure.md +34 -0
  8. package/RAF/001-raf-task-improvements/outcomes/004-add-project-name-to-commits.md +28 -0
  9. package/RAF/001-raf-task-improvements/outcomes/005-add-running-time-display.md +36 -0
  10. package/RAF/001-raf-task-improvements/outcomes/006-add-task-name-to-logs.md +22 -0
  11. package/RAF/001-raf-task-improvements/outcomes/007-show-model-at-task-start.md +52 -0
  12. package/RAF/001-raf-task-improvements/outcomes/009-remove-editor-placeholder-text.md +20 -0
  13. package/RAF/001-raf-task-improvements/outcomes/SUMMARY.md +83 -0
  14. package/RAF/001-raf-task-improvements/plans/001-add-decisions-folder.md +38 -0
  15. package/RAF/001-raf-task-improvements/plans/002-fix-write-error-on-shutdown.md +33 -0
  16. package/RAF/001-raf-task-improvements/plans/003-stash-changes-on-failure.md +37 -0
  17. package/RAF/001-raf-task-improvements/plans/004-add-project-name-to-commits.md +34 -0
  18. package/RAF/001-raf-task-improvements/plans/005-add-running-time-display.md +39 -0
  19. package/RAF/001-raf-task-improvements/plans/006-add-task-name-to-logs.md +37 -0
  20. package/RAF/001-raf-task-improvements/plans/009-remove-editor-placeholder-text.md +34 -0
  21. package/RAF/002-raf-task-improvements-execution/decisions/DECISIONS.md +13 -0
  22. package/RAF/002-raf-task-improvements-execution/input.md +3 -0
  23. package/RAF/002-raf-task-improvements-execution/outcomes/001-commit-show-model-at-task-start.md +17 -0
  24. package/RAF/002-raf-task-improvements-execution/outcomes/002-delete-skipped-plan.md +23 -0
  25. package/RAF/002-raf-task-improvements-execution/outcomes/SUMMARY.md +32 -0
  26. package/RAF/002-raf-task-improvements-execution/plans/001-commit-show-model-at-task-start.md +37 -0
  27. package/RAF/002-raf-task-improvements-execution/plans/002-delete-skipped-plan.md +23 -0
  28. package/RAF/003-multi-project-execution/decisions/DECISIONS.md +68 -0
  29. package/RAF/003-multi-project-execution/input.md +6 -0
  30. package/RAF/003-multi-project-execution/outcomes/001-remove-state-json.md +52 -0
  31. package/RAF/003-multi-project-execution/outcomes/002-update-raf-status.md +50 -0
  32. package/RAF/003-multi-project-execution/outcomes/003-simplify-git-logic.md +35 -0
  33. package/RAF/003-multi-project-execution/outcomes/004-auto-commit-planning.md +43 -0
  34. package/RAF/003-multi-project-execution/outcomes/005-rerun-failed-tasks.md +43 -0
  35. package/RAF/003-multi-project-execution/outcomes/006-multi-project-execution.md +42 -0
  36. package/RAF/003-multi-project-execution/outcomes/007-verify-timeout.md +54 -0
  37. package/RAF/003-multi-project-execution/outcomes/008-move-decisions-file.md +38 -0
  38. package/RAF/003-multi-project-execution/outcomes/SUMMARY.md +79 -0
  39. package/RAF/003-multi-project-execution/plans/001-remove-state-json.md +71 -0
  40. package/RAF/003-multi-project-execution/plans/002-update-raf-status.md +65 -0
  41. package/RAF/003-multi-project-execution/plans/003-simplify-git-logic.md +74 -0
  42. package/RAF/003-multi-project-execution/plans/004-auto-commit-planning.md +57 -0
  43. package/RAF/003-multi-project-execution/plans/005-rerun-failed-tasks.md +69 -0
  44. package/RAF/003-multi-project-execution/plans/006-multi-project-execution.md +81 -0
  45. package/RAF/003-multi-project-execution/plans/007-verify-timeout.md +63 -0
  46. package/RAF/003-multi-project-execution/plans/008-move-decisions-file.md +78 -0
  47. package/RAF/004-task-naming-optimization/decisions.md +22 -0
  48. package/RAF/004-task-naming-optimization/input.md +6 -0
  49. package/RAF/004-task-naming-optimization/outcomes/001-remove-summary-file.md +17 -0
  50. package/RAF/004-task-naming-optimization/outcomes/002-base36-project-numbering.md +32 -0
  51. package/RAF/004-task-naming-optimization/outcomes/003-improve-haiku-prompt.md +20 -0
  52. package/RAF/004-task-naming-optimization/outcomes/SUMMARY.md +28 -0
  53. package/RAF/004-task-naming-optimization/plans/001-remove-summary-file.md +34 -0
  54. package/RAF/004-task-naming-optimization/plans/002-base36-project-numbering.md +56 -0
  55. package/RAF/004-task-naming-optimization/plans/003-improve-haiku-prompt.md +50 -0
  56. package/RAF/005-task-naming-improvements/decisions.md +60 -0
  57. package/RAF/005-task-naming-improvements/input.md +2 -0
  58. package/RAF/005-task-naming-improvements/outcomes/001-enhance-identifier-resolution.md +42 -0
  59. package/RAF/005-task-naming-improvements/outcomes/002-add-identifier-support-to-status.md +38 -0
  60. package/RAF/005-task-naming-improvements/outcomes/003-update-do-for-full-folder-names.md +44 -0
  61. package/RAF/005-task-naming-improvements/outcomes/004-implement-amend-flag-for-plan.md +55 -0
  62. package/RAF/005-task-naming-improvements/outcomes/005-commit-outcomes-on-complete.md +47 -0
  63. package/RAF/005-task-naming-improvements/outcomes/006-update-execution-prompt-commit-schema.md +40 -0
  64. package/RAF/005-task-naming-improvements/outcomes/007-allow-pending-task-amendments.md +38 -0
  65. package/RAF/005-task-naming-improvements/outcomes/008-fix-timeout-label.md +24 -0
  66. package/RAF/005-task-naming-improvements/plans/001-enhance-identifier-resolution.md +46 -0
  67. package/RAF/005-task-naming-improvements/plans/002-add-identifier-support-to-status.md +36 -0
  68. package/RAF/005-task-naming-improvements/plans/003-update-do-for-full-folder-names.md +38 -0
  69. package/RAF/005-task-naming-improvements/plans/004-implement-amend-flag-for-plan.md +67 -0
  70. package/RAF/005-task-naming-improvements/plans/005-commit-outcomes-on-complete.md +86 -0
  71. package/RAF/005-task-naming-improvements/plans/006-update-execution-prompt-commit-schema.md +60 -0
  72. package/RAF/005-task-naming-improvements/plans/007-allow-pending-task-amendments.md +60 -0
  73. package/RAF/005-task-naming-improvements/plans/008-fix-timeout-label.md +31 -0
  74. package/RAF/006-fix-double-summary-headers/decisions.md +28 -0
  75. package/RAF/006-fix-double-summary-headers/input.md +3 -0
  76. package/RAF/006-fix-double-summary-headers/outcomes/001-fix-double-summary-headers.md +29 -0
  77. package/RAF/006-fix-double-summary-headers/outcomes/002-update-readme-for-npm.md +31 -0
  78. package/RAF/006-fix-double-summary-headers/outcomes/003-npm-publish-instructions.md +30 -0
  79. package/RAF/006-fix-double-summary-headers/outcomes/004-flexible-project-lookup.md +47 -0
  80. package/RAF/006-fix-double-summary-headers/plans/001-fix-double-summary-headers.md +42 -0
  81. package/RAF/006-fix-double-summary-headers/plans/002-update-readme-for-npm.md +44 -0
  82. package/RAF/006-fix-double-summary-headers/plans/003-npm-publish-instructions.md +45 -0
  83. package/RAF/006-fix-double-summary-headers/plans/004-flexible-project-lookup.md +40 -0
  84. package/RAF/007-improve-outcome-format/decisions.md +28 -0
  85. package/RAF/007-improve-outcome-format/input.md +2 -0
  86. package/RAF/007-improve-outcome-format/outcomes/001-update-execution-prompt.md +10 -0
  87. package/RAF/007-improve-outcome-format/outcomes/002-update-state-derivation.md +17 -0
  88. package/RAF/007-improve-outcome-format/outcomes/003-update-do-command-outcome-handling.md +16 -0
  89. package/RAF/007-improve-outcome-format/outcomes/004-implement-failure-analysis.md +16 -0
  90. package/RAF/007-improve-outcome-format/outcomes/005-update-documentation.md +15 -0
  91. package/RAF/007-improve-outcome-format/plans/001-update-execution-prompt.md +36 -0
  92. package/RAF/007-improve-outcome-format/plans/002-update-state-derivation.md +35 -0
  93. package/RAF/007-improve-outcome-format/plans/003-update-do-command-outcome-handling.md +37 -0
  94. package/RAF/007-improve-outcome-format/plans/004-implement-failure-analysis.md +44 -0
  95. package/RAF/007-improve-outcome-format/plans/005-update-documentation.md +33 -0
  96. package/RAF/008-beautiful-do/decisions.md +31 -0
  97. package/RAF/008-beautiful-do/input.md +1 -0
  98. package/RAF/008-beautiful-do/outcomes/001-terminal-symbols.md +55 -0
  99. package/RAF/008-beautiful-do/outcomes/002-refactor-do-output.md +95 -0
  100. package/RAF/008-beautiful-do/outcomes/003-refactor-status-output.md +71 -0
  101. package/RAF/008-beautiful-do/outcomes/004-simplify-logger.md +53 -0
  102. package/RAF/008-beautiful-do/outcomes/005-add-tests.md +41 -0
  103. package/RAF/008-beautiful-do/plans/001-terminal-symbols.md +41 -0
  104. package/RAF/008-beautiful-do/plans/002-refactor-do-output.md +44 -0
  105. package/RAF/008-beautiful-do/plans/003-refactor-status-output.md +37 -0
  106. package/RAF/008-beautiful-do/plans/004-simplify-logger.md +32 -0
  107. package/RAF/008-beautiful-do/plans/005-add-tests.md +40 -0
  108. package/RAF/009-system-promt-ammend/decisions.md +13 -0
  109. package/RAF/009-system-promt-ammend/input.md +9 -0
  110. package/RAF/009-system-promt-ammend/outcomes/001-model-override.md +79 -0
  111. package/RAF/009-system-promt-ammend/outcomes/002-system-prompt-append.md +51 -0
  112. package/RAF/009-system-promt-ammend/outcomes/003-retry-context.md +60 -0
  113. package/RAF/009-system-promt-ammend/plans/001-model-override.md +61 -0
  114. package/RAF/009-system-promt-ammend/plans/002-system-prompt-append.md +56 -0
  115. package/RAF/009-system-promt-ammend/plans/003-retry-context.md +76 -0
  116. package/RAF/010-outcome-marker-fallback/decisions.md +19 -0
  117. package/RAF/010-outcome-marker-fallback/input.md +1 -0
  118. package/RAF/010-outcome-marker-fallback/outcomes/001-outcome-file-marker-fallback.md +35 -0
  119. package/RAF/010-outcome-marker-fallback/outcomes/002-creative-project-naming.md +47 -0
  120. package/RAF/010-outcome-marker-fallback/plans/001-outcome-file-marker-fallback.md +58 -0
  121. package/RAF/010-outcome-marker-fallback/plans/002-creative-project-naming.md +68 -0
  122. package/RAF/011-do-task-in-commit/decisions.md +22 -0
  123. package/RAF/011-do-task-in-commit/input.md +1 -0
  124. package/RAF/011-do-task-in-commit/outcomes/001-update-execution-prompt.md +54 -0
  125. package/RAF/011-do-task-in-commit/outcomes/002-update-tests.md +61 -0
  126. package/RAF/011-do-task-in-commit/outcomes/003-update-documentation.md +51 -0
  127. package/RAF/011-do-task-in-commit/plans/001-update-execution-prompt.md +46 -0
  128. package/RAF/011-do-task-in-commit/plans/002-update-tests.md +51 -0
  129. package/RAF/011-do-task-in-commit/plans/003-update-documentation.md +45 -0
  130. package/RAF/012-name-picker-buffet/decisions.md +40 -0
  131. package/RAF/012-name-picker-buffet/input.md +6 -0
  132. package/RAF/012-name-picker-buffet/outcomes/001-name-picker-for-raf-plan.md +49 -0
  133. package/RAF/012-name-picker-buffet/outcomes/002-interactive-project-picker-for-raf-do.md +49 -0
  134. package/RAF/012-name-picker-buffet/outcomes/003-raf-status-truncation.md +55 -0
  135. package/RAF/012-name-picker-buffet/outcomes/004-failure-reason-details.md +65 -0
  136. package/RAF/012-name-picker-buffet/outcomes/005-remove-raf-commits.md +57 -0
  137. package/RAF/012-name-picker-buffet/outcomes/006-update-execution-prompt-for-commits.md +47 -0
  138. package/RAF/012-name-picker-buffet/outcomes/007-fix-plan-mode-user-prompt.md +83 -0
  139. package/RAF/012-name-picker-buffet/outcomes/008-add-auto-flag-for-plan-mode.md +77 -0
  140. package/RAF/012-name-picker-buffet/plans/001-name-picker-for-raf-plan.md +47 -0
  141. package/RAF/012-name-picker-buffet/plans/002-interactive-project-picker-for-raf-do.md +43 -0
  142. package/RAF/012-name-picker-buffet/plans/003-raf-status-truncation.md +36 -0
  143. package/RAF/012-name-picker-buffet/plans/004-failure-reason-details.md +46 -0
  144. package/RAF/012-name-picker-buffet/plans/005-remove-raf-commits.md +42 -0
  145. package/RAF/012-name-picker-buffet/plans/006-update-execution-prompt-for-commits.md +47 -0
  146. package/RAF/012-name-picker-buffet/plans/007-fix-plan-mode-user-prompt.md +55 -0
  147. package/RAF/012-name-picker-buffet/plans/008-add-auto-flag-for-plan-mode.md +49 -0
  148. package/RAF/013-dependencies-watchdog/decisions.md +37 -0
  149. package/RAF/013-dependencies-watchdog/input.md +1 -0
  150. package/RAF/013-dependencies-watchdog/outcomes/001-define-dependency-syntax.md +56 -0
  151. package/RAF/013-dependencies-watchdog/outcomes/002-update-planning-prompts.md +60 -0
  152. package/RAF/013-dependencies-watchdog/outcomes/003-parse-dependencies-update-state.md +81 -0
  153. package/RAF/013-dependencies-watchdog/outcomes/004-implement-dependency-checking-in-do.md +116 -0
  154. package/RAF/013-dependencies-watchdog/outcomes/005-update-execution-prompts.md +75 -0
  155. package/RAF/013-dependencies-watchdog/outcomes/006-add-tests.md +100 -0
  156. package/RAF/013-dependencies-watchdog/outcomes/007-add-act-alias.md +46 -0
  157. package/RAF/013-dependencies-watchdog/outcomes/008-add-exit-message.md +52 -0
  158. package/RAF/013-dependencies-watchdog/plans/001-define-dependency-syntax.md +32 -0
  159. package/RAF/013-dependencies-watchdog/plans/002-update-planning-prompts.md +38 -0
  160. package/RAF/013-dependencies-watchdog/plans/003-parse-dependencies-update-state.md +46 -0
  161. package/RAF/013-dependencies-watchdog/plans/004-implement-dependency-checking-in-do.md +48 -0
  162. package/RAF/013-dependencies-watchdog/plans/005-update-execution-prompts.md +44 -0
  163. package/RAF/013-dependencies-watchdog/plans/006-add-tests.md +54 -0
  164. package/RAF/013-dependencies-watchdog/plans/007-add-act-alias.md +26 -0
  165. package/RAF/013-dependencies-watchdog/plans/008-add-exit-message.md +31 -0
  166. package/RAF/014-watchdog/decisions.md +16 -0
  167. package/RAF/014-watchdog/input.md +2 -0
  168. package/RAF/014-watchdog/outcomes/001-amend-flag-position.md +50 -0
  169. package/RAF/014-watchdog/outcomes/002-details-only-on-failure.md +58 -0
  170. package/RAF/014-watchdog/plans/001-amend-flag-position.md +34 -0
  171. package/RAF/014-watchdog/plans/002-details-only-on-failure.md +46 -0
  172. package/RAF/015-name-lottery/decisions.md +14 -0
  173. package/RAF/015-name-lottery/input.md +3 -0
  174. package/RAF/015-name-lottery/outcomes/001-auto-pick-project-name.md +31 -0
  175. package/RAF/015-name-lottery/outcomes/002-mention-plan-files-in-commit.md +23 -0
  176. package/RAF/015-name-lottery/outcomes/003-fix-input-md-in-amend-flow.md +44 -0
  177. package/RAF/015-name-lottery/plans/001-auto-pick-project-name.md +38 -0
  178. package/RAF/015-name-lottery/plans/002-mention-plan-files-in-commit.md +32 -0
  179. package/RAF/015-name-lottery/plans/003-fix-input-md-in-amend-flow.md +44 -0
  180. package/README.md +116 -0
  181. package/dist/commands/do.d.ts +12 -0
  182. package/dist/commands/do.d.ts.map +1 -0
  183. package/dist/commands/do.js +684 -0
  184. package/dist/commands/do.js.map +1 -0
  185. package/dist/commands/plan.d.ts +3 -0
  186. package/dist/commands/plan.d.ts.map +1 -0
  187. package/dist/commands/plan.js +345 -0
  188. package/dist/commands/plan.js.map +1 -0
  189. package/dist/commands/status.d.ts +3 -0
  190. package/dist/commands/status.d.ts.map +1 -0
  191. package/dist/commands/status.js +117 -0
  192. package/dist/commands/status.js.map +1 -0
  193. package/dist/core/claude-runner.d.ts +78 -0
  194. package/dist/core/claude-runner.d.ts.map +1 -0
  195. package/dist/core/claude-runner.js +297 -0
  196. package/dist/core/claude-runner.js.map +1 -0
  197. package/dist/core/editor.d.ts +10 -0
  198. package/dist/core/editor.d.ts.map +1 -0
  199. package/dist/core/editor.js +77 -0
  200. package/dist/core/editor.js.map +1 -0
  201. package/dist/core/failure-analyzer.d.ts +28 -0
  202. package/dist/core/failure-analyzer.d.ts.map +1 -0
  203. package/dist/core/failure-analyzer.js +305 -0
  204. package/dist/core/failure-analyzer.js.map +1 -0
  205. package/dist/core/git.d.ts +42 -0
  206. package/dist/core/git.d.ts.map +1 -0
  207. package/dist/core/git.js +148 -0
  208. package/dist/core/git.js.map +1 -0
  209. package/dist/core/project-manager.d.ts +72 -0
  210. package/dist/core/project-manager.d.ts.map +1 -0
  211. package/dist/core/project-manager.js +193 -0
  212. package/dist/core/project-manager.js.map +1 -0
  213. package/dist/core/retry-handler.d.ts +19 -0
  214. package/dist/core/retry-handler.d.ts.map +1 -0
  215. package/dist/core/retry-handler.js +51 -0
  216. package/dist/core/retry-handler.js.map +1 -0
  217. package/dist/core/shutdown-handler.d.ts +30 -0
  218. package/dist/core/shutdown-handler.d.ts.map +1 -0
  219. package/dist/core/shutdown-handler.js +79 -0
  220. package/dist/core/shutdown-handler.js.map +1 -0
  221. package/dist/core/state-derivation.d.ts +82 -0
  222. package/dist/core/state-derivation.d.ts.map +1 -0
  223. package/dist/core/state-derivation.js +271 -0
  224. package/dist/core/state-derivation.js.map +1 -0
  225. package/dist/core/state-manager.d.ts +54 -0
  226. package/dist/core/state-manager.d.ts.map +1 -0
  227. package/dist/core/state-manager.js +198 -0
  228. package/dist/core/state-manager.js.map +1 -0
  229. package/dist/index.d.ts +3 -0
  230. package/dist/index.d.ts.map +1 -0
  231. package/dist/index.js +16 -0
  232. package/dist/index.js.map +1 -0
  233. package/dist/parsers/output-parser.d.ts +19 -0
  234. package/dist/parsers/output-parser.d.ts.map +1 -0
  235. package/dist/parsers/output-parser.js +137 -0
  236. package/dist/parsers/output-parser.js.map +1 -0
  237. package/dist/prompts/amend.d.ts +20 -0
  238. package/dist/prompts/amend.d.ts.map +1 -0
  239. package/dist/prompts/amend.js +166 -0
  240. package/dist/prompts/amend.js.map +1 -0
  241. package/dist/prompts/execution.d.ts +30 -0
  242. package/dist/prompts/execution.d.ts.map +1 -0
  243. package/dist/prompts/execution.js +179 -0
  244. package/dist/prompts/execution.js.map +1 -0
  245. package/dist/prompts/planning.d.ts +15 -0
  246. package/dist/prompts/planning.d.ts.map +1 -0
  247. package/dist/prompts/planning.js +163 -0
  248. package/dist/prompts/planning.js.map +1 -0
  249. package/dist/types/config.d.ts +26 -0
  250. package/dist/types/config.d.ts.map +1 -0
  251. package/dist/types/config.js +7 -0
  252. package/dist/types/config.js.map +1 -0
  253. package/dist/types/state.d.ts +33 -0
  254. package/dist/types/state.d.ts.map +1 -0
  255. package/dist/types/state.js +28 -0
  256. package/dist/types/state.js.map +1 -0
  257. package/dist/ui/name-picker-subprocess.d.ts +11 -0
  258. package/dist/ui/name-picker-subprocess.d.ts.map +1 -0
  259. package/dist/ui/name-picker-subprocess.js +83 -0
  260. package/dist/ui/name-picker-subprocess.js.map +1 -0
  261. package/dist/ui/name-picker.d.ts +19 -0
  262. package/dist/ui/name-picker.d.ts.map +1 -0
  263. package/dist/ui/name-picker.js +173 -0
  264. package/dist/ui/name-picker.js.map +1 -0
  265. package/dist/ui/project-picker.d.ts +27 -0
  266. package/dist/ui/project-picker.d.ts.map +1 -0
  267. package/dist/ui/project-picker.js +58 -0
  268. package/dist/ui/project-picker.js.map +1 -0
  269. package/dist/utils/config.d.ts +24 -0
  270. package/dist/utils/config.d.ts.map +1 -0
  271. package/dist/utils/config.js +63 -0
  272. package/dist/utils/config.js.map +1 -0
  273. package/dist/utils/logger.d.ts +32 -0
  274. package/dist/utils/logger.d.ts.map +1 -0
  275. package/dist/utils/logger.js +60 -0
  276. package/dist/utils/logger.js.map +1 -0
  277. package/dist/utils/name-generator.d.ts +20 -0
  278. package/dist/utils/name-generator.d.ts.map +1 -0
  279. package/dist/utils/name-generator.js +183 -0
  280. package/dist/utils/name-generator.js.map +1 -0
  281. package/dist/utils/paths.d.ts +132 -0
  282. package/dist/utils/paths.d.ts.map +1 -0
  283. package/dist/utils/paths.js +412 -0
  284. package/dist/utils/paths.js.map +1 -0
  285. package/dist/utils/status-line.d.ts +14 -0
  286. package/dist/utils/status-line.d.ts.map +1 -0
  287. package/dist/utils/status-line.js +36 -0
  288. package/dist/utils/status-line.js.map +1 -0
  289. package/dist/utils/terminal-symbols.d.ts +50 -0
  290. package/dist/utils/terminal-symbols.d.ts.map +1 -0
  291. package/dist/utils/terminal-symbols.js +97 -0
  292. package/dist/utils/terminal-symbols.js.map +1 -0
  293. package/dist/utils/timer.d.ts +17 -0
  294. package/dist/utils/timer.d.ts.map +1 -0
  295. package/dist/utils/timer.js +56 -0
  296. package/dist/utils/timer.js.map +1 -0
  297. package/dist/utils/validation.d.ts +17 -0
  298. package/dist/utils/validation.d.ts.map +1 -0
  299. package/dist/utils/validation.js +106 -0
  300. package/dist/utils/validation.js.map +1 -0
  301. package/dist/utils/version.d.ts +2 -0
  302. package/dist/utils/version.d.ts.map +1 -0
  303. package/dist/utils/version.js +12 -0
  304. package/dist/utils/version.js.map +1 -0
  305. package/jest.config.ts +30 -0
  306. package/package.json +55 -0
  307. package/src/commands/do.ts +829 -0
  308. package/src/commands/plan.ts +422 -0
  309. package/src/commands/status.ts +146 -0
  310. package/src/core/claude-runner.ts +374 -0
  311. package/src/core/editor.ts +85 -0
  312. package/src/core/failure-analyzer.ts +372 -0
  313. package/src/core/git.ts +166 -0
  314. package/src/core/project-manager.ts +243 -0
  315. package/src/core/retry-handler.ts +72 -0
  316. package/src/core/shutdown-handler.ts +93 -0
  317. package/src/core/state-derivation.ts +343 -0
  318. package/src/index.ts +20 -0
  319. package/src/parsers/output-parser.ts +164 -0
  320. package/src/prompts/amend.ts +194 -0
  321. package/src/prompts/execution.ts +223 -0
  322. package/src/prompts/planning.ts +175 -0
  323. package/src/types/config.ts +35 -0
  324. package/src/ui/name-picker-subprocess.ts +96 -0
  325. package/src/ui/name-picker.ts +198 -0
  326. package/src/ui/project-picker.ts +80 -0
  327. package/src/utils/config.ts +69 -0
  328. package/src/utils/logger.ts +81 -0
  329. package/src/utils/name-generator.ts +211 -0
  330. package/src/utils/paths.ts +497 -0
  331. package/src/utils/status-line.ts +45 -0
  332. package/src/utils/terminal-symbols.ts +124 -0
  333. package/src/utils/timer.ts +64 -0
  334. package/src/utils/validation.ts +132 -0
  335. package/src/utils/version.ts +12 -0
  336. package/tests/unit/claude-runner-interactive.test.ts +343 -0
  337. package/tests/unit/claude-runner.test.ts +629 -0
  338. package/tests/unit/command-output.test.ts +295 -0
  339. package/tests/unit/config.test.ts +72 -0
  340. package/tests/unit/dependency-integration.test.ts +559 -0
  341. package/tests/unit/do-blocked-tasks.test.ts +323 -0
  342. package/tests/unit/do-command.test.ts +198 -0
  343. package/tests/unit/do-multiproject.test.ts +270 -0
  344. package/tests/unit/do-rerun.test.ts +270 -0
  345. package/tests/unit/execution-prompt.test.ts +406 -0
  346. package/tests/unit/failure-analyzer.test.ts +276 -0
  347. package/tests/unit/failure-history.test.ts +143 -0
  348. package/tests/unit/git-stash.test.ts +138 -0
  349. package/tests/unit/git.test.ts +80 -0
  350. package/tests/unit/logger.test.ts +132 -0
  351. package/tests/unit/name-generator.test.ts +283 -0
  352. package/tests/unit/name-picker.test.ts +179 -0
  353. package/tests/unit/outcome-content.test.ts +166 -0
  354. package/tests/unit/output-parser.test.ts +178 -0
  355. package/tests/unit/paths.test.ts +741 -0
  356. package/tests/unit/plan-command-amend-flag.test.ts +115 -0
  357. package/tests/unit/plan-command-amend-input.test.ts +156 -0
  358. package/tests/unit/plan-command-auto-flag.test.ts +112 -0
  359. package/tests/unit/plan-command.test.ts +580 -0
  360. package/tests/unit/planning-prompt.test.ts +137 -0
  361. package/tests/unit/project-manager.test.ts +265 -0
  362. package/tests/unit/project-picker.test.ts +338 -0
  363. package/tests/unit/retry-handler.test.ts +89 -0
  364. package/tests/unit/state-derivation.test.ts +714 -0
  365. package/tests/unit/status-command.test.ts +271 -0
  366. package/tests/unit/status-line.test.ts +92 -0
  367. package/tests/unit/terminal-symbols.test.ts +214 -0
  368. package/tests/unit/timer.test.ts +102 -0
  369. package/tests/unit/validation.test.ts +118 -0
  370. package/tsconfig.json +26 -0
@@ -0,0 +1,629 @@
1
+ import { jest } from '@jest/globals';
2
+ import { EventEmitter } from 'events';
3
+
4
+ // Create mock spawn before importing ClaudeRunner
5
+ const mockSpawn = jest.fn();
6
+ const mockExecSync = jest.fn();
7
+
8
+ jest.unstable_mockModule('node:child_process', () => ({
9
+ spawn: mockSpawn,
10
+ execSync: mockExecSync,
11
+ }));
12
+
13
+ // Import after mocking
14
+ const { ClaudeRunner } = await import('../../src/core/claude-runner.js');
15
+
16
+ describe('ClaudeRunner', () => {
17
+ beforeEach(() => {
18
+ jest.clearAllMocks();
19
+ jest.useFakeTimers();
20
+ mockExecSync.mockReturnValue('/usr/local/bin/claude\n');
21
+ });
22
+
23
+ afterEach(() => {
24
+ jest.useRealTimers();
25
+ });
26
+
27
+ describe('timeout handling', () => {
28
+ /**
29
+ * Creates a mock child process for testing.
30
+ * The mock process emits 'close' event when kill() is called or after a delay.
31
+ */
32
+ function createMockProcess() {
33
+ const stdout = new EventEmitter();
34
+ const stderr = new EventEmitter();
35
+ const proc = new EventEmitter() as any;
36
+ proc.stdout = stdout;
37
+ proc.stderr = stderr;
38
+ proc.kill = jest.fn().mockImplementation(() => {
39
+ // Emit close event when killed
40
+ setImmediate(() => proc.emit('close', 1));
41
+ });
42
+ return proc;
43
+ }
44
+
45
+ it('should set up timeout for each run() call', async () => {
46
+ const mockProc = createMockProcess();
47
+ mockSpawn.mockReturnValue(mockProc);
48
+
49
+ const runner = new ClaudeRunner();
50
+ const runPromise = runner.run('test prompt', { timeout: 5 }); // 5 minutes
51
+
52
+ // Verify process was spawned
53
+ expect(mockSpawn).toHaveBeenCalledTimes(1);
54
+
55
+ // Fast forward to just before timeout (5 minutes = 300000ms)
56
+ jest.advanceTimersByTime(299999);
57
+ expect(mockProc.kill).not.toHaveBeenCalled();
58
+
59
+ // Fast forward past timeout
60
+ jest.advanceTimersByTime(2);
61
+ expect(mockProc.kill).toHaveBeenCalledWith('SIGTERM');
62
+
63
+ const result = await runPromise;
64
+ expect(result.timedOut).toBe(true);
65
+ });
66
+
67
+ it('should set up timeout for each runVerbose() call', async () => {
68
+ const mockProc = createMockProcess();
69
+ mockSpawn.mockReturnValue(mockProc);
70
+
71
+ const runner = new ClaudeRunner();
72
+ const runPromise = runner.runVerbose('test prompt', { timeout: 3 }); // 3 minutes
73
+
74
+ // Verify process was spawned
75
+ expect(mockSpawn).toHaveBeenCalledTimes(1);
76
+
77
+ // Fast forward to just before timeout (3 minutes = 180000ms)
78
+ jest.advanceTimersByTime(179999);
79
+ expect(mockProc.kill).not.toHaveBeenCalled();
80
+
81
+ // Fast forward past timeout
82
+ jest.advanceTimersByTime(2);
83
+ expect(mockProc.kill).toHaveBeenCalledWith('SIGTERM');
84
+
85
+ const result = await runPromise;
86
+ expect(result.timedOut).toBe(true);
87
+ });
88
+
89
+ it('should use default timeout of 60 minutes when not specified', async () => {
90
+ const mockProc = createMockProcess();
91
+ mockSpawn.mockReturnValue(mockProc);
92
+
93
+ const runner = new ClaudeRunner();
94
+ const runPromise = runner.run('test prompt', {}); // No timeout specified
95
+
96
+ // Fast forward to just before default timeout (60 minutes = 3600000ms)
97
+ jest.advanceTimersByTime(3600000 - 1);
98
+ expect(mockProc.kill).not.toHaveBeenCalled();
99
+
100
+ // Fast forward past timeout
101
+ jest.advanceTimersByTime(2);
102
+ expect(mockProc.kill).toHaveBeenCalledWith('SIGTERM');
103
+
104
+ const result = await runPromise;
105
+ expect(result.timedOut).toBe(true);
106
+ });
107
+
108
+ it('should clear timeout when process completes normally', async () => {
109
+ const mockProc = createMockProcess();
110
+ mockSpawn.mockReturnValue(mockProc);
111
+
112
+ const runner = new ClaudeRunner();
113
+ const runPromise = runner.run('test prompt', { timeout: 5 });
114
+
115
+ // Emit some output and close the process normally
116
+ mockProc.stdout.emit('data', Buffer.from('<promise>COMPLETE</promise>'));
117
+ mockProc.emit('close', 0);
118
+
119
+ const result = await runPromise;
120
+ expect(result.timedOut).toBe(false);
121
+ expect(result.exitCode).toBe(0);
122
+
123
+ // Advance timer past timeout - should not cause any issues
124
+ // since timeout was cleared
125
+ jest.advanceTimersByTime(400000);
126
+ expect(mockProc.kill).not.toHaveBeenCalled();
127
+ });
128
+
129
+ it('should apply fresh timeout for each consecutive call', async () => {
130
+ const runner = new ClaudeRunner();
131
+
132
+ // First call with 2 minute timeout
133
+ const mockProc1 = createMockProcess();
134
+ mockSpawn.mockReturnValue(mockProc1);
135
+
136
+ const runPromise1 = runner.run('first prompt', { timeout: 2 });
137
+
138
+ // Complete first call quickly
139
+ mockProc1.stdout.emit('data', Buffer.from('output'));
140
+ mockProc1.emit('close', 0);
141
+ await runPromise1;
142
+
143
+ // Reset spawn mock for second call
144
+ const mockProc2 = createMockProcess();
145
+ mockSpawn.mockReturnValue(mockProc2);
146
+
147
+ // Second call with 3 minute timeout
148
+ const runPromise2 = runner.run('second prompt', { timeout: 3 });
149
+
150
+ // Fast forward 2.5 minutes - past first timeout but before second
151
+ jest.advanceTimersByTime(150000);
152
+ expect(mockProc2.kill).not.toHaveBeenCalled();
153
+
154
+ // Fast forward another minute - past second timeout
155
+ jest.advanceTimersByTime(60000);
156
+ expect(mockProc2.kill).toHaveBeenCalledWith('SIGTERM');
157
+
158
+ const result2 = await runPromise2;
159
+ expect(result2.timedOut).toBe(true);
160
+ });
161
+
162
+ it('should set timedOut flag correctly on timeout', async () => {
163
+ const mockProc = createMockProcess();
164
+ mockSpawn.mockReturnValue(mockProc);
165
+
166
+ const runner = new ClaudeRunner();
167
+ const runPromise = runner.run('test prompt', { timeout: 1 }); // 1 minute
168
+
169
+ // Fast forward past timeout
170
+ jest.advanceTimersByTime(60001);
171
+
172
+ const result = await runPromise;
173
+ expect(result.timedOut).toBe(true);
174
+ expect(result.exitCode).toBe(1); // Process was killed
175
+ });
176
+
177
+ it('should not set timedOut flag when completed before timeout', async () => {
178
+ const mockProc = createMockProcess();
179
+ mockSpawn.mockReturnValue(mockProc);
180
+
181
+ const runner = new ClaudeRunner();
182
+ const runPromise = runner.run('test prompt', { timeout: 10 });
183
+
184
+ // Advance a bit but not to timeout
185
+ jest.advanceTimersByTime(30000);
186
+
187
+ // Complete process
188
+ mockProc.stdout.emit('data', Buffer.from('output'));
189
+ mockProc.emit('close', 0);
190
+
191
+ const result = await runPromise;
192
+ expect(result.timedOut).toBe(false);
193
+ expect(result.exitCode).toBe(0);
194
+ });
195
+
196
+ it('should use default timeout of 60 minutes when timeout is 0', async () => {
197
+ const mockProc = createMockProcess();
198
+ mockSpawn.mockReturnValue(mockProc);
199
+
200
+ const runner = new ClaudeRunner();
201
+ const runPromise = runner.run('test prompt', { timeout: 0 });
202
+
203
+ // Should NOT timeout at 0ms since timeout=0 should use default (60 minutes)
204
+ // Just verify it doesn't immediately trigger
205
+ jest.advanceTimersByTime(1000);
206
+ expect(mockProc.kill).not.toHaveBeenCalled();
207
+
208
+ // Complete the process normally before reaching the 60 minute timeout
209
+ mockProc.stdout.emit('data', Buffer.from('output'));
210
+ mockProc.emit('close', 0);
211
+
212
+ const result = await runPromise;
213
+ expect(result.timedOut).toBe(false);
214
+ expect(result.exitCode).toBe(0);
215
+ });
216
+
217
+ it('should use default timeout of 60 minutes when timeout is negative', async () => {
218
+ const mockProc = createMockProcess();
219
+ mockSpawn.mockReturnValue(mockProc);
220
+
221
+ const runner = new ClaudeRunner();
222
+ const runPromise = runner.run('test prompt', { timeout: -10 });
223
+
224
+ // Should use default 60 minutes for negative values
225
+ jest.advanceTimersByTime(3600000 - 1);
226
+ expect(mockProc.kill).not.toHaveBeenCalled();
227
+
228
+ // Fast forward past 60 minutes
229
+ jest.advanceTimersByTime(2);
230
+ expect(mockProc.kill).toHaveBeenCalledWith('SIGTERM');
231
+
232
+ const result = await runPromise;
233
+ expect(result.timedOut).toBe(true);
234
+ });
235
+
236
+ it('should use default timeout of 60 minutes when timeout is NaN', async () => {
237
+ const mockProc = createMockProcess();
238
+ mockSpawn.mockReturnValue(mockProc);
239
+
240
+ const runner = new ClaudeRunner();
241
+ // @ts-expect-error - testing invalid input
242
+ const runPromise = runner.run('test prompt', { timeout: 'invalid' });
243
+
244
+ // Should use default 60 minutes for NaN
245
+ jest.advanceTimersByTime(3600000 - 1);
246
+ expect(mockProc.kill).not.toHaveBeenCalled();
247
+
248
+ // Fast forward past 60 minutes
249
+ jest.advanceTimersByTime(2);
250
+ expect(mockProc.kill).toHaveBeenCalledWith('SIGTERM');
251
+
252
+ const result = await runPromise;
253
+ expect(result.timedOut).toBe(true);
254
+ });
255
+ });
256
+
257
+ describe('context overflow detection', () => {
258
+ function createMockProcessWithImmediateClose() {
259
+ const stdout = new EventEmitter();
260
+ const stderr = new EventEmitter();
261
+ const proc = new EventEmitter() as any;
262
+ proc.stdout = stdout;
263
+ proc.stderr = stderr;
264
+ proc.kill = jest.fn().mockImplementation(() => {
265
+ // Use synchronous emission for tests without fake timer issues
266
+ process.nextTick(() => proc.emit('close', 1));
267
+ });
268
+ return proc;
269
+ }
270
+
271
+ it('should detect context overflow and kill process', async () => {
272
+ // Use real timers for this test since we're not testing timeout
273
+ jest.useRealTimers();
274
+
275
+ const mockProc = createMockProcessWithImmediateClose();
276
+ mockSpawn.mockReturnValue(mockProc);
277
+
278
+ const runner = new ClaudeRunner();
279
+ const runPromise = runner.run('test prompt', { timeout: 60 });
280
+
281
+ // Emit context overflow message
282
+ mockProc.stdout.emit('data', Buffer.from('Error: context length exceeded'));
283
+
284
+ const result = await runPromise;
285
+ expect(result.contextOverflow).toBe(true);
286
+ expect(mockProc.kill).toHaveBeenCalledWith('SIGTERM');
287
+
288
+ // Restore fake timers for subsequent tests
289
+ jest.useFakeTimers();
290
+ });
291
+
292
+ it('should detect various context overflow patterns', async () => {
293
+ // Use real timers for this test since we're not testing timeout
294
+ jest.useRealTimers();
295
+
296
+ const patterns = [
297
+ 'token limit reached',
298
+ 'maximum context size',
299
+ 'context window full',
300
+ ];
301
+
302
+ for (const pattern of patterns) {
303
+ const mockProc = createMockProcessWithImmediateClose();
304
+ mockSpawn.mockReturnValue(mockProc);
305
+
306
+ const runner = new ClaudeRunner();
307
+ const runPromise = runner.run('test prompt', { timeout: 60 });
308
+
309
+ mockProc.stdout.emit('data', Buffer.from(`Error: ${pattern}`));
310
+
311
+ const result = await runPromise;
312
+ expect(result.contextOverflow).toBe(true);
313
+ }
314
+
315
+ // Restore fake timers for subsequent tests
316
+ jest.useFakeTimers();
317
+ });
318
+ });
319
+
320
+ describe('output collection', () => {
321
+ function createMockProcess() {
322
+ const stdout = new EventEmitter();
323
+ const stderr = new EventEmitter();
324
+ const proc = new EventEmitter() as any;
325
+ proc.stdout = stdout;
326
+ proc.stderr = stderr;
327
+ proc.kill = jest.fn();
328
+ return proc;
329
+ }
330
+
331
+ it('should collect all stdout output', async () => {
332
+ const mockProc = createMockProcess();
333
+ mockSpawn.mockReturnValue(mockProc);
334
+
335
+ const runner = new ClaudeRunner();
336
+ const runPromise = runner.run('test prompt', { timeout: 60 });
337
+
338
+ // Emit multiple chunks
339
+ mockProc.stdout.emit('data', Buffer.from('chunk1'));
340
+ mockProc.stdout.emit('data', Buffer.from('chunk2'));
341
+ mockProc.stdout.emit('data', Buffer.from('chunk3'));
342
+ mockProc.emit('close', 0);
343
+
344
+ const result = await runPromise;
345
+ expect(result.output).toBe('chunk1chunk2chunk3');
346
+ });
347
+
348
+ it('should pass working directory to spawn', async () => {
349
+ const mockProc = createMockProcess();
350
+ mockSpawn.mockReturnValue(mockProc);
351
+
352
+ const runner = new ClaudeRunner();
353
+ const runPromise = runner.run('test prompt', { timeout: 60, cwd: '/custom/path' });
354
+
355
+ mockProc.emit('close', 0);
356
+ await runPromise;
357
+
358
+ expect(mockSpawn).toHaveBeenCalledWith(
359
+ expect.any(String),
360
+ expect.any(Array),
361
+ expect.objectContaining({ cwd: '/custom/path' })
362
+ );
363
+ });
364
+ });
365
+
366
+ describe('model configuration', () => {
367
+ function createMockProcess() {
368
+ const stdout = new EventEmitter();
369
+ const stderr = new EventEmitter();
370
+ const proc = new EventEmitter() as any;
371
+ proc.stdout = stdout;
372
+ proc.stderr = stderr;
373
+ proc.kill = jest.fn();
374
+ return proc;
375
+ }
376
+
377
+ it('should use opus as default model', async () => {
378
+ const mockProc = createMockProcess();
379
+ mockSpawn.mockReturnValue(mockProc);
380
+
381
+ const runner = new ClaudeRunner();
382
+ const runPromise = runner.run('test prompt', { timeout: 60 });
383
+
384
+ mockProc.emit('close', 0);
385
+ await runPromise;
386
+
387
+ expect(mockSpawn).toHaveBeenCalledWith(
388
+ expect.any(String),
389
+ expect.arrayContaining(['--model', 'opus']),
390
+ expect.any(Object)
391
+ );
392
+ });
393
+
394
+ it('should pass model to Claude CLI in run()', async () => {
395
+ const mockProc = createMockProcess();
396
+ mockSpawn.mockReturnValue(mockProc);
397
+
398
+ const runner = new ClaudeRunner({ model: 'sonnet' });
399
+ const runPromise = runner.run('test prompt', { timeout: 60 });
400
+
401
+ mockProc.emit('close', 0);
402
+ await runPromise;
403
+
404
+ expect(mockSpawn).toHaveBeenCalledWith(
405
+ expect.any(String),
406
+ expect.arrayContaining(['--model', 'sonnet']),
407
+ expect.any(Object)
408
+ );
409
+ });
410
+
411
+ it('should pass model to Claude CLI in runVerbose()', async () => {
412
+ const mockProc = createMockProcess();
413
+ mockSpawn.mockReturnValue(mockProc);
414
+
415
+ const runner = new ClaudeRunner({ model: 'haiku' });
416
+ const runPromise = runner.runVerbose('test prompt', { timeout: 60 });
417
+
418
+ mockProc.emit('close', 0);
419
+ await runPromise;
420
+
421
+ expect(mockSpawn).toHaveBeenCalledWith(
422
+ expect.any(String),
423
+ expect.arrayContaining(['--model', 'haiku']),
424
+ expect.any(Object)
425
+ );
426
+ });
427
+
428
+ it('should include model flag in args order before prompt', async () => {
429
+ const mockProc = createMockProcess();
430
+ mockSpawn.mockReturnValue(mockProc);
431
+
432
+ const runner = new ClaudeRunner({ model: 'sonnet' });
433
+ const runPromise = runner.run('test prompt', { timeout: 60 });
434
+
435
+ mockProc.emit('close', 0);
436
+ await runPromise;
437
+
438
+ const spawnArgs = mockSpawn.mock.calls[0][1] as string[];
439
+ const modelIndex = spawnArgs.indexOf('--model');
440
+ const promptFlagIndex = spawnArgs.indexOf('-p');
441
+
442
+ expect(modelIndex).toBeGreaterThanOrEqual(0);
443
+ expect(promptFlagIndex).toBeGreaterThanOrEqual(0);
444
+ expect(modelIndex).toBeLessThan(promptFlagIndex);
445
+ });
446
+ });
447
+
448
+ describe('system prompt append flag', () => {
449
+ function createMockProcess() {
450
+ const stdout = new EventEmitter();
451
+ const stderr = new EventEmitter();
452
+ const proc = new EventEmitter() as any;
453
+ proc.stdout = stdout;
454
+ proc.stderr = stderr;
455
+ proc.kill = jest.fn();
456
+ return proc;
457
+ }
458
+
459
+ it('should use --append-system-prompt flag in run()', async () => {
460
+ const mockProc = createMockProcess();
461
+ mockSpawn.mockReturnValue(mockProc);
462
+
463
+ const runner = new ClaudeRunner();
464
+ const rafPrompt = 'RAF instructions here';
465
+ const runPromise = runner.run(rafPrompt, { timeout: 60 });
466
+
467
+ mockProc.emit('close', 0);
468
+ await runPromise;
469
+
470
+ const spawnArgs = mockSpawn.mock.calls[0][1] as string[];
471
+ expect(spawnArgs).toContain('--append-system-prompt');
472
+ expect(spawnArgs).toContain(rafPrompt);
473
+
474
+ // Verify the --append-system-prompt flag comes before the prompt
475
+ const appendIndex = spawnArgs.indexOf('--append-system-prompt');
476
+ const promptIndex = spawnArgs.indexOf(rafPrompt);
477
+ expect(appendIndex).toBeLessThan(promptIndex);
478
+ expect(spawnArgs[appendIndex + 1]).toBe(rafPrompt);
479
+ });
480
+
481
+ it('should use --append-system-prompt flag in runVerbose()', async () => {
482
+ const mockProc = createMockProcess();
483
+ mockSpawn.mockReturnValue(mockProc);
484
+
485
+ const runner = new ClaudeRunner();
486
+ const rafPrompt = 'RAF verbose instructions here';
487
+ const runPromise = runner.runVerbose(rafPrompt, { timeout: 60 });
488
+
489
+ mockProc.emit('close', 0);
490
+ await runPromise;
491
+
492
+ const spawnArgs = mockSpawn.mock.calls[0][1] as string[];
493
+ expect(spawnArgs).toContain('--append-system-prompt');
494
+ expect(spawnArgs).toContain(rafPrompt);
495
+
496
+ // Verify the --append-system-prompt flag comes before the prompt
497
+ const appendIndex = spawnArgs.indexOf('--append-system-prompt');
498
+ const promptIndex = spawnArgs.indexOf(rafPrompt);
499
+ expect(appendIndex).toBeLessThan(promptIndex);
500
+ expect(spawnArgs[appendIndex + 1]).toBe(rafPrompt);
501
+ });
502
+
503
+ it('should pass minimal trigger prompt with -p flag', async () => {
504
+ const mockProc = createMockProcess();
505
+ mockSpawn.mockReturnValue(mockProc);
506
+
507
+ const runner = new ClaudeRunner();
508
+ const runPromise = runner.run('RAF instructions', { timeout: 60 });
509
+
510
+ mockProc.emit('close', 0);
511
+ await runPromise;
512
+
513
+ const spawnArgs = mockSpawn.mock.calls[0][1] as string[];
514
+ expect(spawnArgs).toContain('-p');
515
+
516
+ // The trigger prompt should follow -p
517
+ const pIndex = spawnArgs.indexOf('-p');
518
+ expect(spawnArgs[pIndex + 1]).toBe('Execute the task as described in the system prompt.');
519
+ });
520
+
521
+ it('should include all required flags in correct order', async () => {
522
+ const mockProc = createMockProcess();
523
+ mockSpawn.mockReturnValue(mockProc);
524
+
525
+ const runner = new ClaudeRunner({ model: 'sonnet' });
526
+ const runPromise = runner.run('test instructions', { timeout: 60 });
527
+
528
+ mockProc.emit('close', 0);
529
+ await runPromise;
530
+
531
+ const spawnArgs = mockSpawn.mock.calls[0][1] as string[];
532
+
533
+ // Verify all expected flags are present
534
+ expect(spawnArgs).toContain('--dangerously-skip-permissions');
535
+ expect(spawnArgs).toContain('--model');
536
+ expect(spawnArgs).toContain('sonnet');
537
+ expect(spawnArgs).toContain('--append-system-prompt');
538
+ expect(spawnArgs).toContain('-p');
539
+ });
540
+ });
541
+
542
+ describe('retry isolation (timeout per attempt)', () => {
543
+ function createMockProcess() {
544
+ const stdout = new EventEmitter();
545
+ const stderr = new EventEmitter();
546
+ const proc = new EventEmitter() as any;
547
+ proc.stdout = stdout;
548
+ proc.stderr = stderr;
549
+ proc.kill = jest.fn().mockImplementation(() => {
550
+ setImmediate(() => proc.emit('close', 1));
551
+ });
552
+ return proc;
553
+ }
554
+
555
+ it('should provide fresh timeout for each retry attempt', async () => {
556
+ const runner = new ClaudeRunner();
557
+ const timeoutMinutes = 2; // 2 minutes = 120000ms
558
+
559
+ // Simulate multiple retry attempts like do.ts does
560
+ // Each attempt should get its own fresh timeout
561
+
562
+ // Attempt 1 - times out
563
+ const mockProc1 = createMockProcess();
564
+ mockSpawn.mockReturnValue(mockProc1);
565
+
566
+ const runPromise1 = runner.run('attempt 1', { timeout: timeoutMinutes });
567
+ jest.advanceTimersByTime(120001); // Past timeout
568
+
569
+ const result1 = await runPromise1;
570
+ expect(result1.timedOut).toBe(true);
571
+
572
+ // Attempt 2 - fresh timeout, succeeds
573
+ const mockProc2 = createMockProcess();
574
+ mockSpawn.mockReturnValue(mockProc2);
575
+
576
+ const runPromise2 = runner.run('attempt 2', { timeout: timeoutMinutes });
577
+
578
+ // Only advance 1 minute - should not timeout
579
+ jest.advanceTimersByTime(60000);
580
+ expect(mockProc2.kill).not.toHaveBeenCalled();
581
+
582
+ // Complete successfully
583
+ mockProc2.stdout.emit('data', Buffer.from('success'));
584
+ mockProc2.emit('close', 0);
585
+
586
+ const result2 = await runPromise2;
587
+ expect(result2.timedOut).toBe(false);
588
+ expect(result2.output).toBe('success');
589
+ });
590
+
591
+ it('should not share elapsed time between attempts', async () => {
592
+ const runner = new ClaudeRunner();
593
+ const timeoutMinutes = 5; // 5 minutes
594
+
595
+ // First attempt runs for 4 minutes then fails
596
+ const mockProc1 = createMockProcess();
597
+ mockSpawn.mockReturnValue(mockProc1);
598
+
599
+ const runPromise1 = runner.run('attempt 1', { timeout: timeoutMinutes });
600
+
601
+ // Run for 4 minutes (240000ms)
602
+ jest.advanceTimersByTime(240000);
603
+
604
+ // Fail without timeout
605
+ mockProc1.stdout.emit('data', Buffer.from('<promise>FAILED</promise>'));
606
+ mockProc1.emit('close', 1);
607
+
608
+ const result1 = await runPromise1;
609
+ expect(result1.timedOut).toBe(false);
610
+
611
+ // Second attempt should have FULL 5 minutes, not 1 minute remaining
612
+ const mockProc2 = createMockProcess();
613
+ mockSpawn.mockReturnValue(mockProc2);
614
+
615
+ const runPromise2 = runner.run('attempt 2', { timeout: timeoutMinutes });
616
+
617
+ // Advance 4 minutes again - should NOT timeout
618
+ jest.advanceTimersByTime(240000);
619
+ expect(mockProc2.kill).not.toHaveBeenCalled();
620
+
621
+ // Advance past total timeout for this attempt
622
+ jest.advanceTimersByTime(60001);
623
+ expect(mockProc2.kill).toHaveBeenCalledWith('SIGTERM');
624
+
625
+ const result2 = await runPromise2;
626
+ expect(result2.timedOut).toBe(true);
627
+ });
628
+ });
629
+ });