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,559 @@
1
+ import * as fs from 'node:fs';
2
+ import * as path from 'node:path';
3
+ import * as os from 'node:os';
4
+ import {
5
+ deriveProjectState,
6
+ getNextExecutableTask,
7
+ getDerivedStats,
8
+ isProjectComplete,
9
+ hasProjectFailed,
10
+ parseDependencies,
11
+ parseOutcomeStatus,
12
+ type DerivedProjectState,
13
+ type DerivedTask,
14
+ } from '../../src/core/state-derivation.js';
15
+ import { getOutcomeFilePath, extractTaskNameFromPlanFile } from '../../src/utils/paths.js';
16
+ import { getExecutionPrompt } from '../../src/prompts/execution.js';
17
+
18
+ /**
19
+ * Integration tests for the complete dependency blocking flow.
20
+ * These tests simulate a full project lifecycle with dependencies,
21
+ * failures, and cascading blocks.
22
+ */
23
+ describe('Dependency Integration Flow', () => {
24
+ let tempDir: string;
25
+ let projectPath: string;
26
+
27
+ beforeEach(() => {
28
+ tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'raf-dep-integration-'));
29
+ projectPath = path.join(tempDir, '001-test-project');
30
+ fs.mkdirSync(projectPath, { recursive: true });
31
+ fs.mkdirSync(path.join(projectPath, 'plans'), { recursive: true });
32
+ fs.mkdirSync(path.join(projectPath, 'outcomes'), { recursive: true });
33
+ fs.writeFileSync(path.join(projectPath, 'input.md'), '# Test Input');
34
+ });
35
+
36
+ afterEach(() => {
37
+ fs.rmSync(tempDir, { recursive: true, force: true });
38
+ });
39
+
40
+ describe('Complete dependency chain simulation', () => {
41
+ /**
42
+ * Simulates a real project with the following dependency structure:
43
+ *
44
+ * 001-setup (no deps)
45
+ * 002-core (depends on 001)
46
+ * 003-api (depends on 001, 002)
47
+ * 004-tests (depends on 003)
48
+ * 005-docs (depends on 003)
49
+ * 006-deploy (depends on 004, 005)
50
+ */
51
+ beforeEach(() => {
52
+ // Create plan files with dependency structure
53
+ fs.writeFileSync(
54
+ path.join(projectPath, 'plans', '001-setup.md'),
55
+ '# Task: Setup\n\n## Objective\nSetup the project infrastructure.\n\n## Requirements\n- Initialize project'
56
+ );
57
+ fs.writeFileSync(
58
+ path.join(projectPath, 'plans', '002-core.md'),
59
+ '# Task: Core\n\n## Objective\nBuild core functionality.\n\n## Dependencies\n001\n\n## Requirements\n- Build core'
60
+ );
61
+ fs.writeFileSync(
62
+ path.join(projectPath, 'plans', '003-api.md'),
63
+ '# Task: API\n\n## Objective\nImplement API layer.\n\n## Dependencies\n001, 002\n\n## Requirements\n- Create API'
64
+ );
65
+ fs.writeFileSync(
66
+ path.join(projectPath, 'plans', '004-tests.md'),
67
+ '# Task: Tests\n\n## Objective\nWrite tests.\n\n## Dependencies\n003\n\n## Requirements\n- Write tests'
68
+ );
69
+ fs.writeFileSync(
70
+ path.join(projectPath, 'plans', '005-docs.md'),
71
+ '# Task: Docs\n\n## Objective\nWrite documentation.\n\n## Dependencies\n003\n\n## Requirements\n- Write docs'
72
+ );
73
+ fs.writeFileSync(
74
+ path.join(projectPath, 'plans', '006-deploy.md'),
75
+ '# Task: Deploy\n\n## Objective\nDeploy the project.\n\n## Dependencies\n004, 005\n\n## Requirements\n- Deploy'
76
+ );
77
+ });
78
+
79
+ it('should correctly derive initial state with all tasks pending', () => {
80
+ const state = deriveProjectState(projectPath);
81
+
82
+ expect(state.tasks).toHaveLength(6);
83
+ expect(state.tasks.every((t) => t.status === 'pending')).toBe(true);
84
+ expect(state.status).toBe('ready');
85
+
86
+ // Verify dependencies are parsed correctly
87
+ expect(state.tasks[0]?.dependencies).toEqual([]);
88
+ expect(state.tasks[1]?.dependencies).toEqual(['001']);
89
+ expect(state.tasks[2]?.dependencies).toEqual(['001', '002']);
90
+ expect(state.tasks[3]?.dependencies).toEqual(['003']);
91
+ expect(state.tasks[4]?.dependencies).toEqual(['003']);
92
+ expect(state.tasks[5]?.dependencies).toEqual(['004', '005']);
93
+ });
94
+
95
+ it('should return first pending task when all are pending', () => {
96
+ const state = deriveProjectState(projectPath);
97
+ const nextTask = getNextExecutableTask(state);
98
+
99
+ expect(nextTask?.id).toBe('001');
100
+ expect(nextTask?.status).toBe('pending');
101
+ });
102
+
103
+ it('should cascade blocks when task 001 fails', () => {
104
+ // Task 001 fails
105
+ fs.writeFileSync(
106
+ path.join(projectPath, 'outcomes', '001-setup.md'),
107
+ '# Outcome\n\nSetup failed.\n\n<promise>FAILED</promise>'
108
+ );
109
+
110
+ const state = deriveProjectState(projectPath);
111
+
112
+ // 001 failed, all others depending on it should be blocked
113
+ expect(state.tasks[0]?.status).toBe('failed'); // 001
114
+ expect(state.tasks[1]?.status).toBe('blocked'); // 002 (depends on 001)
115
+ expect(state.tasks[2]?.status).toBe('blocked'); // 003 (depends on 001, 002)
116
+ expect(state.tasks[3]?.status).toBe('blocked'); // 004 (depends on 003)
117
+ expect(state.tasks[4]?.status).toBe('blocked'); // 005 (depends on 003)
118
+ expect(state.tasks[5]?.status).toBe('blocked'); // 006 (depends on 004, 005)
119
+
120
+ // Stats should reflect this
121
+ const stats = getDerivedStats(state);
122
+ expect(stats.failed).toBe(1);
123
+ expect(stats.blocked).toBe(5);
124
+ expect(stats.pending).toBe(0);
125
+ expect(stats.completed).toBe(0);
126
+ });
127
+
128
+ it('should cascade blocks when task 002 fails (001 completed)', () => {
129
+ // Task 001 completed
130
+ fs.writeFileSync(
131
+ path.join(projectPath, 'outcomes', '001-setup.md'),
132
+ '# Outcome\n\nSetup complete.\n\n<promise>COMPLETE</promise>'
133
+ );
134
+ // Task 002 fails
135
+ fs.writeFileSync(
136
+ path.join(projectPath, 'outcomes', '002-core.md'),
137
+ '# Outcome\n\nCore failed.\n\n<promise>FAILED</promise>'
138
+ );
139
+
140
+ const state = deriveProjectState(projectPath);
141
+
142
+ expect(state.tasks[0]?.status).toBe('completed'); // 001
143
+ expect(state.tasks[1]?.status).toBe('failed'); // 002
144
+ expect(state.tasks[2]?.status).toBe('blocked'); // 003 (depends on 002 which failed)
145
+ expect(state.tasks[3]?.status).toBe('blocked'); // 004 (depends on 003 which is blocked)
146
+ expect(state.tasks[4]?.status).toBe('blocked'); // 005 (depends on 003 which is blocked)
147
+ expect(state.tasks[5]?.status).toBe('blocked'); // 006 (depends on 004, 005 which are blocked)
148
+
149
+ // Stats
150
+ const stats = getDerivedStats(state);
151
+ expect(stats.completed).toBe(1);
152
+ expect(stats.failed).toBe(1);
153
+ expect(stats.blocked).toBe(4);
154
+ expect(stats.pending).toBe(0);
155
+ });
156
+
157
+ it('should cascade blocks when task 003 fails (001, 002 completed)', () => {
158
+ // Tasks 001 and 002 completed
159
+ fs.writeFileSync(
160
+ path.join(projectPath, 'outcomes', '001-setup.md'),
161
+ '<promise>COMPLETE</promise>'
162
+ );
163
+ fs.writeFileSync(
164
+ path.join(projectPath, 'outcomes', '002-core.md'),
165
+ '<promise>COMPLETE</promise>'
166
+ );
167
+ // Task 003 fails
168
+ fs.writeFileSync(
169
+ path.join(projectPath, 'outcomes', '003-api.md'),
170
+ '<promise>FAILED</promise>'
171
+ );
172
+
173
+ const state = deriveProjectState(projectPath);
174
+
175
+ expect(state.tasks[0]?.status).toBe('completed'); // 001
176
+ expect(state.tasks[1]?.status).toBe('completed'); // 002
177
+ expect(state.tasks[2]?.status).toBe('failed'); // 003
178
+ expect(state.tasks[3]?.status).toBe('blocked'); // 004 (depends on 003)
179
+ expect(state.tasks[4]?.status).toBe('blocked'); // 005 (depends on 003)
180
+ expect(state.tasks[5]?.status).toBe('blocked'); // 006 (depends on 004, 005)
181
+
182
+ const stats = getDerivedStats(state);
183
+ expect(stats.completed).toBe(2);
184
+ expect(stats.failed).toBe(1);
185
+ expect(stats.blocked).toBe(3);
186
+ });
187
+
188
+ it('should handle partial failure in parallel branches', () => {
189
+ // Complete chain up to 003
190
+ fs.writeFileSync(
191
+ path.join(projectPath, 'outcomes', '001-setup.md'),
192
+ '<promise>COMPLETE</promise>'
193
+ );
194
+ fs.writeFileSync(
195
+ path.join(projectPath, 'outcomes', '002-core.md'),
196
+ '<promise>COMPLETE</promise>'
197
+ );
198
+ fs.writeFileSync(
199
+ path.join(projectPath, 'outcomes', '003-api.md'),
200
+ '<promise>COMPLETE</promise>'
201
+ );
202
+ // 004 succeeds but 005 fails
203
+ fs.writeFileSync(
204
+ path.join(projectPath, 'outcomes', '004-tests.md'),
205
+ '<promise>COMPLETE</promise>'
206
+ );
207
+ fs.writeFileSync(
208
+ path.join(projectPath, 'outcomes', '005-docs.md'),
209
+ '<promise>FAILED</promise>'
210
+ );
211
+
212
+ const state = deriveProjectState(projectPath);
213
+
214
+ expect(state.tasks[0]?.status).toBe('completed'); // 001
215
+ expect(state.tasks[1]?.status).toBe('completed'); // 002
216
+ expect(state.tasks[2]?.status).toBe('completed'); // 003
217
+ expect(state.tasks[3]?.status).toBe('completed'); // 004
218
+ expect(state.tasks[4]?.status).toBe('failed'); // 005
219
+ expect(state.tasks[5]?.status).toBe('blocked'); // 006 (depends on 005 which failed)
220
+
221
+ const stats = getDerivedStats(state);
222
+ expect(stats.completed).toBe(4);
223
+ expect(stats.failed).toBe(1);
224
+ expect(stats.blocked).toBe(1);
225
+ });
226
+
227
+ it('should mark project complete when all tasks succeed', () => {
228
+ // All tasks complete
229
+ for (const taskNum of ['001', '002', '003', '004', '005', '006']) {
230
+ const taskFile = fs.readdirSync(path.join(projectPath, 'plans'))
231
+ .find((f) => f.startsWith(taskNum));
232
+ const taskName = taskFile ? extractTaskNameFromPlanFile(`plans/${taskFile}`) : taskNum;
233
+ fs.writeFileSync(
234
+ path.join(projectPath, 'outcomes', `${taskNum}-${taskName ?? taskNum}.md`),
235
+ `# Outcome\n\nTask ${taskNum} complete.\n\n<promise>COMPLETE</promise>`
236
+ );
237
+ }
238
+
239
+ const state = deriveProjectState(projectPath);
240
+
241
+ expect(isProjectComplete(state)).toBe(true);
242
+ expect(hasProjectFailed(state)).toBe(false);
243
+ expect(state.tasks.every((t) => t.status === 'completed')).toBe(true);
244
+ });
245
+ });
246
+
247
+ describe('getNextExecutableTask with complex dependency states', () => {
248
+ it('should skip blocked tasks and find next available pending task', () => {
249
+ // Setup: 001 fails, 002 depends on 001 (blocked), 003 has no deps (pending)
250
+ fs.writeFileSync(
251
+ path.join(projectPath, 'plans', '001-first.md'),
252
+ '# Task: First'
253
+ );
254
+ fs.writeFileSync(
255
+ path.join(projectPath, 'plans', '002-second.md'),
256
+ '# Task: Second\n\n## Dependencies\n001'
257
+ );
258
+ fs.writeFileSync(
259
+ path.join(projectPath, 'plans', '003-third.md'),
260
+ '# Task: Third'
261
+ );
262
+
263
+ // 001 fails
264
+ fs.writeFileSync(
265
+ path.join(projectPath, 'outcomes', '001-first.md'),
266
+ '<promise>FAILED</promise>'
267
+ );
268
+
269
+ const state = deriveProjectState(projectPath);
270
+ const nextTask = getNextExecutableTask(state);
271
+
272
+ // Should return 003 (pending) not 002 (blocked)
273
+ expect(nextTask?.id).toBe('003');
274
+ expect(nextTask?.status).toBe('pending');
275
+ });
276
+
277
+ it('should return failed task for retry when no pending tasks exist', () => {
278
+ fs.writeFileSync(
279
+ path.join(projectPath, 'plans', '001-first.md'),
280
+ '# Task: First'
281
+ );
282
+ fs.writeFileSync(
283
+ path.join(projectPath, 'plans', '002-second.md'),
284
+ '# Task: Second\n\n## Dependencies\n001'
285
+ );
286
+
287
+ // 001 fails
288
+ fs.writeFileSync(
289
+ path.join(projectPath, 'outcomes', '001-first.md'),
290
+ '<promise>FAILED</promise>'
291
+ );
292
+
293
+ const state = deriveProjectState(projectPath);
294
+ const nextTask = getNextExecutableTask(state);
295
+
296
+ // Should return 001 for retry (002 is blocked)
297
+ expect(nextTask?.id).toBe('001');
298
+ expect(nextTask?.status).toBe('failed');
299
+ });
300
+
301
+ it('should return null when all tasks are blocked with no failed tasks to retry', () => {
302
+ fs.writeFileSync(
303
+ path.join(projectPath, 'plans', '001-first.md'),
304
+ '# Task: First'
305
+ );
306
+ fs.writeFileSync(
307
+ path.join(projectPath, 'plans', '002-second.md'),
308
+ '# Task: Second\n\n## Dependencies\n001'
309
+ );
310
+
311
+ // 001 completed, but 002 is marked as blocked explicitly (edge case)
312
+ fs.writeFileSync(
313
+ path.join(projectPath, 'outcomes', '001-first.md'),
314
+ '<promise>COMPLETE</promise>'
315
+ );
316
+ fs.writeFileSync(
317
+ path.join(projectPath, 'outcomes', '002-second.md'),
318
+ '<promise>BLOCKED</promise>'
319
+ );
320
+
321
+ const state = deriveProjectState(projectPath);
322
+ const nextTask = getNextExecutableTask(state);
323
+
324
+ // No pending or failed tasks to return
325
+ expect(nextTask).toBeNull();
326
+ });
327
+ });
328
+
329
+ describe('Execution prompt with dependency context', () => {
330
+ it('should include dependency outcomes in execution prompt', () => {
331
+ fs.writeFileSync(
332
+ path.join(projectPath, 'plans', '001-setup.md'),
333
+ '# Task: Setup\n\n## Objective\nSetup the project.'
334
+ );
335
+ fs.writeFileSync(
336
+ path.join(projectPath, 'plans', '002-build.md'),
337
+ '# Task: Build\n\n## Objective\nBuild the project.\n\n## Dependencies\n001'
338
+ );
339
+
340
+ // 001 completed with outcome
341
+ const outcome001 = '# Outcome: Task 001\n\n## Summary\n\nInitialized project with configuration files.\n\n<promise>COMPLETE</promise>';
342
+ fs.writeFileSync(
343
+ path.join(projectPath, 'outcomes', '001-setup.md'),
344
+ outcome001
345
+ );
346
+
347
+ const state = deriveProjectState(projectPath);
348
+ const task002 = state.tasks.find((t) => t.id === '002');
349
+
350
+ expect(task002).toBeDefined();
351
+ expect(task002?.dependencies).toEqual(['001']);
352
+
353
+ // Build execution prompt for task 002
354
+ const prompt = getExecutionPrompt({
355
+ projectPath,
356
+ planPath: path.join(projectPath, 'plans', '002-build.md'),
357
+ taskId: '002',
358
+ taskNumber: 2,
359
+ totalTasks: 2,
360
+ previousOutcomes: [{ taskId: '001', content: outcome001 }],
361
+ autoCommit: true,
362
+ projectNumber: '001',
363
+ outcomeFilePath: path.join(projectPath, 'outcomes', '002-build.md'),
364
+ dependencyIds: ['001'],
365
+ dependencyOutcomes: [{ taskId: '001', content: outcome001 }],
366
+ });
367
+
368
+ expect(prompt).toContain('## Dependency Context');
369
+ expect(prompt).toContain('**Dependencies**: 001');
370
+ expect(prompt).toContain('### Task 001');
371
+ expect(prompt).toContain('Initialized project with configuration files');
372
+ });
373
+
374
+ it('should include multiple dependency outcomes', () => {
375
+ fs.writeFileSync(
376
+ path.join(projectPath, 'plans', '001-setup.md'),
377
+ '# Task: Setup'
378
+ );
379
+ fs.writeFileSync(
380
+ path.join(projectPath, 'plans', '002-core.md'),
381
+ '# Task: Core\n\n## Dependencies\n001'
382
+ );
383
+ fs.writeFileSync(
384
+ path.join(projectPath, 'plans', '003-api.md'),
385
+ '# Task: API\n\n## Dependencies\n001, 002'
386
+ );
387
+
388
+ const outcome001 = '## Summary\n\nSetup complete.\n\n<promise>COMPLETE</promise>';
389
+ const outcome002 = '## Summary\n\nCore complete.\n\n<promise>COMPLETE</promise>';
390
+
391
+ fs.writeFileSync(path.join(projectPath, 'outcomes', '001-setup.md'), outcome001);
392
+ fs.writeFileSync(path.join(projectPath, 'outcomes', '002-core.md'), outcome002);
393
+
394
+ const prompt = getExecutionPrompt({
395
+ projectPath,
396
+ planPath: path.join(projectPath, 'plans', '003-api.md'),
397
+ taskId: '003',
398
+ taskNumber: 3,
399
+ totalTasks: 3,
400
+ previousOutcomes: [
401
+ { taskId: '001', content: outcome001 },
402
+ { taskId: '002', content: outcome002 },
403
+ ],
404
+ autoCommit: true,
405
+ projectNumber: '001',
406
+ outcomeFilePath: path.join(projectPath, 'outcomes', '003-api.md'),
407
+ dependencyIds: ['001', '002'],
408
+ dependencyOutcomes: [
409
+ { taskId: '001', content: outcome001 },
410
+ { taskId: '002', content: outcome002 },
411
+ ],
412
+ });
413
+
414
+ expect(prompt).toContain('**Dependencies**: 001, 002');
415
+ expect(prompt).toContain('### Task 001');
416
+ expect(prompt).toContain('Setup complete');
417
+ expect(prompt).toContain('### Task 002');
418
+ expect(prompt).toContain('Core complete');
419
+ });
420
+ });
421
+
422
+ describe('BLOCKED outcome file recognition', () => {
423
+ it('should recognize BLOCKED marker and set status to blocked', () => {
424
+ fs.writeFileSync(
425
+ path.join(projectPath, 'plans', '001-task.md'),
426
+ '# Task: Task'
427
+ );
428
+
429
+ // Blocked outcome file format
430
+ const blockedContent = `# Outcome: Task 001 Blocked
431
+
432
+ ## Summary
433
+
434
+ This task was automatically blocked because one or more of its dependencies failed or are blocked.
435
+
436
+ ## Blocking Dependencies
437
+
438
+ **Failed dependencies**: 000
439
+
440
+ **Task dependencies**: 000
441
+
442
+ ## Resolution
443
+
444
+ To unblock this task:
445
+ 1. Fix the failed dependency task(s)
446
+ 2. Re-run the project with \`raf do\`
447
+
448
+ <promise>BLOCKED</promise>`;
449
+
450
+ fs.writeFileSync(
451
+ path.join(projectPath, 'outcomes', '001-task.md'),
452
+ blockedContent
453
+ );
454
+
455
+ const state = deriveProjectState(projectPath);
456
+ expect(state.tasks[0]?.status).toBe('blocked');
457
+ });
458
+
459
+ it('should parse BLOCKED marker correctly', () => {
460
+ const content = `Some content\n\n<promise>BLOCKED</promise>`;
461
+ expect(parseOutcomeStatus(content)).toBe('blocked');
462
+ });
463
+
464
+ it('should handle mixed markers using last one', () => {
465
+ // If a blocked task is later retried and succeeds
466
+ const content = `<promise>BLOCKED</promise>\n\nRetried and succeeded\n\n<promise>COMPLETE</promise>`;
467
+ expect(parseOutcomeStatus(content)).toBe('completed');
468
+ });
469
+ });
470
+
471
+ describe('Edge cases', () => {
472
+ it('should handle circular dependency detection (prevented by lower-ID rule)', () => {
473
+ // In practice, circular deps are prevented by only allowing deps on lower-numbered tasks
474
+ // But if somehow created, should not infinite loop
475
+ fs.writeFileSync(
476
+ path.join(projectPath, 'plans', '001-first.md'),
477
+ '# Task: First\n\n## Dependencies\n002' // Invalid: depends on higher-numbered task
478
+ );
479
+ fs.writeFileSync(
480
+ path.join(projectPath, 'plans', '002-second.md'),
481
+ '# Task: Second'
482
+ );
483
+
484
+ // 002 succeeds
485
+ fs.writeFileSync(
486
+ path.join(projectPath, 'outcomes', '002-second.md'),
487
+ '<promise>COMPLETE</promise>'
488
+ );
489
+
490
+ // Should not hang/crash
491
+ const state = deriveProjectState(projectPath);
492
+ expect(state.tasks).toHaveLength(2);
493
+ // Task 001 depends on 002 which is complete, so 001 is pending (dependency satisfied)
494
+ expect(state.tasks[0]?.status).toBe('pending');
495
+ expect(state.tasks[1]?.status).toBe('completed');
496
+ });
497
+
498
+ it('should handle dependency on non-existent task gracefully', () => {
499
+ fs.writeFileSync(
500
+ path.join(projectPath, 'plans', '001-task.md'),
501
+ '# Task: Task\n\n## Dependencies\n999' // 999 doesn't exist
502
+ );
503
+
504
+ const state = deriveProjectState(projectPath);
505
+ // Task should be pending since the non-existent dep doesn't block it
506
+ // (The dependency 999 just won't be found, treated as if satisfied)
507
+ expect(state.tasks[0]?.status).toBe('pending');
508
+ expect(state.tasks[0]?.dependencies).toEqual(['999']);
509
+ });
510
+
511
+ it('should handle self-dependency gracefully', () => {
512
+ fs.writeFileSync(
513
+ path.join(projectPath, 'plans', '001-task.md'),
514
+ '# Task: Task\n\n## Dependencies\n001' // Self-dependency
515
+ );
516
+
517
+ const state = deriveProjectState(projectPath);
518
+ // Self-dependency is not blocked since task isn't failed yet
519
+ expect(state.tasks[0]?.status).toBe('pending');
520
+ });
521
+
522
+ it('should handle empty project gracefully', () => {
523
+ const state = deriveProjectState(projectPath);
524
+
525
+ expect(state.tasks).toEqual([]);
526
+ expect(state.status).toBe('planning');
527
+ expect(isProjectComplete(state)).toBe(true);
528
+ expect(hasProjectFailed(state)).toBe(false);
529
+ });
530
+
531
+ it('should handle task with all dependencies completed', () => {
532
+ fs.writeFileSync(
533
+ path.join(projectPath, 'plans', '001-first.md'),
534
+ '# Task: First'
535
+ );
536
+ fs.writeFileSync(
537
+ path.join(projectPath, 'plans', '002-second.md'),
538
+ '# Task: Second'
539
+ );
540
+ fs.writeFileSync(
541
+ path.join(projectPath, 'plans', '003-third.md'),
542
+ '# Task: Third\n\n## Dependencies\n001, 002'
543
+ );
544
+
545
+ // Both deps complete
546
+ fs.writeFileSync(
547
+ path.join(projectPath, 'outcomes', '001-first.md'),
548
+ '<promise>COMPLETE</promise>'
549
+ );
550
+ fs.writeFileSync(
551
+ path.join(projectPath, 'outcomes', '002-second.md'),
552
+ '<promise>COMPLETE</promise>'
553
+ );
554
+
555
+ const state = deriveProjectState(projectPath);
556
+ expect(state.tasks[2]?.status).toBe('pending'); // Not blocked
557
+ });
558
+ });
559
+ });