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,629 @@
|
|
|
1
|
+
import { jest } from '@jest/globals';
|
|
2
|
+
import { EventEmitter } from 'events';
|
|
3
|
+
|
|
4
|
+
// Create mock spawn before importing ClaudeRunner
|
|
5
|
+
const mockSpawn = jest.fn();
|
|
6
|
+
const mockExecSync = jest.fn();
|
|
7
|
+
|
|
8
|
+
jest.unstable_mockModule('node:child_process', () => ({
|
|
9
|
+
spawn: mockSpawn,
|
|
10
|
+
execSync: mockExecSync,
|
|
11
|
+
}));
|
|
12
|
+
|
|
13
|
+
// Import after mocking
|
|
14
|
+
const { ClaudeRunner } = await import('../../src/core/claude-runner.js');
|
|
15
|
+
|
|
16
|
+
describe('ClaudeRunner', () => {
|
|
17
|
+
beforeEach(() => {
|
|
18
|
+
jest.clearAllMocks();
|
|
19
|
+
jest.useFakeTimers();
|
|
20
|
+
mockExecSync.mockReturnValue('/usr/local/bin/claude\n');
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
afterEach(() => {
|
|
24
|
+
jest.useRealTimers();
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
describe('timeout handling', () => {
|
|
28
|
+
/**
|
|
29
|
+
* Creates a mock child process for testing.
|
|
30
|
+
* The mock process emits 'close' event when kill() is called or after a delay.
|
|
31
|
+
*/
|
|
32
|
+
function createMockProcess() {
|
|
33
|
+
const stdout = new EventEmitter();
|
|
34
|
+
const stderr = new EventEmitter();
|
|
35
|
+
const proc = new EventEmitter() as any;
|
|
36
|
+
proc.stdout = stdout;
|
|
37
|
+
proc.stderr = stderr;
|
|
38
|
+
proc.kill = jest.fn().mockImplementation(() => {
|
|
39
|
+
// Emit close event when killed
|
|
40
|
+
setImmediate(() => proc.emit('close', 1));
|
|
41
|
+
});
|
|
42
|
+
return proc;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
it('should set up timeout for each run() call', async () => {
|
|
46
|
+
const mockProc = createMockProcess();
|
|
47
|
+
mockSpawn.mockReturnValue(mockProc);
|
|
48
|
+
|
|
49
|
+
const runner = new ClaudeRunner();
|
|
50
|
+
const runPromise = runner.run('test prompt', { timeout: 5 }); // 5 minutes
|
|
51
|
+
|
|
52
|
+
// Verify process was spawned
|
|
53
|
+
expect(mockSpawn).toHaveBeenCalledTimes(1);
|
|
54
|
+
|
|
55
|
+
// Fast forward to just before timeout (5 minutes = 300000ms)
|
|
56
|
+
jest.advanceTimersByTime(299999);
|
|
57
|
+
expect(mockProc.kill).not.toHaveBeenCalled();
|
|
58
|
+
|
|
59
|
+
// Fast forward past timeout
|
|
60
|
+
jest.advanceTimersByTime(2);
|
|
61
|
+
expect(mockProc.kill).toHaveBeenCalledWith('SIGTERM');
|
|
62
|
+
|
|
63
|
+
const result = await runPromise;
|
|
64
|
+
expect(result.timedOut).toBe(true);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it('should set up timeout for each runVerbose() call', async () => {
|
|
68
|
+
const mockProc = createMockProcess();
|
|
69
|
+
mockSpawn.mockReturnValue(mockProc);
|
|
70
|
+
|
|
71
|
+
const runner = new ClaudeRunner();
|
|
72
|
+
const runPromise = runner.runVerbose('test prompt', { timeout: 3 }); // 3 minutes
|
|
73
|
+
|
|
74
|
+
// Verify process was spawned
|
|
75
|
+
expect(mockSpawn).toHaveBeenCalledTimes(1);
|
|
76
|
+
|
|
77
|
+
// Fast forward to just before timeout (3 minutes = 180000ms)
|
|
78
|
+
jest.advanceTimersByTime(179999);
|
|
79
|
+
expect(mockProc.kill).not.toHaveBeenCalled();
|
|
80
|
+
|
|
81
|
+
// Fast forward past timeout
|
|
82
|
+
jest.advanceTimersByTime(2);
|
|
83
|
+
expect(mockProc.kill).toHaveBeenCalledWith('SIGTERM');
|
|
84
|
+
|
|
85
|
+
const result = await runPromise;
|
|
86
|
+
expect(result.timedOut).toBe(true);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it('should use default timeout of 60 minutes when not specified', async () => {
|
|
90
|
+
const mockProc = createMockProcess();
|
|
91
|
+
mockSpawn.mockReturnValue(mockProc);
|
|
92
|
+
|
|
93
|
+
const runner = new ClaudeRunner();
|
|
94
|
+
const runPromise = runner.run('test prompt', {}); // No timeout specified
|
|
95
|
+
|
|
96
|
+
// Fast forward to just before default timeout (60 minutes = 3600000ms)
|
|
97
|
+
jest.advanceTimersByTime(3600000 - 1);
|
|
98
|
+
expect(mockProc.kill).not.toHaveBeenCalled();
|
|
99
|
+
|
|
100
|
+
// Fast forward past timeout
|
|
101
|
+
jest.advanceTimersByTime(2);
|
|
102
|
+
expect(mockProc.kill).toHaveBeenCalledWith('SIGTERM');
|
|
103
|
+
|
|
104
|
+
const result = await runPromise;
|
|
105
|
+
expect(result.timedOut).toBe(true);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it('should clear timeout when process completes normally', async () => {
|
|
109
|
+
const mockProc = createMockProcess();
|
|
110
|
+
mockSpawn.mockReturnValue(mockProc);
|
|
111
|
+
|
|
112
|
+
const runner = new ClaudeRunner();
|
|
113
|
+
const runPromise = runner.run('test prompt', { timeout: 5 });
|
|
114
|
+
|
|
115
|
+
// Emit some output and close the process normally
|
|
116
|
+
mockProc.stdout.emit('data', Buffer.from('<promise>COMPLETE</promise>'));
|
|
117
|
+
mockProc.emit('close', 0);
|
|
118
|
+
|
|
119
|
+
const result = await runPromise;
|
|
120
|
+
expect(result.timedOut).toBe(false);
|
|
121
|
+
expect(result.exitCode).toBe(0);
|
|
122
|
+
|
|
123
|
+
// Advance timer past timeout - should not cause any issues
|
|
124
|
+
// since timeout was cleared
|
|
125
|
+
jest.advanceTimersByTime(400000);
|
|
126
|
+
expect(mockProc.kill).not.toHaveBeenCalled();
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it('should apply fresh timeout for each consecutive call', async () => {
|
|
130
|
+
const runner = new ClaudeRunner();
|
|
131
|
+
|
|
132
|
+
// First call with 2 minute timeout
|
|
133
|
+
const mockProc1 = createMockProcess();
|
|
134
|
+
mockSpawn.mockReturnValue(mockProc1);
|
|
135
|
+
|
|
136
|
+
const runPromise1 = runner.run('first prompt', { timeout: 2 });
|
|
137
|
+
|
|
138
|
+
// Complete first call quickly
|
|
139
|
+
mockProc1.stdout.emit('data', Buffer.from('output'));
|
|
140
|
+
mockProc1.emit('close', 0);
|
|
141
|
+
await runPromise1;
|
|
142
|
+
|
|
143
|
+
// Reset spawn mock for second call
|
|
144
|
+
const mockProc2 = createMockProcess();
|
|
145
|
+
mockSpawn.mockReturnValue(mockProc2);
|
|
146
|
+
|
|
147
|
+
// Second call with 3 minute timeout
|
|
148
|
+
const runPromise2 = runner.run('second prompt', { timeout: 3 });
|
|
149
|
+
|
|
150
|
+
// Fast forward 2.5 minutes - past first timeout but before second
|
|
151
|
+
jest.advanceTimersByTime(150000);
|
|
152
|
+
expect(mockProc2.kill).not.toHaveBeenCalled();
|
|
153
|
+
|
|
154
|
+
// Fast forward another minute - past second timeout
|
|
155
|
+
jest.advanceTimersByTime(60000);
|
|
156
|
+
expect(mockProc2.kill).toHaveBeenCalledWith('SIGTERM');
|
|
157
|
+
|
|
158
|
+
const result2 = await runPromise2;
|
|
159
|
+
expect(result2.timedOut).toBe(true);
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
it('should set timedOut flag correctly on timeout', async () => {
|
|
163
|
+
const mockProc = createMockProcess();
|
|
164
|
+
mockSpawn.mockReturnValue(mockProc);
|
|
165
|
+
|
|
166
|
+
const runner = new ClaudeRunner();
|
|
167
|
+
const runPromise = runner.run('test prompt', { timeout: 1 }); // 1 minute
|
|
168
|
+
|
|
169
|
+
// Fast forward past timeout
|
|
170
|
+
jest.advanceTimersByTime(60001);
|
|
171
|
+
|
|
172
|
+
const result = await runPromise;
|
|
173
|
+
expect(result.timedOut).toBe(true);
|
|
174
|
+
expect(result.exitCode).toBe(1); // Process was killed
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
it('should not set timedOut flag when completed before timeout', async () => {
|
|
178
|
+
const mockProc = createMockProcess();
|
|
179
|
+
mockSpawn.mockReturnValue(mockProc);
|
|
180
|
+
|
|
181
|
+
const runner = new ClaudeRunner();
|
|
182
|
+
const runPromise = runner.run('test prompt', { timeout: 10 });
|
|
183
|
+
|
|
184
|
+
// Advance a bit but not to timeout
|
|
185
|
+
jest.advanceTimersByTime(30000);
|
|
186
|
+
|
|
187
|
+
// Complete process
|
|
188
|
+
mockProc.stdout.emit('data', Buffer.from('output'));
|
|
189
|
+
mockProc.emit('close', 0);
|
|
190
|
+
|
|
191
|
+
const result = await runPromise;
|
|
192
|
+
expect(result.timedOut).toBe(false);
|
|
193
|
+
expect(result.exitCode).toBe(0);
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
it('should use default timeout of 60 minutes when timeout is 0', async () => {
|
|
197
|
+
const mockProc = createMockProcess();
|
|
198
|
+
mockSpawn.mockReturnValue(mockProc);
|
|
199
|
+
|
|
200
|
+
const runner = new ClaudeRunner();
|
|
201
|
+
const runPromise = runner.run('test prompt', { timeout: 0 });
|
|
202
|
+
|
|
203
|
+
// Should NOT timeout at 0ms since timeout=0 should use default (60 minutes)
|
|
204
|
+
// Just verify it doesn't immediately trigger
|
|
205
|
+
jest.advanceTimersByTime(1000);
|
|
206
|
+
expect(mockProc.kill).not.toHaveBeenCalled();
|
|
207
|
+
|
|
208
|
+
// Complete the process normally before reaching the 60 minute timeout
|
|
209
|
+
mockProc.stdout.emit('data', Buffer.from('output'));
|
|
210
|
+
mockProc.emit('close', 0);
|
|
211
|
+
|
|
212
|
+
const result = await runPromise;
|
|
213
|
+
expect(result.timedOut).toBe(false);
|
|
214
|
+
expect(result.exitCode).toBe(0);
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
it('should use default timeout of 60 minutes when timeout is negative', async () => {
|
|
218
|
+
const mockProc = createMockProcess();
|
|
219
|
+
mockSpawn.mockReturnValue(mockProc);
|
|
220
|
+
|
|
221
|
+
const runner = new ClaudeRunner();
|
|
222
|
+
const runPromise = runner.run('test prompt', { timeout: -10 });
|
|
223
|
+
|
|
224
|
+
// Should use default 60 minutes for negative values
|
|
225
|
+
jest.advanceTimersByTime(3600000 - 1);
|
|
226
|
+
expect(mockProc.kill).not.toHaveBeenCalled();
|
|
227
|
+
|
|
228
|
+
// Fast forward past 60 minutes
|
|
229
|
+
jest.advanceTimersByTime(2);
|
|
230
|
+
expect(mockProc.kill).toHaveBeenCalledWith('SIGTERM');
|
|
231
|
+
|
|
232
|
+
const result = await runPromise;
|
|
233
|
+
expect(result.timedOut).toBe(true);
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
it('should use default timeout of 60 minutes when timeout is NaN', async () => {
|
|
237
|
+
const mockProc = createMockProcess();
|
|
238
|
+
mockSpawn.mockReturnValue(mockProc);
|
|
239
|
+
|
|
240
|
+
const runner = new ClaudeRunner();
|
|
241
|
+
// @ts-expect-error - testing invalid input
|
|
242
|
+
const runPromise = runner.run('test prompt', { timeout: 'invalid' });
|
|
243
|
+
|
|
244
|
+
// Should use default 60 minutes for NaN
|
|
245
|
+
jest.advanceTimersByTime(3600000 - 1);
|
|
246
|
+
expect(mockProc.kill).not.toHaveBeenCalled();
|
|
247
|
+
|
|
248
|
+
// Fast forward past 60 minutes
|
|
249
|
+
jest.advanceTimersByTime(2);
|
|
250
|
+
expect(mockProc.kill).toHaveBeenCalledWith('SIGTERM');
|
|
251
|
+
|
|
252
|
+
const result = await runPromise;
|
|
253
|
+
expect(result.timedOut).toBe(true);
|
|
254
|
+
});
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
describe('context overflow detection', () => {
|
|
258
|
+
function createMockProcessWithImmediateClose() {
|
|
259
|
+
const stdout = new EventEmitter();
|
|
260
|
+
const stderr = new EventEmitter();
|
|
261
|
+
const proc = new EventEmitter() as any;
|
|
262
|
+
proc.stdout = stdout;
|
|
263
|
+
proc.stderr = stderr;
|
|
264
|
+
proc.kill = jest.fn().mockImplementation(() => {
|
|
265
|
+
// Use synchronous emission for tests without fake timer issues
|
|
266
|
+
process.nextTick(() => proc.emit('close', 1));
|
|
267
|
+
});
|
|
268
|
+
return proc;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
it('should detect context overflow and kill process', async () => {
|
|
272
|
+
// Use real timers for this test since we're not testing timeout
|
|
273
|
+
jest.useRealTimers();
|
|
274
|
+
|
|
275
|
+
const mockProc = createMockProcessWithImmediateClose();
|
|
276
|
+
mockSpawn.mockReturnValue(mockProc);
|
|
277
|
+
|
|
278
|
+
const runner = new ClaudeRunner();
|
|
279
|
+
const runPromise = runner.run('test prompt', { timeout: 60 });
|
|
280
|
+
|
|
281
|
+
// Emit context overflow message
|
|
282
|
+
mockProc.stdout.emit('data', Buffer.from('Error: context length exceeded'));
|
|
283
|
+
|
|
284
|
+
const result = await runPromise;
|
|
285
|
+
expect(result.contextOverflow).toBe(true);
|
|
286
|
+
expect(mockProc.kill).toHaveBeenCalledWith('SIGTERM');
|
|
287
|
+
|
|
288
|
+
// Restore fake timers for subsequent tests
|
|
289
|
+
jest.useFakeTimers();
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
it('should detect various context overflow patterns', async () => {
|
|
293
|
+
// Use real timers for this test since we're not testing timeout
|
|
294
|
+
jest.useRealTimers();
|
|
295
|
+
|
|
296
|
+
const patterns = [
|
|
297
|
+
'token limit reached',
|
|
298
|
+
'maximum context size',
|
|
299
|
+
'context window full',
|
|
300
|
+
];
|
|
301
|
+
|
|
302
|
+
for (const pattern of patterns) {
|
|
303
|
+
const mockProc = createMockProcessWithImmediateClose();
|
|
304
|
+
mockSpawn.mockReturnValue(mockProc);
|
|
305
|
+
|
|
306
|
+
const runner = new ClaudeRunner();
|
|
307
|
+
const runPromise = runner.run('test prompt', { timeout: 60 });
|
|
308
|
+
|
|
309
|
+
mockProc.stdout.emit('data', Buffer.from(`Error: ${pattern}`));
|
|
310
|
+
|
|
311
|
+
const result = await runPromise;
|
|
312
|
+
expect(result.contextOverflow).toBe(true);
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// Restore fake timers for subsequent tests
|
|
316
|
+
jest.useFakeTimers();
|
|
317
|
+
});
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
describe('output collection', () => {
|
|
321
|
+
function createMockProcess() {
|
|
322
|
+
const stdout = new EventEmitter();
|
|
323
|
+
const stderr = new EventEmitter();
|
|
324
|
+
const proc = new EventEmitter() as any;
|
|
325
|
+
proc.stdout = stdout;
|
|
326
|
+
proc.stderr = stderr;
|
|
327
|
+
proc.kill = jest.fn();
|
|
328
|
+
return proc;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
it('should collect all stdout output', async () => {
|
|
332
|
+
const mockProc = createMockProcess();
|
|
333
|
+
mockSpawn.mockReturnValue(mockProc);
|
|
334
|
+
|
|
335
|
+
const runner = new ClaudeRunner();
|
|
336
|
+
const runPromise = runner.run('test prompt', { timeout: 60 });
|
|
337
|
+
|
|
338
|
+
// Emit multiple chunks
|
|
339
|
+
mockProc.stdout.emit('data', Buffer.from('chunk1'));
|
|
340
|
+
mockProc.stdout.emit('data', Buffer.from('chunk2'));
|
|
341
|
+
mockProc.stdout.emit('data', Buffer.from('chunk3'));
|
|
342
|
+
mockProc.emit('close', 0);
|
|
343
|
+
|
|
344
|
+
const result = await runPromise;
|
|
345
|
+
expect(result.output).toBe('chunk1chunk2chunk3');
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
it('should pass working directory to spawn', async () => {
|
|
349
|
+
const mockProc = createMockProcess();
|
|
350
|
+
mockSpawn.mockReturnValue(mockProc);
|
|
351
|
+
|
|
352
|
+
const runner = new ClaudeRunner();
|
|
353
|
+
const runPromise = runner.run('test prompt', { timeout: 60, cwd: '/custom/path' });
|
|
354
|
+
|
|
355
|
+
mockProc.emit('close', 0);
|
|
356
|
+
await runPromise;
|
|
357
|
+
|
|
358
|
+
expect(mockSpawn).toHaveBeenCalledWith(
|
|
359
|
+
expect.any(String),
|
|
360
|
+
expect.any(Array),
|
|
361
|
+
expect.objectContaining({ cwd: '/custom/path' })
|
|
362
|
+
);
|
|
363
|
+
});
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
describe('model configuration', () => {
|
|
367
|
+
function createMockProcess() {
|
|
368
|
+
const stdout = new EventEmitter();
|
|
369
|
+
const stderr = new EventEmitter();
|
|
370
|
+
const proc = new EventEmitter() as any;
|
|
371
|
+
proc.stdout = stdout;
|
|
372
|
+
proc.stderr = stderr;
|
|
373
|
+
proc.kill = jest.fn();
|
|
374
|
+
return proc;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
it('should use opus as default model', async () => {
|
|
378
|
+
const mockProc = createMockProcess();
|
|
379
|
+
mockSpawn.mockReturnValue(mockProc);
|
|
380
|
+
|
|
381
|
+
const runner = new ClaudeRunner();
|
|
382
|
+
const runPromise = runner.run('test prompt', { timeout: 60 });
|
|
383
|
+
|
|
384
|
+
mockProc.emit('close', 0);
|
|
385
|
+
await runPromise;
|
|
386
|
+
|
|
387
|
+
expect(mockSpawn).toHaveBeenCalledWith(
|
|
388
|
+
expect.any(String),
|
|
389
|
+
expect.arrayContaining(['--model', 'opus']),
|
|
390
|
+
expect.any(Object)
|
|
391
|
+
);
|
|
392
|
+
});
|
|
393
|
+
|
|
394
|
+
it('should pass model to Claude CLI in run()', async () => {
|
|
395
|
+
const mockProc = createMockProcess();
|
|
396
|
+
mockSpawn.mockReturnValue(mockProc);
|
|
397
|
+
|
|
398
|
+
const runner = new ClaudeRunner({ model: 'sonnet' });
|
|
399
|
+
const runPromise = runner.run('test prompt', { timeout: 60 });
|
|
400
|
+
|
|
401
|
+
mockProc.emit('close', 0);
|
|
402
|
+
await runPromise;
|
|
403
|
+
|
|
404
|
+
expect(mockSpawn).toHaveBeenCalledWith(
|
|
405
|
+
expect.any(String),
|
|
406
|
+
expect.arrayContaining(['--model', 'sonnet']),
|
|
407
|
+
expect.any(Object)
|
|
408
|
+
);
|
|
409
|
+
});
|
|
410
|
+
|
|
411
|
+
it('should pass model to Claude CLI in runVerbose()', async () => {
|
|
412
|
+
const mockProc = createMockProcess();
|
|
413
|
+
mockSpawn.mockReturnValue(mockProc);
|
|
414
|
+
|
|
415
|
+
const runner = new ClaudeRunner({ model: 'haiku' });
|
|
416
|
+
const runPromise = runner.runVerbose('test prompt', { timeout: 60 });
|
|
417
|
+
|
|
418
|
+
mockProc.emit('close', 0);
|
|
419
|
+
await runPromise;
|
|
420
|
+
|
|
421
|
+
expect(mockSpawn).toHaveBeenCalledWith(
|
|
422
|
+
expect.any(String),
|
|
423
|
+
expect.arrayContaining(['--model', 'haiku']),
|
|
424
|
+
expect.any(Object)
|
|
425
|
+
);
|
|
426
|
+
});
|
|
427
|
+
|
|
428
|
+
it('should include model flag in args order before prompt', async () => {
|
|
429
|
+
const mockProc = createMockProcess();
|
|
430
|
+
mockSpawn.mockReturnValue(mockProc);
|
|
431
|
+
|
|
432
|
+
const runner = new ClaudeRunner({ model: 'sonnet' });
|
|
433
|
+
const runPromise = runner.run('test prompt', { timeout: 60 });
|
|
434
|
+
|
|
435
|
+
mockProc.emit('close', 0);
|
|
436
|
+
await runPromise;
|
|
437
|
+
|
|
438
|
+
const spawnArgs = mockSpawn.mock.calls[0][1] as string[];
|
|
439
|
+
const modelIndex = spawnArgs.indexOf('--model');
|
|
440
|
+
const promptFlagIndex = spawnArgs.indexOf('-p');
|
|
441
|
+
|
|
442
|
+
expect(modelIndex).toBeGreaterThanOrEqual(0);
|
|
443
|
+
expect(promptFlagIndex).toBeGreaterThanOrEqual(0);
|
|
444
|
+
expect(modelIndex).toBeLessThan(promptFlagIndex);
|
|
445
|
+
});
|
|
446
|
+
});
|
|
447
|
+
|
|
448
|
+
describe('system prompt append flag', () => {
|
|
449
|
+
function createMockProcess() {
|
|
450
|
+
const stdout = new EventEmitter();
|
|
451
|
+
const stderr = new EventEmitter();
|
|
452
|
+
const proc = new EventEmitter() as any;
|
|
453
|
+
proc.stdout = stdout;
|
|
454
|
+
proc.stderr = stderr;
|
|
455
|
+
proc.kill = jest.fn();
|
|
456
|
+
return proc;
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
it('should use --append-system-prompt flag in run()', async () => {
|
|
460
|
+
const mockProc = createMockProcess();
|
|
461
|
+
mockSpawn.mockReturnValue(mockProc);
|
|
462
|
+
|
|
463
|
+
const runner = new ClaudeRunner();
|
|
464
|
+
const rafPrompt = 'RAF instructions here';
|
|
465
|
+
const runPromise = runner.run(rafPrompt, { timeout: 60 });
|
|
466
|
+
|
|
467
|
+
mockProc.emit('close', 0);
|
|
468
|
+
await runPromise;
|
|
469
|
+
|
|
470
|
+
const spawnArgs = mockSpawn.mock.calls[0][1] as string[];
|
|
471
|
+
expect(spawnArgs).toContain('--append-system-prompt');
|
|
472
|
+
expect(spawnArgs).toContain(rafPrompt);
|
|
473
|
+
|
|
474
|
+
// Verify the --append-system-prompt flag comes before the prompt
|
|
475
|
+
const appendIndex = spawnArgs.indexOf('--append-system-prompt');
|
|
476
|
+
const promptIndex = spawnArgs.indexOf(rafPrompt);
|
|
477
|
+
expect(appendIndex).toBeLessThan(promptIndex);
|
|
478
|
+
expect(spawnArgs[appendIndex + 1]).toBe(rafPrompt);
|
|
479
|
+
});
|
|
480
|
+
|
|
481
|
+
it('should use --append-system-prompt flag in runVerbose()', async () => {
|
|
482
|
+
const mockProc = createMockProcess();
|
|
483
|
+
mockSpawn.mockReturnValue(mockProc);
|
|
484
|
+
|
|
485
|
+
const runner = new ClaudeRunner();
|
|
486
|
+
const rafPrompt = 'RAF verbose instructions here';
|
|
487
|
+
const runPromise = runner.runVerbose(rafPrompt, { timeout: 60 });
|
|
488
|
+
|
|
489
|
+
mockProc.emit('close', 0);
|
|
490
|
+
await runPromise;
|
|
491
|
+
|
|
492
|
+
const spawnArgs = mockSpawn.mock.calls[0][1] as string[];
|
|
493
|
+
expect(spawnArgs).toContain('--append-system-prompt');
|
|
494
|
+
expect(spawnArgs).toContain(rafPrompt);
|
|
495
|
+
|
|
496
|
+
// Verify the --append-system-prompt flag comes before the prompt
|
|
497
|
+
const appendIndex = spawnArgs.indexOf('--append-system-prompt');
|
|
498
|
+
const promptIndex = spawnArgs.indexOf(rafPrompt);
|
|
499
|
+
expect(appendIndex).toBeLessThan(promptIndex);
|
|
500
|
+
expect(spawnArgs[appendIndex + 1]).toBe(rafPrompt);
|
|
501
|
+
});
|
|
502
|
+
|
|
503
|
+
it('should pass minimal trigger prompt with -p flag', async () => {
|
|
504
|
+
const mockProc = createMockProcess();
|
|
505
|
+
mockSpawn.mockReturnValue(mockProc);
|
|
506
|
+
|
|
507
|
+
const runner = new ClaudeRunner();
|
|
508
|
+
const runPromise = runner.run('RAF instructions', { timeout: 60 });
|
|
509
|
+
|
|
510
|
+
mockProc.emit('close', 0);
|
|
511
|
+
await runPromise;
|
|
512
|
+
|
|
513
|
+
const spawnArgs = mockSpawn.mock.calls[0][1] as string[];
|
|
514
|
+
expect(spawnArgs).toContain('-p');
|
|
515
|
+
|
|
516
|
+
// The trigger prompt should follow -p
|
|
517
|
+
const pIndex = spawnArgs.indexOf('-p');
|
|
518
|
+
expect(spawnArgs[pIndex + 1]).toBe('Execute the task as described in the system prompt.');
|
|
519
|
+
});
|
|
520
|
+
|
|
521
|
+
it('should include all required flags in correct order', async () => {
|
|
522
|
+
const mockProc = createMockProcess();
|
|
523
|
+
mockSpawn.mockReturnValue(mockProc);
|
|
524
|
+
|
|
525
|
+
const runner = new ClaudeRunner({ model: 'sonnet' });
|
|
526
|
+
const runPromise = runner.run('test instructions', { timeout: 60 });
|
|
527
|
+
|
|
528
|
+
mockProc.emit('close', 0);
|
|
529
|
+
await runPromise;
|
|
530
|
+
|
|
531
|
+
const spawnArgs = mockSpawn.mock.calls[0][1] as string[];
|
|
532
|
+
|
|
533
|
+
// Verify all expected flags are present
|
|
534
|
+
expect(spawnArgs).toContain('--dangerously-skip-permissions');
|
|
535
|
+
expect(spawnArgs).toContain('--model');
|
|
536
|
+
expect(spawnArgs).toContain('sonnet');
|
|
537
|
+
expect(spawnArgs).toContain('--append-system-prompt');
|
|
538
|
+
expect(spawnArgs).toContain('-p');
|
|
539
|
+
});
|
|
540
|
+
});
|
|
541
|
+
|
|
542
|
+
describe('retry isolation (timeout per attempt)', () => {
|
|
543
|
+
function createMockProcess() {
|
|
544
|
+
const stdout = new EventEmitter();
|
|
545
|
+
const stderr = new EventEmitter();
|
|
546
|
+
const proc = new EventEmitter() as any;
|
|
547
|
+
proc.stdout = stdout;
|
|
548
|
+
proc.stderr = stderr;
|
|
549
|
+
proc.kill = jest.fn().mockImplementation(() => {
|
|
550
|
+
setImmediate(() => proc.emit('close', 1));
|
|
551
|
+
});
|
|
552
|
+
return proc;
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
it('should provide fresh timeout for each retry attempt', async () => {
|
|
556
|
+
const runner = new ClaudeRunner();
|
|
557
|
+
const timeoutMinutes = 2; // 2 minutes = 120000ms
|
|
558
|
+
|
|
559
|
+
// Simulate multiple retry attempts like do.ts does
|
|
560
|
+
// Each attempt should get its own fresh timeout
|
|
561
|
+
|
|
562
|
+
// Attempt 1 - times out
|
|
563
|
+
const mockProc1 = createMockProcess();
|
|
564
|
+
mockSpawn.mockReturnValue(mockProc1);
|
|
565
|
+
|
|
566
|
+
const runPromise1 = runner.run('attempt 1', { timeout: timeoutMinutes });
|
|
567
|
+
jest.advanceTimersByTime(120001); // Past timeout
|
|
568
|
+
|
|
569
|
+
const result1 = await runPromise1;
|
|
570
|
+
expect(result1.timedOut).toBe(true);
|
|
571
|
+
|
|
572
|
+
// Attempt 2 - fresh timeout, succeeds
|
|
573
|
+
const mockProc2 = createMockProcess();
|
|
574
|
+
mockSpawn.mockReturnValue(mockProc2);
|
|
575
|
+
|
|
576
|
+
const runPromise2 = runner.run('attempt 2', { timeout: timeoutMinutes });
|
|
577
|
+
|
|
578
|
+
// Only advance 1 minute - should not timeout
|
|
579
|
+
jest.advanceTimersByTime(60000);
|
|
580
|
+
expect(mockProc2.kill).not.toHaveBeenCalled();
|
|
581
|
+
|
|
582
|
+
// Complete successfully
|
|
583
|
+
mockProc2.stdout.emit('data', Buffer.from('success'));
|
|
584
|
+
mockProc2.emit('close', 0);
|
|
585
|
+
|
|
586
|
+
const result2 = await runPromise2;
|
|
587
|
+
expect(result2.timedOut).toBe(false);
|
|
588
|
+
expect(result2.output).toBe('success');
|
|
589
|
+
});
|
|
590
|
+
|
|
591
|
+
it('should not share elapsed time between attempts', async () => {
|
|
592
|
+
const runner = new ClaudeRunner();
|
|
593
|
+
const timeoutMinutes = 5; // 5 minutes
|
|
594
|
+
|
|
595
|
+
// First attempt runs for 4 minutes then fails
|
|
596
|
+
const mockProc1 = createMockProcess();
|
|
597
|
+
mockSpawn.mockReturnValue(mockProc1);
|
|
598
|
+
|
|
599
|
+
const runPromise1 = runner.run('attempt 1', { timeout: timeoutMinutes });
|
|
600
|
+
|
|
601
|
+
// Run for 4 minutes (240000ms)
|
|
602
|
+
jest.advanceTimersByTime(240000);
|
|
603
|
+
|
|
604
|
+
// Fail without timeout
|
|
605
|
+
mockProc1.stdout.emit('data', Buffer.from('<promise>FAILED</promise>'));
|
|
606
|
+
mockProc1.emit('close', 1);
|
|
607
|
+
|
|
608
|
+
const result1 = await runPromise1;
|
|
609
|
+
expect(result1.timedOut).toBe(false);
|
|
610
|
+
|
|
611
|
+
// Second attempt should have FULL 5 minutes, not 1 minute remaining
|
|
612
|
+
const mockProc2 = createMockProcess();
|
|
613
|
+
mockSpawn.mockReturnValue(mockProc2);
|
|
614
|
+
|
|
615
|
+
const runPromise2 = runner.run('attempt 2', { timeout: timeoutMinutes });
|
|
616
|
+
|
|
617
|
+
// Advance 4 minutes again - should NOT timeout
|
|
618
|
+
jest.advanceTimersByTime(240000);
|
|
619
|
+
expect(mockProc2.kill).not.toHaveBeenCalled();
|
|
620
|
+
|
|
621
|
+
// Advance past total timeout for this attempt
|
|
622
|
+
jest.advanceTimersByTime(60001);
|
|
623
|
+
expect(mockProc2.kill).toHaveBeenCalledWith('SIGTERM');
|
|
624
|
+
|
|
625
|
+
const result2 = await runPromise2;
|
|
626
|
+
expect(result2.timedOut).toBe(true);
|
|
627
|
+
});
|
|
628
|
+
});
|
|
629
|
+
});
|