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.
- package/.claude/settings.local.json +32 -0
- package/CLAUDE.md +187 -0
- package/LICENSE +21 -0
- package/RAF/001-raf-task-improvements/input.md +9 -0
- package/RAF/001-raf-task-improvements/outcomes/001-add-decisions-folder.md +21 -0
- package/RAF/001-raf-task-improvements/outcomes/002-fix-write-error-on-shutdown.md +22 -0
- package/RAF/001-raf-task-improvements/outcomes/003-stash-changes-on-failure.md +34 -0
- package/RAF/001-raf-task-improvements/outcomes/004-add-project-name-to-commits.md +28 -0
- package/RAF/001-raf-task-improvements/outcomes/005-add-running-time-display.md +36 -0
- package/RAF/001-raf-task-improvements/outcomes/006-add-task-name-to-logs.md +22 -0
- package/RAF/001-raf-task-improvements/outcomes/007-show-model-at-task-start.md +52 -0
- package/RAF/001-raf-task-improvements/outcomes/009-remove-editor-placeholder-text.md +20 -0
- package/RAF/001-raf-task-improvements/outcomes/SUMMARY.md +83 -0
- package/RAF/001-raf-task-improvements/plans/001-add-decisions-folder.md +38 -0
- package/RAF/001-raf-task-improvements/plans/002-fix-write-error-on-shutdown.md +33 -0
- package/RAF/001-raf-task-improvements/plans/003-stash-changes-on-failure.md +37 -0
- package/RAF/001-raf-task-improvements/plans/004-add-project-name-to-commits.md +34 -0
- package/RAF/001-raf-task-improvements/plans/005-add-running-time-display.md +39 -0
- package/RAF/001-raf-task-improvements/plans/006-add-task-name-to-logs.md +37 -0
- package/RAF/001-raf-task-improvements/plans/009-remove-editor-placeholder-text.md +34 -0
- package/RAF/002-raf-task-improvements-execution/decisions/DECISIONS.md +13 -0
- package/RAF/002-raf-task-improvements-execution/input.md +3 -0
- package/RAF/002-raf-task-improvements-execution/outcomes/001-commit-show-model-at-task-start.md +17 -0
- package/RAF/002-raf-task-improvements-execution/outcomes/002-delete-skipped-plan.md +23 -0
- package/RAF/002-raf-task-improvements-execution/outcomes/SUMMARY.md +32 -0
- package/RAF/002-raf-task-improvements-execution/plans/001-commit-show-model-at-task-start.md +37 -0
- package/RAF/002-raf-task-improvements-execution/plans/002-delete-skipped-plan.md +23 -0
- package/RAF/003-multi-project-execution/decisions/DECISIONS.md +68 -0
- package/RAF/003-multi-project-execution/input.md +6 -0
- package/RAF/003-multi-project-execution/outcomes/001-remove-state-json.md +52 -0
- package/RAF/003-multi-project-execution/outcomes/002-update-raf-status.md +50 -0
- package/RAF/003-multi-project-execution/outcomes/003-simplify-git-logic.md +35 -0
- package/RAF/003-multi-project-execution/outcomes/004-auto-commit-planning.md +43 -0
- package/RAF/003-multi-project-execution/outcomes/005-rerun-failed-tasks.md +43 -0
- package/RAF/003-multi-project-execution/outcomes/006-multi-project-execution.md +42 -0
- package/RAF/003-multi-project-execution/outcomes/007-verify-timeout.md +54 -0
- package/RAF/003-multi-project-execution/outcomes/008-move-decisions-file.md +38 -0
- package/RAF/003-multi-project-execution/outcomes/SUMMARY.md +79 -0
- package/RAF/003-multi-project-execution/plans/001-remove-state-json.md +71 -0
- package/RAF/003-multi-project-execution/plans/002-update-raf-status.md +65 -0
- package/RAF/003-multi-project-execution/plans/003-simplify-git-logic.md +74 -0
- package/RAF/003-multi-project-execution/plans/004-auto-commit-planning.md +57 -0
- package/RAF/003-multi-project-execution/plans/005-rerun-failed-tasks.md +69 -0
- package/RAF/003-multi-project-execution/plans/006-multi-project-execution.md +81 -0
- package/RAF/003-multi-project-execution/plans/007-verify-timeout.md +63 -0
- package/RAF/003-multi-project-execution/plans/008-move-decisions-file.md +78 -0
- package/RAF/004-task-naming-optimization/decisions.md +22 -0
- package/RAF/004-task-naming-optimization/input.md +6 -0
- package/RAF/004-task-naming-optimization/outcomes/001-remove-summary-file.md +17 -0
- package/RAF/004-task-naming-optimization/outcomes/002-base36-project-numbering.md +32 -0
- package/RAF/004-task-naming-optimization/outcomes/003-improve-haiku-prompt.md +20 -0
- package/RAF/004-task-naming-optimization/outcomes/SUMMARY.md +28 -0
- package/RAF/004-task-naming-optimization/plans/001-remove-summary-file.md +34 -0
- package/RAF/004-task-naming-optimization/plans/002-base36-project-numbering.md +56 -0
- package/RAF/004-task-naming-optimization/plans/003-improve-haiku-prompt.md +50 -0
- package/RAF/005-task-naming-improvements/decisions.md +60 -0
- package/RAF/005-task-naming-improvements/input.md +2 -0
- package/RAF/005-task-naming-improvements/outcomes/001-enhance-identifier-resolution.md +42 -0
- package/RAF/005-task-naming-improvements/outcomes/002-add-identifier-support-to-status.md +38 -0
- package/RAF/005-task-naming-improvements/outcomes/003-update-do-for-full-folder-names.md +44 -0
- package/RAF/005-task-naming-improvements/outcomes/004-implement-amend-flag-for-plan.md +55 -0
- package/RAF/005-task-naming-improvements/outcomes/005-commit-outcomes-on-complete.md +47 -0
- package/RAF/005-task-naming-improvements/outcomes/006-update-execution-prompt-commit-schema.md +40 -0
- package/RAF/005-task-naming-improvements/outcomes/007-allow-pending-task-amendments.md +38 -0
- package/RAF/005-task-naming-improvements/outcomes/008-fix-timeout-label.md +24 -0
- package/RAF/005-task-naming-improvements/plans/001-enhance-identifier-resolution.md +46 -0
- package/RAF/005-task-naming-improvements/plans/002-add-identifier-support-to-status.md +36 -0
- package/RAF/005-task-naming-improvements/plans/003-update-do-for-full-folder-names.md +38 -0
- package/RAF/005-task-naming-improvements/plans/004-implement-amend-flag-for-plan.md +67 -0
- package/RAF/005-task-naming-improvements/plans/005-commit-outcomes-on-complete.md +86 -0
- package/RAF/005-task-naming-improvements/plans/006-update-execution-prompt-commit-schema.md +60 -0
- package/RAF/005-task-naming-improvements/plans/007-allow-pending-task-amendments.md +60 -0
- package/RAF/005-task-naming-improvements/plans/008-fix-timeout-label.md +31 -0
- package/RAF/006-fix-double-summary-headers/decisions.md +28 -0
- package/RAF/006-fix-double-summary-headers/input.md +3 -0
- package/RAF/006-fix-double-summary-headers/outcomes/001-fix-double-summary-headers.md +29 -0
- package/RAF/006-fix-double-summary-headers/outcomes/002-update-readme-for-npm.md +31 -0
- package/RAF/006-fix-double-summary-headers/outcomes/003-npm-publish-instructions.md +30 -0
- package/RAF/006-fix-double-summary-headers/outcomes/004-flexible-project-lookup.md +47 -0
- package/RAF/006-fix-double-summary-headers/plans/001-fix-double-summary-headers.md +42 -0
- package/RAF/006-fix-double-summary-headers/plans/002-update-readme-for-npm.md +44 -0
- package/RAF/006-fix-double-summary-headers/plans/003-npm-publish-instructions.md +45 -0
- package/RAF/006-fix-double-summary-headers/plans/004-flexible-project-lookup.md +40 -0
- package/RAF/007-improve-outcome-format/decisions.md +28 -0
- package/RAF/007-improve-outcome-format/input.md +2 -0
- package/RAF/007-improve-outcome-format/outcomes/001-update-execution-prompt.md +10 -0
- package/RAF/007-improve-outcome-format/outcomes/002-update-state-derivation.md +17 -0
- package/RAF/007-improve-outcome-format/outcomes/003-update-do-command-outcome-handling.md +16 -0
- package/RAF/007-improve-outcome-format/outcomes/004-implement-failure-analysis.md +16 -0
- package/RAF/007-improve-outcome-format/outcomes/005-update-documentation.md +15 -0
- package/RAF/007-improve-outcome-format/plans/001-update-execution-prompt.md +36 -0
- package/RAF/007-improve-outcome-format/plans/002-update-state-derivation.md +35 -0
- package/RAF/007-improve-outcome-format/plans/003-update-do-command-outcome-handling.md +37 -0
- package/RAF/007-improve-outcome-format/plans/004-implement-failure-analysis.md +44 -0
- package/RAF/007-improve-outcome-format/plans/005-update-documentation.md +33 -0
- package/RAF/008-beautiful-do/decisions.md +31 -0
- package/RAF/008-beautiful-do/input.md +1 -0
- package/RAF/008-beautiful-do/outcomes/001-terminal-symbols.md +55 -0
- package/RAF/008-beautiful-do/outcomes/002-refactor-do-output.md +95 -0
- package/RAF/008-beautiful-do/outcomes/003-refactor-status-output.md +71 -0
- package/RAF/008-beautiful-do/outcomes/004-simplify-logger.md +53 -0
- package/RAF/008-beautiful-do/outcomes/005-add-tests.md +41 -0
- package/RAF/008-beautiful-do/plans/001-terminal-symbols.md +41 -0
- package/RAF/008-beautiful-do/plans/002-refactor-do-output.md +44 -0
- package/RAF/008-beautiful-do/plans/003-refactor-status-output.md +37 -0
- package/RAF/008-beautiful-do/plans/004-simplify-logger.md +32 -0
- package/RAF/008-beautiful-do/plans/005-add-tests.md +40 -0
- package/RAF/009-system-promt-ammend/decisions.md +13 -0
- package/RAF/009-system-promt-ammend/input.md +9 -0
- package/RAF/009-system-promt-ammend/outcomes/001-model-override.md +79 -0
- package/RAF/009-system-promt-ammend/outcomes/002-system-prompt-append.md +51 -0
- package/RAF/009-system-promt-ammend/outcomes/003-retry-context.md +60 -0
- package/RAF/009-system-promt-ammend/plans/001-model-override.md +61 -0
- package/RAF/009-system-promt-ammend/plans/002-system-prompt-append.md +56 -0
- package/RAF/009-system-promt-ammend/plans/003-retry-context.md +76 -0
- package/RAF/010-outcome-marker-fallback/decisions.md +19 -0
- package/RAF/010-outcome-marker-fallback/input.md +1 -0
- package/RAF/010-outcome-marker-fallback/outcomes/001-outcome-file-marker-fallback.md +35 -0
- package/RAF/010-outcome-marker-fallback/outcomes/002-creative-project-naming.md +47 -0
- package/RAF/010-outcome-marker-fallback/plans/001-outcome-file-marker-fallback.md +58 -0
- package/RAF/010-outcome-marker-fallback/plans/002-creative-project-naming.md +68 -0
- package/RAF/011-do-task-in-commit/decisions.md +22 -0
- package/RAF/011-do-task-in-commit/input.md +1 -0
- package/RAF/011-do-task-in-commit/outcomes/001-update-execution-prompt.md +54 -0
- package/RAF/011-do-task-in-commit/outcomes/002-update-tests.md +61 -0
- package/RAF/011-do-task-in-commit/outcomes/003-update-documentation.md +51 -0
- package/RAF/011-do-task-in-commit/plans/001-update-execution-prompt.md +46 -0
- package/RAF/011-do-task-in-commit/plans/002-update-tests.md +51 -0
- package/RAF/011-do-task-in-commit/plans/003-update-documentation.md +45 -0
- package/RAF/012-name-picker-buffet/decisions.md +40 -0
- package/RAF/012-name-picker-buffet/input.md +6 -0
- package/RAF/012-name-picker-buffet/outcomes/001-name-picker-for-raf-plan.md +49 -0
- package/RAF/012-name-picker-buffet/outcomes/002-interactive-project-picker-for-raf-do.md +49 -0
- package/RAF/012-name-picker-buffet/outcomes/003-raf-status-truncation.md +55 -0
- package/RAF/012-name-picker-buffet/outcomes/004-failure-reason-details.md +65 -0
- package/RAF/012-name-picker-buffet/outcomes/005-remove-raf-commits.md +57 -0
- package/RAF/012-name-picker-buffet/outcomes/006-update-execution-prompt-for-commits.md +47 -0
- package/RAF/012-name-picker-buffet/outcomes/007-fix-plan-mode-user-prompt.md +83 -0
- package/RAF/012-name-picker-buffet/outcomes/008-add-auto-flag-for-plan-mode.md +77 -0
- package/RAF/012-name-picker-buffet/plans/001-name-picker-for-raf-plan.md +47 -0
- package/RAF/012-name-picker-buffet/plans/002-interactive-project-picker-for-raf-do.md +43 -0
- package/RAF/012-name-picker-buffet/plans/003-raf-status-truncation.md +36 -0
- package/RAF/012-name-picker-buffet/plans/004-failure-reason-details.md +46 -0
- package/RAF/012-name-picker-buffet/plans/005-remove-raf-commits.md +42 -0
- package/RAF/012-name-picker-buffet/plans/006-update-execution-prompt-for-commits.md +47 -0
- package/RAF/012-name-picker-buffet/plans/007-fix-plan-mode-user-prompt.md +55 -0
- package/RAF/012-name-picker-buffet/plans/008-add-auto-flag-for-plan-mode.md +49 -0
- package/RAF/013-dependencies-watchdog/decisions.md +37 -0
- package/RAF/013-dependencies-watchdog/input.md +1 -0
- package/RAF/013-dependencies-watchdog/outcomes/001-define-dependency-syntax.md +56 -0
- package/RAF/013-dependencies-watchdog/outcomes/002-update-planning-prompts.md +60 -0
- package/RAF/013-dependencies-watchdog/outcomes/003-parse-dependencies-update-state.md +81 -0
- package/RAF/013-dependencies-watchdog/outcomes/004-implement-dependency-checking-in-do.md +116 -0
- package/RAF/013-dependencies-watchdog/outcomes/005-update-execution-prompts.md +75 -0
- package/RAF/013-dependencies-watchdog/outcomes/006-add-tests.md +100 -0
- package/RAF/013-dependencies-watchdog/outcomes/007-add-act-alias.md +46 -0
- package/RAF/013-dependencies-watchdog/outcomes/008-add-exit-message.md +52 -0
- package/RAF/013-dependencies-watchdog/plans/001-define-dependency-syntax.md +32 -0
- package/RAF/013-dependencies-watchdog/plans/002-update-planning-prompts.md +38 -0
- package/RAF/013-dependencies-watchdog/plans/003-parse-dependencies-update-state.md +46 -0
- package/RAF/013-dependencies-watchdog/plans/004-implement-dependency-checking-in-do.md +48 -0
- package/RAF/013-dependencies-watchdog/plans/005-update-execution-prompts.md +44 -0
- package/RAF/013-dependencies-watchdog/plans/006-add-tests.md +54 -0
- package/RAF/013-dependencies-watchdog/plans/007-add-act-alias.md +26 -0
- package/RAF/013-dependencies-watchdog/plans/008-add-exit-message.md +31 -0
- package/RAF/014-watchdog/decisions.md +16 -0
- package/RAF/014-watchdog/input.md +2 -0
- package/RAF/014-watchdog/outcomes/001-amend-flag-position.md +50 -0
- package/RAF/014-watchdog/outcomes/002-details-only-on-failure.md +58 -0
- package/RAF/014-watchdog/plans/001-amend-flag-position.md +34 -0
- package/RAF/014-watchdog/plans/002-details-only-on-failure.md +46 -0
- package/RAF/015-name-lottery/decisions.md +14 -0
- package/RAF/015-name-lottery/input.md +3 -0
- package/RAF/015-name-lottery/outcomes/001-auto-pick-project-name.md +31 -0
- package/RAF/015-name-lottery/outcomes/002-mention-plan-files-in-commit.md +23 -0
- package/RAF/015-name-lottery/outcomes/003-fix-input-md-in-amend-flow.md +44 -0
- package/RAF/015-name-lottery/plans/001-auto-pick-project-name.md +38 -0
- package/RAF/015-name-lottery/plans/002-mention-plan-files-in-commit.md +32 -0
- package/RAF/015-name-lottery/plans/003-fix-input-md-in-amend-flow.md +44 -0
- package/README.md +116 -0
- package/dist/commands/do.d.ts +12 -0
- package/dist/commands/do.d.ts.map +1 -0
- package/dist/commands/do.js +684 -0
- package/dist/commands/do.js.map +1 -0
- package/dist/commands/plan.d.ts +3 -0
- package/dist/commands/plan.d.ts.map +1 -0
- package/dist/commands/plan.js +345 -0
- package/dist/commands/plan.js.map +1 -0
- package/dist/commands/status.d.ts +3 -0
- package/dist/commands/status.d.ts.map +1 -0
- package/dist/commands/status.js +117 -0
- package/dist/commands/status.js.map +1 -0
- package/dist/core/claude-runner.d.ts +78 -0
- package/dist/core/claude-runner.d.ts.map +1 -0
- package/dist/core/claude-runner.js +297 -0
- package/dist/core/claude-runner.js.map +1 -0
- package/dist/core/editor.d.ts +10 -0
- package/dist/core/editor.d.ts.map +1 -0
- package/dist/core/editor.js +77 -0
- package/dist/core/editor.js.map +1 -0
- package/dist/core/failure-analyzer.d.ts +28 -0
- package/dist/core/failure-analyzer.d.ts.map +1 -0
- package/dist/core/failure-analyzer.js +305 -0
- package/dist/core/failure-analyzer.js.map +1 -0
- package/dist/core/git.d.ts +42 -0
- package/dist/core/git.d.ts.map +1 -0
- package/dist/core/git.js +148 -0
- package/dist/core/git.js.map +1 -0
- package/dist/core/project-manager.d.ts +72 -0
- package/dist/core/project-manager.d.ts.map +1 -0
- package/dist/core/project-manager.js +193 -0
- package/dist/core/project-manager.js.map +1 -0
- package/dist/core/retry-handler.d.ts +19 -0
- package/dist/core/retry-handler.d.ts.map +1 -0
- package/dist/core/retry-handler.js +51 -0
- package/dist/core/retry-handler.js.map +1 -0
- package/dist/core/shutdown-handler.d.ts +30 -0
- package/dist/core/shutdown-handler.d.ts.map +1 -0
- package/dist/core/shutdown-handler.js +79 -0
- package/dist/core/shutdown-handler.js.map +1 -0
- package/dist/core/state-derivation.d.ts +82 -0
- package/dist/core/state-derivation.d.ts.map +1 -0
- package/dist/core/state-derivation.js +271 -0
- package/dist/core/state-derivation.js.map +1 -0
- package/dist/core/state-manager.d.ts +54 -0
- package/dist/core/state-manager.d.ts.map +1 -0
- package/dist/core/state-manager.js +198 -0
- package/dist/core/state-manager.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +16 -0
- package/dist/index.js.map +1 -0
- package/dist/parsers/output-parser.d.ts +19 -0
- package/dist/parsers/output-parser.d.ts.map +1 -0
- package/dist/parsers/output-parser.js +137 -0
- package/dist/parsers/output-parser.js.map +1 -0
- package/dist/prompts/amend.d.ts +20 -0
- package/dist/prompts/amend.d.ts.map +1 -0
- package/dist/prompts/amend.js +166 -0
- package/dist/prompts/amend.js.map +1 -0
- package/dist/prompts/execution.d.ts +30 -0
- package/dist/prompts/execution.d.ts.map +1 -0
- package/dist/prompts/execution.js +179 -0
- package/dist/prompts/execution.js.map +1 -0
- package/dist/prompts/planning.d.ts +15 -0
- package/dist/prompts/planning.d.ts.map +1 -0
- package/dist/prompts/planning.js +163 -0
- package/dist/prompts/planning.js.map +1 -0
- package/dist/types/config.d.ts +26 -0
- package/dist/types/config.d.ts.map +1 -0
- package/dist/types/config.js +7 -0
- package/dist/types/config.js.map +1 -0
- package/dist/types/state.d.ts +33 -0
- package/dist/types/state.d.ts.map +1 -0
- package/dist/types/state.js +28 -0
- package/dist/types/state.js.map +1 -0
- package/dist/ui/name-picker-subprocess.d.ts +11 -0
- package/dist/ui/name-picker-subprocess.d.ts.map +1 -0
- package/dist/ui/name-picker-subprocess.js +83 -0
- package/dist/ui/name-picker-subprocess.js.map +1 -0
- package/dist/ui/name-picker.d.ts +19 -0
- package/dist/ui/name-picker.d.ts.map +1 -0
- package/dist/ui/name-picker.js +173 -0
- package/dist/ui/name-picker.js.map +1 -0
- package/dist/ui/project-picker.d.ts +27 -0
- package/dist/ui/project-picker.d.ts.map +1 -0
- package/dist/ui/project-picker.js +58 -0
- package/dist/ui/project-picker.js.map +1 -0
- package/dist/utils/config.d.ts +24 -0
- package/dist/utils/config.d.ts.map +1 -0
- package/dist/utils/config.js +63 -0
- package/dist/utils/config.js.map +1 -0
- package/dist/utils/logger.d.ts +32 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +60 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/utils/name-generator.d.ts +20 -0
- package/dist/utils/name-generator.d.ts.map +1 -0
- package/dist/utils/name-generator.js +183 -0
- package/dist/utils/name-generator.js.map +1 -0
- package/dist/utils/paths.d.ts +132 -0
- package/dist/utils/paths.d.ts.map +1 -0
- package/dist/utils/paths.js +412 -0
- package/dist/utils/paths.js.map +1 -0
- package/dist/utils/status-line.d.ts +14 -0
- package/dist/utils/status-line.d.ts.map +1 -0
- package/dist/utils/status-line.js +36 -0
- package/dist/utils/status-line.js.map +1 -0
- package/dist/utils/terminal-symbols.d.ts +50 -0
- package/dist/utils/terminal-symbols.d.ts.map +1 -0
- package/dist/utils/terminal-symbols.js +97 -0
- package/dist/utils/terminal-symbols.js.map +1 -0
- package/dist/utils/timer.d.ts +17 -0
- package/dist/utils/timer.d.ts.map +1 -0
- package/dist/utils/timer.js +56 -0
- package/dist/utils/timer.js.map +1 -0
- package/dist/utils/validation.d.ts +17 -0
- package/dist/utils/validation.d.ts.map +1 -0
- package/dist/utils/validation.js +106 -0
- package/dist/utils/validation.js.map +1 -0
- package/dist/utils/version.d.ts +2 -0
- package/dist/utils/version.d.ts.map +1 -0
- package/dist/utils/version.js +12 -0
- package/dist/utils/version.js.map +1 -0
- package/jest.config.ts +30 -0
- package/package.json +55 -0
- package/src/commands/do.ts +829 -0
- package/src/commands/plan.ts +422 -0
- package/src/commands/status.ts +146 -0
- package/src/core/claude-runner.ts +374 -0
- package/src/core/editor.ts +85 -0
- package/src/core/failure-analyzer.ts +372 -0
- package/src/core/git.ts +166 -0
- package/src/core/project-manager.ts +243 -0
- package/src/core/retry-handler.ts +72 -0
- package/src/core/shutdown-handler.ts +93 -0
- package/src/core/state-derivation.ts +343 -0
- package/src/index.ts +20 -0
- package/src/parsers/output-parser.ts +164 -0
- package/src/prompts/amend.ts +194 -0
- package/src/prompts/execution.ts +223 -0
- package/src/prompts/planning.ts +175 -0
- package/src/types/config.ts +35 -0
- package/src/ui/name-picker-subprocess.ts +96 -0
- package/src/ui/name-picker.ts +198 -0
- package/src/ui/project-picker.ts +80 -0
- package/src/utils/config.ts +69 -0
- package/src/utils/logger.ts +81 -0
- package/src/utils/name-generator.ts +211 -0
- package/src/utils/paths.ts +497 -0
- package/src/utils/status-line.ts +45 -0
- package/src/utils/terminal-symbols.ts +124 -0
- package/src/utils/timer.ts +64 -0
- package/src/utils/validation.ts +132 -0
- package/src/utils/version.ts +12 -0
- package/tests/unit/claude-runner-interactive.test.ts +343 -0
- package/tests/unit/claude-runner.test.ts +629 -0
- package/tests/unit/command-output.test.ts +295 -0
- package/tests/unit/config.test.ts +72 -0
- package/tests/unit/dependency-integration.test.ts +559 -0
- package/tests/unit/do-blocked-tasks.test.ts +323 -0
- package/tests/unit/do-command.test.ts +198 -0
- package/tests/unit/do-multiproject.test.ts +270 -0
- package/tests/unit/do-rerun.test.ts +270 -0
- package/tests/unit/execution-prompt.test.ts +406 -0
- package/tests/unit/failure-analyzer.test.ts +276 -0
- package/tests/unit/failure-history.test.ts +143 -0
- package/tests/unit/git-stash.test.ts +138 -0
- package/tests/unit/git.test.ts +80 -0
- package/tests/unit/logger.test.ts +132 -0
- package/tests/unit/name-generator.test.ts +283 -0
- package/tests/unit/name-picker.test.ts +179 -0
- package/tests/unit/outcome-content.test.ts +166 -0
- package/tests/unit/output-parser.test.ts +178 -0
- package/tests/unit/paths.test.ts +741 -0
- package/tests/unit/plan-command-amend-flag.test.ts +115 -0
- package/tests/unit/plan-command-amend-input.test.ts +156 -0
- package/tests/unit/plan-command-auto-flag.test.ts +112 -0
- package/tests/unit/plan-command.test.ts +580 -0
- package/tests/unit/planning-prompt.test.ts +137 -0
- package/tests/unit/project-manager.test.ts +265 -0
- package/tests/unit/project-picker.test.ts +338 -0
- package/tests/unit/retry-handler.test.ts +89 -0
- package/tests/unit/state-derivation.test.ts +714 -0
- package/tests/unit/status-command.test.ts +271 -0
- package/tests/unit/status-line.test.ts +92 -0
- package/tests/unit/terminal-symbols.test.ts +214 -0
- package/tests/unit/timer.test.ts +102 -0
- package/tests/unit/validation.test.ts +118 -0
- package/tsconfig.json +26 -0
|
@@ -0,0 +1,829 @@
|
|
|
1
|
+
import * as fs from 'node:fs';
|
|
2
|
+
import { Command } from 'commander';
|
|
3
|
+
import { ProjectManager } from '../core/project-manager.js';
|
|
4
|
+
import { ClaudeRunner } from '../core/claude-runner.js';
|
|
5
|
+
import { shutdownHandler } from '../core/shutdown-handler.js';
|
|
6
|
+
import { stashChanges, hasUncommittedChanges } from '../core/git.js';
|
|
7
|
+
import { getExecutionPrompt } from '../prompts/execution.js';
|
|
8
|
+
import { parseOutput, isRetryableFailure } from '../parsers/output-parser.js';
|
|
9
|
+
import { validatePlansExist, resolveModelOption } from '../utils/validation.js';
|
|
10
|
+
import { getRafDir, extractProjectNumber, extractProjectName, extractTaskNameFromPlanFile, resolveProjectIdentifierWithDetails, getOutcomeFilePath } from '../utils/paths.js';
|
|
11
|
+
import { pickPendingProject, getPendingProjects } from '../ui/project-picker.js';
|
|
12
|
+
import { logger } from '../utils/logger.js';
|
|
13
|
+
import { getConfig } from '../utils/config.js';
|
|
14
|
+
import { createTaskTimer, formatElapsedTime } from '../utils/timer.js';
|
|
15
|
+
import { createStatusLine } from '../utils/status-line.js';
|
|
16
|
+
import {
|
|
17
|
+
SYMBOLS,
|
|
18
|
+
formatProjectHeader,
|
|
19
|
+
formatSummary,
|
|
20
|
+
formatTaskProgress,
|
|
21
|
+
} from '../utils/terminal-symbols.js';
|
|
22
|
+
import {
|
|
23
|
+
deriveProjectState,
|
|
24
|
+
getNextExecutableTask,
|
|
25
|
+
getDerivedStats,
|
|
26
|
+
isProjectComplete,
|
|
27
|
+
hasProjectFailed,
|
|
28
|
+
parseOutcomeStatus,
|
|
29
|
+
type DerivedTask,
|
|
30
|
+
type DerivedProjectState,
|
|
31
|
+
} from '../core/state-derivation.js';
|
|
32
|
+
import { analyzeFailure } from '../core/failure-analyzer.js';
|
|
33
|
+
import type { DoCommandOptions } from '../types/config.js';
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Format failure history for console output.
|
|
37
|
+
* Shows attempts that failed before eventual success or final failure.
|
|
38
|
+
* Returns empty string if no failures occurred.
|
|
39
|
+
*/
|
|
40
|
+
export function formatRetryHistoryForConsole(
|
|
41
|
+
taskId: string,
|
|
42
|
+
taskName: string,
|
|
43
|
+
failureHistory: Array<{ attempt: number; reason: string }>,
|
|
44
|
+
finalAttempt: number,
|
|
45
|
+
success: boolean
|
|
46
|
+
): string {
|
|
47
|
+
if (failureHistory.length === 0) {
|
|
48
|
+
return '';
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const lines: string[] = [];
|
|
52
|
+
const displayName = taskName !== taskId ? `${taskId} (${taskName})` : taskId;
|
|
53
|
+
lines.push(` Task ${displayName}:`);
|
|
54
|
+
|
|
55
|
+
for (const { attempt, reason } of failureHistory) {
|
|
56
|
+
lines.push(` Attempt ${attempt}: Failed - ${reason}`);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (success) {
|
|
60
|
+
lines.push(` Attempt ${finalAttempt}: Succeeded`);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return lines.join('\n');
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Retry history for a single task.
|
|
68
|
+
*/
|
|
69
|
+
interface TaskRetryHistory {
|
|
70
|
+
taskId: string;
|
|
71
|
+
taskName: string;
|
|
72
|
+
failureHistory: Array<{ attempt: number; reason: string }>;
|
|
73
|
+
finalAttempt: number;
|
|
74
|
+
success: boolean;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Result of executing a single project.
|
|
79
|
+
*/
|
|
80
|
+
interface ProjectExecutionResult {
|
|
81
|
+
projectName: string;
|
|
82
|
+
projectPath: string;
|
|
83
|
+
success: boolean;
|
|
84
|
+
tasksCompleted: number;
|
|
85
|
+
totalTasks: number;
|
|
86
|
+
error?: string;
|
|
87
|
+
retryHistory?: TaskRetryHistory[];
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export function createDoCommand(): Command {
|
|
91
|
+
const command = new Command('do')
|
|
92
|
+
.description('Execute planned tasks for one or more projects')
|
|
93
|
+
.alias('act')
|
|
94
|
+
.argument('[projects...]', 'Project identifier(s): number (3), name (my-project), or folder (001-my-project)')
|
|
95
|
+
.option('-t, --timeout <minutes>', 'Timeout per task in minutes', '60')
|
|
96
|
+
.option('-v, --verbose', 'Show full Claude output')
|
|
97
|
+
.option('-d, --debug', 'Save all logs and show debug output')
|
|
98
|
+
.option('-f, --force', 'Re-run all tasks regardless of status')
|
|
99
|
+
.option('-m, --model <name>', 'Claude model to use (sonnet, haiku, opus)')
|
|
100
|
+
.option('--sonnet', 'Use Sonnet model (shorthand for --model sonnet)')
|
|
101
|
+
.action(async (projects: string[], options: DoCommandOptions) => {
|
|
102
|
+
await runDoCommand(projects, options);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
return command;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
async function runDoCommand(projectIdentifiersArg: string[], options: DoCommandOptions): Promise<void> {
|
|
109
|
+
const rafDir = getRafDir();
|
|
110
|
+
let projectIdentifiers = projectIdentifiersArg;
|
|
111
|
+
|
|
112
|
+
// Validate and resolve model option
|
|
113
|
+
let model: string;
|
|
114
|
+
try {
|
|
115
|
+
model = resolveModelOption(options.model as string | undefined, options.sonnet);
|
|
116
|
+
} catch (error) {
|
|
117
|
+
logger.error((error as Error).message);
|
|
118
|
+
process.exit(1);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Handle empty project list - show interactive picker
|
|
122
|
+
if (projectIdentifiers.length === 0) {
|
|
123
|
+
// Check if there are any pending projects
|
|
124
|
+
const pendingProjects = getPendingProjects(rafDir);
|
|
125
|
+
|
|
126
|
+
if (pendingProjects.length === 0) {
|
|
127
|
+
logger.info('No pending projects found.');
|
|
128
|
+
logger.info("Run 'raf plan' to create a new project.");
|
|
129
|
+
process.exit(0);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
try {
|
|
133
|
+
const selectedProject = await pickPendingProject(rafDir);
|
|
134
|
+
|
|
135
|
+
if (!selectedProject) {
|
|
136
|
+
// This shouldn't happen since we already checked pendingProjects.length
|
|
137
|
+
logger.info('No pending projects found.');
|
|
138
|
+
process.exit(0);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Use the selected project
|
|
142
|
+
projectIdentifiers = [selectedProject];
|
|
143
|
+
} catch (error) {
|
|
144
|
+
// Handle Ctrl+C (user cancellation)
|
|
145
|
+
if (error instanceof Error && error.message.includes('User force closed')) {
|
|
146
|
+
process.exit(0);
|
|
147
|
+
}
|
|
148
|
+
throw error;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Resolve all project identifiers and remove duplicates
|
|
153
|
+
const resolvedProjects: Array<{ identifier: string; path: string; name: string }> = [];
|
|
154
|
+
const seenPaths = new Set<string>();
|
|
155
|
+
const errors: Array<{ identifier: string; error: string }> = [];
|
|
156
|
+
|
|
157
|
+
for (const identifier of projectIdentifiers) {
|
|
158
|
+
const result = resolveProjectIdentifierWithDetails(rafDir, identifier);
|
|
159
|
+
|
|
160
|
+
if (!result.path) {
|
|
161
|
+
if (result.error === 'ambiguous' && result.matches) {
|
|
162
|
+
const matchList = result.matches
|
|
163
|
+
.map((m) => ` - ${m.folder}`)
|
|
164
|
+
.join('\n');
|
|
165
|
+
errors.push({
|
|
166
|
+
identifier,
|
|
167
|
+
error: `Ambiguous project name. Multiple projects match:\n${matchList}\nPlease specify the project ID or full folder name.`,
|
|
168
|
+
});
|
|
169
|
+
} else {
|
|
170
|
+
errors.push({ identifier, error: 'Project not found' });
|
|
171
|
+
}
|
|
172
|
+
continue;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const projectPath = result.path;
|
|
176
|
+
|
|
177
|
+
// Skip duplicates
|
|
178
|
+
if (seenPaths.has(projectPath)) {
|
|
179
|
+
logger.info(`Skipping duplicate: ${identifier}`);
|
|
180
|
+
continue;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
seenPaths.add(projectPath);
|
|
184
|
+
const projectName = extractProjectName(projectPath) ?? identifier;
|
|
185
|
+
resolvedProjects.push({ identifier, path: projectPath, name: projectName });
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Report resolution errors
|
|
189
|
+
for (const { identifier, error } of errors) {
|
|
190
|
+
logger.error(`${identifier}: ${error}`);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
if (resolvedProjects.length === 0) {
|
|
194
|
+
logger.error('No valid projects to execute.');
|
|
195
|
+
logger.info("Run 'raf status' to see available projects.");
|
|
196
|
+
process.exit(1);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// Get configuration
|
|
200
|
+
const config = getConfig();
|
|
201
|
+
const timeout = Number(options.timeout) || config.timeout;
|
|
202
|
+
const verbose = options.verbose ?? false;
|
|
203
|
+
const debug = options.debug ?? false;
|
|
204
|
+
const force = options.force ?? false;
|
|
205
|
+
const maxRetries = config.maxRetries;
|
|
206
|
+
const autoCommit = config.autoCommit;
|
|
207
|
+
|
|
208
|
+
// Configure logger
|
|
209
|
+
logger.configure({ verbose, debug });
|
|
210
|
+
|
|
211
|
+
// Log Claude model name once (verbose mode only for multi-project)
|
|
212
|
+
if (verbose && model && resolvedProjects.length > 1) {
|
|
213
|
+
logger.info(`Using model: ${model}`);
|
|
214
|
+
logger.newline();
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// Execute projects
|
|
218
|
+
const results: ProjectExecutionResult[] = [];
|
|
219
|
+
const isMultiProject = resolvedProjects.length > 1;
|
|
220
|
+
|
|
221
|
+
for (const [i, project] of resolvedProjects.entries()) {
|
|
222
|
+
if (isMultiProject && verbose) {
|
|
223
|
+
logger.info(`=== Project ${i + 1}/${resolvedProjects.length}: ${project.name} ===`);
|
|
224
|
+
logger.newline();
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
try {
|
|
228
|
+
const result = await executeSingleProject(
|
|
229
|
+
project.path,
|
|
230
|
+
project.name,
|
|
231
|
+
{
|
|
232
|
+
timeout,
|
|
233
|
+
verbose,
|
|
234
|
+
debug,
|
|
235
|
+
force,
|
|
236
|
+
maxRetries,
|
|
237
|
+
autoCommit,
|
|
238
|
+
showModel: !isMultiProject, // Only show model for single project
|
|
239
|
+
model,
|
|
240
|
+
}
|
|
241
|
+
);
|
|
242
|
+
results.push(result);
|
|
243
|
+
} catch (error) {
|
|
244
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
245
|
+
results.push({
|
|
246
|
+
projectName: project.name,
|
|
247
|
+
projectPath: project.path,
|
|
248
|
+
success: false,
|
|
249
|
+
tasksCompleted: 0,
|
|
250
|
+
totalTasks: 0,
|
|
251
|
+
error: errorMessage,
|
|
252
|
+
});
|
|
253
|
+
logger.error(`Project ${project.name} failed: ${errorMessage}`);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
if (isMultiProject && i < resolvedProjects.length - 1) {
|
|
257
|
+
logger.newline();
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// Show multi-project summary
|
|
262
|
+
if (isMultiProject) {
|
|
263
|
+
printMultiProjectSummary(results, verbose);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// Exit with appropriate code
|
|
267
|
+
const anyFailed = results.some((r) => !r.success);
|
|
268
|
+
if (anyFailed) {
|
|
269
|
+
process.exit(1);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
interface SingleProjectOptions {
|
|
274
|
+
timeout: number;
|
|
275
|
+
verbose: boolean;
|
|
276
|
+
debug: boolean;
|
|
277
|
+
force: boolean;
|
|
278
|
+
maxRetries: number;
|
|
279
|
+
autoCommit: boolean;
|
|
280
|
+
showModel: boolean;
|
|
281
|
+
model: string;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
async function executeSingleProject(
|
|
285
|
+
projectPath: string,
|
|
286
|
+
projectName: string,
|
|
287
|
+
options: SingleProjectOptions
|
|
288
|
+
): Promise<ProjectExecutionResult> {
|
|
289
|
+
const { timeout, verbose, debug, force, maxRetries, autoCommit, showModel, model } = options;
|
|
290
|
+
|
|
291
|
+
if (!validatePlansExist(projectPath)) {
|
|
292
|
+
return {
|
|
293
|
+
projectName,
|
|
294
|
+
projectPath,
|
|
295
|
+
success: false,
|
|
296
|
+
tasksCompleted: 0,
|
|
297
|
+
totalTasks: 0,
|
|
298
|
+
error: 'No plan files found. Run planning first.',
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// Derive state from folder structure
|
|
303
|
+
let state = deriveProjectState(projectPath);
|
|
304
|
+
|
|
305
|
+
// Check if project is already complete
|
|
306
|
+
if (isProjectComplete(state) && !force) {
|
|
307
|
+
if (verbose) {
|
|
308
|
+
logger.info('All tasks completed. Use --force to re-run.');
|
|
309
|
+
} else {
|
|
310
|
+
const stats = getDerivedStats(state);
|
|
311
|
+
logger.info(formatSummary(stats.completed, stats.failed, stats.pending, undefined, stats.blocked));
|
|
312
|
+
}
|
|
313
|
+
const stats = getDerivedStats(state);
|
|
314
|
+
return {
|
|
315
|
+
projectName,
|
|
316
|
+
projectPath,
|
|
317
|
+
success: true,
|
|
318
|
+
tasksCompleted: stats.completed,
|
|
319
|
+
totalTasks: stats.total,
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// Set up shutdown handler
|
|
324
|
+
const claudeRunner = new ClaudeRunner({ model });
|
|
325
|
+
const projectManager = new ProjectManager();
|
|
326
|
+
shutdownHandler.init();
|
|
327
|
+
shutdownHandler.registerClaudeRunner(claudeRunner);
|
|
328
|
+
|
|
329
|
+
// Start project timer
|
|
330
|
+
const projectStartTime = Date.now();
|
|
331
|
+
|
|
332
|
+
if (verbose) {
|
|
333
|
+
logger.info(`Executing project: ${projectName}`);
|
|
334
|
+
logger.info(`Tasks: ${state.tasks.length}, Task timeout: ${timeout} minutes`);
|
|
335
|
+
|
|
336
|
+
// Log Claude model name
|
|
337
|
+
if (showModel && model) {
|
|
338
|
+
logger.info(`Using model: ${model}`);
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
logger.newline();
|
|
342
|
+
} else {
|
|
343
|
+
// Minimal mode: show project header
|
|
344
|
+
logger.info(formatProjectHeader(projectName, state.tasks.length));
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// Execute tasks
|
|
348
|
+
const totalTasks = state.tasks.length;
|
|
349
|
+
|
|
350
|
+
// Track tasks completed in this session (for force mode)
|
|
351
|
+
const completedInSession = new Set<string>();
|
|
352
|
+
|
|
353
|
+
// Track retry history for console output at the end
|
|
354
|
+
const projectRetryHistory: TaskRetryHistory[] = [];
|
|
355
|
+
|
|
356
|
+
// Helper function to get the next task to process (including blocked tasks for outcome generation)
|
|
357
|
+
function getNextTaskToProcess(currentState: DerivedProjectState): DerivedTask | null {
|
|
358
|
+
if (force) {
|
|
359
|
+
// Find first task that hasn't been executed in this session
|
|
360
|
+
for (const t of currentState.tasks) {
|
|
361
|
+
if (!completedInSession.has(t.id)) {
|
|
362
|
+
return t;
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
return null;
|
|
366
|
+
}
|
|
367
|
+
// Normal mode: first check for blocked tasks that need outcome files generated
|
|
368
|
+
for (const t of currentState.tasks) {
|
|
369
|
+
if (t.status === 'blocked') {
|
|
370
|
+
const outcomeFilePath = getOutcomeFilePath(projectPath, t.id, extractTaskNameFromPlanFile(t.planFile) ?? t.id);
|
|
371
|
+
// Only return blocked task if it doesn't have an outcome file yet
|
|
372
|
+
if (!fs.existsSync(outcomeFilePath)) {
|
|
373
|
+
return t;
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
// Then get next executable task (pending or failed)
|
|
378
|
+
return getNextExecutableTask(currentState);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
/**
|
|
382
|
+
* Generate a blocked outcome file for a task.
|
|
383
|
+
* @param task - The blocked task
|
|
384
|
+
* @param taskState - Current state to find which dependencies caused the block
|
|
385
|
+
*/
|
|
386
|
+
function generateBlockedOutcome(task: DerivedTask, taskState: DerivedProjectState): string {
|
|
387
|
+
// Find which dependencies are failed or blocked
|
|
388
|
+
const failedDeps: string[] = [];
|
|
389
|
+
const blockedDeps: string[] = [];
|
|
390
|
+
|
|
391
|
+
for (const depId of task.dependencies) {
|
|
392
|
+
const depTask = taskState.tasks.find((t) => t.id === depId);
|
|
393
|
+
if (depTask) {
|
|
394
|
+
if (depTask.status === 'failed') {
|
|
395
|
+
failedDeps.push(depId);
|
|
396
|
+
} else if (depTask.status === 'blocked') {
|
|
397
|
+
blockedDeps.push(depId);
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
const lines: string[] = [
|
|
403
|
+
`# Outcome: Task ${task.id} Blocked`,
|
|
404
|
+
'',
|
|
405
|
+
'## Summary',
|
|
406
|
+
'',
|
|
407
|
+
'This task was automatically blocked because one or more of its dependencies failed or are blocked.',
|
|
408
|
+
'',
|
|
409
|
+
'## Blocking Dependencies',
|
|
410
|
+
'',
|
|
411
|
+
];
|
|
412
|
+
|
|
413
|
+
if (failedDeps.length > 0) {
|
|
414
|
+
lines.push(`**Failed dependencies**: ${failedDeps.join(', ')}`);
|
|
415
|
+
}
|
|
416
|
+
if (blockedDeps.length > 0) {
|
|
417
|
+
lines.push(`**Blocked dependencies**: ${blockedDeps.join(', ')}`);
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
lines.push('');
|
|
421
|
+
lines.push(`**Task dependencies**: ${task.dependencies.join(', ')}`);
|
|
422
|
+
lines.push('');
|
|
423
|
+
lines.push('## Resolution');
|
|
424
|
+
lines.push('');
|
|
425
|
+
lines.push('To unblock this task:');
|
|
426
|
+
lines.push('1. Fix the failed dependency task(s)');
|
|
427
|
+
lines.push('2. Re-run the project with `raf do`');
|
|
428
|
+
lines.push('');
|
|
429
|
+
lines.push('<promise>BLOCKED</promise>');
|
|
430
|
+
|
|
431
|
+
return lines.join('\n');
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
let task = getNextTaskToProcess(state);
|
|
435
|
+
|
|
436
|
+
while (task) {
|
|
437
|
+
const taskIndex = state.tasks.findIndex((t) => t.id === task!.id);
|
|
438
|
+
const taskNumber = taskIndex + 1;
|
|
439
|
+
const taskName = extractTaskNameFromPlanFile(task.planFile);
|
|
440
|
+
const displayName = taskName ?? task.id;
|
|
441
|
+
|
|
442
|
+
// Handle blocked tasks separately - skip Claude execution
|
|
443
|
+
if (task.status === 'blocked') {
|
|
444
|
+
// Find which dependency caused the block for the message
|
|
445
|
+
const failedOrBlockedDeps = task.dependencies.filter((depId) => {
|
|
446
|
+
const depTask = state.tasks.find((t) => t.id === depId);
|
|
447
|
+
return depTask && (depTask.status === 'failed' || depTask.status === 'blocked');
|
|
448
|
+
});
|
|
449
|
+
const blockingDep = failedOrBlockedDeps[0] ?? task.dependencies[0];
|
|
450
|
+
|
|
451
|
+
if (verbose) {
|
|
452
|
+
const taskContext = `[Task ${taskNumber}/${totalTasks}: ${displayName}]`;
|
|
453
|
+
logger.setContext(taskContext);
|
|
454
|
+
logger.warn(`Task ${task.id} blocked by failed dependency: ${blockingDep}`);
|
|
455
|
+
} else {
|
|
456
|
+
// Minimal mode: show blocked task line with distinct symbol
|
|
457
|
+
logger.info(formatTaskProgress(taskNumber, totalTasks, 'blocked', displayName));
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
// Generate blocked outcome file
|
|
461
|
+
const blockedOutcome = generateBlockedOutcome(task, state);
|
|
462
|
+
projectManager.saveOutcome(projectPath, task.id, blockedOutcome);
|
|
463
|
+
|
|
464
|
+
completedInSession.add(task.id);
|
|
465
|
+
logger.clearContext();
|
|
466
|
+
|
|
467
|
+
// Re-derive state to cascade blocking to dependent tasks
|
|
468
|
+
state = deriveProjectState(projectPath);
|
|
469
|
+
task = getNextTaskToProcess(state);
|
|
470
|
+
continue;
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
if (verbose) {
|
|
474
|
+
const taskContext = `[Task ${taskNumber}/${totalTasks}: ${displayName}]`;
|
|
475
|
+
logger.setContext(taskContext);
|
|
476
|
+
|
|
477
|
+
// Log task execution status
|
|
478
|
+
if (task.status === 'failed') {
|
|
479
|
+
logger.info(`Retrying task ${task.id} (previously failed)...`);
|
|
480
|
+
} else if (task.status === 'completed' && force) {
|
|
481
|
+
logger.info(`Re-running task ${task.id} (force mode)...`);
|
|
482
|
+
} else {
|
|
483
|
+
logger.info(`Executing task ${task.id}...`);
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
// Get previous outcomes for context
|
|
488
|
+
const previousOutcomes = projectManager.readOutcomes(projectPath);
|
|
489
|
+
|
|
490
|
+
// Get dependency outcomes - filter to only include outcomes for tasks this task depends on
|
|
491
|
+
const dependencyIds = task.dependencies;
|
|
492
|
+
const dependencyOutcomes = dependencyIds.length > 0
|
|
493
|
+
? previousOutcomes.filter((o) => dependencyIds.includes(o.taskId))
|
|
494
|
+
: [];
|
|
495
|
+
|
|
496
|
+
// Extract project number for commit message
|
|
497
|
+
const projectNumber = extractProjectNumber(projectPath) ?? '000';
|
|
498
|
+
|
|
499
|
+
// Compute outcome file path for this task
|
|
500
|
+
const outcomeFilePath = getOutcomeFilePath(projectPath, task.id, displayName);
|
|
501
|
+
|
|
502
|
+
// Execute with retries
|
|
503
|
+
let success = false;
|
|
504
|
+
let attempts = 0;
|
|
505
|
+
let lastOutput = '';
|
|
506
|
+
let failureReason = '';
|
|
507
|
+
// Track failure history for each attempt (attempt number -> reason)
|
|
508
|
+
const failureHistory: Array<{ attempt: number; reason: string }> = [];
|
|
509
|
+
|
|
510
|
+
// Set up timer for elapsed time tracking
|
|
511
|
+
const statusLine = createStatusLine();
|
|
512
|
+
const timer = createTaskTimer(verbose ? undefined : (elapsed) => {
|
|
513
|
+
// Show running status with task name and timer (updates in place)
|
|
514
|
+
statusLine.update(formatTaskProgress(taskNumber, totalTasks, 'running', displayName, elapsed));
|
|
515
|
+
});
|
|
516
|
+
timer.start();
|
|
517
|
+
|
|
518
|
+
while (!success && attempts < maxRetries) {
|
|
519
|
+
attempts++;
|
|
520
|
+
|
|
521
|
+
if (verbose && attempts > 1) {
|
|
522
|
+
logger.info(` Retry ${attempts}/${maxRetries}...`);
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
// Build execution prompt (inside loop to include retry context on retries)
|
|
526
|
+
// Check if previous outcome file exists for retry context
|
|
527
|
+
const previousOutcomeFileForRetry = attempts > 1 && fs.existsSync(outcomeFilePath)
|
|
528
|
+
? outcomeFilePath
|
|
529
|
+
: undefined;
|
|
530
|
+
|
|
531
|
+
const prompt = getExecutionPrompt({
|
|
532
|
+
projectPath,
|
|
533
|
+
planPath: projectManager.getPlanPath(projectPath, task.planFile),
|
|
534
|
+
taskId: task.id,
|
|
535
|
+
taskNumber,
|
|
536
|
+
totalTasks,
|
|
537
|
+
previousOutcomes,
|
|
538
|
+
autoCommit,
|
|
539
|
+
projectNumber,
|
|
540
|
+
outcomeFilePath,
|
|
541
|
+
attemptNumber: attempts,
|
|
542
|
+
previousOutcomeFile: previousOutcomeFileForRetry,
|
|
543
|
+
dependencyIds,
|
|
544
|
+
dependencyOutcomes,
|
|
545
|
+
});
|
|
546
|
+
|
|
547
|
+
// Run Claude
|
|
548
|
+
const result = verbose
|
|
549
|
+
? await claudeRunner.runVerbose(prompt, { timeout })
|
|
550
|
+
: await claudeRunner.run(prompt, { timeout });
|
|
551
|
+
|
|
552
|
+
lastOutput = result.output;
|
|
553
|
+
|
|
554
|
+
// Parse result
|
|
555
|
+
const parsed = parseOutput(result.output);
|
|
556
|
+
|
|
557
|
+
if (result.timedOut) {
|
|
558
|
+
failureReason = 'Task timed out';
|
|
559
|
+
failureHistory.push({ attempt: attempts, reason: failureReason });
|
|
560
|
+
if (!isRetryableFailure(parsed)) {
|
|
561
|
+
break;
|
|
562
|
+
}
|
|
563
|
+
continue;
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
if (result.contextOverflow || parsed.contextOverflow) {
|
|
567
|
+
failureReason = 'Context overflow - task too large';
|
|
568
|
+
failureHistory.push({ attempt: attempts, reason: failureReason });
|
|
569
|
+
break; // Not retryable
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
if (parsed.result === 'complete') {
|
|
573
|
+
success = true;
|
|
574
|
+
} else if (parsed.result === 'failed') {
|
|
575
|
+
failureReason = parsed.failureReason ?? 'Unknown failure';
|
|
576
|
+
failureHistory.push({ attempt: attempts, reason: failureReason });
|
|
577
|
+
if (!isRetryableFailure(parsed)) {
|
|
578
|
+
break;
|
|
579
|
+
}
|
|
580
|
+
} else {
|
|
581
|
+
// Unknown result - check outcome file as fallback
|
|
582
|
+
if (fs.existsSync(outcomeFilePath)) {
|
|
583
|
+
const outcomeContent = fs.readFileSync(outcomeFilePath, 'utf-8');
|
|
584
|
+
const outcomeStatus = parseOutcomeStatus(outcomeContent);
|
|
585
|
+
if (outcomeStatus === 'completed') {
|
|
586
|
+
success = true;
|
|
587
|
+
} else if (outcomeStatus === 'failed') {
|
|
588
|
+
failureReason = 'Task failed (from outcome file)';
|
|
589
|
+
failureHistory.push({ attempt: attempts, reason: failureReason });
|
|
590
|
+
} else {
|
|
591
|
+
failureReason = 'No completion marker found in output or outcome file';
|
|
592
|
+
failureHistory.push({ attempt: attempts, reason: failureReason });
|
|
593
|
+
}
|
|
594
|
+
} else {
|
|
595
|
+
failureReason = 'No completion marker found';
|
|
596
|
+
failureHistory.push({ attempt: attempts, reason: failureReason });
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
// Stop timer and clear status line
|
|
602
|
+
const elapsedMs = timer.stop();
|
|
603
|
+
statusLine.clear();
|
|
604
|
+
const elapsedFormatted = formatElapsedTime(elapsedMs);
|
|
605
|
+
|
|
606
|
+
// Save log if debug mode or failure
|
|
607
|
+
if (debug || !success) {
|
|
608
|
+
projectManager.saveLog(projectPath, task.id, lastOutput);
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
// Track retry history if there were failures (for console output)
|
|
612
|
+
if (failureHistory.length > 0) {
|
|
613
|
+
projectRetryHistory.push({
|
|
614
|
+
taskId: task.id,
|
|
615
|
+
taskName: displayName,
|
|
616
|
+
failureHistory,
|
|
617
|
+
finalAttempt: attempts,
|
|
618
|
+
success,
|
|
619
|
+
});
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
if (success) {
|
|
623
|
+
// Check if Claude wrote an outcome file with valid marker
|
|
624
|
+
// If so, keep it as-is; otherwise create fallback
|
|
625
|
+
// NOTE: Successful outcomes do NOT get ## Details section appended
|
|
626
|
+
let outcomeContent: string;
|
|
627
|
+
const claudeWroteOutcome = fs.existsSync(outcomeFilePath);
|
|
628
|
+
|
|
629
|
+
if (claudeWroteOutcome) {
|
|
630
|
+
const existingContent = fs.readFileSync(outcomeFilePath, 'utf-8');
|
|
631
|
+
const status = parseOutcomeStatus(existingContent);
|
|
632
|
+
|
|
633
|
+
if (status === 'completed') {
|
|
634
|
+
// Claude wrote a valid outcome - keep it as-is (no metadata added)
|
|
635
|
+
outcomeContent = existingContent;
|
|
636
|
+
} else {
|
|
637
|
+
// Outcome file exists but no valid COMPLETE marker - create fallback
|
|
638
|
+
outcomeContent = `## Status: SUCCESS
|
|
639
|
+
|
|
640
|
+
# Task ${task.id} - Completed
|
|
641
|
+
|
|
642
|
+
Task completed. No detailed report provided.
|
|
643
|
+
|
|
644
|
+
<promise>COMPLETE</promise>
|
|
645
|
+
`;
|
|
646
|
+
}
|
|
647
|
+
} else {
|
|
648
|
+
// No outcome file - create fallback
|
|
649
|
+
outcomeContent = `## Status: SUCCESS
|
|
650
|
+
|
|
651
|
+
# Task ${task.id} - Completed
|
|
652
|
+
|
|
653
|
+
Task completed. No detailed report provided.
|
|
654
|
+
|
|
655
|
+
<promise>COMPLETE</promise>
|
|
656
|
+
`;
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
projectManager.saveOutcome(projectPath, task.id, outcomeContent);
|
|
660
|
+
|
|
661
|
+
if (verbose) {
|
|
662
|
+
logger.success(` Task ${task.id} completed (${elapsedFormatted})`);
|
|
663
|
+
} else {
|
|
664
|
+
// Minimal mode: show completed task line
|
|
665
|
+
logger.info(formatTaskProgress(taskNumber, totalTasks, 'completed', displayName, elapsedMs));
|
|
666
|
+
}
|
|
667
|
+
completedInSession.add(task.id);
|
|
668
|
+
} else {
|
|
669
|
+
// Stash any uncommitted changes on complete failure
|
|
670
|
+
let stashName: string | undefined;
|
|
671
|
+
if (hasUncommittedChanges()) {
|
|
672
|
+
const projectNum = extractProjectNumber(projectPath) ?? '000';
|
|
673
|
+
stashName = `raf-${projectNum}-task-${task.id}-failed`;
|
|
674
|
+
const stashed = stashChanges(stashName);
|
|
675
|
+
if (verbose && stashed) {
|
|
676
|
+
logger.info(` Changes stashed as: ${stashName}`);
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
if (verbose) {
|
|
681
|
+
logger.error(` Task ${task.id} failed: ${failureReason} (${elapsedFormatted})`);
|
|
682
|
+
logger.info(' Analyzing failure...');
|
|
683
|
+
} else {
|
|
684
|
+
// Minimal mode: show failed task line
|
|
685
|
+
logger.info(formatTaskProgress(taskNumber, totalTasks, 'failed', displayName, elapsedMs));
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
// Analyze failure and generate structured report
|
|
689
|
+
const analysisReport = await analyzeFailure(lastOutput, failureReason, task.id);
|
|
690
|
+
|
|
691
|
+
// Save failure outcome with status marker, analysis, and details
|
|
692
|
+
// NOTE: Failed outcomes keep ## Details section for debugging
|
|
693
|
+
const outcomeContent = `## Status: FAILED
|
|
694
|
+
|
|
695
|
+
# Task ${task.id} - Failed
|
|
696
|
+
|
|
697
|
+
${analysisReport}
|
|
698
|
+
|
|
699
|
+
## Details
|
|
700
|
+
- Attempts: ${attempts}
|
|
701
|
+
- Elapsed time: ${elapsedFormatted}
|
|
702
|
+
- Failed at: ${new Date().toISOString()}
|
|
703
|
+
${stashName ? `- Stash: ${stashName}` : ''}
|
|
704
|
+
`;
|
|
705
|
+
projectManager.saveOutcome(projectPath, task.id, outcomeContent);
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
if (verbose) {
|
|
709
|
+
logger.newline();
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
// Clear context before next task
|
|
713
|
+
logger.clearContext();
|
|
714
|
+
|
|
715
|
+
// Re-derive state to get updated task statuses
|
|
716
|
+
state = deriveProjectState(projectPath);
|
|
717
|
+
|
|
718
|
+
// Get next task to process
|
|
719
|
+
task = getNextTaskToProcess(state);
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
// Ensure context is cleared for summary
|
|
723
|
+
logger.clearContext();
|
|
724
|
+
|
|
725
|
+
// Get final stats
|
|
726
|
+
const stats = getDerivedStats(state);
|
|
727
|
+
const projectElapsedMs = Date.now() - projectStartTime;
|
|
728
|
+
|
|
729
|
+
if (isProjectComplete(state)) {
|
|
730
|
+
if (verbose) {
|
|
731
|
+
logger.success('All tasks completed!');
|
|
732
|
+
|
|
733
|
+
// Verbose summary
|
|
734
|
+
logger.newline();
|
|
735
|
+
logger.info('Summary:');
|
|
736
|
+
logger.info(` Completed: ${stats.completed}`);
|
|
737
|
+
logger.info(` Failed: ${stats.failed}`);
|
|
738
|
+
logger.info(` Blocked: ${stats.blocked}`);
|
|
739
|
+
logger.info(` Pending: ${stats.pending}`);
|
|
740
|
+
} else {
|
|
741
|
+
// Minimal summary with elapsed time
|
|
742
|
+
logger.info(formatSummary(stats.completed, stats.failed, stats.pending, projectElapsedMs, stats.blocked));
|
|
743
|
+
}
|
|
744
|
+
} else if (hasProjectFailed(state)) {
|
|
745
|
+
if (verbose) {
|
|
746
|
+
logger.warn('Some tasks failed.');
|
|
747
|
+
logger.newline();
|
|
748
|
+
logger.info('Summary:');
|
|
749
|
+
logger.info(` Completed: ${stats.completed}`);
|
|
750
|
+
logger.info(` Failed: ${stats.failed}`);
|
|
751
|
+
logger.info(` Blocked: ${stats.blocked}`);
|
|
752
|
+
logger.info(` Pending: ${stats.pending}`);
|
|
753
|
+
} else {
|
|
754
|
+
// Minimal summary for failures
|
|
755
|
+
logger.info(formatSummary(stats.completed, stats.failed, stats.pending, projectElapsedMs, stats.blocked));
|
|
756
|
+
}
|
|
757
|
+
} else {
|
|
758
|
+
// Project incomplete (pending tasks remain)
|
|
759
|
+
if (verbose) {
|
|
760
|
+
logger.newline();
|
|
761
|
+
logger.info('Summary:');
|
|
762
|
+
logger.info(` Completed: ${stats.completed}`);
|
|
763
|
+
logger.info(` Failed: ${stats.failed}`);
|
|
764
|
+
logger.info(` Blocked: ${stats.blocked}`);
|
|
765
|
+
logger.info(` Pending: ${stats.pending}`);
|
|
766
|
+
} else {
|
|
767
|
+
logger.info(formatSummary(stats.completed, stats.failed, stats.pending, projectElapsedMs, stats.blocked));
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
// Show retry history for tasks that had failures (even if eventually successful)
|
|
772
|
+
if (projectRetryHistory.length > 0) {
|
|
773
|
+
logger.newline();
|
|
774
|
+
logger.info('Retry history:');
|
|
775
|
+
for (const history of projectRetryHistory) {
|
|
776
|
+
const retryOutput = formatRetryHistoryForConsole(
|
|
777
|
+
history.taskId,
|
|
778
|
+
history.taskName,
|
|
779
|
+
history.failureHistory,
|
|
780
|
+
history.finalAttempt,
|
|
781
|
+
history.success
|
|
782
|
+
);
|
|
783
|
+
if (retryOutput) {
|
|
784
|
+
logger.info(retryOutput);
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
return {
|
|
790
|
+
projectName,
|
|
791
|
+
projectPath,
|
|
792
|
+
success: stats.failed === 0 && stats.pending === 0,
|
|
793
|
+
tasksCompleted: stats.completed,
|
|
794
|
+
totalTasks: stats.total,
|
|
795
|
+
retryHistory: projectRetryHistory,
|
|
796
|
+
};
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
function printMultiProjectSummary(results: ProjectExecutionResult[], verbose: boolean): void {
|
|
800
|
+
logger.newline();
|
|
801
|
+
|
|
802
|
+
if (verbose) {
|
|
803
|
+
logger.info('=== Multi-Project Summary ===');
|
|
804
|
+
logger.newline();
|
|
805
|
+
|
|
806
|
+
for (const result of results) {
|
|
807
|
+
const statusSymbol = result.success ? SYMBOLS.completed : SYMBOLS.failed;
|
|
808
|
+
const statusText = result.success
|
|
809
|
+
? `Completed (${result.tasksCompleted}/${result.totalTasks} tasks)`
|
|
810
|
+
: result.error
|
|
811
|
+
? `Error: ${result.error}`
|
|
812
|
+
: `Failed (${result.tasksCompleted}/${result.totalTasks} tasks)`;
|
|
813
|
+
|
|
814
|
+
logger.info(`${statusSymbol} ${result.projectName}: ${statusText}`);
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
const completed = results.filter((r) => r.success).length;
|
|
818
|
+
const failed = results.length - completed;
|
|
819
|
+
|
|
820
|
+
logger.newline();
|
|
821
|
+
logger.info(`Total: ${completed} completed, ${failed} failed`);
|
|
822
|
+
} else {
|
|
823
|
+
// Minimal multi-project summary: just show each project result
|
|
824
|
+
for (const result of results) {
|
|
825
|
+
const symbol = result.success ? SYMBOLS.completed : SYMBOLS.failed;
|
|
826
|
+
logger.info(`${symbol} ${result.projectName}`);
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
}
|