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,422 @@
|
|
|
1
|
+
import * as fs from 'node:fs';
|
|
2
|
+
import { Command } from 'commander';
|
|
3
|
+
import { ProjectManager } from '../core/project-manager.js';
|
|
4
|
+
import { ClaudeRunner } from '../core/claude-runner.js';
|
|
5
|
+
import { openEditor, getInputTemplate } from '../core/editor.js';
|
|
6
|
+
import { shutdownHandler } from '../core/shutdown-handler.js';
|
|
7
|
+
import { getPlanningPrompt } from '../prompts/planning.js';
|
|
8
|
+
import { getAmendPrompt } from '../prompts/amend.js';
|
|
9
|
+
import {
|
|
10
|
+
validateEnvironment,
|
|
11
|
+
reportValidation,
|
|
12
|
+
validateProjectName,
|
|
13
|
+
resolveModelOption,
|
|
14
|
+
} from '../utils/validation.js';
|
|
15
|
+
import { logger } from '../utils/logger.js';
|
|
16
|
+
import { generateProjectNames } from '../utils/name-generator.js';
|
|
17
|
+
import { pickProjectName } from '../ui/name-picker.js';
|
|
18
|
+
import {
|
|
19
|
+
getPlansDir,
|
|
20
|
+
getRafDir,
|
|
21
|
+
resolveProjectIdentifier,
|
|
22
|
+
resolveProjectIdentifierWithDetails,
|
|
23
|
+
getInputPath,
|
|
24
|
+
extractTaskNameFromPlanFile,
|
|
25
|
+
} from '../utils/paths.js';
|
|
26
|
+
import {
|
|
27
|
+
deriveProjectState,
|
|
28
|
+
isProjectComplete,
|
|
29
|
+
DerivedTask,
|
|
30
|
+
} from '../core/state-derivation.js';
|
|
31
|
+
|
|
32
|
+
interface PlanCommandOptions {
|
|
33
|
+
amend?: boolean;
|
|
34
|
+
model?: string;
|
|
35
|
+
sonnet?: boolean;
|
|
36
|
+
auto?: boolean;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function createPlanCommand(): Command {
|
|
40
|
+
const command = new Command('plan')
|
|
41
|
+
.description('Create a new project and interactively plan tasks with Claude')
|
|
42
|
+
.argument('[projectName]', 'Optional project name (will be prompted if not provided)')
|
|
43
|
+
.option(
|
|
44
|
+
'-a, --amend',
|
|
45
|
+
'Add tasks to an existing project (requires project identifier as argument)'
|
|
46
|
+
)
|
|
47
|
+
.option('-m, --model <name>', 'Claude model to use (sonnet, haiku, opus)')
|
|
48
|
+
.option('--sonnet', 'Use Sonnet model (shorthand for --model sonnet)')
|
|
49
|
+
.option('-y, --auto', "Skip Claude's permission prompts for file operations")
|
|
50
|
+
.action(async (projectName: string | undefined, options: PlanCommandOptions) => {
|
|
51
|
+
// Validate and resolve model option
|
|
52
|
+
let model: string;
|
|
53
|
+
try {
|
|
54
|
+
model = resolveModelOption(options.model, options.sonnet);
|
|
55
|
+
} catch (error) {
|
|
56
|
+
logger.error((error as Error).message);
|
|
57
|
+
process.exit(1);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const autoMode = options.auto ?? false;
|
|
61
|
+
|
|
62
|
+
if (options.amend) {
|
|
63
|
+
if (!projectName) {
|
|
64
|
+
logger.error('--amend requires a project identifier');
|
|
65
|
+
logger.error('Usage: raf plan <project> --amend');
|
|
66
|
+
logger.error(' or: raf plan --amend <project>');
|
|
67
|
+
process.exit(1);
|
|
68
|
+
}
|
|
69
|
+
await runAmendCommand(projectName, model, autoMode);
|
|
70
|
+
} else {
|
|
71
|
+
await runPlanCommand(projectName, model, autoMode);
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
return command;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
async function runPlanCommand(projectName?: string, model?: string, autoMode: boolean = false): Promise<void> {
|
|
79
|
+
// Validate environment
|
|
80
|
+
const validation = validateEnvironment();
|
|
81
|
+
reportValidation(validation);
|
|
82
|
+
|
|
83
|
+
if (!validation.valid) {
|
|
84
|
+
process.exit(1);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Check if project name matches an existing project
|
|
88
|
+
if (projectName) {
|
|
89
|
+
const rafDir = getRafDir();
|
|
90
|
+
const existingProject = resolveProjectIdentifier(rafDir, projectName);
|
|
91
|
+
if (existingProject) {
|
|
92
|
+
logger.error(`Project already exists: ${existingProject}`);
|
|
93
|
+
logger.error(`To add tasks to an existing project, use: raf plan --amend ${projectName}`);
|
|
94
|
+
process.exit(1);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Open editor for user input
|
|
99
|
+
logger.info('Opening editor for project description...');
|
|
100
|
+
logger.info('(Save and close the editor when done)');
|
|
101
|
+
logger.newline();
|
|
102
|
+
|
|
103
|
+
let userInput: string;
|
|
104
|
+
try {
|
|
105
|
+
userInput = await openEditor(getInputTemplate());
|
|
106
|
+
} catch (error) {
|
|
107
|
+
logger.error(`Failed to get input: ${error}`);
|
|
108
|
+
process.exit(1);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Check if input is empty or just the template
|
|
112
|
+
const cleanInput = userInput
|
|
113
|
+
.replace(/<!--[\s\S]*?-->/g, '')
|
|
114
|
+
.replace(/^#.*$/gm, '')
|
|
115
|
+
.trim();
|
|
116
|
+
|
|
117
|
+
if (!cleanInput) {
|
|
118
|
+
logger.error('No project description provided. Aborting.');
|
|
119
|
+
process.exit(1);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Get or generate project name
|
|
123
|
+
let finalProjectName = projectName;
|
|
124
|
+
if (!finalProjectName) {
|
|
125
|
+
logger.info('Generating project name suggestions...');
|
|
126
|
+
const suggestedNames = await generateProjectNames(cleanInput);
|
|
127
|
+
logger.newline();
|
|
128
|
+
|
|
129
|
+
if (autoMode) {
|
|
130
|
+
// Auto-select the first generated name
|
|
131
|
+
// generateProjectNames always returns at least one fallback name
|
|
132
|
+
finalProjectName = suggestedNames[0] ?? 'project';
|
|
133
|
+
logger.info(`Auto-selected project name: ${finalProjectName}`);
|
|
134
|
+
} else {
|
|
135
|
+
finalProjectName = await pickProjectName(suggestedNames);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (!validateProjectName(finalProjectName)) {
|
|
140
|
+
logger.error('Invalid project name. Use only letters, numbers, hyphens, and underscores.');
|
|
141
|
+
process.exit(1);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Create project
|
|
145
|
+
const projectManager = new ProjectManager();
|
|
146
|
+
const projectPath = projectManager.createProject(finalProjectName);
|
|
147
|
+
|
|
148
|
+
logger.success(`Created project: ${projectPath}`);
|
|
149
|
+
logger.newline();
|
|
150
|
+
|
|
151
|
+
// Save input
|
|
152
|
+
projectManager.saveInput(projectPath, userInput);
|
|
153
|
+
|
|
154
|
+
// Set up shutdown handler
|
|
155
|
+
const claudeRunner = new ClaudeRunner({ model });
|
|
156
|
+
shutdownHandler.init();
|
|
157
|
+
shutdownHandler.registerClaudeRunner(claudeRunner);
|
|
158
|
+
|
|
159
|
+
// Register cleanup callback for empty project folder
|
|
160
|
+
shutdownHandler.onShutdown(() => {
|
|
161
|
+
projectManager.cleanupEmptyProject(projectPath);
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
// Run planning session
|
|
165
|
+
logger.info('Starting planning session with Claude...');
|
|
166
|
+
logger.info('Claude will interview you about each task.');
|
|
167
|
+
if (model) {
|
|
168
|
+
logger.info(`Using model: ${model}`);
|
|
169
|
+
}
|
|
170
|
+
if (autoMode) {
|
|
171
|
+
logger.warn('Auto mode enabled: permission prompts will be skipped.');
|
|
172
|
+
}
|
|
173
|
+
logger.newline();
|
|
174
|
+
|
|
175
|
+
const { systemPrompt, userMessage } = getPlanningPrompt({
|
|
176
|
+
projectPath,
|
|
177
|
+
inputContent: userInput,
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
try {
|
|
181
|
+
const exitCode = await claudeRunner.runInteractive(systemPrompt, userMessage, {
|
|
182
|
+
dangerouslySkipPermissions: autoMode,
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
if (exitCode !== 0) {
|
|
186
|
+
logger.warn(`Claude exited with code ${exitCode}`);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Check for created plan files
|
|
190
|
+
const plansDir = getPlansDir(projectPath);
|
|
191
|
+
const planFiles = fs.existsSync(plansDir)
|
|
192
|
+
? fs.readdirSync(plansDir).filter((f) => f.endsWith('.md')).sort()
|
|
193
|
+
: [];
|
|
194
|
+
|
|
195
|
+
if (planFiles.length === 0) {
|
|
196
|
+
logger.warn('No plan files were created.');
|
|
197
|
+
} else {
|
|
198
|
+
logger.newline();
|
|
199
|
+
logger.success(`Planning complete! Created ${planFiles.length} task(s).`);
|
|
200
|
+
logger.newline();
|
|
201
|
+
logger.info('Plans created:');
|
|
202
|
+
for (const planFile of planFiles) {
|
|
203
|
+
logger.info(` - plans/${planFile}`);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
logger.newline();
|
|
207
|
+
logger.info(`Run 'raf do ${finalProjectName}' to execute the plans.`);
|
|
208
|
+
}
|
|
209
|
+
} catch (error) {
|
|
210
|
+
logger.error(`Planning failed: ${error}`);
|
|
211
|
+
throw error;
|
|
212
|
+
} finally {
|
|
213
|
+
// Cleanup empty project folder if no plans were created
|
|
214
|
+
projectManager.cleanupEmptyProject(projectPath);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
async function runAmendCommand(identifier: string, model?: string, autoMode: boolean = false): Promise<void> {
|
|
219
|
+
// Validate environment
|
|
220
|
+
const validation = validateEnvironment();
|
|
221
|
+
reportValidation(validation);
|
|
222
|
+
|
|
223
|
+
if (!validation.valid) {
|
|
224
|
+
process.exit(1);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// Resolve the project
|
|
228
|
+
const rafDir = getRafDir();
|
|
229
|
+
const result = resolveProjectIdentifierWithDetails(rafDir, identifier);
|
|
230
|
+
|
|
231
|
+
if (!result.path) {
|
|
232
|
+
if (result.error === 'ambiguous' && result.matches) {
|
|
233
|
+
logger.error(`Ambiguous project name: ${identifier}`);
|
|
234
|
+
logger.error('Multiple projects match:');
|
|
235
|
+
for (const match of result.matches) {
|
|
236
|
+
logger.error(` - ${match.folder}`);
|
|
237
|
+
}
|
|
238
|
+
logger.error('Please specify the project ID or full folder name.');
|
|
239
|
+
} else {
|
|
240
|
+
logger.error(`Project not found: ${identifier}`);
|
|
241
|
+
}
|
|
242
|
+
process.exit(1);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
const projectPath = result.path;
|
|
246
|
+
|
|
247
|
+
// Load existing project state
|
|
248
|
+
const projectState = deriveProjectState(projectPath);
|
|
249
|
+
|
|
250
|
+
if (projectState.tasks.length === 0) {
|
|
251
|
+
logger.error(`Project has no tasks yet. Use 'raf plan' instead.`);
|
|
252
|
+
process.exit(1);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// Check if project is fully completed and show warning
|
|
256
|
+
if (isProjectComplete(projectState)) {
|
|
257
|
+
logger.warn('Project is fully completed. New tasks will extend the existing plan.');
|
|
258
|
+
logger.newline();
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// Get existing tasks with their names
|
|
262
|
+
const plansDir = getPlansDir(projectPath);
|
|
263
|
+
const existingTasks: Array<DerivedTask & { taskName: string }> = projectState.tasks.map(
|
|
264
|
+
(task) => {
|
|
265
|
+
const planFile = task.planFile.replace('plans/', '');
|
|
266
|
+
const taskName = extractTaskNameFromPlanFile(planFile) ?? 'unknown';
|
|
267
|
+
return { ...task, taskName };
|
|
268
|
+
}
|
|
269
|
+
);
|
|
270
|
+
|
|
271
|
+
// Calculate next task number
|
|
272
|
+
const maxTaskNumber = Math.max(
|
|
273
|
+
...projectState.tasks.map((t) => parseInt(t.id, 10))
|
|
274
|
+
);
|
|
275
|
+
const nextTaskNumber = maxTaskNumber + 1;
|
|
276
|
+
|
|
277
|
+
// Load original input
|
|
278
|
+
const inputPath = getInputPath(projectPath);
|
|
279
|
+
const originalInput = fs.existsSync(inputPath)
|
|
280
|
+
? fs.readFileSync(inputPath, 'utf-8')
|
|
281
|
+
: '';
|
|
282
|
+
|
|
283
|
+
// Show existing tasks summary
|
|
284
|
+
logger.info('Amending existing project:');
|
|
285
|
+
logger.info(` Path: ${projectPath}`);
|
|
286
|
+
logger.info(` Existing tasks: ${existingTasks.length}`);
|
|
287
|
+
logger.newline();
|
|
288
|
+
logger.info('Current tasks:');
|
|
289
|
+
for (const task of existingTasks) {
|
|
290
|
+
const statusIcon =
|
|
291
|
+
task.status === 'completed' ? '[done]' : task.status === 'failed' ? '[fail]' : '[ ]';
|
|
292
|
+
logger.info(` ${statusIcon} ${task.id}: ${task.taskName}`);
|
|
293
|
+
}
|
|
294
|
+
logger.newline();
|
|
295
|
+
|
|
296
|
+
// Open editor for new task description
|
|
297
|
+
const editorTemplate = getAmendTemplate(existingTasks, nextTaskNumber);
|
|
298
|
+
logger.info('Opening editor for new task description...');
|
|
299
|
+
logger.info('(Save and close the editor when done)');
|
|
300
|
+
logger.newline();
|
|
301
|
+
|
|
302
|
+
let userInput: string;
|
|
303
|
+
try {
|
|
304
|
+
userInput = await openEditor(editorTemplate);
|
|
305
|
+
} catch (error) {
|
|
306
|
+
logger.error(`Failed to get input: ${error}`);
|
|
307
|
+
process.exit(1);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// Extract new task description (remove the template comments)
|
|
311
|
+
const cleanInput = userInput
|
|
312
|
+
.replace(/<!--[\s\S]*?-->/g, '')
|
|
313
|
+
.replace(/^#.*$/gm, '')
|
|
314
|
+
.trim();
|
|
315
|
+
|
|
316
|
+
if (!cleanInput) {
|
|
317
|
+
logger.error('No new task description provided. Aborting.');
|
|
318
|
+
process.exit(1);
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// Append new task description to input.md with separator
|
|
322
|
+
const separator = '\n\n---\n\n';
|
|
323
|
+
const updatedInput = originalInput.trim()
|
|
324
|
+
? `${originalInput.trimEnd()}${separator}${cleanInput}`
|
|
325
|
+
: cleanInput;
|
|
326
|
+
fs.writeFileSync(inputPath, updatedInput);
|
|
327
|
+
|
|
328
|
+
// Set up shutdown handler
|
|
329
|
+
const claudeRunner = new ClaudeRunner({ model });
|
|
330
|
+
shutdownHandler.init();
|
|
331
|
+
shutdownHandler.registerClaudeRunner(claudeRunner);
|
|
332
|
+
|
|
333
|
+
// Run amend planning session
|
|
334
|
+
logger.info('Starting amendment session with Claude...');
|
|
335
|
+
logger.info('Claude will interview you about each new task.');
|
|
336
|
+
if (model) {
|
|
337
|
+
logger.info(`Using model: ${model}`);
|
|
338
|
+
}
|
|
339
|
+
if (autoMode) {
|
|
340
|
+
logger.warn('Auto mode enabled: permission prompts will be skipped.');
|
|
341
|
+
}
|
|
342
|
+
logger.newline();
|
|
343
|
+
|
|
344
|
+
const { systemPrompt, userMessage } = getAmendPrompt({
|
|
345
|
+
projectPath,
|
|
346
|
+
existingTasks,
|
|
347
|
+
nextTaskNumber,
|
|
348
|
+
newTaskDescription: cleanInput,
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
try {
|
|
352
|
+
const exitCode = await claudeRunner.runInteractive(systemPrompt, userMessage, {
|
|
353
|
+
dangerouslySkipPermissions: autoMode,
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
if (exitCode !== 0) {
|
|
357
|
+
logger.warn(`Claude exited with code ${exitCode}`);
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
// Check for new plan files
|
|
361
|
+
const allPlanFiles = fs.existsSync(plansDir)
|
|
362
|
+
? fs.readdirSync(plansDir).filter((f) => f.endsWith('.md')).sort()
|
|
363
|
+
: [];
|
|
364
|
+
|
|
365
|
+
const newPlanFiles = allPlanFiles.filter((f) => {
|
|
366
|
+
const match = f.match(/^(\d{2,3})-/);
|
|
367
|
+
if (match && match[1]) {
|
|
368
|
+
return parseInt(match[1], 10) >= nextTaskNumber;
|
|
369
|
+
}
|
|
370
|
+
return false;
|
|
371
|
+
});
|
|
372
|
+
|
|
373
|
+
if (newPlanFiles.length === 0) {
|
|
374
|
+
logger.warn('No new plan files were created.');
|
|
375
|
+
} else {
|
|
376
|
+
logger.newline();
|
|
377
|
+
logger.success(`Amendment complete! Added ${newPlanFiles.length} new task(s).`);
|
|
378
|
+
logger.newline();
|
|
379
|
+
logger.info('New plans created:');
|
|
380
|
+
for (const planFile of newPlanFiles) {
|
|
381
|
+
logger.info(` - plans/${planFile}`);
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
logger.newline();
|
|
385
|
+
logger.info(`Total tasks: ${allPlanFiles.length}`);
|
|
386
|
+
logger.info(`Run 'raf do ${identifier}' to execute the new tasks.`);
|
|
387
|
+
}
|
|
388
|
+
} catch (error) {
|
|
389
|
+
logger.error(`Amendment failed: ${error}`);
|
|
390
|
+
throw error;
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
/**
|
|
395
|
+
* Generate editor template for amend mode showing existing tasks.
|
|
396
|
+
*/
|
|
397
|
+
function getAmendTemplate(
|
|
398
|
+
existingTasks: Array<DerivedTask & { taskName: string }>,
|
|
399
|
+
nextTaskNumber: number
|
|
400
|
+
): string {
|
|
401
|
+
const taskList = existingTasks
|
|
402
|
+
.map((task) => {
|
|
403
|
+
const status =
|
|
404
|
+
task.status === 'completed'
|
|
405
|
+
? '[COMPLETED]'
|
|
406
|
+
: task.status === 'failed'
|
|
407
|
+
? '[FAILED]'
|
|
408
|
+
: '[PENDING]';
|
|
409
|
+
return `# ${task.id}: ${task.taskName} ${status}`;
|
|
410
|
+
})
|
|
411
|
+
.join('\n');
|
|
412
|
+
|
|
413
|
+
return `# Describe the new tasks you want to add
|
|
414
|
+
#
|
|
415
|
+
# Existing tasks (read-only reference):
|
|
416
|
+
${taskList}
|
|
417
|
+
#
|
|
418
|
+
# New tasks will be numbered starting from ${nextTaskNumber.toString().padStart(3, '0')}
|
|
419
|
+
#
|
|
420
|
+
# Describe what you want to add below:
|
|
421
|
+
`;
|
|
422
|
+
}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import { getRafDir, resolveProjectIdentifier, extractProjectName } from '../utils/paths.js';
|
|
3
|
+
import { logger } from '../utils/logger.js';
|
|
4
|
+
import type { StatusCommandOptions } from '../types/config.js';
|
|
5
|
+
import {
|
|
6
|
+
deriveProjectState,
|
|
7
|
+
getDerivedStats,
|
|
8
|
+
discoverProjects,
|
|
9
|
+
type DerivedTaskStatus,
|
|
10
|
+
} from '../core/state-derivation.js';
|
|
11
|
+
import { SYMBOLS, formatProgressBar, type TaskStatus } from '../utils/terminal-symbols.js';
|
|
12
|
+
|
|
13
|
+
/** Maximum number of projects to display in status list */
|
|
14
|
+
const MAX_DISPLAYED_PROJECTS = 10;
|
|
15
|
+
|
|
16
|
+
export function createStatusCommand(): Command {
|
|
17
|
+
const command = new Command('status')
|
|
18
|
+
.description('Show status of a project or list all projects')
|
|
19
|
+
.argument('[identifier]', 'Project identifier: number (3), name (my-project), or folder (001-my-project)')
|
|
20
|
+
.option('--json', 'Output as JSON')
|
|
21
|
+
.action(async (identifier?: string, options?: StatusCommandOptions) => {
|
|
22
|
+
await runStatusCommand(identifier, options);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
return command;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async function runStatusCommand(
|
|
29
|
+
identifier?: string,
|
|
30
|
+
options?: StatusCommandOptions
|
|
31
|
+
): Promise<void> {
|
|
32
|
+
const rafDir = getRafDir();
|
|
33
|
+
|
|
34
|
+
if (!identifier) {
|
|
35
|
+
// List all projects
|
|
36
|
+
await listAllProjects(rafDir, options);
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Show specific project - resolve identifier (number, name, or full folder name)
|
|
41
|
+
const projectPath = resolveProjectIdentifier(rafDir, identifier);
|
|
42
|
+
|
|
43
|
+
if (!projectPath) {
|
|
44
|
+
logger.error(`Project not found: ${identifier}`);
|
|
45
|
+
process.exit(1);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Derive state from folder structure
|
|
49
|
+
const state = deriveProjectState(projectPath);
|
|
50
|
+
const stats = getDerivedStats(state);
|
|
51
|
+
const projectName = extractProjectName(projectPath) ?? identifier;
|
|
52
|
+
const projectStatus = state.status;
|
|
53
|
+
|
|
54
|
+
if (options?.json) {
|
|
55
|
+
console.log(JSON.stringify({ projectName, status: projectStatus, state, stats }, null, 2));
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Convert derived task statuses to TaskStatus for progress bar
|
|
60
|
+
const taskStatuses: TaskStatus[] = state.tasks.map((t) => derivedStatusToTaskStatus(t.status));
|
|
61
|
+
const progressBar = formatProgressBar(taskStatuses);
|
|
62
|
+
|
|
63
|
+
// Display compact project status
|
|
64
|
+
logger.info(`${SYMBOLS.project} ${projectName}`);
|
|
65
|
+
logger.info(`${progressBar} (${stats.completed}/${stats.total})`);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
async function listAllProjects(
|
|
69
|
+
rafDir: string,
|
|
70
|
+
options?: StatusCommandOptions
|
|
71
|
+
): Promise<void> {
|
|
72
|
+
const allProjects = discoverProjects(rafDir);
|
|
73
|
+
|
|
74
|
+
if (allProjects.length === 0) {
|
|
75
|
+
logger.info('No projects found.');
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (options?.json) {
|
|
80
|
+
const projectsWithState = allProjects.map((p) => {
|
|
81
|
+
try {
|
|
82
|
+
const state = deriveProjectState(p.path);
|
|
83
|
+
const stats = getDerivedStats(state);
|
|
84
|
+
return {
|
|
85
|
+
...p,
|
|
86
|
+
status: state.status,
|
|
87
|
+
state,
|
|
88
|
+
stats,
|
|
89
|
+
};
|
|
90
|
+
} catch {
|
|
91
|
+
return { ...p, status: null, state: null, stats: null };
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
console.log(JSON.stringify(projectsWithState, null, 2));
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Truncate to last N projects if needed (projects are sorted by number ascending)
|
|
99
|
+
const totalProjects = allProjects.length;
|
|
100
|
+
const hiddenCount = Math.max(0, totalProjects - MAX_DISPLAYED_PROJECTS);
|
|
101
|
+
const displayedProjects = hiddenCount > 0
|
|
102
|
+
? allProjects.slice(-MAX_DISPLAYED_PROJECTS)
|
|
103
|
+
: allProjects;
|
|
104
|
+
|
|
105
|
+
// Show truncation indicator at top if there are hidden projects
|
|
106
|
+
if (hiddenCount > 0) {
|
|
107
|
+
logger.dim(`... and ${hiddenCount} more project${hiddenCount === 1 ? '' : 's'}`);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
for (const project of displayedProjects) {
|
|
111
|
+
try {
|
|
112
|
+
const state = deriveProjectState(project.path);
|
|
113
|
+
const stats = getDerivedStats(state);
|
|
114
|
+
|
|
115
|
+
// Convert derived task statuses to TaskStatus for progress bar
|
|
116
|
+
const taskStatuses: TaskStatus[] = state.tasks.map((t) => derivedStatusToTaskStatus(t.status));
|
|
117
|
+
const progressBar = formatProgressBar(taskStatuses);
|
|
118
|
+
|
|
119
|
+
// Format: "001 my-project ✓✓●○○ (2/5)"
|
|
120
|
+
const projectNumber = String(project.number).padStart(3, '0');
|
|
121
|
+
const counts = `(${stats.completed}/${stats.total})`;
|
|
122
|
+
logger.info(`${projectNumber} ${project.name} ${progressBar} ${counts}`);
|
|
123
|
+
} catch {
|
|
124
|
+
// Failed to derive state - show minimal info
|
|
125
|
+
const projectNumber = String(project.number).padStart(3, '0');
|
|
126
|
+
logger.info(`${projectNumber} ${project.name}`);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Convert DerivedTaskStatus to TaskStatus for terminal symbols.
|
|
133
|
+
* Note: DerivedTaskStatus doesn't have 'running' - tasks are either pending, completed, or failed.
|
|
134
|
+
*/
|
|
135
|
+
function derivedStatusToTaskStatus(status: DerivedTaskStatus): TaskStatus {
|
|
136
|
+
switch (status) {
|
|
137
|
+
case 'pending':
|
|
138
|
+
return 'pending';
|
|
139
|
+
case 'completed':
|
|
140
|
+
return 'completed';
|
|
141
|
+
case 'failed':
|
|
142
|
+
return 'failed';
|
|
143
|
+
default:
|
|
144
|
+
return 'pending';
|
|
145
|
+
}
|
|
146
|
+
}
|