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,283 @@
|
|
|
1
|
+
import { jest } from '@jest/globals';
|
|
2
|
+
|
|
3
|
+
// Mock execSync before importing the module
|
|
4
|
+
const mockExecSync = jest.fn();
|
|
5
|
+
jest.unstable_mockModule('node:child_process', () => ({
|
|
6
|
+
execSync: mockExecSync,
|
|
7
|
+
}));
|
|
8
|
+
|
|
9
|
+
// Import after mocking
|
|
10
|
+
const { generateProjectName, generateProjectNames, sanitizeGeneratedName, escapeShellArg } =
|
|
11
|
+
await import('../../src/utils/name-generator.js');
|
|
12
|
+
|
|
13
|
+
describe('Name Generator', () => {
|
|
14
|
+
beforeEach(() => {
|
|
15
|
+
jest.clearAllMocks();
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
describe('generateProjectName', () => {
|
|
19
|
+
it('should return sanitized name from Sonnet response', async () => {
|
|
20
|
+
mockExecSync.mockReturnValue('user-auth-system\n');
|
|
21
|
+
|
|
22
|
+
const result = await generateProjectName('Build a user authentication system');
|
|
23
|
+
|
|
24
|
+
expect(result).toBe('user-auth-system');
|
|
25
|
+
expect(mockExecSync).toHaveBeenCalledTimes(1);
|
|
26
|
+
expect(mockExecSync).toHaveBeenCalledWith(
|
|
27
|
+
expect.stringContaining('claude --model sonnet --print'),
|
|
28
|
+
expect.any(Object)
|
|
29
|
+
);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it('should sanitize Sonnet response with quotes', async () => {
|
|
33
|
+
mockExecSync.mockReturnValue('"api-rate-limiter"');
|
|
34
|
+
|
|
35
|
+
const result = await generateProjectName('Create an API rate limiting service');
|
|
36
|
+
|
|
37
|
+
expect(result).toBe('api-rate-limiter');
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it('should sanitize Sonnet response with special characters', async () => {
|
|
41
|
+
mockExecSync.mockReturnValue('Some Project! Name');
|
|
42
|
+
|
|
43
|
+
const result = await generateProjectName('Some project description');
|
|
44
|
+
|
|
45
|
+
expect(result).toBe('some-project-name');
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('should fall back to word extraction when Sonnet fails', async () => {
|
|
49
|
+
mockExecSync.mockImplementation(() => {
|
|
50
|
+
throw new Error('Command failed');
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
const result = await generateProjectName('Build a user authentication system with OAuth');
|
|
54
|
+
|
|
55
|
+
expect(result).toBe('build-user-authentication');
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it('should fall back to word extraction when Sonnet returns empty', async () => {
|
|
59
|
+
mockExecSync.mockReturnValue('');
|
|
60
|
+
|
|
61
|
+
const result = await generateProjectName('Implement caching layer for database');
|
|
62
|
+
|
|
63
|
+
expect(result).toBe('implement-caching-layer');
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it('should fall back to word extraction when Sonnet returns single char', async () => {
|
|
67
|
+
mockExecSync.mockReturnValue('a');
|
|
68
|
+
|
|
69
|
+
const result = await generateProjectName('Add new logging functionality');
|
|
70
|
+
|
|
71
|
+
expect(result).toBe('add-new-logging');
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it('should return "project" when description has no meaningful words', async () => {
|
|
75
|
+
mockExecSync.mockImplementation(() => {
|
|
76
|
+
throw new Error('Command failed');
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
const result = await generateProjectName('a b c');
|
|
80
|
+
|
|
81
|
+
expect(result).toBe('project');
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it('should truncate long names from Sonnet', async () => {
|
|
85
|
+
const longName =
|
|
86
|
+
'this-is-a-very-long-project-name-that-exceeds-the-maximum-allowed-length-for-folder-names';
|
|
87
|
+
mockExecSync.mockReturnValue(longName);
|
|
88
|
+
|
|
89
|
+
const result = await generateProjectName('Some project');
|
|
90
|
+
|
|
91
|
+
expect(result.length).toBeLessThanOrEqual(50);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it('should handle multiline Sonnet response', async () => {
|
|
95
|
+
mockExecSync.mockReturnValue('project-name\nSome extra explanation\n');
|
|
96
|
+
|
|
97
|
+
const result = await generateProjectName('Some project');
|
|
98
|
+
|
|
99
|
+
// Should take first line after trim
|
|
100
|
+
expect(result).toBe('project-name-some-extra-explanation');
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it('should convert uppercase to lowercase', async () => {
|
|
104
|
+
mockExecSync.mockReturnValue('API-Gateway-Service');
|
|
105
|
+
|
|
106
|
+
const result = await generateProjectName('Build an API gateway');
|
|
107
|
+
|
|
108
|
+
expect(result).toBe('api-gateway-service');
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
describe('generateProjectNames', () => {
|
|
113
|
+
it('should return multiple sanitized names from Sonnet response', async () => {
|
|
114
|
+
mockExecSync.mockReturnValue(
|
|
115
|
+
'phoenix-rise\nturbo-boost\nbug-squasher\ncatalyst\nmerlin\n'
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
const result = await generateProjectNames('Build a user authentication system');
|
|
119
|
+
|
|
120
|
+
expect(result).toEqual([
|
|
121
|
+
'phoenix-rise',
|
|
122
|
+
'turbo-boost',
|
|
123
|
+
'bug-squasher',
|
|
124
|
+
'catalyst',
|
|
125
|
+
'merlin',
|
|
126
|
+
]);
|
|
127
|
+
expect(mockExecSync).toHaveBeenCalledTimes(1);
|
|
128
|
+
expect(mockExecSync).toHaveBeenCalledWith(
|
|
129
|
+
expect.stringContaining('Generate 5 creative project names'),
|
|
130
|
+
expect.any(Object)
|
|
131
|
+
);
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it('should handle names with numbering prefixes', async () => {
|
|
135
|
+
mockExecSync.mockReturnValue(
|
|
136
|
+
'1. phoenix-rise\n2. turbo-boost\n3. bug-squasher\n4. catalyst\n5. merlin\n'
|
|
137
|
+
);
|
|
138
|
+
|
|
139
|
+
const result = await generateProjectNames('Some project');
|
|
140
|
+
|
|
141
|
+
expect(result).toEqual([
|
|
142
|
+
'phoenix-rise',
|
|
143
|
+
'turbo-boost',
|
|
144
|
+
'bug-squasher',
|
|
145
|
+
'catalyst',
|
|
146
|
+
'merlin',
|
|
147
|
+
]);
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
it('should handle names with colon prefixes', async () => {
|
|
151
|
+
mockExecSync.mockReturnValue(
|
|
152
|
+
'1: phoenix-rise\n2: turbo-boost\n3: bug-squasher\n'
|
|
153
|
+
);
|
|
154
|
+
|
|
155
|
+
const result = await generateProjectNames('Some project');
|
|
156
|
+
|
|
157
|
+
expect(result).toEqual(['phoenix-rise', 'turbo-boost', 'bug-squasher']);
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
it('should remove duplicate names', async () => {
|
|
161
|
+
mockExecSync.mockReturnValue(
|
|
162
|
+
'phoenix\nturbo-boost\nphoenix\ncatalyst\nturbo-boost\n'
|
|
163
|
+
);
|
|
164
|
+
|
|
165
|
+
const result = await generateProjectNames('Some project');
|
|
166
|
+
|
|
167
|
+
expect(result).toEqual(['phoenix', 'turbo-boost', 'catalyst']);
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
it('should limit to 5 names maximum', async () => {
|
|
171
|
+
mockExecSync.mockReturnValue(
|
|
172
|
+
'name-one\nname-two\nname-three\nname-four\nname-five\nname-six\nname-seven\n'
|
|
173
|
+
);
|
|
174
|
+
|
|
175
|
+
const result = await generateProjectNames('Some project');
|
|
176
|
+
|
|
177
|
+
expect(result.length).toBe(5);
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
it('should return 3+ names when available', async () => {
|
|
181
|
+
mockExecSync.mockReturnValue('phoenix\nturbo-boost\ncatalyst\n');
|
|
182
|
+
|
|
183
|
+
const result = await generateProjectNames('Some project');
|
|
184
|
+
|
|
185
|
+
expect(result.length).toBe(3);
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
it('should fall back to single name when Sonnet fails', async () => {
|
|
189
|
+
mockExecSync.mockImplementation(() => {
|
|
190
|
+
throw new Error('Command failed');
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
const result = await generateProjectNames('Build a user authentication system with OAuth');
|
|
194
|
+
|
|
195
|
+
expect(result).toEqual(['build-user-authentication']);
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
it('should fall back to single name when too few names returned', async () => {
|
|
199
|
+
mockExecSync.mockReturnValue('phoenix\nturbo\n');
|
|
200
|
+
|
|
201
|
+
const result = await generateProjectNames('Build something awesome');
|
|
202
|
+
|
|
203
|
+
// Only 2 names returned, so fallback kicks in
|
|
204
|
+
expect(result).toEqual(['build-something-awesome']);
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
it('should filter out invalid/short names', async () => {
|
|
208
|
+
mockExecSync.mockReturnValue('phoenix\na\nturbo-boost\nb\ncatalyst\n');
|
|
209
|
+
|
|
210
|
+
const result = await generateProjectNames('Some project');
|
|
211
|
+
|
|
212
|
+
expect(result).toEqual(['phoenix', 'turbo-boost', 'catalyst']);
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
it('should sanitize names with special characters', async () => {
|
|
216
|
+
mockExecSync.mockReturnValue(
|
|
217
|
+
'Phoenix Rise!\nTurbo-Boost!!!\nBug Squasher\n'
|
|
218
|
+
);
|
|
219
|
+
|
|
220
|
+
const result = await generateProjectNames('Some project');
|
|
221
|
+
|
|
222
|
+
expect(result).toEqual(['phoenix-rise', 'turbo-boost', 'bug-squasher']);
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
it('should handle empty response', async () => {
|
|
226
|
+
mockExecSync.mockReturnValue('');
|
|
227
|
+
|
|
228
|
+
const result = await generateProjectNames('Some project');
|
|
229
|
+
|
|
230
|
+
expect(result).toEqual(['some-project']);
|
|
231
|
+
});
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
describe('sanitizeGeneratedName', () => {
|
|
235
|
+
it('should remove quotes', () => {
|
|
236
|
+
expect(sanitizeGeneratedName('"hello-world"')).toBe('hello-world');
|
|
237
|
+
expect(sanitizeGeneratedName("'hello-world'")).toBe('hello-world');
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
it('should remove numbering prefixes', () => {
|
|
241
|
+
expect(sanitizeGeneratedName('1. hello-world')).toBe('hello-world');
|
|
242
|
+
expect(sanitizeGeneratedName('1: hello-world')).toBe('hello-world');
|
|
243
|
+
expect(sanitizeGeneratedName('1) hello-world')).toBe('hello-world');
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
it('should convert to lowercase', () => {
|
|
247
|
+
expect(sanitizeGeneratedName('Hello-World')).toBe('hello-world');
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
it('should replace special characters with hyphens', () => {
|
|
251
|
+
expect(sanitizeGeneratedName('hello world!')).toBe('hello-world');
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
it('should return null for too short names', () => {
|
|
255
|
+
expect(sanitizeGeneratedName('a')).toBeNull();
|
|
256
|
+
expect(sanitizeGeneratedName('')).toBeNull();
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
it('should truncate long names', () => {
|
|
260
|
+
const longName = 'a'.repeat(100);
|
|
261
|
+
const result = sanitizeGeneratedName(longName);
|
|
262
|
+
expect(result?.length).toBeLessThanOrEqual(50);
|
|
263
|
+
});
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
describe('escapeShellArg', () => {
|
|
267
|
+
it('should escape double quotes', () => {
|
|
268
|
+
expect(escapeShellArg('hello "world"')).toBe('hello \\"world\\"');
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
it('should escape backslashes', () => {
|
|
272
|
+
expect(escapeShellArg('hello\\world')).toBe('hello\\\\world');
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
it('should escape dollar signs', () => {
|
|
276
|
+
expect(escapeShellArg('$HOME')).toBe('\\$HOME');
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
it('should escape backticks', () => {
|
|
280
|
+
expect(escapeShellArg('`whoami`')).toBe('\\`whoami\\`');
|
|
281
|
+
});
|
|
282
|
+
});
|
|
283
|
+
});
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import { jest } from '@jest/globals';
|
|
2
|
+
|
|
3
|
+
// Mock @inquirer/prompts before importing the module
|
|
4
|
+
const mockSelect = jest.fn();
|
|
5
|
+
const mockInput = jest.fn();
|
|
6
|
+
jest.unstable_mockModule('@inquirer/prompts', () => ({
|
|
7
|
+
select: mockSelect,
|
|
8
|
+
input: mockInput,
|
|
9
|
+
}));
|
|
10
|
+
|
|
11
|
+
// Import after mocking
|
|
12
|
+
const { pickProjectName, enableDirectMode, disableDirectMode } = await import(
|
|
13
|
+
'../../src/ui/name-picker.js'
|
|
14
|
+
);
|
|
15
|
+
|
|
16
|
+
describe('Name Picker', () => {
|
|
17
|
+
beforeAll(async () => {
|
|
18
|
+
// Enable direct mode for testing (bypasses subprocess)
|
|
19
|
+
await enableDirectMode();
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
afterAll(() => {
|
|
23
|
+
disableDirectMode();
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
beforeEach(() => {
|
|
27
|
+
jest.clearAllMocks();
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
describe('pickProjectName', () => {
|
|
31
|
+
it('should display generated names as choices', async () => {
|
|
32
|
+
mockSelect.mockResolvedValue('phoenix-rise');
|
|
33
|
+
|
|
34
|
+
const result = await pickProjectName(['phoenix-rise', 'turbo-boost', 'catalyst']);
|
|
35
|
+
|
|
36
|
+
expect(result).toBe('phoenix-rise');
|
|
37
|
+
expect(mockSelect).toHaveBeenCalledTimes(1);
|
|
38
|
+
expect(mockSelect).toHaveBeenCalledWith({
|
|
39
|
+
message: 'Select a project name:',
|
|
40
|
+
choices: [
|
|
41
|
+
{ name: 'phoenix-rise', value: 'phoenix-rise' },
|
|
42
|
+
{ name: 'turbo-boost', value: 'turbo-boost' },
|
|
43
|
+
{ name: 'catalyst', value: 'catalyst' },
|
|
44
|
+
{ name: 'Other (enter custom name)', value: '__OTHER__' },
|
|
45
|
+
],
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('should return selected name from list', async () => {
|
|
50
|
+
mockSelect.mockResolvedValue('turbo-boost');
|
|
51
|
+
|
|
52
|
+
const result = await pickProjectName(['phoenix', 'turbo-boost', 'merlin']);
|
|
53
|
+
|
|
54
|
+
expect(result).toBe('turbo-boost');
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it('should prompt for custom name when "Other" is selected', async () => {
|
|
58
|
+
mockSelect.mockResolvedValue('__OTHER__');
|
|
59
|
+
mockInput.mockResolvedValue('custom-project');
|
|
60
|
+
|
|
61
|
+
const result = await pickProjectName(['phoenix', 'turbo']);
|
|
62
|
+
|
|
63
|
+
expect(result).toBe('custom-project');
|
|
64
|
+
expect(mockInput).toHaveBeenCalledTimes(1);
|
|
65
|
+
expect(mockInput).toHaveBeenCalledWith({
|
|
66
|
+
message: 'Enter project name:',
|
|
67
|
+
validate: expect.any(Function),
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it('should prompt for custom name immediately when names array is empty', async () => {
|
|
72
|
+
mockInput.mockResolvedValue('my-project');
|
|
73
|
+
|
|
74
|
+
const result = await pickProjectName([]);
|
|
75
|
+
|
|
76
|
+
expect(result).toBe('my-project');
|
|
77
|
+
expect(mockSelect).not.toHaveBeenCalled();
|
|
78
|
+
expect(mockInput).toHaveBeenCalledTimes(1);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it('should convert custom name to lowercase', async () => {
|
|
82
|
+
mockSelect.mockResolvedValue('__OTHER__');
|
|
83
|
+
mockInput.mockResolvedValue('MyProject');
|
|
84
|
+
|
|
85
|
+
const result = await pickProjectName(['phoenix']);
|
|
86
|
+
|
|
87
|
+
expect(result).toBe('myproject');
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it('should trim custom name', async () => {
|
|
91
|
+
mockSelect.mockResolvedValue('__OTHER__');
|
|
92
|
+
mockInput.mockResolvedValue(' my-project ');
|
|
93
|
+
|
|
94
|
+
const result = await pickProjectName(['phoenix']);
|
|
95
|
+
|
|
96
|
+
expect(result).toBe('my-project');
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
describe('custom name validation', () => {
|
|
100
|
+
let validateFn: (value: string) => boolean | string;
|
|
101
|
+
|
|
102
|
+
beforeEach(async () => {
|
|
103
|
+
mockSelect.mockResolvedValue('__OTHER__');
|
|
104
|
+
mockInput.mockImplementation(async (config) => {
|
|
105
|
+
validateFn = config.validate;
|
|
106
|
+
return 'valid-name';
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
await pickProjectName(['phoenix']);
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it('should reject empty names', () => {
|
|
113
|
+
expect(validateFn('')).toBe('Project name cannot be empty');
|
|
114
|
+
expect(validateFn(' ')).toBe('Project name cannot be empty');
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it('should reject names longer than 50 characters', () => {
|
|
118
|
+
const longName = 'a'.repeat(51);
|
|
119
|
+
expect(validateFn(longName)).toBe('Project name must be 50 characters or less');
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it('should accept valid names', () => {
|
|
123
|
+
expect(validateFn('valid-name')).toBe(true);
|
|
124
|
+
expect(validateFn('my_project')).toBe(true);
|
|
125
|
+
expect(validateFn('project123')).toBe(true);
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it('should reject names with special characters', () => {
|
|
129
|
+
expect(validateFn('hello world')).toBe(
|
|
130
|
+
'Project name can only contain letters, numbers, hyphens, and underscores'
|
|
131
|
+
);
|
|
132
|
+
expect(validateFn('project!')).toBe(
|
|
133
|
+
'Project name can only contain letters, numbers, hyphens, and underscores'
|
|
134
|
+
);
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it('should reject names starting with hyphen', () => {
|
|
138
|
+
expect(validateFn('-project')).toBe(
|
|
139
|
+
'Project name can only contain letters, numbers, hyphens, and underscores'
|
|
140
|
+
);
|
|
141
|
+
});
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
it('should handle 5 generated names', async () => {
|
|
145
|
+
const names = ['name-one', 'name-two', 'name-three', 'name-four', 'name-five'];
|
|
146
|
+
mockSelect.mockResolvedValue('name-three');
|
|
147
|
+
|
|
148
|
+
const result = await pickProjectName(names);
|
|
149
|
+
|
|
150
|
+
expect(result).toBe('name-three');
|
|
151
|
+
expect(mockSelect).toHaveBeenCalledWith({
|
|
152
|
+
message: 'Select a project name:',
|
|
153
|
+
choices: [
|
|
154
|
+
{ name: 'name-one', value: 'name-one' },
|
|
155
|
+
{ name: 'name-two', value: 'name-two' },
|
|
156
|
+
{ name: 'name-three', value: 'name-three' },
|
|
157
|
+
{ name: 'name-four', value: 'name-four' },
|
|
158
|
+
{ name: 'name-five', value: 'name-five' },
|
|
159
|
+
{ name: 'Other (enter custom name)', value: '__OTHER__' },
|
|
160
|
+
],
|
|
161
|
+
});
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
it('should handle single generated name', async () => {
|
|
165
|
+
mockSelect.mockResolvedValue('only-option');
|
|
166
|
+
|
|
167
|
+
const result = await pickProjectName(['only-option']);
|
|
168
|
+
|
|
169
|
+
expect(result).toBe('only-option');
|
|
170
|
+
expect(mockSelect).toHaveBeenCalledWith({
|
|
171
|
+
message: 'Select a project name:',
|
|
172
|
+
choices: [
|
|
173
|
+
{ name: 'only-option', value: 'only-option' },
|
|
174
|
+
{ name: 'Other (enter custom name)', value: '__OTHER__' },
|
|
175
|
+
],
|
|
176
|
+
});
|
|
177
|
+
});
|
|
178
|
+
});
|
|
179
|
+
});
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import { formatRetryHistoryForConsole } from '../../src/commands/do.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Tests for outcome content generation and retry history formatting.
|
|
5
|
+
* Verifies:
|
|
6
|
+
* - Successful outcomes do NOT contain ## Details or ## Failure History
|
|
7
|
+
* - Failed outcomes DO contain ## Details (for debugging)
|
|
8
|
+
* - Retry history is correctly formatted for console output
|
|
9
|
+
*/
|
|
10
|
+
describe('Outcome Content Format', () => {
|
|
11
|
+
/**
|
|
12
|
+
* Helper that generates a successful outcome content matching the template in do.ts.
|
|
13
|
+
* NOTE: Successful outcomes no longer include ## Details section.
|
|
14
|
+
*/
|
|
15
|
+
function generateSuccessOutcome(taskId: string): string {
|
|
16
|
+
return `## Status: SUCCESS
|
|
17
|
+
|
|
18
|
+
# Task ${taskId} - Completed
|
|
19
|
+
|
|
20
|
+
Task completed. No detailed report provided.
|
|
21
|
+
|
|
22
|
+
<promise>COMPLETE</promise>
|
|
23
|
+
`;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Helper that generates a failed outcome content matching the template in do.ts.
|
|
28
|
+
* NOTE: Failed outcomes still include ## Details section for debugging.
|
|
29
|
+
*/
|
|
30
|
+
function generateFailedOutcome(
|
|
31
|
+
taskId: string,
|
|
32
|
+
attempts: number = 1,
|
|
33
|
+
elapsed: string = '1m 30s',
|
|
34
|
+
stashName?: string
|
|
35
|
+
): string {
|
|
36
|
+
return `## Status: FAILED
|
|
37
|
+
|
|
38
|
+
# Task ${taskId} - Failed
|
|
39
|
+
|
|
40
|
+
## Failure Reason
|
|
41
|
+
Task failed for some reason.
|
|
42
|
+
|
|
43
|
+
<promise>FAILED</promise>
|
|
44
|
+
|
|
45
|
+
## Details
|
|
46
|
+
- Attempts: ${attempts}
|
|
47
|
+
- Elapsed time: ${elapsed}
|
|
48
|
+
- Failed at: ${new Date().toISOString()}
|
|
49
|
+
${stashName ? `- Stash: ${stashName}` : ''}
|
|
50
|
+
`;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
describe('successful outcomes', () => {
|
|
54
|
+
it('should not contain ## Details section', () => {
|
|
55
|
+
const outcome = generateSuccessOutcome('001');
|
|
56
|
+
|
|
57
|
+
expect(outcome).not.toContain('## Details');
|
|
58
|
+
expect(outcome).not.toContain('Attempts:');
|
|
59
|
+
expect(outcome).not.toContain('Elapsed time:');
|
|
60
|
+
expect(outcome).not.toContain('Completed at:');
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('should not contain ## Failure History section', () => {
|
|
64
|
+
const outcome = generateSuccessOutcome('001');
|
|
65
|
+
|
|
66
|
+
expect(outcome).not.toContain('## Failure History');
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('should contain the completion marker', () => {
|
|
70
|
+
const outcome = generateSuccessOutcome('001');
|
|
71
|
+
|
|
72
|
+
expect(outcome).toContain('<promise>COMPLETE</promise>');
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('should contain basic structure', () => {
|
|
76
|
+
const outcome = generateSuccessOutcome('002');
|
|
77
|
+
|
|
78
|
+
expect(outcome).toContain('## Status: SUCCESS');
|
|
79
|
+
expect(outcome).toContain('# Task 002 - Completed');
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
describe('failed outcomes', () => {
|
|
84
|
+
it('should contain ## Details section for debugging', () => {
|
|
85
|
+
const outcome = generateFailedOutcome('001', 3, '5m 10s');
|
|
86
|
+
|
|
87
|
+
expect(outcome).toContain('## Details');
|
|
88
|
+
expect(outcome).toContain('Attempts: 3');
|
|
89
|
+
expect(outcome).toContain('Elapsed time: 5m 10s');
|
|
90
|
+
expect(outcome).toContain('Failed at:');
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it('should not contain ## Failure History section', () => {
|
|
94
|
+
const outcome = generateFailedOutcome('001');
|
|
95
|
+
|
|
96
|
+
expect(outcome).not.toContain('## Failure History');
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it('should contain the failure marker', () => {
|
|
100
|
+
const outcome = generateFailedOutcome('001');
|
|
101
|
+
|
|
102
|
+
expect(outcome).toContain('<promise>FAILED</promise>');
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it('should include stash name when provided', () => {
|
|
106
|
+
const outcome = generateFailedOutcome('001', 2, '3m', 'raf-001-task-001-failed');
|
|
107
|
+
|
|
108
|
+
expect(outcome).toContain('Stash: raf-001-task-001-failed');
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
describe('Retry History Console Output', () => {
|
|
114
|
+
describe('formatRetryHistoryForConsole', () => {
|
|
115
|
+
it('should return empty string when no failures', () => {
|
|
116
|
+
const result = formatRetryHistoryForConsole('001', 'my-task', [], 1, true);
|
|
117
|
+
expect(result).toBe('');
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it('should format single failure with eventual success', () => {
|
|
121
|
+
const failureHistory = [{ attempt: 1, reason: 'Connection timeout' }];
|
|
122
|
+
const result = formatRetryHistoryForConsole('001', 'my-task', failureHistory, 2, true);
|
|
123
|
+
|
|
124
|
+
expect(result).toContain('Task 001 (my-task):');
|
|
125
|
+
expect(result).toContain('Attempt 1: Failed - Connection timeout');
|
|
126
|
+
expect(result).toContain('Attempt 2: Succeeded');
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it('should format multiple failures with eventual success', () => {
|
|
130
|
+
const failureHistory = [
|
|
131
|
+
{ attempt: 1, reason: 'Connection timeout' },
|
|
132
|
+
{ attempt: 2, reason: 'API error' },
|
|
133
|
+
];
|
|
134
|
+
const result = formatRetryHistoryForConsole('001', 'my-task', failureHistory, 3, true);
|
|
135
|
+
|
|
136
|
+
expect(result).toContain('Task 001 (my-task):');
|
|
137
|
+
expect(result).toContain('Attempt 1: Failed - Connection timeout');
|
|
138
|
+
expect(result).toContain('Attempt 2: Failed - API error');
|
|
139
|
+
expect(result).toContain('Attempt 3: Succeeded');
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
it('should format failures without success (final failure)', () => {
|
|
143
|
+
const failureHistory = [
|
|
144
|
+
{ attempt: 1, reason: 'Connection timeout' },
|
|
145
|
+
{ attempt: 2, reason: 'API error' },
|
|
146
|
+
{ attempt: 3, reason: 'Max retries exceeded' },
|
|
147
|
+
];
|
|
148
|
+
const result = formatRetryHistoryForConsole('001', 'my-task', failureHistory, 3, false);
|
|
149
|
+
|
|
150
|
+
expect(result).toContain('Task 001 (my-task):');
|
|
151
|
+
expect(result).toContain('Attempt 1: Failed - Connection timeout');
|
|
152
|
+
expect(result).toContain('Attempt 2: Failed - API error');
|
|
153
|
+
expect(result).toContain('Attempt 3: Failed - Max retries exceeded');
|
|
154
|
+
expect(result).not.toContain('Succeeded');
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
it('should handle task name same as task id', () => {
|
|
158
|
+
const failureHistory = [{ attempt: 1, reason: 'Error' }];
|
|
159
|
+
const result = formatRetryHistoryForConsole('001', '001', failureHistory, 2, true);
|
|
160
|
+
|
|
161
|
+
// When taskName equals taskId, should just show taskId
|
|
162
|
+
expect(result).toContain('Task 001:');
|
|
163
|
+
expect(result).not.toContain('Task 001 (001):');
|
|
164
|
+
});
|
|
165
|
+
});
|
|
166
|
+
});
|