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,211 @@
1
+ import { execSync } from 'node:child_process';
2
+ import { logger } from './logger.js';
3
+ import { sanitizeProjectName } from './validation.js';
4
+
5
+ const SONNET_MODEL = 'sonnet';
6
+
7
+ const NAME_GENERATION_PROMPT = `Generate a short, punchy, creative project name (1-3 words, kebab-case).
8
+
9
+ Be creative! Use metaphors, analogies, or evocative words that capture the SPIRIT of the project.
10
+ Don't literally describe what it does - make it memorable and fun.
11
+
12
+ Good examples:
13
+ - Bug fix → 'bug-squasher', 'exterminator', 'patch-adams'
14
+ - Performance optimization → 'turbo-boost', 'lightning-rod', 'speed-demon'
15
+ - Auth system → 'gatekeeper', 'bouncer', 'key-master'
16
+ - Refactoring → 'spring-cleaning', 'phoenix', 'makeover'
17
+ - New feature → 'moonshot', 'secret-sauce', 'magic-wand'
18
+
19
+ Output ONLY the kebab-case name. No quotes, no explanation.
20
+
21
+ Project description:`;
22
+
23
+ const MULTI_NAME_GENERATION_PROMPT = `Generate 5 creative project names for the description below.
24
+
25
+ IMPORTANT: Each name should use a DIFFERENT naming style:
26
+ 1. **Metaphorical** - Use a metaphor or analogy (e.g., 'phoenix', 'lighthouse', 'compass')
27
+ 2. **Fun/Playful** - Make it fun or quirky (e.g., 'turbo-boost', 'magic-beans', 'ninja-move')
28
+ 3. **Action-oriented** - Focus on what it does with flair (e.g., 'bug-squasher', 'speed-demon', 'data-whisperer')
29
+ 4. **Abstract** - Use abstract/poetic concepts (e.g., 'horizon', 'cascade', 'catalyst')
30
+ 5. **Cultural reference** - Reference pop culture, mythology, or literature (e.g., 'atlas', 'merlin', 'gandalf')
31
+
32
+ Rules:
33
+ - Each name should be 1-3 words in kebab-case
34
+ - Names must be lowercase with hyphens only
35
+ - Make them memorable and evocative
36
+ - If the project has many unrelated tasks, prefer abstract/metaphorical/fun names over descriptive ones
37
+
38
+ Output format: ONLY output 5 names, one per line, no numbers, no explanations, no quotes.
39
+
40
+ Project description:`;
41
+
42
+ /**
43
+ * Generate a single project name using Claude Sonnet.
44
+ * Falls back to extracting words from the description if the API call fails.
45
+ */
46
+ export async function generateProjectName(description: string): Promise<string> {
47
+ try {
48
+ const name = await callSonnetForName(description);
49
+ if (name) {
50
+ const sanitized = sanitizeGeneratedName(name);
51
+ if (sanitized) {
52
+ logger.debug(`Generated project name: ${sanitized}`);
53
+ return sanitized;
54
+ }
55
+ }
56
+ } catch (error) {
57
+ logger.debug(`Failed to generate name with Sonnet: ${error}`);
58
+ }
59
+
60
+ // Fallback to extracting words from description
61
+ return generateFallbackName(description);
62
+ }
63
+
64
+ /**
65
+ * Generate multiple project name suggestions using Claude Sonnet.
66
+ * Returns 3-5 unique names with varied styles.
67
+ */
68
+ export async function generateProjectNames(description: string): Promise<string[]> {
69
+ try {
70
+ const names = await callSonnetForMultipleNames(description);
71
+ if (names.length >= 3) {
72
+ logger.debug(`Generated ${names.length} project names`);
73
+ return names;
74
+ }
75
+ } catch (error) {
76
+ logger.debug(`Failed to generate names with Sonnet: ${error}`);
77
+ }
78
+
79
+ // Fallback: generate a single fallback name
80
+ const fallbackName = generateFallbackName(description);
81
+ logger.debug(`Using fallback name: ${fallbackName}`);
82
+ return [fallbackName];
83
+ }
84
+
85
+ /**
86
+ * Call Claude Sonnet to generate a single project name.
87
+ */
88
+ async function callSonnetForName(description: string): Promise<string | null> {
89
+ try {
90
+ const fullPrompt = `${NAME_GENERATION_PROMPT}\n${description}`;
91
+
92
+ // Use claude CLI with --model sonnet and --print for non-interactive output
93
+ const result = execSync(
94
+ `claude --model ${SONNET_MODEL} --print "${escapeShellArg(fullPrompt)}"`,
95
+ {
96
+ encoding: 'utf-8',
97
+ timeout: 30000, // 30 second timeout
98
+ stdio: ['pipe', 'pipe', 'pipe'],
99
+ }
100
+ );
101
+
102
+ return result.trim();
103
+ } catch (error) {
104
+ logger.debug(`Sonnet API call failed: ${error}`);
105
+ return null;
106
+ }
107
+ }
108
+
109
+ /**
110
+ * Call Claude Sonnet to generate multiple project names.
111
+ */
112
+ async function callSonnetForMultipleNames(description: string): Promise<string[]> {
113
+ try {
114
+ const fullPrompt = `${MULTI_NAME_GENERATION_PROMPT}\n${description}`;
115
+
116
+ const result = execSync(
117
+ `claude --model ${SONNET_MODEL} --print "${escapeShellArg(fullPrompt)}"`,
118
+ {
119
+ encoding: 'utf-8',
120
+ timeout: 30000,
121
+ stdio: ['pipe', 'pipe', 'pipe'],
122
+ }
123
+ );
124
+
125
+ // Parse the multiline response
126
+ const lines = result
127
+ .trim()
128
+ .split('\n')
129
+ .map((line) => line.trim())
130
+ .filter((line) => line.length > 0);
131
+
132
+ // Sanitize and validate each name
133
+ const validNames: string[] = [];
134
+ for (const line of lines) {
135
+ const sanitized = sanitizeGeneratedName(line);
136
+ if (sanitized && !validNames.includes(sanitized)) {
137
+ validNames.push(sanitized);
138
+ }
139
+ }
140
+
141
+ // Return 3-5 names
142
+ return validNames.slice(0, 5);
143
+ } catch (error) {
144
+ logger.debug(`Sonnet API call for multiple names failed: ${error}`);
145
+ return [];
146
+ }
147
+ }
148
+
149
+ /**
150
+ * Escape a string for use as a shell argument.
151
+ */
152
+ function escapeShellArg(arg: string): string {
153
+ // Replace double quotes with escaped double quotes
154
+ // Replace backslashes with escaped backslashes
155
+ return arg
156
+ .replace(/\\/g, '\\\\')
157
+ .replace(/"/g, '\\"')
158
+ .replace(/\$/g, '\\$')
159
+ .replace(/`/g, '\\`');
160
+ }
161
+
162
+ /**
163
+ * Sanitize the generated name to ensure it's a valid kebab-case folder name.
164
+ */
165
+ function sanitizeGeneratedName(name: string): string | null {
166
+ // Remove any leading/trailing whitespace and quotes
167
+ let sanitized = name.trim().replace(/^["']|["']$/g, '');
168
+
169
+ // Remove any numbering prefix like "1." or "1:" or "1)"
170
+ sanitized = sanitized.replace(/^\d+[.:)]\s*/, '');
171
+
172
+ // Convert to lowercase
173
+ sanitized = sanitized.toLowerCase();
174
+
175
+ // Replace any non-alphanumeric characters with hyphens
176
+ sanitized = sanitized.replace(/[^a-z0-9]+/g, '-');
177
+
178
+ // Remove leading and trailing hyphens
179
+ sanitized = sanitized.replace(/^-+|-+$/g, '');
180
+
181
+ // Truncate if too long (max 50 chars)
182
+ sanitized = sanitized.substring(0, 50);
183
+
184
+ // Return null if the result is empty or too short
185
+ if (sanitized.length < 2) {
186
+ return null;
187
+ }
188
+
189
+ return sanitized;
190
+ }
191
+
192
+ /**
193
+ * Generate a fallback name by extracting meaningful words from the description.
194
+ */
195
+ function generateFallbackName(description: string): string {
196
+ // Extract first meaningful words from input
197
+ const words = description
198
+ .split(/\s+/)
199
+ .filter((w) => w.length > 2)
200
+ .slice(0, 3)
201
+ .join('-')
202
+ .toLowerCase()
203
+ .replace(/[^a-z0-9-]/g, '');
204
+
205
+ const name = sanitizeProjectName(words || 'project');
206
+ logger.debug(`Using fallback project name: ${name}`);
207
+ return name;
208
+ }
209
+
210
+ // Export for testing
211
+ export { sanitizeGeneratedName, escapeShellArg };
@@ -0,0 +1,497 @@
1
+ import * as fs from 'node:fs';
2
+ import * as path from 'node:path';
3
+
4
+ export const RAF_DIR = 'RAF';
5
+
6
+ // Base36 constants
7
+ const BASE36_CHARS = '0123456789abcdefghijklmnopqrstuvwxyz';
8
+ const BASE36_START = 1000; // First base36 project number
9
+ const MAX_NUMERIC = 999; // Last numeric project number
10
+
11
+ /**
12
+ * Encode a project number >= 1000 to a 3-character base36 string.
13
+ * The encoding offsets by 1000, so:
14
+ * - 1000 -> 'a00'
15
+ * - 1001 -> 'a01'
16
+ * - 1035 -> 'a0z'
17
+ * - 1036 -> 'a10'
18
+ * - etc.
19
+ *
20
+ * First character uses a-z (10-35 in base36, letters only).
21
+ * Second and third characters use 0-9a-z (0-35 in base36).
22
+ */
23
+ export function encodeBase36(num: number): string {
24
+ if (num < BASE36_START) {
25
+ throw new Error(`encodeBase36 only accepts numbers >= ${BASE36_START}, got ${num}`);
26
+ }
27
+
28
+ // Offset to get the base36 representation starting at 'a00' for 1000
29
+ const offset = num - BASE36_START;
30
+
31
+ // First character: 'a' + (offset / 36^2), uses only letters a-z
32
+ // This gives us 26 possible first characters (a-z)
33
+ const firstCharIndex = Math.floor(offset / (36 * 36));
34
+ if (firstCharIndex >= 26) {
35
+ throw new Error(`Project number ${num} exceeds maximum base36 capacity (max: ${BASE36_START + 26 * 36 * 36 - 1})`);
36
+ }
37
+ const firstChar = BASE36_CHARS[10 + firstCharIndex]; // Start from 'a' (index 10)
38
+
39
+ // Second and third characters: remaining value in base36
40
+ const remainder = offset % (36 * 36);
41
+ const secondCharIndex = Math.floor(remainder / 36);
42
+ const thirdCharIndex = remainder % 36;
43
+
44
+ const secondChar = BASE36_CHARS[secondCharIndex];
45
+ const thirdChar = BASE36_CHARS[thirdCharIndex];
46
+
47
+ return `${firstChar}${secondChar}${thirdChar}`;
48
+ }
49
+
50
+ /**
51
+ * Decode a 3-character base36 string to a project number.
52
+ * Returns the decoded number >= 1000, or null if invalid format.
53
+ */
54
+ export function decodeBase36(str: string): number | null {
55
+ if (str.length !== 3) {
56
+ return null;
57
+ }
58
+
59
+ const lower = str.toLowerCase();
60
+ const firstChar = lower.charAt(0);
61
+ const secondChar = lower.charAt(1);
62
+ const thirdChar = lower.charAt(2);
63
+
64
+ // First character must be a letter (a-z)
65
+ const firstIndex = BASE36_CHARS.indexOf(firstChar);
66
+ if (firstIndex < 10 || firstIndex > 35) {
67
+ return null; // Not a letter
68
+ }
69
+
70
+ // Second and third characters can be 0-9 or a-z
71
+ const secondIndex = BASE36_CHARS.indexOf(secondChar);
72
+ const thirdIndex = BASE36_CHARS.indexOf(thirdChar);
73
+
74
+ if (secondIndex === -1 || thirdIndex === -1) {
75
+ return null;
76
+ }
77
+
78
+ // Calculate the offset from 1000
79
+ const letterOffset = firstIndex - 10; // 'a' = 0, 'b' = 1, etc.
80
+ const offset = letterOffset * 36 * 36 + secondIndex * 36 + thirdIndex;
81
+
82
+ return BASE36_START + offset;
83
+ }
84
+
85
+ /**
86
+ * Check if a string is a valid base36 project prefix.
87
+ * Valid format: starts with a-z, followed by two base36 chars (0-9, a-z).
88
+ */
89
+ export function isBase36Prefix(str: string): boolean {
90
+ if (str.length !== 3) {
91
+ return false;
92
+ }
93
+ return /^[a-z][0-9a-z]{2}$/.test(str.toLowerCase());
94
+ }
95
+
96
+ export function getRafDir(): string {
97
+ return path.resolve(process.cwd(), RAF_DIR);
98
+ }
99
+
100
+ export function ensureRafDir(): string {
101
+ const rafDir = getRafDir();
102
+ if (!fs.existsSync(rafDir)) {
103
+ fs.mkdirSync(rafDir, { recursive: true });
104
+ }
105
+ return rafDir;
106
+ }
107
+
108
+ export function getNextProjectNumber(rafDir: string): number {
109
+ if (!fs.existsSync(rafDir)) {
110
+ return 1;
111
+ }
112
+
113
+ const entries = fs.readdirSync(rafDir, { withFileTypes: true });
114
+ const numbers: number[] = [];
115
+
116
+ for (const entry of entries) {
117
+ if (entry.isDirectory()) {
118
+ // Try numeric format first (2-3 digits)
119
+ const numericMatch = entry.name.match(/^(\d{2,3})-/);
120
+ if (numericMatch && numericMatch[1]) {
121
+ numbers.push(parseInt(numericMatch[1], 10));
122
+ continue;
123
+ }
124
+
125
+ // Try base36 format
126
+ const base36Match = entry.name.match(/^([a-z][0-9a-z]{2})-/i);
127
+ if (base36Match && base36Match[1]) {
128
+ const decoded = decodeBase36(base36Match[1].toLowerCase());
129
+ if (decoded !== null) {
130
+ numbers.push(decoded);
131
+ }
132
+ }
133
+ }
134
+ }
135
+
136
+ if (numbers.length === 0) {
137
+ return 1;
138
+ }
139
+
140
+ return Math.max(...numbers) + 1;
141
+ }
142
+
143
+ export function formatProjectNumber(num: number): string {
144
+ if (num <= MAX_NUMERIC) {
145
+ return num.toString().padStart(3, '0');
146
+ }
147
+ return encodeBase36(num);
148
+ }
149
+
150
+ export function getProjectDir(rafDir: string, projectName: string): string | null {
151
+ if (!fs.existsSync(rafDir)) {
152
+ return null;
153
+ }
154
+
155
+ const entries = fs.readdirSync(rafDir, { withFileTypes: true });
156
+
157
+ for (const entry of entries) {
158
+ if (entry.isDirectory()) {
159
+ // Try numeric format first
160
+ const numericMatch = entry.name.match(/^\d{2,3}-(.+)$/);
161
+ if (numericMatch && numericMatch[1] === projectName) {
162
+ return path.join(rafDir, entry.name);
163
+ }
164
+
165
+ // Try base36 format
166
+ const base36Match = entry.name.match(/^[a-z][0-9a-z]{2}-(.+)$/i);
167
+ if (base36Match && base36Match[1] === projectName) {
168
+ return path.join(rafDir, entry.name);
169
+ }
170
+ }
171
+ }
172
+
173
+ return null;
174
+ }
175
+
176
+ /**
177
+ * Extract project number prefix from a project path.
178
+ * Supports both numeric (001-999) and base36 (a00-zzz) formats.
179
+ * E.g., "/Users/foo/RAF/001-my-project" -> "001"
180
+ * E.g., "/Users/foo/RAF/a00-my-project" -> "a00"
181
+ * Returns the prefix string (e.g., "001" or "a00") or null if not found.
182
+ */
183
+ export function extractProjectNumber(projectPath: string): string | null {
184
+ const folderName = path.basename(projectPath);
185
+
186
+ // Try numeric format first (2-3 digits)
187
+ const numericMatch = folderName.match(/^(\d{2,3})-/);
188
+ if (numericMatch && numericMatch[1]) {
189
+ return numericMatch[1];
190
+ }
191
+
192
+ // Try base36 format (letter followed by two alphanumeric chars)
193
+ const base36Match = folderName.match(/^([a-z][0-9a-z]{2})-/i);
194
+ if (base36Match && base36Match[1]) {
195
+ return base36Match[1].toLowerCase();
196
+ }
197
+
198
+ return null;
199
+ }
200
+
201
+ /**
202
+ * Parse a project prefix string to its numeric value.
203
+ * Handles both numeric (001-999) and base36 (a00-zzz) formats.
204
+ * Returns the numeric project number or null if invalid.
205
+ */
206
+ export function parseProjectPrefix(prefix: string): number | null {
207
+ // Try numeric first
208
+ if (/^\d{2,3}$/.test(prefix)) {
209
+ return parseInt(prefix, 10);
210
+ }
211
+
212
+ // Try base36
213
+ if (isBase36Prefix(prefix)) {
214
+ return decodeBase36(prefix);
215
+ }
216
+
217
+ return null;
218
+ }
219
+
220
+ /**
221
+ * Extract project name from a project path (without number prefix).
222
+ * Supports both numeric (001-999) and base36 (a00-zzz) formats.
223
+ * E.g., "/Users/foo/RAF/001-my-project" -> "my-project"
224
+ * E.g., "/Users/foo/RAF/a00-my-project" -> "my-project"
225
+ * Returns the project name or null if not found.
226
+ */
227
+ export function extractProjectName(projectPath: string): string | null {
228
+ const folderName = path.basename(projectPath);
229
+
230
+ // Try numeric format first
231
+ const numericMatch = folderName.match(/^\d{2,3}-(.+)$/);
232
+ if (numericMatch && numericMatch[1]) {
233
+ return numericMatch[1];
234
+ }
235
+
236
+ // Try base36 format
237
+ const base36Match = folderName.match(/^[a-z][0-9a-z]{2}-(.+)$/i);
238
+ if (base36Match && base36Match[1]) {
239
+ return base36Match[1];
240
+ }
241
+
242
+ return null;
243
+ }
244
+
245
+ /**
246
+ * Extract task name from a plan filename (without number prefix and extension).
247
+ * E.g., "002-fix-login-bug.md" -> "fix-login-bug"
248
+ * Returns the task name or null if not found.
249
+ */
250
+ export function extractTaskNameFromPlanFile(planFilename: string): string | null {
251
+ const basename = path.basename(planFilename, '.md');
252
+ const match = basename.match(/^\d{2,3}-(.+)$/);
253
+ return match && match[1] ? match[1] : null;
254
+ }
255
+
256
+ export function listProjects(rafDir: string): Array<{ number: number; name: string; path: string }> {
257
+ if (!fs.existsSync(rafDir)) {
258
+ return [];
259
+ }
260
+
261
+ const entries = fs.readdirSync(rafDir, { withFileTypes: true });
262
+ const projects: Array<{ number: number; name: string; path: string }> = [];
263
+
264
+ for (const entry of entries) {
265
+ if (entry.isDirectory()) {
266
+ // Try numeric format first
267
+ const numericMatch = entry.name.match(/^(\d{2,3})-(.+)$/);
268
+ if (numericMatch && numericMatch[1] && numericMatch[2]) {
269
+ projects.push({
270
+ number: parseInt(numericMatch[1], 10),
271
+ name: numericMatch[2],
272
+ path: path.join(rafDir, entry.name),
273
+ });
274
+ continue;
275
+ }
276
+
277
+ // Try base36 format
278
+ const base36Match = entry.name.match(/^([a-z][0-9a-z]{2})-(.+)$/i);
279
+ if (base36Match && base36Match[1] && base36Match[2]) {
280
+ const decoded = decodeBase36(base36Match[1].toLowerCase());
281
+ if (decoded !== null) {
282
+ projects.push({
283
+ number: decoded,
284
+ name: base36Match[2],
285
+ path: path.join(rafDir, entry.name),
286
+ });
287
+ }
288
+ }
289
+ }
290
+ }
291
+
292
+ return projects.sort((a, b) => a.number - b.number);
293
+ }
294
+
295
+ export function getPlansDir(projectPath: string): string {
296
+ return path.join(projectPath, 'plans');
297
+ }
298
+
299
+ export function getOutcomesDir(projectPath: string): string {
300
+ return path.join(projectPath, 'outcomes');
301
+ }
302
+
303
+ export function getOutcomeFilePath(projectPath: string, taskId: string, taskName: string): string {
304
+ return path.join(projectPath, 'outcomes', `${taskId}-${taskName}.md`);
305
+ }
306
+
307
+ export function getDecisionsPath(projectPath: string): string {
308
+ return path.join(projectPath, 'decisions.md');
309
+ }
310
+
311
+ export function getLogsDir(projectPath: string): string {
312
+ return path.join(projectPath, 'logs');
313
+ }
314
+
315
+ export function getInputPath(projectPath: string): string {
316
+ return path.join(projectPath, 'input.md');
317
+ }
318
+
319
+ /**
320
+ * Result of resolving a project identifier.
321
+ */
322
+ export interface ProjectResolutionResult {
323
+ /** Resolved project path (null if not found or ambiguous) */
324
+ path: string | null;
325
+ /** Error type if resolution failed */
326
+ error?: 'not_found' | 'ambiguous';
327
+ /** For ambiguous matches, list of matching projects */
328
+ matches?: Array<{ number: number; name: string; path: string; folder: string }>;
329
+ }
330
+
331
+ /**
332
+ * Parse project information from a folder name.
333
+ * Returns null if the folder name doesn't match expected project format.
334
+ */
335
+ function parseProjectFolder(
336
+ rafDir: string,
337
+ folderName: string
338
+ ): { number: number; name: string; path: string; folder: string } | null {
339
+ // Try numeric format first
340
+ const numericMatch = folderName.match(/^(\d{2,3})-(.+)$/);
341
+ if (numericMatch && numericMatch[1] && numericMatch[2]) {
342
+ return {
343
+ number: parseInt(numericMatch[1], 10),
344
+ name: numericMatch[2],
345
+ path: path.join(rafDir, folderName),
346
+ folder: folderName,
347
+ };
348
+ }
349
+
350
+ // Try base36 format
351
+ const base36Match = folderName.match(/^([a-z][0-9a-z]{2})-(.+)$/i);
352
+ if (base36Match && base36Match[1] && base36Match[2]) {
353
+ const decoded = decodeBase36(base36Match[1].toLowerCase());
354
+ if (decoded !== null) {
355
+ return {
356
+ number: decoded,
357
+ name: base36Match[2],
358
+ path: path.join(rafDir, folderName),
359
+ folder: folderName,
360
+ };
361
+ }
362
+ }
363
+
364
+ return null;
365
+ }
366
+
367
+ /**
368
+ * Resolve a project identifier with detailed result including ambiguity detection.
369
+ *
370
+ * Supported identifier formats (checked in this order):
371
+ * 1. Full folder name (e.g., "001-fix-stuff", "a01-important-project")
372
+ * - Must be an exact match to an existing folder
373
+ * - Pattern: numeric prefix (2-3 digits) or base36 prefix, followed by hyphen and name
374
+ * 2. Numeric ID (e.g., "003", "3", "1000")
375
+ * - Looks up by project number
376
+ * 3. Base36 prefix (e.g., "a00", "a01")
377
+ * - Looks up by base36 project number (for projects >= 1000)
378
+ * 4. Project name (e.g., "my-project", "fix-stuff")
379
+ * - Looks up by the name portion of the folder (after the prefix)
380
+ * - Case-insensitive matching
381
+ * - Returns error if multiple projects have the same name
382
+ *
383
+ * @param rafDir - The RAF directory containing project folders
384
+ * @param identifier - The identifier to resolve
385
+ * @returns Resolution result with path, error type, and matches for ambiguous cases
386
+ */
387
+ export function resolveProjectIdentifierWithDetails(
388
+ rafDir: string,
389
+ identifier: string
390
+ ): ProjectResolutionResult {
391
+ if (!fs.existsSync(rafDir)) {
392
+ return { path: null, error: 'not_found' };
393
+ }
394
+
395
+ // Pattern to match full folder names: NNN-name or XXX-name (base36)
396
+ // Supports 2-3 digit numeric prefixes or 3-char base36 prefixes
397
+ const fullFolderPattern = /^(\d{2,3}|[a-z][0-9a-z]{2})-(.+)$/i;
398
+ const fullFolderMatch = identifier.match(fullFolderPattern);
399
+
400
+ // Check if identifier is a full folder name (exact match required)
401
+ if (fullFolderMatch) {
402
+ // Check for exact case-insensitive match by listing directory
403
+ const entries = fs.readdirSync(rafDir, { withFileTypes: true });
404
+ for (const entry of entries) {
405
+ if (entry.isDirectory() && entry.name.toLowerCase() === identifier.toLowerCase()) {
406
+ return { path: path.join(rafDir, entry.name) };
407
+ }
408
+ }
409
+ // Full folder name format but doesn't exist as a folder.
410
+ // Fall through to name-based matching - the entire identifier might be a project name
411
+ // (e.g., "fix-double-summary-headers" looks like "fix-xxx" but is actually a name).
412
+ }
413
+
414
+ // Check if it's a numeric identifier (e.g., "003", "3", "1000")
415
+ const isNumeric = /^\d+$/.test(identifier);
416
+
417
+ // Check if it's a base36 identifier (e.g., "a00", "a01")
418
+ const isBase36 = isBase36Prefix(identifier);
419
+
420
+ // Convert base36 to numeric for matching
421
+ let targetNumber: number | null = null;
422
+ if (isNumeric) {
423
+ targetNumber = parseInt(identifier, 10);
424
+ } else if (isBase36) {
425
+ targetNumber = decodeBase36(identifier);
426
+ }
427
+
428
+ const entries = fs.readdirSync(rafDir, { withFileTypes: true });
429
+ const nameMatches: Array<{ number: number; name: string; path: string; folder: string }> = [];
430
+
431
+ for (const entry of entries) {
432
+ if (entry.isDirectory()) {
433
+ const project = parseProjectFolder(rafDir, entry.name);
434
+
435
+ if (project) {
436
+ if (targetNumber !== null) {
437
+ // Match by number (either numeric or base36 identifier)
438
+ if (project.number === targetNumber) {
439
+ return { path: project.path };
440
+ }
441
+ } else {
442
+ // Match by name (case-insensitive)
443
+ if (project.name.toLowerCase() === identifier.toLowerCase()) {
444
+ nameMatches.push(project);
445
+ }
446
+ }
447
+ }
448
+ }
449
+ }
450
+
451
+ // Handle name matches
452
+ if (nameMatches.length === 1) {
453
+ return { path: nameMatches[0]!.path };
454
+ } else if (nameMatches.length > 1) {
455
+ // Multiple projects with the same name - ambiguous
456
+ return {
457
+ path: null,
458
+ error: 'ambiguous',
459
+ matches: nameMatches.sort((a, b) => a.number - b.number),
460
+ };
461
+ }
462
+
463
+ return { path: null, error: 'not_found' };
464
+ }
465
+
466
+ /**
467
+ * Resolve a project identifier to a full project path.
468
+ *
469
+ * Supported identifier formats (checked in this order):
470
+ * 1. Full folder name (e.g., "001-fix-stuff", "a01-important-project")
471
+ * - Must be an exact match to an existing folder
472
+ * - Pattern: numeric prefix (2-3 digits) or base36 prefix, followed by hyphen and name
473
+ * 2. Numeric ID (e.g., "003", "3", "1000")
474
+ * - Looks up by project number
475
+ * 3. Base36 prefix (e.g., "a00", "a01")
476
+ * - Looks up by base36 project number (for projects >= 1000)
477
+ * 4. Project name (e.g., "my-project", "fix-stuff")
478
+ * - Looks up by the name portion of the folder (after the prefix)
479
+ * - Case-insensitive matching
480
+ *
481
+ * Note: For ambiguity detection (multiple projects with same name), use
482
+ * resolveProjectIdentifierWithDetails instead.
483
+ *
484
+ * This function is designed to be extensible for future task-level references
485
+ * like "001-project/002-task".
486
+ *
487
+ * @param rafDir - The RAF directory containing project folders
488
+ * @param identifier - The identifier to resolve
489
+ * @returns The full project path or null if not found (or ambiguous)
490
+ */
491
+ export function resolveProjectIdentifier(
492
+ rafDir: string,
493
+ identifier: string
494
+ ): string | null {
495
+ const result = resolveProjectIdentifierWithDetails(rafDir, identifier);
496
+ return result.path;
497
+ }