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,270 @@
|
|
|
1
|
+
import * as fs from 'node:fs';
|
|
2
|
+
import * as path from 'node:path';
|
|
3
|
+
import * as os from 'node:os';
|
|
4
|
+
import { resolveProjectIdentifier } from '../../src/utils/paths.js';
|
|
5
|
+
|
|
6
|
+
describe('Multi-Project Execution', () => {
|
|
7
|
+
let tempDir: string;
|
|
8
|
+
|
|
9
|
+
beforeEach(() => {
|
|
10
|
+
tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'raf-multiproject-test-'));
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
afterEach(() => {
|
|
14
|
+
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
describe('Project Resolution', () => {
|
|
18
|
+
it('should resolve multiple projects by number', () => {
|
|
19
|
+
fs.mkdirSync(path.join(tempDir, '001-project-a'));
|
|
20
|
+
fs.mkdirSync(path.join(tempDir, '002-project-b'));
|
|
21
|
+
fs.mkdirSync(path.join(tempDir, '003-project-c'));
|
|
22
|
+
|
|
23
|
+
const identifiers = ['001', '002', '003'];
|
|
24
|
+
const resolved = identifiers.map((id) => resolveProjectIdentifier(tempDir, id));
|
|
25
|
+
|
|
26
|
+
expect(resolved).toHaveLength(3);
|
|
27
|
+
expect(resolved[0]).toBe(path.join(tempDir, '001-project-a'));
|
|
28
|
+
expect(resolved[1]).toBe(path.join(tempDir, '002-project-b'));
|
|
29
|
+
expect(resolved[2]).toBe(path.join(tempDir, '003-project-c'));
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it('should resolve multiple projects by name', () => {
|
|
33
|
+
fs.mkdirSync(path.join(tempDir, '001-project-a'));
|
|
34
|
+
fs.mkdirSync(path.join(tempDir, '002-project-b'));
|
|
35
|
+
|
|
36
|
+
const identifiers = ['project-a', 'project-b'];
|
|
37
|
+
const resolved = identifiers.map((id) => resolveProjectIdentifier(tempDir, id));
|
|
38
|
+
|
|
39
|
+
expect(resolved).toHaveLength(2);
|
|
40
|
+
expect(resolved[0]).toBe(path.join(tempDir, '001-project-a'));
|
|
41
|
+
expect(resolved[1]).toBe(path.join(tempDir, '002-project-b'));
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('should resolve mixed number and name identifiers', () => {
|
|
45
|
+
fs.mkdirSync(path.join(tempDir, '001-project-a'));
|
|
46
|
+
fs.mkdirSync(path.join(tempDir, '002-project-b'));
|
|
47
|
+
fs.mkdirSync(path.join(tempDir, '003-project-c'));
|
|
48
|
+
|
|
49
|
+
const identifiers = ['001', 'project-b', '3'];
|
|
50
|
+
const resolved = identifiers.map((id) => resolveProjectIdentifier(tempDir, id));
|
|
51
|
+
|
|
52
|
+
expect(resolved).toHaveLength(3);
|
|
53
|
+
expect(resolved[0]).toBe(path.join(tempDir, '001-project-a'));
|
|
54
|
+
expect(resolved[1]).toBe(path.join(tempDir, '002-project-b'));
|
|
55
|
+
expect(resolved[2]).toBe(path.join(tempDir, '003-project-c'));
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it('should return null for non-existent projects in list', () => {
|
|
59
|
+
fs.mkdirSync(path.join(tempDir, '001-project-a'));
|
|
60
|
+
fs.mkdirSync(path.join(tempDir, '003-project-c'));
|
|
61
|
+
|
|
62
|
+
const identifiers = ['001', '002', '003'];
|
|
63
|
+
const resolved = identifiers.map((id) => resolveProjectIdentifier(tempDir, id));
|
|
64
|
+
|
|
65
|
+
expect(resolved[0]).toBe(path.join(tempDir, '001-project-a'));
|
|
66
|
+
expect(resolved[1]).toBeNull(); // 002 doesn't exist
|
|
67
|
+
expect(resolved[2]).toBe(path.join(tempDir, '003-project-c'));
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
describe('Duplicate Detection', () => {
|
|
72
|
+
it('should detect duplicates when same project specified by number', () => {
|
|
73
|
+
fs.mkdirSync(path.join(tempDir, '001-project-a'));
|
|
74
|
+
|
|
75
|
+
const identifiers = ['001', '001'];
|
|
76
|
+
const resolved = identifiers.map((id) => resolveProjectIdentifier(tempDir, id));
|
|
77
|
+
const uniquePaths = new Set(resolved.filter(Boolean));
|
|
78
|
+
|
|
79
|
+
expect(uniquePaths.size).toBe(1);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it('should detect duplicates when same project specified by number and name', () => {
|
|
83
|
+
fs.mkdirSync(path.join(tempDir, '001-project-a'));
|
|
84
|
+
|
|
85
|
+
const identifiers = ['001', 'project-a'];
|
|
86
|
+
const resolved = identifiers.map((id) => resolveProjectIdentifier(tempDir, id));
|
|
87
|
+
const uniquePaths = new Set(resolved.filter(Boolean));
|
|
88
|
+
|
|
89
|
+
expect(uniquePaths.size).toBe(1);
|
|
90
|
+
expect(resolved[0]).toBe(resolved[1]); // Both resolve to same path
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it('should detect duplicates with different number formats', () => {
|
|
94
|
+
fs.mkdirSync(path.join(tempDir, '005-project'));
|
|
95
|
+
|
|
96
|
+
const identifiers = ['5', '05', '005'];
|
|
97
|
+
const resolved = identifiers.map((id) => resolveProjectIdentifier(tempDir, id));
|
|
98
|
+
const uniquePaths = new Set(resolved.filter(Boolean));
|
|
99
|
+
|
|
100
|
+
expect(uniquePaths.size).toBe(1);
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
describe('Edge Cases', () => {
|
|
105
|
+
it('should handle empty project list', () => {
|
|
106
|
+
const identifiers: string[] = [];
|
|
107
|
+
const resolved = identifiers.map((id) => resolveProjectIdentifier(tempDir, id));
|
|
108
|
+
expect(resolved).toHaveLength(0);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it('should handle all invalid identifiers', () => {
|
|
112
|
+
const identifiers = ['999', 'non-existent', 'fake'];
|
|
113
|
+
const resolved = identifiers.map((id) => resolveProjectIdentifier(tempDir, id));
|
|
114
|
+
const valid = resolved.filter(Boolean);
|
|
115
|
+
|
|
116
|
+
expect(valid).toHaveLength(0);
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it('should handle single project', () => {
|
|
120
|
+
fs.mkdirSync(path.join(tempDir, '001-project'));
|
|
121
|
+
|
|
122
|
+
const identifiers = ['001'];
|
|
123
|
+
const resolved = identifiers.map((id) => resolveProjectIdentifier(tempDir, id));
|
|
124
|
+
|
|
125
|
+
expect(resolved).toHaveLength(1);
|
|
126
|
+
expect(resolved[0]).toBe(path.join(tempDir, '001-project'));
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it('should handle projects with similar names', () => {
|
|
130
|
+
fs.mkdirSync(path.join(tempDir, '001-test'));
|
|
131
|
+
fs.mkdirSync(path.join(tempDir, '002-test-feature'));
|
|
132
|
+
fs.mkdirSync(path.join(tempDir, '003-testing'));
|
|
133
|
+
|
|
134
|
+
// Exact name match only
|
|
135
|
+
expect(resolveProjectIdentifier(tempDir, 'test')).toBe(path.join(tempDir, '001-test'));
|
|
136
|
+
expect(resolveProjectIdentifier(tempDir, 'test-feature')).toBe(path.join(tempDir, '002-test-feature'));
|
|
137
|
+
expect(resolveProjectIdentifier(tempDir, 'testing')).toBe(path.join(tempDir, '003-testing'));
|
|
138
|
+
});
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
describe('Order Preservation', () => {
|
|
142
|
+
it('should resolve projects in the order specified', () => {
|
|
143
|
+
fs.mkdirSync(path.join(tempDir, '001-project-a'));
|
|
144
|
+
fs.mkdirSync(path.join(tempDir, '002-project-b'));
|
|
145
|
+
fs.mkdirSync(path.join(tempDir, '003-project-c'));
|
|
146
|
+
|
|
147
|
+
// Specify out of order
|
|
148
|
+
const identifiers = ['003', '001', '002'];
|
|
149
|
+
const resolved = identifiers.map((id) => resolveProjectIdentifier(tempDir, id));
|
|
150
|
+
|
|
151
|
+
expect(resolved[0]).toBe(path.join(tempDir, '003-project-c'));
|
|
152
|
+
expect(resolved[1]).toBe(path.join(tempDir, '001-project-a'));
|
|
153
|
+
expect(resolved[2]).toBe(path.join(tempDir, '002-project-b'));
|
|
154
|
+
});
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
describe('Full Folder Name Support', () => {
|
|
158
|
+
it('should resolve single project by full folder name with numeric prefix', () => {
|
|
159
|
+
fs.mkdirSync(path.join(tempDir, '001-fix-stuff'));
|
|
160
|
+
|
|
161
|
+
const result = resolveProjectIdentifier(tempDir, '001-fix-stuff');
|
|
162
|
+
expect(result).toBe(path.join(tempDir, '001-fix-stuff'));
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
it('should resolve single project by full folder name with base36 prefix', () => {
|
|
166
|
+
fs.mkdirSync(path.join(tempDir, 'a00-important-project'));
|
|
167
|
+
|
|
168
|
+
const result = resolveProjectIdentifier(tempDir, 'a00-important-project');
|
|
169
|
+
expect(result).toBe(path.join(tempDir, 'a00-important-project'));
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
it('should resolve multiple projects by full folder names', () => {
|
|
173
|
+
fs.mkdirSync(path.join(tempDir, '001-project-a'));
|
|
174
|
+
fs.mkdirSync(path.join(tempDir, '002-project-b'));
|
|
175
|
+
|
|
176
|
+
const identifiers = ['001-project-a', '002-project-b'];
|
|
177
|
+
const resolved = identifiers.map((id) => resolveProjectIdentifier(tempDir, id));
|
|
178
|
+
|
|
179
|
+
expect(resolved).toHaveLength(2);
|
|
180
|
+
expect(resolved[0]).toBe(path.join(tempDir, '001-project-a'));
|
|
181
|
+
expect(resolved[1]).toBe(path.join(tempDir, '002-project-b'));
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
it('should resolve mixed identifier formats including full folder names', () => {
|
|
185
|
+
fs.mkdirSync(path.join(tempDir, '003-numeric-id'));
|
|
186
|
+
fs.mkdirSync(path.join(tempDir, '001-full-folder-name'));
|
|
187
|
+
fs.mkdirSync(path.join(tempDir, '002-by-name'));
|
|
188
|
+
|
|
189
|
+
// Mix of number, full folder name, and project name
|
|
190
|
+
const identifiers = ['3', '001-full-folder-name', 'by-name'];
|
|
191
|
+
const resolved = identifiers.map((id) => resolveProjectIdentifier(tempDir, id));
|
|
192
|
+
|
|
193
|
+
expect(resolved).toHaveLength(3);
|
|
194
|
+
expect(resolved[0]).toBe(path.join(tempDir, '003-numeric-id'));
|
|
195
|
+
expect(resolved[1]).toBe(path.join(tempDir, '001-full-folder-name'));
|
|
196
|
+
expect(resolved[2]).toBe(path.join(tempDir, '002-by-name'));
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
it('should return null for invalid full folder name (wrong prefix)', () => {
|
|
200
|
+
fs.mkdirSync(path.join(tempDir, '001-my-project'));
|
|
201
|
+
|
|
202
|
+
// Correct name but wrong prefix
|
|
203
|
+
const result = resolveProjectIdentifier(tempDir, '002-my-project');
|
|
204
|
+
expect(result).toBeNull();
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
it('should return null for invalid full folder name (wrong name)', () => {
|
|
208
|
+
fs.mkdirSync(path.join(tempDir, '001-my-project'));
|
|
209
|
+
|
|
210
|
+
// Correct prefix but wrong name
|
|
211
|
+
const result = resolveProjectIdentifier(tempDir, '001-other-project');
|
|
212
|
+
expect(result).toBeNull();
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
it('should detect duplicates when same project specified by number and full folder name', () => {
|
|
216
|
+
fs.mkdirSync(path.join(tempDir, '001-project-a'));
|
|
217
|
+
|
|
218
|
+
const identifiers = ['001', '001-project-a'];
|
|
219
|
+
const resolved = identifiers.map((id) => resolveProjectIdentifier(tempDir, id));
|
|
220
|
+
const uniquePaths = new Set(resolved.filter(Boolean));
|
|
221
|
+
|
|
222
|
+
expect(uniquePaths.size).toBe(1);
|
|
223
|
+
expect(resolved[0]).toBe(resolved[1]); // Both resolve to same path
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
it('should detect duplicates when same project specified by name and full folder name', () => {
|
|
227
|
+
fs.mkdirSync(path.join(tempDir, '001-project-a'));
|
|
228
|
+
|
|
229
|
+
const identifiers = ['project-a', '001-project-a'];
|
|
230
|
+
const resolved = identifiers.map((id) => resolveProjectIdentifier(tempDir, id));
|
|
231
|
+
const uniquePaths = new Set(resolved.filter(Boolean));
|
|
232
|
+
|
|
233
|
+
expect(uniquePaths.size).toBe(1);
|
|
234
|
+
expect(resolved[0]).toBe(resolved[1]); // Both resolve to same path
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
it('should handle case-insensitive full folder name matching', () => {
|
|
238
|
+
fs.mkdirSync(path.join(tempDir, '001-My-Project'));
|
|
239
|
+
|
|
240
|
+
const result = resolveProjectIdentifier(tempDir, '001-my-project');
|
|
241
|
+
expect(result).toBe(path.join(tempDir, '001-My-Project'));
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
it('should handle full folder names with multiple hyphens', () => {
|
|
245
|
+
fs.mkdirSync(path.join(tempDir, '001-my-cool-project-name'));
|
|
246
|
+
|
|
247
|
+
const result = resolveProjectIdentifier(tempDir, '001-my-cool-project-name');
|
|
248
|
+
expect(result).toBe(path.join(tempDir, '001-my-cool-project-name'));
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
it('should handle 2-digit prefix full folder names', () => {
|
|
252
|
+
fs.mkdirSync(path.join(tempDir, '01-short-prefix'));
|
|
253
|
+
|
|
254
|
+
const result = resolveProjectIdentifier(tempDir, '01-short-prefix');
|
|
255
|
+
expect(result).toBe(path.join(tempDir, '01-short-prefix'));
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
it('should resolve mixed base36 and numeric full folder names', () => {
|
|
259
|
+
fs.mkdirSync(path.join(tempDir, '001-numeric'));
|
|
260
|
+
fs.mkdirSync(path.join(tempDir, 'a01-base36'));
|
|
261
|
+
|
|
262
|
+
const identifiers = ['001-numeric', 'a01-base36'];
|
|
263
|
+
const resolved = identifiers.map((id) => resolveProjectIdentifier(tempDir, id));
|
|
264
|
+
|
|
265
|
+
expect(resolved).toHaveLength(2);
|
|
266
|
+
expect(resolved[0]).toBe(path.join(tempDir, '001-numeric'));
|
|
267
|
+
expect(resolved[1]).toBe(path.join(tempDir, 'a01-base36'));
|
|
268
|
+
});
|
|
269
|
+
});
|
|
270
|
+
});
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
import * as fs from 'node:fs';
|
|
2
|
+
import * as path from 'node:path';
|
|
3
|
+
import * as os from 'node:os';
|
|
4
|
+
import {
|
|
5
|
+
deriveProjectState,
|
|
6
|
+
getNextExecutableTask,
|
|
7
|
+
isProjectComplete,
|
|
8
|
+
type DerivedProjectState,
|
|
9
|
+
} from '../../src/core/state-derivation.js';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Tests for the re-run functionality of the do command.
|
|
13
|
+
* These tests verify the task selection logic used by raf do when resuming projects.
|
|
14
|
+
*/
|
|
15
|
+
describe('do command rerun functionality', () => {
|
|
16
|
+
let tempDir: string;
|
|
17
|
+
let projectPath: string;
|
|
18
|
+
|
|
19
|
+
beforeEach(() => {
|
|
20
|
+
tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'raf-do-rerun-test-'));
|
|
21
|
+
projectPath = path.join(tempDir, '001-test-project');
|
|
22
|
+
fs.mkdirSync(projectPath, { recursive: true });
|
|
23
|
+
fs.mkdirSync(path.join(projectPath, 'plans'), { recursive: true });
|
|
24
|
+
fs.mkdirSync(path.join(projectPath, 'outcomes'), { recursive: true });
|
|
25
|
+
fs.writeFileSync(path.join(projectPath, 'input.md'), '# Test Input');
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
afterEach(() => {
|
|
29
|
+
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
describe('skip completed tasks', () => {
|
|
33
|
+
it('should skip tasks with SUCCESS outcome', () => {
|
|
34
|
+
// Set up plans
|
|
35
|
+
fs.writeFileSync(path.join(projectPath, 'plans', '001-task.md'), '# Task 1');
|
|
36
|
+
fs.writeFileSync(path.join(projectPath, 'plans', '002-task.md'), '# Task 2');
|
|
37
|
+
fs.writeFileSync(path.join(projectPath, 'plans', '003-task.md'), '# Task 3');
|
|
38
|
+
|
|
39
|
+
// Mark first task as complete
|
|
40
|
+
fs.writeFileSync(
|
|
41
|
+
path.join(projectPath, 'outcomes', '001-task.md'),
|
|
42
|
+
'# Task 001 - Completed\n\n<promise>COMPLETE</promise>'
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
const state = deriveProjectState(projectPath);
|
|
46
|
+
const nextTask = getNextExecutableTask(state);
|
|
47
|
+
|
|
48
|
+
expect(nextTask?.id).toBe('002');
|
|
49
|
+
expect(nextTask?.status).toBe('pending');
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('should skip multiple completed tasks', () => {
|
|
53
|
+
fs.writeFileSync(path.join(projectPath, 'plans', '001-task.md'), '# Task 1');
|
|
54
|
+
fs.writeFileSync(path.join(projectPath, 'plans', '002-task.md'), '# Task 2');
|
|
55
|
+
fs.writeFileSync(path.join(projectPath, 'plans', '003-task.md'), '# Task 3');
|
|
56
|
+
|
|
57
|
+
// Mark first two tasks as complete
|
|
58
|
+
fs.writeFileSync(
|
|
59
|
+
path.join(projectPath, 'outcomes', '001-task.md'),
|
|
60
|
+
'# Task 001 - Completed\n\n<promise>COMPLETE</promise>'
|
|
61
|
+
);
|
|
62
|
+
fs.writeFileSync(
|
|
63
|
+
path.join(projectPath, 'outcomes', '002-task.md'),
|
|
64
|
+
'# Task 002 - Completed\n\n<promise>COMPLETE</promise>'
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
const state = deriveProjectState(projectPath);
|
|
68
|
+
const nextTask = getNextExecutableTask(state);
|
|
69
|
+
|
|
70
|
+
expect(nextTask?.id).toBe('003');
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
describe('retry failed tasks', () => {
|
|
75
|
+
it('should retry tasks with FAILED outcome', () => {
|
|
76
|
+
fs.writeFileSync(path.join(projectPath, 'plans', '001-task.md'), '# Task 1');
|
|
77
|
+
fs.writeFileSync(path.join(projectPath, 'plans', '002-task.md'), '# Task 2');
|
|
78
|
+
|
|
79
|
+
// Mark first task as failed
|
|
80
|
+
fs.writeFileSync(
|
|
81
|
+
path.join(projectPath, 'outcomes', '001-task.md'),
|
|
82
|
+
'# Task 001 - Failed\n\n<promise>FAILED</promise>'
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
const state = deriveProjectState(projectPath);
|
|
86
|
+
const nextTask = getNextExecutableTask(state);
|
|
87
|
+
|
|
88
|
+
// Should return the pending task first, then failed
|
|
89
|
+
expect(nextTask?.id).toBe('002');
|
|
90
|
+
expect(nextTask?.status).toBe('pending');
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it('should retry failed task when no pending tasks remain', () => {
|
|
94
|
+
fs.writeFileSync(path.join(projectPath, 'plans', '001-task.md'), '# Task 1');
|
|
95
|
+
fs.writeFileSync(path.join(projectPath, 'plans', '002-task.md'), '# Task 2');
|
|
96
|
+
|
|
97
|
+
// Mark first as complete, second as failed
|
|
98
|
+
fs.writeFileSync(
|
|
99
|
+
path.join(projectPath, 'outcomes', '001-task.md'),
|
|
100
|
+
'# Task 001 - Completed\n\n<promise>COMPLETE</promise>'
|
|
101
|
+
);
|
|
102
|
+
fs.writeFileSync(
|
|
103
|
+
path.join(projectPath, 'outcomes', '002-task.md'),
|
|
104
|
+
'# Task 002 - Failed\n\n<promise>FAILED</promise>'
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
const state = deriveProjectState(projectPath);
|
|
108
|
+
const nextTask = getNextExecutableTask(state);
|
|
109
|
+
|
|
110
|
+
expect(nextTask?.id).toBe('002');
|
|
111
|
+
expect(nextTask?.status).toBe('failed');
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
describe('run pending tasks', () => {
|
|
116
|
+
it('should run tasks without outcome files', () => {
|
|
117
|
+
fs.writeFileSync(path.join(projectPath, 'plans', '001-task.md'), '# Task 1');
|
|
118
|
+
fs.writeFileSync(path.join(projectPath, 'plans', '002-task.md'), '# Task 2');
|
|
119
|
+
|
|
120
|
+
const state = deriveProjectState(projectPath);
|
|
121
|
+
const nextTask = getNextExecutableTask(state);
|
|
122
|
+
|
|
123
|
+
expect(nextTask?.id).toBe('001');
|
|
124
|
+
expect(nextTask?.status).toBe('pending');
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it('should handle outcome file without status marker as pending', () => {
|
|
128
|
+
fs.writeFileSync(path.join(projectPath, 'plans', '001-task.md'), '# Task 1');
|
|
129
|
+
fs.writeFileSync(
|
|
130
|
+
path.join(projectPath, 'outcomes', '001-task.md'),
|
|
131
|
+
'# Old format without status marker'
|
|
132
|
+
);
|
|
133
|
+
|
|
134
|
+
const state = deriveProjectState(projectPath);
|
|
135
|
+
|
|
136
|
+
expect(state.tasks[0]?.status).toBe('pending');
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
it('should handle empty outcome file as pending', () => {
|
|
140
|
+
fs.writeFileSync(path.join(projectPath, 'plans', '001-task.md'), '# Task 1');
|
|
141
|
+
fs.writeFileSync(path.join(projectPath, 'outcomes', '001-task.md'), '');
|
|
142
|
+
|
|
143
|
+
const state = deriveProjectState(projectPath);
|
|
144
|
+
|
|
145
|
+
expect(state.tasks[0]?.status).toBe('pending');
|
|
146
|
+
});
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
describe('all complete detection', () => {
|
|
150
|
+
it('should detect all tasks completed', () => {
|
|
151
|
+
fs.writeFileSync(path.join(projectPath, 'plans', '001-task.md'), '# Task 1');
|
|
152
|
+
fs.writeFileSync(path.join(projectPath, 'plans', '002-task.md'), '# Task 2');
|
|
153
|
+
|
|
154
|
+
fs.writeFileSync(
|
|
155
|
+
path.join(projectPath, 'outcomes', '001-task.md'),
|
|
156
|
+
'# Task 001 - Completed\n\n<promise>COMPLETE</promise>'
|
|
157
|
+
);
|
|
158
|
+
fs.writeFileSync(
|
|
159
|
+
path.join(projectPath, 'outcomes', '002-task.md'),
|
|
160
|
+
'# Task 002 - Completed\n\n<promise>COMPLETE</promise>'
|
|
161
|
+
);
|
|
162
|
+
|
|
163
|
+
const state = deriveProjectState(projectPath);
|
|
164
|
+
|
|
165
|
+
expect(isProjectComplete(state)).toBe(true);
|
|
166
|
+
expect(getNextExecutableTask(state)).toBeNull();
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
it('should not report complete when failed tasks exist', () => {
|
|
170
|
+
fs.writeFileSync(path.join(projectPath, 'plans', '001-task.md'), '# Task 1');
|
|
171
|
+
fs.writeFileSync(
|
|
172
|
+
path.join(projectPath, 'outcomes', '001-task.md'),
|
|
173
|
+
'# Task 001 - Failed\n\n<promise>FAILED</promise>'
|
|
174
|
+
);
|
|
175
|
+
|
|
176
|
+
const state = deriveProjectState(projectPath);
|
|
177
|
+
|
|
178
|
+
expect(isProjectComplete(state)).toBe(false);
|
|
179
|
+
});
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
describe('force mode task selection', () => {
|
|
183
|
+
/**
|
|
184
|
+
* Test that force mode logic would select all tasks regardless of status.
|
|
185
|
+
* The actual force mode implementation uses a Set to track tasks completed in the current session,
|
|
186
|
+
* and returns tasks that haven't been completed in this session.
|
|
187
|
+
*/
|
|
188
|
+
it('should identify all tasks for force mode execution', () => {
|
|
189
|
+
fs.writeFileSync(path.join(projectPath, 'plans', '001-task.md'), '# Task 1');
|
|
190
|
+
fs.writeFileSync(path.join(projectPath, 'plans', '002-task.md'), '# Task 2');
|
|
191
|
+
fs.writeFileSync(path.join(projectPath, 'plans', '003-task.md'), '# Task 3');
|
|
192
|
+
|
|
193
|
+
// All tasks completed
|
|
194
|
+
fs.writeFileSync(
|
|
195
|
+
path.join(projectPath, 'outcomes', '001-task.md'),
|
|
196
|
+
'# Task 001 - Completed\n\n<promise>COMPLETE</promise>'
|
|
197
|
+
);
|
|
198
|
+
fs.writeFileSync(
|
|
199
|
+
path.join(projectPath, 'outcomes', '002-task.md'),
|
|
200
|
+
'# Task 002 - Completed\n\n<promise>COMPLETE</promise>'
|
|
201
|
+
);
|
|
202
|
+
fs.writeFileSync(
|
|
203
|
+
path.join(projectPath, 'outcomes', '003-task.md'),
|
|
204
|
+
'# Task 003 - Completed\n\n<promise>COMPLETE</promise>'
|
|
205
|
+
);
|
|
206
|
+
|
|
207
|
+
const state = deriveProjectState(projectPath);
|
|
208
|
+
|
|
209
|
+
// In force mode, all tasks should be available regardless of status
|
|
210
|
+
expect(state.tasks).toHaveLength(3);
|
|
211
|
+
expect(state.tasks.every((t) => t.status === 'completed')).toBe(true);
|
|
212
|
+
|
|
213
|
+
// Simulate force mode task selection
|
|
214
|
+
const completedInSession = new Set<string>();
|
|
215
|
+
function getNextForceTask(
|
|
216
|
+
s: DerivedProjectState,
|
|
217
|
+
completed: Set<string>
|
|
218
|
+
) {
|
|
219
|
+
for (const t of s.tasks) {
|
|
220
|
+
if (!completed.has(t.id)) {
|
|
221
|
+
return t;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
return null;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// First iteration - should get task 001
|
|
228
|
+
let task = getNextForceTask(state, completedInSession);
|
|
229
|
+
expect(task?.id).toBe('001');
|
|
230
|
+
completedInSession.add(task!.id);
|
|
231
|
+
|
|
232
|
+
// Second iteration - should get task 002
|
|
233
|
+
task = getNextForceTask(state, completedInSession);
|
|
234
|
+
expect(task?.id).toBe('002');
|
|
235
|
+
completedInSession.add(task!.id);
|
|
236
|
+
|
|
237
|
+
// Third iteration - should get task 003
|
|
238
|
+
task = getNextForceTask(state, completedInSession);
|
|
239
|
+
expect(task?.id).toBe('003');
|
|
240
|
+
completedInSession.add(task!.id);
|
|
241
|
+
|
|
242
|
+
// Fourth iteration - no more tasks
|
|
243
|
+
task = getNextForceTask(state, completedInSession);
|
|
244
|
+
expect(task).toBeNull();
|
|
245
|
+
});
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
describe('task status logging', () => {
|
|
249
|
+
it('should identify failed tasks for retry logging', () => {
|
|
250
|
+
fs.writeFileSync(path.join(projectPath, 'plans', '001-task.md'), '# Task 1');
|
|
251
|
+
fs.writeFileSync(path.join(projectPath, 'plans', '002-task.md'), '# Task 2');
|
|
252
|
+
|
|
253
|
+
fs.writeFileSync(
|
|
254
|
+
path.join(projectPath, 'outcomes', '001-task.md'),
|
|
255
|
+
'# Task 001 - Completed\n\n<promise>COMPLETE</promise>'
|
|
256
|
+
);
|
|
257
|
+
fs.writeFileSync(
|
|
258
|
+
path.join(projectPath, 'outcomes', '002-task.md'),
|
|
259
|
+
'# Task 002 - Failed\n\n<promise>FAILED</promise>'
|
|
260
|
+
);
|
|
261
|
+
|
|
262
|
+
const state = deriveProjectState(projectPath);
|
|
263
|
+
const nextTask = getNextExecutableTask(state);
|
|
264
|
+
|
|
265
|
+
// The task being retried should have failed status
|
|
266
|
+
expect(nextTask?.id).toBe('002');
|
|
267
|
+
expect(nextTask?.status).toBe('failed');
|
|
268
|
+
});
|
|
269
|
+
});
|
|
270
|
+
});
|