rafcode 1.2.0 → 2.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.md +118 -22
- package/RAF/aaaabt-verbose-chronicle/decisions.md +25 -0
- package/RAF/aaaabt-verbose-chronicle/input.md +3 -0
- package/RAF/aaaabt-verbose-chronicle/outcomes/01-amend-iteration-references.md +25 -0
- package/RAF/aaaabt-verbose-chronicle/outcomes/02-verbose-task-name-display.md +31 -0
- package/RAF/aaaabt-verbose-chronicle/outcomes/03-verbose-streaming-fix.md +48 -0
- package/RAF/aaaabt-verbose-chronicle/outcomes/04-commit-verification-before-halt.md +56 -0
- package/RAF/aaaabt-verbose-chronicle/plans/01-amend-iteration-references.md +35 -0
- package/RAF/aaaabt-verbose-chronicle/plans/02-verbose-task-name-display.md +38 -0
- package/RAF/aaaabt-verbose-chronicle/plans/03-verbose-streaming-fix.md +45 -0
- package/RAF/aaaabt-verbose-chronicle/plans/04-commit-verification-before-halt.md +62 -0
- package/RAF/aaaacu-worktree-weaver/decisions.md +88 -0
- package/RAF/aaaacu-worktree-weaver/input.md +27 -0
- package/RAF/aaaacu-worktree-weaver/outcomes/01-git-worktree-utilities.md +41 -0
- package/RAF/aaaacu-worktree-weaver/outcomes/02-worktree-plan-command.md +44 -0
- package/RAF/aaaacu-worktree-weaver/outcomes/03-worktree-do-command.md +58 -0
- package/RAF/aaaacu-worktree-weaver/outcomes/04-auto-merge-and-cleanup.md +40 -0
- package/RAF/aaaacu-worktree-weaver/outcomes/05-worktree-tests.md +43 -0
- package/RAF/aaaacu-worktree-weaver/outcomes/06-update-documentation.md +33 -0
- package/RAF/aaaacu-worktree-weaver/plans/01-git-worktree-utilities.md +51 -0
- package/RAF/aaaacu-worktree-weaver/plans/02-worktree-plan-command.md +93 -0
- package/RAF/aaaacu-worktree-weaver/plans/03-worktree-do-command.md +81 -0
- package/RAF/aaaacu-worktree-weaver/plans/04-auto-merge-and-cleanup.md +51 -0
- package/RAF/aaaacu-worktree-weaver/plans/05-worktree-tests.md +52 -0
- package/RAF/aaaacu-worktree-weaver/plans/06-update-documentation.md +55 -0
- package/RAF/aaaacv-trim-the-fat/decisions.md +34 -0
- package/RAF/aaaacv-trim-the-fat/input.md +5 -0
- package/RAF/aaaacv-trim-the-fat/outcomes/01-add-worktree-support-to-status.md +43 -0
- package/RAF/aaaacv-trim-the-fat/outcomes/02-remove-multi-project-from-do.md +50 -0
- package/RAF/aaaacv-trim-the-fat/outcomes/03-commit-artifacts-on-amend.md +35 -0
- package/RAF/aaaacv-trim-the-fat/outcomes/04-worktree-aware-exit-messages.md +36 -0
- package/RAF/aaaacv-trim-the-fat/plans/01-add-worktree-support-to-status.md +43 -0
- package/RAF/aaaacv-trim-the-fat/plans/02-remove-multi-project-from-do.md +44 -0
- package/RAF/aaaacv-trim-the-fat/plans/03-commit-artifacts-on-amend.md +38 -0
- package/RAF/aaaacv-trim-the-fat/plans/04-worktree-aware-exit-messages.md +38 -0
- package/RAF/aaaacw-prune-cycle/decisions.md +25 -0
- package/RAF/aaaacw-prune-cycle/input.md +5 -0
- package/RAF/aaaacw-prune-cycle/outcomes/01-create-worktree-from-branch.md +32 -0
- package/RAF/aaaacw-prune-cycle/outcomes/02-cleanup-worktree-on-success.md +33 -0
- package/RAF/aaaacw-prune-cycle/outcomes/03-amend-recreate-worktree.md +40 -0
- package/RAF/aaaacw-prune-cycle/plans/01-create-worktree-from-branch.md +31 -0
- package/RAF/aaaacw-prune-cycle/plans/02-cleanup-worktree-on-success.md +38 -0
- package/RAF/aaaacw-prune-cycle/plans/03-amend-recreate-worktree.md +50 -0
- package/RAF/aaaacx-epoch-shift/decisions.md +25 -0
- package/RAF/aaaacx-epoch-shift/input.md +1 -0
- package/RAF/aaaacx-epoch-shift/outcomes/01-epoch-id-generation.md +34 -0
- package/RAF/aaaacx-epoch-shift/outcomes/02-update-pattern-matching.md +32 -0
- package/RAF/aaaacx-epoch-shift/outcomes/03-update-tests.md +59 -0
- package/RAF/aaaacx-epoch-shift/outcomes/04-update-documentation.md +30 -0
- package/RAF/aaaacx-epoch-shift/plans/01-epoch-id-generation.md +40 -0
- package/RAF/aaaacx-epoch-shift/plans/02-update-pattern-matching.md +60 -0
- package/RAF/aaaacx-epoch-shift/plans/03-update-tests.md +48 -0
- package/RAF/aaaacx-epoch-shift/plans/04-update-documentation.md +44 -0
- package/RAF/ahmpro-merge-guardian/decisions.md +25 -0
- package/RAF/ahmpro-merge-guardian/input.md +4 -0
- package/RAF/ahmpro-merge-guardian/outcomes/001-fix-amend-worktree-commit.md +45 -0
- package/RAF/ahmpro-merge-guardian/outcomes/002-base36-task-ids.md +55 -0
- package/RAF/ahmpro-merge-guardian/outcomes/003-worktree-pr-creation.md +41 -0
- package/RAF/ahmpro-merge-guardian/outcomes/004-post-execution-picker.md +53 -0
- package/RAF/ahmpro-merge-guardian/plans/001-fix-amend-worktree-commit.md +39 -0
- package/RAF/ahmpro-merge-guardian/plans/002-base36-task-ids.md +43 -0
- package/RAF/ahmpro-merge-guardian/plans/003-worktree-pr-creation.md +43 -0
- package/RAF/ahmpro-merge-guardian/plans/004-post-execution-picker.md +51 -0
- package/RAF/ahnbcu-letterjam/decisions.md +13 -0
- package/RAF/ahnbcu-letterjam/input.md +3 -0
- package/RAF/ahnbcu-letterjam/outcomes/01-base26-encoding.md +42 -0
- package/RAF/ahnbcu-letterjam/outcomes/02-update-tests.md +38 -0
- package/RAF/ahnbcu-letterjam/outcomes/03-migrate-command.md +51 -0
- package/RAF/ahnbcu-letterjam/outcomes/04-update-documentation.md +29 -0
- package/RAF/ahnbcu-letterjam/plans/01-base26-encoding.md +36 -0
- package/RAF/ahnbcu-letterjam/plans/02-update-tests.md +37 -0
- package/RAF/ahnbcu-letterjam/plans/03-migrate-command.md +49 -0
- package/RAF/ahnbcu-letterjam/plans/04-update-documentation.md +39 -0
- package/RAF/ahnwrk-worktree-weaver/decisions.md +19 -0
- package/RAF/ahnwrk-worktree-weaver/input.md +4 -0
- package/RAF/ahnwrk-worktree-weaver/outcomes/01-remove-co-authored-by.md +20 -0
- package/RAF/ahnwrk-worktree-weaver/outcomes/02-update-task-status-format.md +21 -0
- package/RAF/ahnwrk-worktree-weaver/outcomes/03-worktree-auto-discovery.md +34 -0
- package/RAF/ahnwrk-worktree-weaver/outcomes/04-fix-pr-description.md +30 -0
- package/RAF/ahnwrk-worktree-weaver/plans/01-remove-co-authored-by.md +26 -0
- package/RAF/ahnwrk-worktree-weaver/plans/02-update-task-status-format.md +27 -0
- package/RAF/ahnwrk-worktree-weaver/plans/03-worktree-auto-discovery.md +37 -0
- package/RAF/ahnwrk-worktree-weaver/plans/04-fix-pr-description.md +50 -0
- package/README.md +56 -10
- package/dist/commands/do.d.ts +15 -0
- package/dist/commands/do.d.ts.map +1 -1
- package/dist/commands/do.js +400 -131
- package/dist/commands/do.js.map +1 -1
- package/dist/commands/migrate.d.ts +14 -0
- package/dist/commands/migrate.d.ts.map +1 -0
- package/dist/commands/migrate.js +228 -0
- package/dist/commands/migrate.js.map +1 -0
- package/dist/commands/plan.d.ts.map +1 -1
- package/dist/commands/plan.js +237 -40
- package/dist/commands/plan.js.map +1 -1
- package/dist/commands/status.d.ts.map +1 -1
- package/dist/commands/status.js +221 -47
- package/dist/commands/status.js.map +1 -1
- package/dist/core/claude-runner.d.ts +52 -1
- package/dist/core/claude-runner.d.ts.map +1 -1
- package/dist/core/claude-runner.js +195 -17
- package/dist/core/claude-runner.js.map +1 -1
- package/dist/core/git.d.ts +29 -5
- package/dist/core/git.d.ts.map +1 -1
- package/dist/core/git.js +95 -18
- package/dist/core/git.js.map +1 -1
- package/dist/core/project-manager.d.ts.map +1 -1
- package/dist/core/project-manager.js +2 -2
- package/dist/core/project-manager.js.map +1 -1
- package/dist/core/pull-request.d.ts +84 -0
- package/dist/core/pull-request.d.ts.map +1 -0
- package/dist/core/pull-request.js +414 -0
- package/dist/core/pull-request.js.map +1 -0
- package/dist/core/state-derivation.d.ts +3 -3
- package/dist/core/state-derivation.d.ts.map +1 -1
- package/dist/core/state-derivation.js +18 -14
- package/dist/core/state-derivation.js.map +1 -1
- package/dist/core/worktree.d.ts +120 -0
- package/dist/core/worktree.d.ts.map +1 -0
- package/dist/core/worktree.js +322 -0
- package/dist/core/worktree.js.map +1 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/parsers/stream-renderer.d.ts +42 -0
- package/dist/parsers/stream-renderer.d.ts.map +1 -0
- package/dist/parsers/stream-renderer.js +100 -0
- package/dist/parsers/stream-renderer.js.map +1 -0
- package/dist/prompts/amend.d.ts +1 -0
- package/dist/prompts/amend.d.ts.map +1 -1
- package/dist/prompts/amend.js +25 -10
- package/dist/prompts/amend.js.map +1 -1
- package/dist/prompts/execution.js +5 -5
- package/dist/prompts/execution.js.map +1 -1
- package/dist/prompts/planning.d.ts +1 -0
- package/dist/prompts/planning.d.ts.map +1 -1
- package/dist/prompts/planning.js +11 -10
- package/dist/prompts/planning.js.map +1 -1
- package/dist/types/config.d.ts +5 -0
- package/dist/types/config.d.ts.map +1 -1
- package/dist/ui/project-picker.d.ts +34 -3
- package/dist/ui/project-picker.d.ts.map +1 -1
- package/dist/ui/project-picker.js +78 -10
- package/dist/ui/project-picker.js.map +1 -1
- package/dist/utils/paths.d.ts +38 -43
- package/dist/utils/paths.d.ts.map +1 -1
- package/dist/utils/paths.js +123 -193
- package/dist/utils/paths.js.map +1 -1
- package/dist/utils/terminal-symbols.d.ts +2 -2
- package/dist/utils/terminal-symbols.js +3 -3
- package/dist/utils/terminal-symbols.js.map +1 -1
- package/dist/utils/validation.d.ts.map +1 -1
- package/dist/utils/validation.js +2 -8
- package/dist/utils/validation.js.map +1 -1
- package/package.json +1 -1
- package/src/commands/do.ts +471 -142
- package/src/commands/migrate.ts +269 -0
- package/src/commands/plan.ts +264 -40
- package/src/commands/status.ts +252 -45
- package/src/core/claude-runner.ts +270 -17
- package/src/core/git.ts +99 -19
- package/src/core/project-manager.ts +2 -1
- package/src/core/pull-request.ts +480 -0
- package/src/core/state-derivation.ts +18 -14
- package/src/core/worktree.ts +357 -0
- package/src/index.ts +2 -0
- package/src/parsers/stream-renderer.ts +139 -0
- package/src/prompts/amend.ts +27 -9
- package/src/prompts/execution.ts +5 -5
- package/src/prompts/planning.ts +12 -10
- package/src/types/config.ts +6 -0
- package/src/ui/project-picker.ts +110 -10
- package/src/utils/paths.ts +129 -214
- package/src/utils/terminal-symbols.ts +3 -3
- package/src/utils/validation.ts +2 -9
- package/tests/unit/amend-prompt.test.ts +85 -0
- package/tests/unit/claude-runner.test.ts +567 -1
- package/tests/unit/commit-planning-artifacts-worktree.test.ts +327 -0
- package/tests/unit/commit-planning-artifacts.test.ts +303 -35
- package/tests/unit/dependency-integration.test.ts +95 -95
- package/tests/unit/do-blocked-tasks.test.ts +53 -53
- package/tests/unit/do-command.test.ts +39 -132
- package/tests/unit/do-rerun.test.ts +65 -65
- package/tests/unit/do-worktree-cleanup.test.ts +151 -0
- package/tests/unit/execution-prompt.test.ts +71 -60
- package/tests/unit/failure-history.test.ts +19 -19
- package/tests/unit/git-commit-helpers.test.ts +103 -0
- package/tests/unit/git-stash.test.ts +4 -4
- package/tests/unit/migrate-command.test.ts +197 -0
- package/tests/unit/outcome-content.test.ts +20 -20
- package/tests/unit/paths.test.ts +269 -467
- package/tests/unit/plan-amend-worktree-recreate.test.ts +246 -0
- package/tests/unit/plan-command.test.ts +144 -93
- package/tests/unit/planning-prompt.test.ts +41 -4
- package/tests/unit/post-execution-picker.test.ts +251 -0
- package/tests/unit/project-manager.test.ts +20 -8
- package/tests/unit/project-picker.test.ts +425 -42
- package/tests/unit/pull-request.test.ts +852 -0
- package/tests/unit/state-derivation.test.ts +138 -137
- package/tests/unit/status-command.test.ts +344 -76
- package/tests/unit/stream-renderer.test.ts +286 -0
- package/tests/unit/terminal-symbols.test.ts +4 -4
- package/tests/unit/worktree-integration.test.ts +405 -0
- package/tests/unit/worktree.test.ts +523 -0
- package/tests/unit/do-multiproject.test.ts +0 -270
- /package/RAF/{001-raf-task-improvements → aaaaab-raf-task-improvements}/input.md +0 -0
- /package/RAF/{001-raf-task-improvements/outcomes/001-add-decisions-folder.md → aaaaab-raf-task-improvements/outcomes/01-add-decisions-folder.md} +0 -0
- /package/RAF/{001-raf-task-improvements/outcomes/002-fix-write-error-on-shutdown.md → aaaaab-raf-task-improvements/outcomes/02-fix-write-error-on-shutdown.md} +0 -0
- /package/RAF/{001-raf-task-improvements/outcomes/003-stash-changes-on-failure.md → aaaaab-raf-task-improvements/outcomes/03-stash-changes-on-failure.md} +0 -0
- /package/RAF/{001-raf-task-improvements/outcomes/004-add-project-name-to-commits.md → aaaaab-raf-task-improvements/outcomes/04-add-project-name-to-commits.md} +0 -0
- /package/RAF/{001-raf-task-improvements/outcomes/005-add-running-time-display.md → aaaaab-raf-task-improvements/outcomes/05-add-running-time-display.md} +0 -0
- /package/RAF/{001-raf-task-improvements/outcomes/006-add-task-name-to-logs.md → aaaaab-raf-task-improvements/outcomes/06-add-task-name-to-logs.md} +0 -0
- /package/RAF/{001-raf-task-improvements/outcomes/007-show-model-at-task-start.md → aaaaab-raf-task-improvements/outcomes/07-show-model-at-task-start.md} +0 -0
- /package/RAF/{001-raf-task-improvements/outcomes/009-remove-editor-placeholder-text.md → aaaaab-raf-task-improvements/outcomes/09-remove-editor-placeholder-text.md} +0 -0
- /package/RAF/{001-raf-task-improvements → aaaaab-raf-task-improvements}/outcomes/SUMMARY.md +0 -0
- /package/RAF/{001-raf-task-improvements/plans/001-add-decisions-folder.md → aaaaab-raf-task-improvements/plans/01-add-decisions-folder.md} +0 -0
- /package/RAF/{001-raf-task-improvements/plans/002-fix-write-error-on-shutdown.md → aaaaab-raf-task-improvements/plans/02-fix-write-error-on-shutdown.md} +0 -0
- /package/RAF/{001-raf-task-improvements/plans/003-stash-changes-on-failure.md → aaaaab-raf-task-improvements/plans/03-stash-changes-on-failure.md} +0 -0
- /package/RAF/{001-raf-task-improvements/plans/004-add-project-name-to-commits.md → aaaaab-raf-task-improvements/plans/04-add-project-name-to-commits.md} +0 -0
- /package/RAF/{001-raf-task-improvements/plans/005-add-running-time-display.md → aaaaab-raf-task-improvements/plans/05-add-running-time-display.md} +0 -0
- /package/RAF/{001-raf-task-improvements/plans/006-add-task-name-to-logs.md → aaaaab-raf-task-improvements/plans/06-add-task-name-to-logs.md} +0 -0
- /package/RAF/{001-raf-task-improvements/plans/009-remove-editor-placeholder-text.md → aaaaab-raf-task-improvements/plans/09-remove-editor-placeholder-text.md} +0 -0
- /package/RAF/{002-raf-task-improvements-execution → aaaaac-raf-task-improvements-execution}/decisions/DECISIONS.md +0 -0
- /package/RAF/{002-raf-task-improvements-execution → aaaaac-raf-task-improvements-execution}/input.md +0 -0
- /package/RAF/{002-raf-task-improvements-execution/outcomes/001-commit-show-model-at-task-start.md → aaaaac-raf-task-improvements-execution/outcomes/01-commit-show-model-at-task-start.md} +0 -0
- /package/RAF/{002-raf-task-improvements-execution/outcomes/002-delete-skipped-plan.md → aaaaac-raf-task-improvements-execution/outcomes/02-delete-skipped-plan.md} +0 -0
- /package/RAF/{002-raf-task-improvements-execution → aaaaac-raf-task-improvements-execution}/outcomes/SUMMARY.md +0 -0
- /package/RAF/{002-raf-task-improvements-execution/plans/001-commit-show-model-at-task-start.md → aaaaac-raf-task-improvements-execution/plans/01-commit-show-model-at-task-start.md} +0 -0
- /package/RAF/{002-raf-task-improvements-execution/plans/002-delete-skipped-plan.md → aaaaac-raf-task-improvements-execution/plans/02-delete-skipped-plan.md} +0 -0
- /package/RAF/{003-multi-project-execution → aaaaad-multi-project-execution}/decisions/DECISIONS.md +0 -0
- /package/RAF/{003-multi-project-execution → aaaaad-multi-project-execution}/input.md +0 -0
- /package/RAF/{003-multi-project-execution/outcomes/001-remove-state-json.md → aaaaad-multi-project-execution/outcomes/01-remove-state-json.md} +0 -0
- /package/RAF/{003-multi-project-execution/outcomes/002-update-raf-status.md → aaaaad-multi-project-execution/outcomes/02-update-raf-status.md} +0 -0
- /package/RAF/{003-multi-project-execution/outcomes/003-simplify-git-logic.md → aaaaad-multi-project-execution/outcomes/03-simplify-git-logic.md} +0 -0
- /package/RAF/{003-multi-project-execution/outcomes/004-auto-commit-planning.md → aaaaad-multi-project-execution/outcomes/04-auto-commit-planning.md} +0 -0
- /package/RAF/{003-multi-project-execution/outcomes/005-rerun-failed-tasks.md → aaaaad-multi-project-execution/outcomes/05-rerun-failed-tasks.md} +0 -0
- /package/RAF/{003-multi-project-execution/outcomes/006-multi-project-execution.md → aaaaad-multi-project-execution/outcomes/06-multi-project-execution.md} +0 -0
- /package/RAF/{003-multi-project-execution/outcomes/007-verify-timeout.md → aaaaad-multi-project-execution/outcomes/07-verify-timeout.md} +0 -0
- /package/RAF/{003-multi-project-execution/outcomes/008-move-decisions-file.md → aaaaad-multi-project-execution/outcomes/08-move-decisions-file.md} +0 -0
- /package/RAF/{003-multi-project-execution → aaaaad-multi-project-execution}/outcomes/SUMMARY.md +0 -0
- /package/RAF/{003-multi-project-execution/plans/001-remove-state-json.md → aaaaad-multi-project-execution/plans/01-remove-state-json.md} +0 -0
- /package/RAF/{003-multi-project-execution/plans/002-update-raf-status.md → aaaaad-multi-project-execution/plans/02-update-raf-status.md} +0 -0
- /package/RAF/{003-multi-project-execution/plans/003-simplify-git-logic.md → aaaaad-multi-project-execution/plans/03-simplify-git-logic.md} +0 -0
- /package/RAF/{003-multi-project-execution/plans/004-auto-commit-planning.md → aaaaad-multi-project-execution/plans/04-auto-commit-planning.md} +0 -0
- /package/RAF/{003-multi-project-execution/plans/005-rerun-failed-tasks.md → aaaaad-multi-project-execution/plans/05-rerun-failed-tasks.md} +0 -0
- /package/RAF/{003-multi-project-execution/plans/006-multi-project-execution.md → aaaaad-multi-project-execution/plans/06-multi-project-execution.md} +0 -0
- /package/RAF/{003-multi-project-execution/plans/007-verify-timeout.md → aaaaad-multi-project-execution/plans/07-verify-timeout.md} +0 -0
- /package/RAF/{003-multi-project-execution/plans/008-move-decisions-file.md → aaaaad-multi-project-execution/plans/08-move-decisions-file.md} +0 -0
- /package/RAF/{004-task-naming-optimization → aaaaae-task-naming-optimization}/decisions.md +0 -0
- /package/RAF/{004-task-naming-optimization → aaaaae-task-naming-optimization}/input.md +0 -0
- /package/RAF/{004-task-naming-optimization/outcomes/001-remove-summary-file.md → aaaaae-task-naming-optimization/outcomes/01-remove-summary-file.md} +0 -0
- /package/RAF/{004-task-naming-optimization/outcomes/002-base36-project-numbering.md → aaaaae-task-naming-optimization/outcomes/02-base36-project-numbering.md} +0 -0
- /package/RAF/{004-task-naming-optimization/outcomes/003-improve-haiku-prompt.md → aaaaae-task-naming-optimization/outcomes/03-improve-haiku-prompt.md} +0 -0
- /package/RAF/{004-task-naming-optimization → aaaaae-task-naming-optimization}/outcomes/SUMMARY.md +0 -0
- /package/RAF/{004-task-naming-optimization/plans/001-remove-summary-file.md → aaaaae-task-naming-optimization/plans/01-remove-summary-file.md} +0 -0
- /package/RAF/{004-task-naming-optimization/plans/002-base36-project-numbering.md → aaaaae-task-naming-optimization/plans/02-base36-project-numbering.md} +0 -0
- /package/RAF/{004-task-naming-optimization/plans/003-improve-haiku-prompt.md → aaaaae-task-naming-optimization/plans/03-improve-haiku-prompt.md} +0 -0
- /package/RAF/{005-task-naming-improvements → aaaaaf-task-naming-improvements}/decisions.md +0 -0
- /package/RAF/{005-task-naming-improvements → aaaaaf-task-naming-improvements}/input.md +0 -0
- /package/RAF/{005-task-naming-improvements/outcomes/001-enhance-identifier-resolution.md → aaaaaf-task-naming-improvements/outcomes/01-enhance-identifier-resolution.md} +0 -0
- /package/RAF/{005-task-naming-improvements/outcomes/002-add-identifier-support-to-status.md → aaaaaf-task-naming-improvements/outcomes/02-add-identifier-support-to-status.md} +0 -0
- /package/RAF/{005-task-naming-improvements/outcomes/003-update-do-for-full-folder-names.md → aaaaaf-task-naming-improvements/outcomes/03-update-do-for-full-folder-names.md} +0 -0
- /package/RAF/{005-task-naming-improvements/outcomes/004-implement-amend-flag-for-plan.md → aaaaaf-task-naming-improvements/outcomes/04-implement-amend-flag-for-plan.md} +0 -0
- /package/RAF/{005-task-naming-improvements/outcomes/005-commit-outcomes-on-complete.md → aaaaaf-task-naming-improvements/outcomes/05-commit-outcomes-on-complete.md} +0 -0
- /package/RAF/{005-task-naming-improvements/outcomes/006-update-execution-prompt-commit-schema.md → aaaaaf-task-naming-improvements/outcomes/06-update-execution-prompt-commit-schema.md} +0 -0
- /package/RAF/{005-task-naming-improvements/outcomes/007-allow-pending-task-amendments.md → aaaaaf-task-naming-improvements/outcomes/07-allow-pending-task-amendments.md} +0 -0
- /package/RAF/{005-task-naming-improvements/outcomes/008-fix-timeout-label.md → aaaaaf-task-naming-improvements/outcomes/08-fix-timeout-label.md} +0 -0
- /package/RAF/{005-task-naming-improvements/plans/001-enhance-identifier-resolution.md → aaaaaf-task-naming-improvements/plans/01-enhance-identifier-resolution.md} +0 -0
- /package/RAF/{005-task-naming-improvements/plans/002-add-identifier-support-to-status.md → aaaaaf-task-naming-improvements/plans/02-add-identifier-support-to-status.md} +0 -0
- /package/RAF/{005-task-naming-improvements/plans/003-update-do-for-full-folder-names.md → aaaaaf-task-naming-improvements/plans/03-update-do-for-full-folder-names.md} +0 -0
- /package/RAF/{005-task-naming-improvements/plans/004-implement-amend-flag-for-plan.md → aaaaaf-task-naming-improvements/plans/04-implement-amend-flag-for-plan.md} +0 -0
- /package/RAF/{005-task-naming-improvements/plans/005-commit-outcomes-on-complete.md → aaaaaf-task-naming-improvements/plans/05-commit-outcomes-on-complete.md} +0 -0
- /package/RAF/{005-task-naming-improvements/plans/006-update-execution-prompt-commit-schema.md → aaaaaf-task-naming-improvements/plans/06-update-execution-prompt-commit-schema.md} +0 -0
- /package/RAF/{005-task-naming-improvements/plans/007-allow-pending-task-amendments.md → aaaaaf-task-naming-improvements/plans/07-allow-pending-task-amendments.md} +0 -0
- /package/RAF/{005-task-naming-improvements/plans/008-fix-timeout-label.md → aaaaaf-task-naming-improvements/plans/08-fix-timeout-label.md} +0 -0
- /package/RAF/{006-fix-double-summary-headers → aaaaag-fix-double-summary-headers}/decisions.md +0 -0
- /package/RAF/{006-fix-double-summary-headers → aaaaag-fix-double-summary-headers}/input.md +0 -0
- /package/RAF/{006-fix-double-summary-headers/outcomes/001-fix-double-summary-headers.md → aaaaag-fix-double-summary-headers/outcomes/01-fix-double-summary-headers.md} +0 -0
- /package/RAF/{006-fix-double-summary-headers/outcomes/002-update-readme-for-npm.md → aaaaag-fix-double-summary-headers/outcomes/02-update-readme-for-npm.md} +0 -0
- /package/RAF/{006-fix-double-summary-headers/outcomes/003-npm-publish-instructions.md → aaaaag-fix-double-summary-headers/outcomes/03-npm-publish-instructions.md} +0 -0
- /package/RAF/{006-fix-double-summary-headers/outcomes/004-flexible-project-lookup.md → aaaaag-fix-double-summary-headers/outcomes/04-flexible-project-lookup.md} +0 -0
- /package/RAF/{006-fix-double-summary-headers/plans/001-fix-double-summary-headers.md → aaaaag-fix-double-summary-headers/plans/01-fix-double-summary-headers.md} +0 -0
- /package/RAF/{006-fix-double-summary-headers/plans/002-update-readme-for-npm.md → aaaaag-fix-double-summary-headers/plans/02-update-readme-for-npm.md} +0 -0
- /package/RAF/{006-fix-double-summary-headers/plans/003-npm-publish-instructions.md → aaaaag-fix-double-summary-headers/plans/03-npm-publish-instructions.md} +0 -0
- /package/RAF/{006-fix-double-summary-headers/plans/004-flexible-project-lookup.md → aaaaag-fix-double-summary-headers/plans/04-flexible-project-lookup.md} +0 -0
- /package/RAF/{007-improve-outcome-format → aaaaah-improve-outcome-format}/decisions.md +0 -0
- /package/RAF/{007-improve-outcome-format → aaaaah-improve-outcome-format}/input.md +0 -0
- /package/RAF/{007-improve-outcome-format/outcomes/001-update-execution-prompt.md → aaaaah-improve-outcome-format/outcomes/01-update-execution-prompt.md} +0 -0
- /package/RAF/{007-improve-outcome-format/outcomes/002-update-state-derivation.md → aaaaah-improve-outcome-format/outcomes/02-update-state-derivation.md} +0 -0
- /package/RAF/{007-improve-outcome-format/outcomes/003-update-do-command-outcome-handling.md → aaaaah-improve-outcome-format/outcomes/03-update-do-command-outcome-handling.md} +0 -0
- /package/RAF/{007-improve-outcome-format/outcomes/004-implement-failure-analysis.md → aaaaah-improve-outcome-format/outcomes/04-implement-failure-analysis.md} +0 -0
- /package/RAF/{007-improve-outcome-format/outcomes/005-update-documentation.md → aaaaah-improve-outcome-format/outcomes/05-update-documentation.md} +0 -0
- /package/RAF/{007-improve-outcome-format/plans/001-update-execution-prompt.md → aaaaah-improve-outcome-format/plans/01-update-execution-prompt.md} +0 -0
- /package/RAF/{007-improve-outcome-format/plans/002-update-state-derivation.md → aaaaah-improve-outcome-format/plans/02-update-state-derivation.md} +0 -0
- /package/RAF/{007-improve-outcome-format/plans/003-update-do-command-outcome-handling.md → aaaaah-improve-outcome-format/plans/03-update-do-command-outcome-handling.md} +0 -0
- /package/RAF/{007-improve-outcome-format/plans/004-implement-failure-analysis.md → aaaaah-improve-outcome-format/plans/04-implement-failure-analysis.md} +0 -0
- /package/RAF/{007-improve-outcome-format/plans/005-update-documentation.md → aaaaah-improve-outcome-format/plans/05-update-documentation.md} +0 -0
- /package/RAF/{008-beautiful-do → aaaaai-beautiful-do}/decisions.md +0 -0
- /package/RAF/{008-beautiful-do → aaaaai-beautiful-do}/input.md +0 -0
- /package/RAF/{008-beautiful-do/outcomes/001-terminal-symbols.md → aaaaai-beautiful-do/outcomes/01-terminal-symbols.md} +0 -0
- /package/RAF/{008-beautiful-do/outcomes/002-refactor-do-output.md → aaaaai-beautiful-do/outcomes/02-refactor-do-output.md} +0 -0
- /package/RAF/{008-beautiful-do/outcomes/003-refactor-status-output.md → aaaaai-beautiful-do/outcomes/03-refactor-status-output.md} +0 -0
- /package/RAF/{008-beautiful-do/outcomes/004-simplify-logger.md → aaaaai-beautiful-do/outcomes/04-simplify-logger.md} +0 -0
- /package/RAF/{008-beautiful-do/outcomes/005-add-tests.md → aaaaai-beautiful-do/outcomes/05-add-tests.md} +0 -0
- /package/RAF/{008-beautiful-do/plans/001-terminal-symbols.md → aaaaai-beautiful-do/plans/01-terminal-symbols.md} +0 -0
- /package/RAF/{008-beautiful-do/plans/002-refactor-do-output.md → aaaaai-beautiful-do/plans/02-refactor-do-output.md} +0 -0
- /package/RAF/{008-beautiful-do/plans/003-refactor-status-output.md → aaaaai-beautiful-do/plans/03-refactor-status-output.md} +0 -0
- /package/RAF/{008-beautiful-do/plans/004-simplify-logger.md → aaaaai-beautiful-do/plans/04-simplify-logger.md} +0 -0
- /package/RAF/{008-beautiful-do/plans/005-add-tests.md → aaaaai-beautiful-do/plans/05-add-tests.md} +0 -0
- /package/RAF/{009-system-promt-ammend → aaaaaj-system-promt-ammend}/decisions.md +0 -0
- /package/RAF/{009-system-promt-ammend → aaaaaj-system-promt-ammend}/input.md +0 -0
- /package/RAF/{009-system-promt-ammend/outcomes/001-model-override.md → aaaaaj-system-promt-ammend/outcomes/01-model-override.md} +0 -0
- /package/RAF/{009-system-promt-ammend/outcomes/002-system-prompt-append.md → aaaaaj-system-promt-ammend/outcomes/02-system-prompt-append.md} +0 -0
- /package/RAF/{009-system-promt-ammend/outcomes/003-retry-context.md → aaaaaj-system-promt-ammend/outcomes/03-retry-context.md} +0 -0
- /package/RAF/{009-system-promt-ammend/plans/001-model-override.md → aaaaaj-system-promt-ammend/plans/01-model-override.md} +0 -0
- /package/RAF/{009-system-promt-ammend/plans/002-system-prompt-append.md → aaaaaj-system-promt-ammend/plans/02-system-prompt-append.md} +0 -0
- /package/RAF/{009-system-promt-ammend/plans/003-retry-context.md → aaaaaj-system-promt-ammend/plans/03-retry-context.md} +0 -0
- /package/RAF/{010-outcome-marker-fallback → aaaabk-outcome-marker-fallback}/decisions.md +0 -0
- /package/RAF/{010-outcome-marker-fallback → aaaabk-outcome-marker-fallback}/input.md +0 -0
- /package/RAF/{010-outcome-marker-fallback/outcomes/001-outcome-file-marker-fallback.md → aaaabk-outcome-marker-fallback/outcomes/01-outcome-file-marker-fallback.md} +0 -0
- /package/RAF/{010-outcome-marker-fallback/outcomes/002-creative-project-naming.md → aaaabk-outcome-marker-fallback/outcomes/02-creative-project-naming.md} +0 -0
- /package/RAF/{010-outcome-marker-fallback/plans/001-outcome-file-marker-fallback.md → aaaabk-outcome-marker-fallback/plans/01-outcome-file-marker-fallback.md} +0 -0
- /package/RAF/{010-outcome-marker-fallback/plans/002-creative-project-naming.md → aaaabk-outcome-marker-fallback/plans/02-creative-project-naming.md} +0 -0
- /package/RAF/{011-do-task-in-commit → aaaabl-do-task-in-commit}/decisions.md +0 -0
- /package/RAF/{011-do-task-in-commit → aaaabl-do-task-in-commit}/input.md +0 -0
- /package/RAF/{011-do-task-in-commit/outcomes/001-update-execution-prompt.md → aaaabl-do-task-in-commit/outcomes/01-update-execution-prompt.md} +0 -0
- /package/RAF/{011-do-task-in-commit/outcomes/002-update-tests.md → aaaabl-do-task-in-commit/outcomes/02-update-tests.md} +0 -0
- /package/RAF/{011-do-task-in-commit/outcomes/003-update-documentation.md → aaaabl-do-task-in-commit/outcomes/03-update-documentation.md} +0 -0
- /package/RAF/{011-do-task-in-commit/plans/001-update-execution-prompt.md → aaaabl-do-task-in-commit/plans/01-update-execution-prompt.md} +0 -0
- /package/RAF/{011-do-task-in-commit/plans/002-update-tests.md → aaaabl-do-task-in-commit/plans/02-update-tests.md} +0 -0
- /package/RAF/{011-do-task-in-commit/plans/003-update-documentation.md → aaaabl-do-task-in-commit/plans/03-update-documentation.md} +0 -0
- /package/RAF/{012-name-picker-buffet → aaaabm-name-picker-buffet}/decisions.md +0 -0
- /package/RAF/{012-name-picker-buffet → aaaabm-name-picker-buffet}/input.md +0 -0
- /package/RAF/{012-name-picker-buffet/outcomes/001-name-picker-for-raf-plan.md → aaaabm-name-picker-buffet/outcomes/01-name-picker-for-raf-plan.md} +0 -0
- /package/RAF/{012-name-picker-buffet/outcomes/002-interactive-project-picker-for-raf-do.md → aaaabm-name-picker-buffet/outcomes/02-interactive-project-picker-for-raf-do.md} +0 -0
- /package/RAF/{012-name-picker-buffet/outcomes/003-raf-status-truncation.md → aaaabm-name-picker-buffet/outcomes/03-raf-status-truncation.md} +0 -0
- /package/RAF/{012-name-picker-buffet/outcomes/004-failure-reason-details.md → aaaabm-name-picker-buffet/outcomes/04-failure-reason-details.md} +0 -0
- /package/RAF/{012-name-picker-buffet/outcomes/005-remove-raf-commits.md → aaaabm-name-picker-buffet/outcomes/05-remove-raf-commits.md} +0 -0
- /package/RAF/{012-name-picker-buffet/outcomes/006-update-execution-prompt-for-commits.md → aaaabm-name-picker-buffet/outcomes/06-update-execution-prompt-for-commits.md} +0 -0
- /package/RAF/{012-name-picker-buffet/outcomes/007-fix-plan-mode-user-prompt.md → aaaabm-name-picker-buffet/outcomes/07-fix-plan-mode-user-prompt.md} +0 -0
- /package/RAF/{012-name-picker-buffet/outcomes/008-add-auto-flag-for-plan-mode.md → aaaabm-name-picker-buffet/outcomes/08-add-auto-flag-for-plan-mode.md} +0 -0
- /package/RAF/{012-name-picker-buffet/plans/001-name-picker-for-raf-plan.md → aaaabm-name-picker-buffet/plans/01-name-picker-for-raf-plan.md} +0 -0
- /package/RAF/{012-name-picker-buffet/plans/002-interactive-project-picker-for-raf-do.md → aaaabm-name-picker-buffet/plans/02-interactive-project-picker-for-raf-do.md} +0 -0
- /package/RAF/{012-name-picker-buffet/plans/003-raf-status-truncation.md → aaaabm-name-picker-buffet/plans/03-raf-status-truncation.md} +0 -0
- /package/RAF/{012-name-picker-buffet/plans/004-failure-reason-details.md → aaaabm-name-picker-buffet/plans/04-failure-reason-details.md} +0 -0
- /package/RAF/{012-name-picker-buffet/plans/005-remove-raf-commits.md → aaaabm-name-picker-buffet/plans/05-remove-raf-commits.md} +0 -0
- /package/RAF/{012-name-picker-buffet/plans/006-update-execution-prompt-for-commits.md → aaaabm-name-picker-buffet/plans/06-update-execution-prompt-for-commits.md} +0 -0
- /package/RAF/{012-name-picker-buffet/plans/007-fix-plan-mode-user-prompt.md → aaaabm-name-picker-buffet/plans/07-fix-plan-mode-user-prompt.md} +0 -0
- /package/RAF/{012-name-picker-buffet/plans/008-add-auto-flag-for-plan-mode.md → aaaabm-name-picker-buffet/plans/08-add-auto-flag-for-plan-mode.md} +0 -0
- /package/RAF/{013-dependencies-watchdog → aaaabn-dependencies-watchdog}/decisions.md +0 -0
- /package/RAF/{013-dependencies-watchdog → aaaabn-dependencies-watchdog}/input.md +0 -0
- /package/RAF/{013-dependencies-watchdog/outcomes/001-define-dependency-syntax.md → aaaabn-dependencies-watchdog/outcomes/01-define-dependency-syntax.md} +0 -0
- /package/RAF/{013-dependencies-watchdog/outcomes/002-update-planning-prompts.md → aaaabn-dependencies-watchdog/outcomes/02-update-planning-prompts.md} +0 -0
- /package/RAF/{013-dependencies-watchdog/outcomes/003-parse-dependencies-update-state.md → aaaabn-dependencies-watchdog/outcomes/03-parse-dependencies-update-state.md} +0 -0
- /package/RAF/{013-dependencies-watchdog/outcomes/004-implement-dependency-checking-in-do.md → aaaabn-dependencies-watchdog/outcomes/04-implement-dependency-checking-in-do.md} +0 -0
- /package/RAF/{013-dependencies-watchdog/outcomes/005-update-execution-prompts.md → aaaabn-dependencies-watchdog/outcomes/05-update-execution-prompts.md} +0 -0
- /package/RAF/{013-dependencies-watchdog/outcomes/006-add-tests.md → aaaabn-dependencies-watchdog/outcomes/06-add-tests.md} +0 -0
- /package/RAF/{013-dependencies-watchdog/outcomes/007-add-act-alias.md → aaaabn-dependencies-watchdog/outcomes/07-add-act-alias.md} +0 -0
- /package/RAF/{013-dependencies-watchdog/outcomes/008-add-exit-message.md → aaaabn-dependencies-watchdog/outcomes/08-add-exit-message.md} +0 -0
- /package/RAF/{013-dependencies-watchdog/plans/001-define-dependency-syntax.md → aaaabn-dependencies-watchdog/plans/01-define-dependency-syntax.md} +0 -0
- /package/RAF/{013-dependencies-watchdog/plans/002-update-planning-prompts.md → aaaabn-dependencies-watchdog/plans/02-update-planning-prompts.md} +0 -0
- /package/RAF/{013-dependencies-watchdog/plans/003-parse-dependencies-update-state.md → aaaabn-dependencies-watchdog/plans/03-parse-dependencies-update-state.md} +0 -0
- /package/RAF/{013-dependencies-watchdog/plans/004-implement-dependency-checking-in-do.md → aaaabn-dependencies-watchdog/plans/04-implement-dependency-checking-in-do.md} +0 -0
- /package/RAF/{013-dependencies-watchdog/plans/005-update-execution-prompts.md → aaaabn-dependencies-watchdog/plans/05-update-execution-prompts.md} +0 -0
- /package/RAF/{013-dependencies-watchdog/plans/006-add-tests.md → aaaabn-dependencies-watchdog/plans/06-add-tests.md} +0 -0
- /package/RAF/{013-dependencies-watchdog/plans/007-add-act-alias.md → aaaabn-dependencies-watchdog/plans/07-add-act-alias.md} +0 -0
- /package/RAF/{013-dependencies-watchdog/plans/008-add-exit-message.md → aaaabn-dependencies-watchdog/plans/08-add-exit-message.md} +0 -0
- /package/RAF/{014-watchdog → aaaabo-watchdog}/decisions.md +0 -0
- /package/RAF/{014-watchdog → aaaabo-watchdog}/input.md +0 -0
- /package/RAF/{014-watchdog/outcomes/001-amend-flag-position.md → aaaabo-watchdog/outcomes/01-amend-flag-position.md} +0 -0
- /package/RAF/{014-watchdog/outcomes/002-details-only-on-failure.md → aaaabo-watchdog/outcomes/02-details-only-on-failure.md} +0 -0
- /package/RAF/{014-watchdog/plans/001-amend-flag-position.md → aaaabo-watchdog/plans/01-amend-flag-position.md} +0 -0
- /package/RAF/{014-watchdog/plans/002-details-only-on-failure.md → aaaabo-watchdog/plans/02-details-only-on-failure.md} +0 -0
- /package/RAF/{015-name-lottery → aaaabp-name-lottery}/decisions.md +0 -0
- /package/RAF/{015-name-lottery → aaaabp-name-lottery}/input.md +0 -0
- /package/RAF/{015-name-lottery/outcomes/001-auto-pick-project-name.md → aaaabp-name-lottery/outcomes/01-auto-pick-project-name.md} +0 -0
- /package/RAF/{015-name-lottery/outcomes/002-mention-plan-files-in-commit.md → aaaabp-name-lottery/outcomes/02-mention-plan-files-in-commit.md} +0 -0
- /package/RAF/{015-name-lottery/outcomes/003-fix-input-md-in-amend-flow.md → aaaabp-name-lottery/outcomes/03-fix-input-md-in-amend-flow.md} +0 -0
- /package/RAF/{015-name-lottery/plans/001-auto-pick-project-name.md → aaaabp-name-lottery/plans/01-auto-pick-project-name.md} +0 -0
- /package/RAF/{015-name-lottery/plans/002-mention-plan-files-in-commit.md → aaaabp-name-lottery/plans/02-mention-plan-files-in-commit.md} +0 -0
- /package/RAF/{015-name-lottery/plans/003-fix-input-md-in-amend-flow.md → aaaabp-name-lottery/plans/03-fix-input-md-in-amend-flow.md} +0 -0
- /package/RAF/{016-planning-scalpel → aaaabq-planning-scalpel}/decisions.md +0 -0
- /package/RAF/{016-planning-scalpel → aaaabq-planning-scalpel}/input.md +0 -0
- /package/RAF/{016-planning-scalpel/outcomes/001-update-git-commit-instructions.md → aaaabq-planning-scalpel/outcomes/01-update-git-commit-instructions.md} +0 -0
- /package/RAF/{016-planning-scalpel/plans/001-update-git-commit-instructions.md → aaaabq-planning-scalpel/plans/01-update-git-commit-instructions.md} +0 -0
- /package/RAF/{017-decision-vault → aaaabr-decision-vault}/decisions.md +0 -0
- /package/RAF/{017-decision-vault → aaaabr-decision-vault}/input.md +0 -0
- /package/RAF/{017-decision-vault/outcomes/001-create-git-commit-utility.md → aaaabr-decision-vault/outcomes/01-create-git-commit-utility.md} +0 -0
- /package/RAF/{017-decision-vault/outcomes/002-integrate-commit-into-plan.md → aaaabr-decision-vault/outcomes/02-integrate-commit-into-plan.md} +0 -0
- /package/RAF/{017-decision-vault/outcomes/003-add-tests-for-planning-commit.md → aaaabr-decision-vault/outcomes/03-add-tests-for-planning-commit.md} +0 -0
- /package/RAF/{017-decision-vault/plans/001-create-git-commit-utility.md → aaaabr-decision-vault/plans/01-create-git-commit-utility.md} +0 -0
- /package/RAF/{017-decision-vault/plans/002-integrate-commit-into-plan.md → aaaabr-decision-vault/plans/02-integrate-commit-into-plan.md} +0 -0
- /package/RAF/{017-decision-vault/plans/003-add-tests-for-planning-commit.md → aaaabr-decision-vault/plans/03-add-tests-for-planning-commit.md} +0 -0
- /package/RAF/{018-workflow-forge → aaaabs-workflow-forge}/decisions.md +0 -0
- /package/RAF/{018-workflow-forge → aaaabs-workflow-forge}/input.md +0 -0
- /package/RAF/{018-workflow-forge/outcomes/001-add-task-number-progress.md → aaaabs-workflow-forge/outcomes/01-add-task-number-progress.md} +0 -0
- /package/RAF/{018-workflow-forge/outcomes/002-update-plan-do-prompts.md → aaaabs-workflow-forge/outcomes/02-update-plan-do-prompts.md} +0 -0
- /package/RAF/{018-workflow-forge/plans/001-add-task-number-progress.md → aaaabs-workflow-forge/plans/01-add-task-number-progress.md} +0 -0
- /package/RAF/{018-workflow-forge/plans/002-update-plan-do-prompts.md → aaaabs-workflow-forge/plans/02-update-plan-do-prompts.md} +0 -0
|
@@ -0,0 +1,852 @@
|
|
|
1
|
+
import { jest } from '@jest/globals';
|
|
2
|
+
import * as path from 'node:path';
|
|
3
|
+
|
|
4
|
+
// Mock child_process before importing the module
|
|
5
|
+
const mockExecSync = jest.fn();
|
|
6
|
+
const mockSpawn = jest.fn();
|
|
7
|
+
jest.unstable_mockModule('node:child_process', () => ({
|
|
8
|
+
execSync: mockExecSync,
|
|
9
|
+
spawn: mockSpawn,
|
|
10
|
+
}));
|
|
11
|
+
|
|
12
|
+
// Mock fs
|
|
13
|
+
const mockExistsSync = jest.fn();
|
|
14
|
+
const mockReadFileSync = jest.fn();
|
|
15
|
+
const mockReaddirSync = jest.fn();
|
|
16
|
+
const mockWriteFileSync = jest.fn();
|
|
17
|
+
const mockUnlinkSync = jest.fn();
|
|
18
|
+
jest.unstable_mockModule('node:fs', () => ({
|
|
19
|
+
existsSync: mockExistsSync,
|
|
20
|
+
readFileSync: mockReadFileSync,
|
|
21
|
+
readdirSync: mockReaddirSync,
|
|
22
|
+
writeFileSync: mockWriteFileSync,
|
|
23
|
+
unlinkSync: mockUnlinkSync,
|
|
24
|
+
}));
|
|
25
|
+
|
|
26
|
+
// Mock os
|
|
27
|
+
const mockTmpdir = jest.fn().mockReturnValue('/tmp');
|
|
28
|
+
jest.unstable_mockModule('node:os', () => ({
|
|
29
|
+
tmpdir: mockTmpdir,
|
|
30
|
+
}));
|
|
31
|
+
|
|
32
|
+
// Mock logger to prevent console output
|
|
33
|
+
jest.unstable_mockModule('../../src/utils/logger.js', () => ({
|
|
34
|
+
logger: {
|
|
35
|
+
debug: jest.fn(),
|
|
36
|
+
info: jest.fn(),
|
|
37
|
+
warn: jest.fn(),
|
|
38
|
+
error: jest.fn(),
|
|
39
|
+
},
|
|
40
|
+
}));
|
|
41
|
+
|
|
42
|
+
// Import after mocking
|
|
43
|
+
const {
|
|
44
|
+
isGhInstalled,
|
|
45
|
+
isGhAuthenticated,
|
|
46
|
+
isGitHubRemote,
|
|
47
|
+
isBranchPushed,
|
|
48
|
+
pushBranch,
|
|
49
|
+
detectBaseBranch,
|
|
50
|
+
prPreflight,
|
|
51
|
+
generatePrTitle,
|
|
52
|
+
readProjectContext,
|
|
53
|
+
generatePrBody,
|
|
54
|
+
createPullRequest,
|
|
55
|
+
filterClaudeOutput,
|
|
56
|
+
} = await import('../../src/core/pull-request.js');
|
|
57
|
+
|
|
58
|
+
describe('pull-request utilities', () => {
|
|
59
|
+
beforeEach(() => {
|
|
60
|
+
jest.clearAllMocks();
|
|
61
|
+
mockTmpdir.mockReturnValue('/tmp');
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
describe('isGhInstalled', () => {
|
|
65
|
+
it('should return true when gh is installed', () => {
|
|
66
|
+
mockExecSync.mockReturnValue('gh version 2.40.0');
|
|
67
|
+
expect(isGhInstalled()).toBe(true);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it('should return false when gh is not installed', () => {
|
|
71
|
+
mockExecSync.mockImplementation(() => {
|
|
72
|
+
throw new Error('command not found: gh');
|
|
73
|
+
});
|
|
74
|
+
expect(isGhInstalled()).toBe(false);
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
describe('isGhAuthenticated', () => {
|
|
79
|
+
it('should return true when gh is authenticated', () => {
|
|
80
|
+
mockExecSync.mockReturnValue('Logged in to github.com');
|
|
81
|
+
expect(isGhAuthenticated()).toBe(true);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it('should return false when gh is not authenticated', () => {
|
|
85
|
+
mockExecSync.mockImplementation(() => {
|
|
86
|
+
throw new Error('not logged in');
|
|
87
|
+
});
|
|
88
|
+
expect(isGhAuthenticated()).toBe(false);
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
describe('isGitHubRemote', () => {
|
|
93
|
+
it('should return true for github.com SSH remote', () => {
|
|
94
|
+
mockExecSync.mockReturnValue('git@github.com:user/repo.git\n');
|
|
95
|
+
expect(isGitHubRemote()).toBe(true);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it('should return true for github.com HTTPS remote', () => {
|
|
99
|
+
mockExecSync.mockReturnValue('https://github.com/user/repo.git\n');
|
|
100
|
+
expect(isGitHubRemote()).toBe(true);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it('should return false for non-GitHub remote', () => {
|
|
104
|
+
mockExecSync.mockReturnValue('git@gitlab.com:user/repo.git\n');
|
|
105
|
+
expect(isGitHubRemote()).toBe(false);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it('should return false when no remote exists', () => {
|
|
109
|
+
mockExecSync.mockImplementation(() => {
|
|
110
|
+
throw new Error('No such remote');
|
|
111
|
+
});
|
|
112
|
+
expect(isGitHubRemote()).toBe(false);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it('should pass cwd when provided', () => {
|
|
116
|
+
mockExecSync.mockReturnValue('git@github.com:user/repo.git\n');
|
|
117
|
+
isGitHubRemote('/some/worktree');
|
|
118
|
+
expect(mockExecSync).toHaveBeenCalledWith(
|
|
119
|
+
'git remote get-url origin',
|
|
120
|
+
expect.objectContaining({ cwd: '/some/worktree' }),
|
|
121
|
+
);
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
describe('isBranchPushed', () => {
|
|
126
|
+
it('should return true when branch exists on remote', () => {
|
|
127
|
+
mockExecSync.mockReturnValue('abc123\trefs/heads/my-branch\n');
|
|
128
|
+
expect(isBranchPushed('my-branch')).toBe(true);
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it('should return false when branch does not exist on remote', () => {
|
|
132
|
+
mockExecSync.mockReturnValue('');
|
|
133
|
+
expect(isBranchPushed('my-branch')).toBe(false);
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it('should return false when command fails', () => {
|
|
137
|
+
mockExecSync.mockImplementation(() => {
|
|
138
|
+
throw new Error('fatal: not a git repository');
|
|
139
|
+
});
|
|
140
|
+
expect(isBranchPushed('my-branch')).toBe(false);
|
|
141
|
+
});
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
describe('pushBranch', () => {
|
|
145
|
+
it('should return true on successful push', () => {
|
|
146
|
+
mockExecSync.mockReturnValue('');
|
|
147
|
+
expect(pushBranch('my-branch')).toBe(true);
|
|
148
|
+
expect(mockExecSync).toHaveBeenCalledWith(
|
|
149
|
+
'git push -u origin "my-branch"',
|
|
150
|
+
expect.any(Object),
|
|
151
|
+
);
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
it('should return false on push failure', () => {
|
|
155
|
+
mockExecSync.mockImplementation(() => {
|
|
156
|
+
throw new Error('push failed');
|
|
157
|
+
});
|
|
158
|
+
expect(pushBranch('my-branch')).toBe(false);
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
it('should pass cwd when provided', () => {
|
|
162
|
+
mockExecSync.mockReturnValue('');
|
|
163
|
+
pushBranch('my-branch', '/some/worktree');
|
|
164
|
+
expect(mockExecSync).toHaveBeenCalledWith(
|
|
165
|
+
'git push -u origin "my-branch"',
|
|
166
|
+
expect.objectContaining({ cwd: '/some/worktree' }),
|
|
167
|
+
);
|
|
168
|
+
});
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
describe('detectBaseBranch', () => {
|
|
172
|
+
it('should detect base branch from remote HEAD', () => {
|
|
173
|
+
mockExecSync.mockReturnValue('refs/remotes/origin/main\n');
|
|
174
|
+
expect(detectBaseBranch()).toBe('main');
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
it('should detect master from remote HEAD', () => {
|
|
178
|
+
mockExecSync.mockReturnValue('refs/remotes/origin/master\n');
|
|
179
|
+
expect(detectBaseBranch()).toBe('master');
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
it('should fall back to checking main branch existence', () => {
|
|
183
|
+
let callCount = 0;
|
|
184
|
+
mockExecSync.mockImplementation((cmd: unknown) => {
|
|
185
|
+
callCount++;
|
|
186
|
+
const cmdStr = cmd as string;
|
|
187
|
+
if (cmdStr.includes('symbolic-ref')) {
|
|
188
|
+
throw new Error('not set');
|
|
189
|
+
}
|
|
190
|
+
if (cmdStr.includes('refs/heads/main')) {
|
|
191
|
+
return '';
|
|
192
|
+
}
|
|
193
|
+
throw new Error('no such ref');
|
|
194
|
+
});
|
|
195
|
+
expect(detectBaseBranch()).toBe('main');
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
it('should fall back to master if main does not exist', () => {
|
|
199
|
+
mockExecSync.mockImplementation((cmd: unknown) => {
|
|
200
|
+
const cmdStr = cmd as string;
|
|
201
|
+
if (cmdStr.includes('symbolic-ref')) {
|
|
202
|
+
throw new Error('not set');
|
|
203
|
+
}
|
|
204
|
+
if (cmdStr.includes('refs/heads/main')) {
|
|
205
|
+
throw new Error('no such ref');
|
|
206
|
+
}
|
|
207
|
+
if (cmdStr.includes('refs/heads/master')) {
|
|
208
|
+
return '';
|
|
209
|
+
}
|
|
210
|
+
throw new Error('no such ref');
|
|
211
|
+
});
|
|
212
|
+
expect(detectBaseBranch()).toBe('master');
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
it('should return null when no base branch is found', () => {
|
|
216
|
+
mockExecSync.mockImplementation(() => {
|
|
217
|
+
throw new Error('not found');
|
|
218
|
+
});
|
|
219
|
+
expect(detectBaseBranch()).toBeNull();
|
|
220
|
+
});
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
describe('prPreflight', () => {
|
|
224
|
+
it('should return ready=true when all checks pass', () => {
|
|
225
|
+
mockExecSync.mockImplementation((cmd: unknown) => {
|
|
226
|
+
const cmdStr = cmd as string;
|
|
227
|
+
if (cmdStr.includes('gh --version')) return 'gh version 2.40.0';
|
|
228
|
+
if (cmdStr.includes('gh auth status')) return 'Logged in';
|
|
229
|
+
if (cmdStr.includes('git remote get-url')) return 'git@github.com:user/repo.git';
|
|
230
|
+
if (cmdStr.includes('git ls-remote')) return 'abc123\trefs/heads/my-branch';
|
|
231
|
+
return '';
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
const result = prPreflight('my-branch');
|
|
235
|
+
expect(result.ready).toBe(true);
|
|
236
|
+
expect(result.ghInstalled).toBe(true);
|
|
237
|
+
expect(result.ghAuthenticated).toBe(true);
|
|
238
|
+
expect(result.isGitHubRemote).toBe(true);
|
|
239
|
+
expect(result.branchPushed).toBe(true);
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
it('should fail if gh is not installed', () => {
|
|
243
|
+
mockExecSync.mockImplementation((cmd: unknown) => {
|
|
244
|
+
const cmdStr = cmd as string;
|
|
245
|
+
if (cmdStr.includes('gh --version')) throw new Error('not found');
|
|
246
|
+
return '';
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
const result = prPreflight('my-branch');
|
|
250
|
+
expect(result.ready).toBe(false);
|
|
251
|
+
expect(result.ghInstalled).toBe(false);
|
|
252
|
+
expect(result.error).toContain('not installed');
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
it('should fail if gh is not authenticated', () => {
|
|
256
|
+
mockExecSync.mockImplementation((cmd: unknown) => {
|
|
257
|
+
const cmdStr = cmd as string;
|
|
258
|
+
if (cmdStr.includes('gh --version')) return 'gh version 2.40.0';
|
|
259
|
+
if (cmdStr.includes('gh auth status')) throw new Error('not logged in');
|
|
260
|
+
return '';
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
const result = prPreflight('my-branch');
|
|
264
|
+
expect(result.ready).toBe(false);
|
|
265
|
+
expect(result.ghAuthenticated).toBe(false);
|
|
266
|
+
expect(result.error).toContain('not authenticated');
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
it('should fail if remote is not GitHub', () => {
|
|
270
|
+
mockExecSync.mockImplementation((cmd: unknown) => {
|
|
271
|
+
const cmdStr = cmd as string;
|
|
272
|
+
if (cmdStr.includes('gh --version')) return 'gh version 2.40.0';
|
|
273
|
+
if (cmdStr.includes('gh auth status')) return 'Logged in';
|
|
274
|
+
if (cmdStr.includes('git remote get-url')) return 'git@gitlab.com:user/repo.git';
|
|
275
|
+
return '';
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
const result = prPreflight('my-branch');
|
|
279
|
+
expect(result.ready).toBe(false);
|
|
280
|
+
expect(result.isGitHubRemote).toBe(false);
|
|
281
|
+
expect(result.error).toContain('not a GitHub');
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
it('should be ready even if branch is not yet pushed', () => {
|
|
285
|
+
mockExecSync.mockImplementation((cmd: unknown) => {
|
|
286
|
+
const cmdStr = cmd as string;
|
|
287
|
+
if (cmdStr.includes('gh --version')) return 'gh version 2.40.0';
|
|
288
|
+
if (cmdStr.includes('gh auth status')) return 'Logged in';
|
|
289
|
+
if (cmdStr.includes('git remote get-url')) return 'git@github.com:user/repo.git';
|
|
290
|
+
if (cmdStr.includes('git ls-remote')) return '';
|
|
291
|
+
return '';
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
const result = prPreflight('my-branch');
|
|
295
|
+
expect(result.ready).toBe(true);
|
|
296
|
+
expect(result.branchPushed).toBe(false);
|
|
297
|
+
});
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
describe('generatePrTitle', () => {
|
|
301
|
+
it('should convert project name to human-readable title', () => {
|
|
302
|
+
expect(generatePrTitle('/path/to/RAF/acbfhg-merge-guardian')).toBe('Merge guardian');
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
it('should capitalize first word only', () => {
|
|
306
|
+
expect(generatePrTitle('/path/to/RAF/aabcde-fix-login-bug')).toBe('Fix login bug');
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
it('should handle single-word names', () => {
|
|
310
|
+
expect(generatePrTitle('/path/to/RAF/aabcde-refactoring')).toBe('Refactoring');
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
it('should return fallback for invalid path', () => {
|
|
314
|
+
expect(generatePrTitle('/path/to/invalid')).toBe('Feature branch');
|
|
315
|
+
});
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
describe('readProjectContext', () => {
|
|
319
|
+
const projectPath = '/path/to/RAF/acbfhg-merge-guardian';
|
|
320
|
+
|
|
321
|
+
it('should read input.md, decisions.md, and outcomes', () => {
|
|
322
|
+
mockExistsSync.mockImplementation((p: unknown) => {
|
|
323
|
+
const pStr = p as string;
|
|
324
|
+
if (pStr.endsWith('input.md')) return true;
|
|
325
|
+
if (pStr.endsWith('decisions.md')) return true;
|
|
326
|
+
if (pStr.endsWith('outcomes')) return true;
|
|
327
|
+
return false;
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
mockReadFileSync.mockImplementation((p: unknown) => {
|
|
331
|
+
const pStr = p as string;
|
|
332
|
+
if (pStr.endsWith('input.md')) return 'Build a feature';
|
|
333
|
+
if (pStr.endsWith('decisions.md')) return '# Decisions\nUse React';
|
|
334
|
+
if (pStr.endsWith('01-setup.md')) return '# Setup complete\n<promise>COMPLETE</promise>';
|
|
335
|
+
return '';
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
mockReaddirSync.mockReturnValue(['01-setup.md', '02-implement.md']);
|
|
339
|
+
|
|
340
|
+
const context = readProjectContext(projectPath);
|
|
341
|
+
|
|
342
|
+
expect(context.input).toBe('Build a feature');
|
|
343
|
+
expect(context.decisions).toBe('# Decisions\nUse React');
|
|
344
|
+
expect(context.outcomes.length).toBe(2);
|
|
345
|
+
expect(context.outcomes[0]!.taskId).toBe('01');
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
it('should handle missing input.md', () => {
|
|
349
|
+
mockExistsSync.mockReturnValue(false);
|
|
350
|
+
|
|
351
|
+
const context = readProjectContext(projectPath);
|
|
352
|
+
|
|
353
|
+
expect(context.input).toBeNull();
|
|
354
|
+
expect(context.decisions).toBeNull();
|
|
355
|
+
expect(context.outcomes).toEqual([]);
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
it('should handle outcomes directory with no matching files', () => {
|
|
359
|
+
mockExistsSync.mockImplementation((p: unknown) => {
|
|
360
|
+
const pStr = p as string;
|
|
361
|
+
if (pStr.endsWith('outcomes')) return true;
|
|
362
|
+
return false;
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
mockReaddirSync.mockReturnValue(['README.md', '.gitkeep']);
|
|
366
|
+
|
|
367
|
+
const context = readProjectContext(projectPath);
|
|
368
|
+
|
|
369
|
+
expect(context.outcomes).toEqual([]);
|
|
370
|
+
});
|
|
371
|
+
});
|
|
372
|
+
|
|
373
|
+
describe('filterClaudeOutput', () => {
|
|
374
|
+
it('should pass through clean markdown', () => {
|
|
375
|
+
const input = '## Summary\n- Added a feature\n\n## Test Plan\n- Run tests';
|
|
376
|
+
expect(filterClaudeOutput(input)).toBe(input);
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
it('should remove warning emoji lines', () => {
|
|
380
|
+
const input = '⚠ Some warning\n## Summary\n- Feature added';
|
|
381
|
+
expect(filterClaudeOutput(input)).toBe('## Summary\n- Feature added');
|
|
382
|
+
});
|
|
383
|
+
|
|
384
|
+
it('should remove [warn] prefixed lines', () => {
|
|
385
|
+
const input = '[warn] something happened\n## Summary\nContent here';
|
|
386
|
+
expect(filterClaudeOutput(input)).toBe('## Summary\nContent here');
|
|
387
|
+
});
|
|
388
|
+
|
|
389
|
+
it('should remove console.warn lines', () => {
|
|
390
|
+
const input = 'console.warn: deprecation notice\n## Summary\n- Done';
|
|
391
|
+
expect(filterClaudeOutput(input)).toBe('## Summary\n- Done');
|
|
392
|
+
});
|
|
393
|
+
|
|
394
|
+
it('should remove Note: lines', () => {
|
|
395
|
+
const input = 'Note: using fallback\n## Summary\n- Changes';
|
|
396
|
+
expect(filterClaudeOutput(input)).toBe('## Summary\n- Changes');
|
|
397
|
+
});
|
|
398
|
+
|
|
399
|
+
it('should remove Loading/Connecting/Processing lines', () => {
|
|
400
|
+
const input = 'Loading model...\nConnecting to API\n## Summary\n- Done';
|
|
401
|
+
expect(filterClaudeOutput(input)).toBe('## Summary\n- Done');
|
|
402
|
+
});
|
|
403
|
+
|
|
404
|
+
it('should remove lines with progress indicators', () => {
|
|
405
|
+
const input = '› Initializing\n> Running\n## Summary\n- Feature';
|
|
406
|
+
expect(filterClaudeOutput(input)).toBe('## Summary\n- Feature');
|
|
407
|
+
});
|
|
408
|
+
|
|
409
|
+
it('should handle empty input', () => {
|
|
410
|
+
expect(filterClaudeOutput('')).toBe('');
|
|
411
|
+
});
|
|
412
|
+
|
|
413
|
+
it('should handle input that is all log lines', () => {
|
|
414
|
+
const input = '⚠ warning\n[info] starting\nLoading data';
|
|
415
|
+
expect(filterClaudeOutput(input)).toBe('');
|
|
416
|
+
});
|
|
417
|
+
|
|
418
|
+
it('should preserve blank lines between markdown sections', () => {
|
|
419
|
+
const input = '## Summary\nContent\n\n## Test Plan\nVerify';
|
|
420
|
+
expect(filterClaudeOutput(input)).toBe(input);
|
|
421
|
+
});
|
|
422
|
+
});
|
|
423
|
+
|
|
424
|
+
describe('generatePrBody', () => {
|
|
425
|
+
it('should return fallback body when no context is available', async () => {
|
|
426
|
+
mockExistsSync.mockReturnValue(false);
|
|
427
|
+
|
|
428
|
+
const body = await generatePrBody('/path/to/RAF/acbfhg-project');
|
|
429
|
+
|
|
430
|
+
expect(body).toContain('## Summary');
|
|
431
|
+
expect(body).toContain('## Key Decisions');
|
|
432
|
+
expect(body).toContain('## What Was Done');
|
|
433
|
+
expect(body).toContain('## Test Plan');
|
|
434
|
+
});
|
|
435
|
+
|
|
436
|
+
it('should return fallback body with all 4 sections when outcomes exist', async () => {
|
|
437
|
+
mockExistsSync.mockImplementation((p: unknown) => {
|
|
438
|
+
const pStr = p as string;
|
|
439
|
+
if (pStr.endsWith('input.md')) return true;
|
|
440
|
+
if (pStr.endsWith('decisions.md')) return false;
|
|
441
|
+
if (pStr.endsWith('outcomes')) return true;
|
|
442
|
+
return false;
|
|
443
|
+
});
|
|
444
|
+
|
|
445
|
+
mockReadFileSync.mockImplementation((p: unknown) => {
|
|
446
|
+
const pStr = p as string;
|
|
447
|
+
if (pStr.endsWith('input.md')) return 'Add dark mode toggle';
|
|
448
|
+
if (pStr.endsWith('01-task.md')) return 'Done';
|
|
449
|
+
return '';
|
|
450
|
+
});
|
|
451
|
+
|
|
452
|
+
mockReaddirSync.mockReturnValue(['01-task.md']);
|
|
453
|
+
|
|
454
|
+
// Mock spawn to fail (forcing fallback)
|
|
455
|
+
mockExecSync.mockImplementation((cmd: unknown) => {
|
|
456
|
+
const cmdStr = cmd as string;
|
|
457
|
+
if (cmdStr.includes('which claude')) throw new Error('not found');
|
|
458
|
+
return '';
|
|
459
|
+
});
|
|
460
|
+
|
|
461
|
+
const body = await generatePrBody('/path/to/RAF/acbfhg-project');
|
|
462
|
+
|
|
463
|
+
expect(body).toContain('## Summary');
|
|
464
|
+
expect(body).toContain('Add dark mode toggle');
|
|
465
|
+
expect(body).toContain('## Key Decisions');
|
|
466
|
+
expect(body).toContain('No decisions recorded');
|
|
467
|
+
expect(body).toContain('## What Was Done');
|
|
468
|
+
expect(body).toContain('1 task(s) completed');
|
|
469
|
+
expect(body).toContain('## Test Plan');
|
|
470
|
+
});
|
|
471
|
+
|
|
472
|
+
it('should include prompt with 4-section structure', async () => {
|
|
473
|
+
mockExistsSync.mockImplementation((p: unknown) => {
|
|
474
|
+
const pStr = p as string;
|
|
475
|
+
if (pStr.endsWith('input.md')) return true;
|
|
476
|
+
if (pStr.endsWith('decisions.md')) return false;
|
|
477
|
+
if (pStr.endsWith('outcomes')) return false;
|
|
478
|
+
return false;
|
|
479
|
+
});
|
|
480
|
+
|
|
481
|
+
mockReadFileSync.mockImplementation((p: unknown) => {
|
|
482
|
+
const pStr = p as string;
|
|
483
|
+
if (pStr.endsWith('input.md')) return 'Some feature';
|
|
484
|
+
return '';
|
|
485
|
+
});
|
|
486
|
+
|
|
487
|
+
// Capture the prompt passed to Claude
|
|
488
|
+
let capturedPrompt = '';
|
|
489
|
+
mockExecSync.mockImplementation((cmd: unknown) => {
|
|
490
|
+
const cmdStr = cmd as string;
|
|
491
|
+
if (cmdStr.includes('which claude')) return '/usr/local/bin/claude';
|
|
492
|
+
return '';
|
|
493
|
+
});
|
|
494
|
+
|
|
495
|
+
// Mock spawn to capture prompt
|
|
496
|
+
const mockProc = {
|
|
497
|
+
stdout: { on: jest.fn() },
|
|
498
|
+
stderr: { on: jest.fn() },
|
|
499
|
+
on: jest.fn(),
|
|
500
|
+
kill: jest.fn(),
|
|
501
|
+
};
|
|
502
|
+
mockSpawn.mockReturnValue(mockProc);
|
|
503
|
+
|
|
504
|
+
// Start the promise but don't await yet
|
|
505
|
+
const bodyPromise = generatePrBody('/path/to/RAF/acbfhg-project');
|
|
506
|
+
|
|
507
|
+
// Get the prompt from spawn args
|
|
508
|
+
const spawnArgs = mockSpawn.mock.calls[0] as unknown[];
|
|
509
|
+
const args = spawnArgs[1] as string[];
|
|
510
|
+
capturedPrompt = args[args.length - 1]!;
|
|
511
|
+
|
|
512
|
+
// Simulate successful Claude response
|
|
513
|
+
const closeHandler = (mockProc.on as jest.Mock).mock.calls.find(
|
|
514
|
+
(call: unknown[]) => call[0] === 'close',
|
|
515
|
+
);
|
|
516
|
+
const stdoutHandler = (mockProc.stdout.on as jest.Mock).mock.calls.find(
|
|
517
|
+
(call: unknown[]) => call[0] === 'data',
|
|
518
|
+
);
|
|
519
|
+
|
|
520
|
+
// Emit data then close
|
|
521
|
+
(stdoutHandler![1] as (data: Buffer) => void)(Buffer.from('## Summary\nTest\n\n## Key Decisions\n- Decision\n\n## What Was Done\n- Work\n\n## Test Plan\n- Verify'));
|
|
522
|
+
(closeHandler![1] as (code: number) => void)(0);
|
|
523
|
+
|
|
524
|
+
await bodyPromise;
|
|
525
|
+
|
|
526
|
+
// Verify prompt structure
|
|
527
|
+
expect(capturedPrompt).toContain('## Summary');
|
|
528
|
+
expect(capturedPrompt).toContain('## Key Decisions');
|
|
529
|
+
expect(capturedPrompt).toContain('## What Was Done');
|
|
530
|
+
expect(capturedPrompt).toContain('## Test Plan');
|
|
531
|
+
});
|
|
532
|
+
|
|
533
|
+
it('should use sonnet model', async () => {
|
|
534
|
+
mockExistsSync.mockImplementation((p: unknown) => {
|
|
535
|
+
const pStr = p as string;
|
|
536
|
+
if (pStr.endsWith('input.md')) return true;
|
|
537
|
+
return false;
|
|
538
|
+
});
|
|
539
|
+
|
|
540
|
+
mockReadFileSync.mockImplementation(() => 'Feature input');
|
|
541
|
+
|
|
542
|
+
mockExecSync.mockImplementation((cmd: unknown) => {
|
|
543
|
+
const cmdStr = cmd as string;
|
|
544
|
+
if (cmdStr.includes('which claude')) return '/usr/local/bin/claude';
|
|
545
|
+
return '';
|
|
546
|
+
});
|
|
547
|
+
|
|
548
|
+
const mockProc = {
|
|
549
|
+
stdout: { on: jest.fn() },
|
|
550
|
+
stderr: { on: jest.fn() },
|
|
551
|
+
on: jest.fn(),
|
|
552
|
+
kill: jest.fn(),
|
|
553
|
+
};
|
|
554
|
+
mockSpawn.mockReturnValue(mockProc);
|
|
555
|
+
|
|
556
|
+
const bodyPromise = generatePrBody('/path/to/RAF/acbfhg-project');
|
|
557
|
+
|
|
558
|
+
// Verify spawn was called with sonnet model
|
|
559
|
+
const spawnArgs = mockSpawn.mock.calls[0] as unknown[];
|
|
560
|
+
const args = spawnArgs[1] as string[];
|
|
561
|
+
expect(args).toContain('--model');
|
|
562
|
+
expect(args).toContain('sonnet');
|
|
563
|
+
|
|
564
|
+
// Close the process to resolve the promise
|
|
565
|
+
const closeHandler = (mockProc.on as jest.Mock).mock.calls.find(
|
|
566
|
+
(call: unknown[]) => call[0] === 'close',
|
|
567
|
+
);
|
|
568
|
+
const stdoutHandler = (mockProc.stdout.on as jest.Mock).mock.calls.find(
|
|
569
|
+
(call: unknown[]) => call[0] === 'data',
|
|
570
|
+
);
|
|
571
|
+
|
|
572
|
+
(stdoutHandler![1] as (data: Buffer) => void)(Buffer.from('## Summary\nContent'));
|
|
573
|
+
(closeHandler![1] as (code: number) => void)(0);
|
|
574
|
+
|
|
575
|
+
await bodyPromise;
|
|
576
|
+
});
|
|
577
|
+
});
|
|
578
|
+
|
|
579
|
+
describe('createPullRequest', () => {
|
|
580
|
+
it('should fail if preflight checks fail', async () => {
|
|
581
|
+
mockExecSync.mockImplementation((cmd: unknown) => {
|
|
582
|
+
const cmdStr = cmd as string;
|
|
583
|
+
if (cmdStr.includes('gh --version')) throw new Error('not found');
|
|
584
|
+
return '';
|
|
585
|
+
});
|
|
586
|
+
|
|
587
|
+
const result = await createPullRequest('my-branch', '/path/to/RAF/acbfhg-project');
|
|
588
|
+
|
|
589
|
+
expect(result.success).toBe(false);
|
|
590
|
+
expect(result.error).toContain('not installed');
|
|
591
|
+
});
|
|
592
|
+
|
|
593
|
+
it('should fail if base branch cannot be detected', async () => {
|
|
594
|
+
// All preflight checks pass
|
|
595
|
+
mockExecSync.mockImplementation((cmd: unknown) => {
|
|
596
|
+
const cmdStr = cmd as string;
|
|
597
|
+
if (cmdStr.includes('gh --version')) return 'gh version 2.40.0';
|
|
598
|
+
if (cmdStr.includes('gh auth status')) return 'Logged in';
|
|
599
|
+
if (cmdStr.includes('git remote get-url')) return 'git@github.com:user/repo.git';
|
|
600
|
+
if (cmdStr.includes('git ls-remote')) return 'abc123\trefs/heads/my-branch';
|
|
601
|
+
// Base branch detection fails
|
|
602
|
+
throw new Error('not found');
|
|
603
|
+
});
|
|
604
|
+
|
|
605
|
+
const result = await createPullRequest('my-branch', '/path/to/RAF/acbfhg-project');
|
|
606
|
+
|
|
607
|
+
expect(result.success).toBe(false);
|
|
608
|
+
expect(result.error).toContain('base branch');
|
|
609
|
+
});
|
|
610
|
+
|
|
611
|
+
it('should push branch if not already pushed', async () => {
|
|
612
|
+
let pushCalled = false;
|
|
613
|
+
mockExecSync.mockImplementation((cmd: unknown) => {
|
|
614
|
+
const cmdStr = cmd as string;
|
|
615
|
+
if (cmdStr.includes('gh --version')) return 'gh version 2.40.0';
|
|
616
|
+
if (cmdStr.includes('gh auth status')) return 'Logged in';
|
|
617
|
+
if (cmdStr.includes('git remote get-url')) return 'git@github.com:user/repo.git';
|
|
618
|
+
if (cmdStr.includes('git ls-remote')) return ''; // not pushed
|
|
619
|
+
if (cmdStr.includes('git push -u origin')) { pushCalled = true; return ''; }
|
|
620
|
+
if (cmdStr.includes('symbolic-ref')) return 'refs/remotes/origin/main\n';
|
|
621
|
+
if (cmdStr.includes('which claude')) throw new Error('not found');
|
|
622
|
+
if (cmdStr.includes('gh pr create')) return 'https://github.com/user/repo/pull/1\n';
|
|
623
|
+
return '';
|
|
624
|
+
});
|
|
625
|
+
|
|
626
|
+
// Mock fs for generatePrBody fallback
|
|
627
|
+
mockExistsSync.mockReturnValue(false);
|
|
628
|
+
|
|
629
|
+
const result = await createPullRequest('my-branch', '/path/to/RAF/acbfhg-project');
|
|
630
|
+
|
|
631
|
+
expect(pushCalled).toBe(true);
|
|
632
|
+
expect(result.success).toBe(true);
|
|
633
|
+
expect(result.prUrl).toBe('https://github.com/user/repo/pull/1');
|
|
634
|
+
});
|
|
635
|
+
|
|
636
|
+
it('should fail if push fails', async () => {
|
|
637
|
+
mockExecSync.mockImplementation((cmd: unknown) => {
|
|
638
|
+
const cmdStr = cmd as string;
|
|
639
|
+
if (cmdStr.includes('gh --version')) return 'gh version 2.40.0';
|
|
640
|
+
if (cmdStr.includes('gh auth status')) return 'Logged in';
|
|
641
|
+
if (cmdStr.includes('git remote get-url')) return 'git@github.com:user/repo.git';
|
|
642
|
+
if (cmdStr.includes('git ls-remote')) return ''; // not pushed
|
|
643
|
+
if (cmdStr.includes('symbolic-ref')) return 'refs/remotes/origin/main\n';
|
|
644
|
+
if (cmdStr.includes('git push -u origin')) throw new Error('permission denied');
|
|
645
|
+
return '';
|
|
646
|
+
});
|
|
647
|
+
|
|
648
|
+
const result = await createPullRequest('my-branch', '/path/to/RAF/acbfhg-project');
|
|
649
|
+
|
|
650
|
+
expect(result.success).toBe(false);
|
|
651
|
+
expect(result.error).toContain('Failed to push');
|
|
652
|
+
});
|
|
653
|
+
|
|
654
|
+
it('should use --body-file with temp file instead of --body', async () => {
|
|
655
|
+
let ghPrCmd = '';
|
|
656
|
+
mockExecSync.mockImplementation((cmd: unknown) => {
|
|
657
|
+
const cmdStr = cmd as string;
|
|
658
|
+
if (cmdStr.includes('gh --version')) return 'gh version 2.40.0';
|
|
659
|
+
if (cmdStr.includes('gh auth status')) return 'Logged in';
|
|
660
|
+
if (cmdStr.includes('git remote get-url')) return 'git@github.com:user/repo.git';
|
|
661
|
+
if (cmdStr.includes('git ls-remote')) return 'abc123\trefs/heads/my-branch';
|
|
662
|
+
if (cmdStr.includes('symbolic-ref')) return 'refs/remotes/origin/main\n';
|
|
663
|
+
if (cmdStr.includes('which claude')) throw new Error('not found');
|
|
664
|
+
if (cmdStr.includes('gh pr create')) {
|
|
665
|
+
ghPrCmd = cmdStr;
|
|
666
|
+
return 'https://github.com/user/repo/pull/42\n';
|
|
667
|
+
}
|
|
668
|
+
return '';
|
|
669
|
+
});
|
|
670
|
+
|
|
671
|
+
mockExistsSync.mockReturnValue(false);
|
|
672
|
+
|
|
673
|
+
await createPullRequest('my-branch', '/path/to/RAF/acbfhg-project');
|
|
674
|
+
|
|
675
|
+
expect(ghPrCmd).toContain('--body-file');
|
|
676
|
+
expect(ghPrCmd).not.toContain('--body "');
|
|
677
|
+
expect(mockWriteFileSync).toHaveBeenCalledWith(
|
|
678
|
+
expect.stringContaining('/tmp/raf-pr-body-'),
|
|
679
|
+
expect.any(String),
|
|
680
|
+
'utf-8',
|
|
681
|
+
);
|
|
682
|
+
});
|
|
683
|
+
|
|
684
|
+
it('should clean up temp file after successful PR creation', async () => {
|
|
685
|
+
mockExecSync.mockImplementation((cmd: unknown) => {
|
|
686
|
+
const cmdStr = cmd as string;
|
|
687
|
+
if (cmdStr.includes('gh --version')) return 'gh version 2.40.0';
|
|
688
|
+
if (cmdStr.includes('gh auth status')) return 'Logged in';
|
|
689
|
+
if (cmdStr.includes('git remote get-url')) return 'git@github.com:user/repo.git';
|
|
690
|
+
if (cmdStr.includes('git ls-remote')) return 'abc123\trefs/heads/my-branch';
|
|
691
|
+
if (cmdStr.includes('symbolic-ref')) return 'refs/remotes/origin/main\n';
|
|
692
|
+
if (cmdStr.includes('which claude')) throw new Error('not found');
|
|
693
|
+
if (cmdStr.includes('gh pr create')) return 'https://github.com/user/repo/pull/1\n';
|
|
694
|
+
return '';
|
|
695
|
+
});
|
|
696
|
+
|
|
697
|
+
mockExistsSync.mockReturnValue(false);
|
|
698
|
+
|
|
699
|
+
await createPullRequest('my-branch', '/path/to/RAF/acbfhg-project');
|
|
700
|
+
|
|
701
|
+
expect(mockUnlinkSync).toHaveBeenCalledWith(
|
|
702
|
+
expect.stringContaining('/tmp/raf-pr-body-'),
|
|
703
|
+
);
|
|
704
|
+
});
|
|
705
|
+
|
|
706
|
+
it('should clean up temp file even on PR creation failure', async () => {
|
|
707
|
+
mockExecSync.mockImplementation((cmd: unknown) => {
|
|
708
|
+
const cmdStr = cmd as string;
|
|
709
|
+
if (cmdStr.includes('gh --version')) return 'gh version 2.40.0';
|
|
710
|
+
if (cmdStr.includes('gh auth status')) return 'Logged in';
|
|
711
|
+
if (cmdStr.includes('git remote get-url')) return 'git@github.com:user/repo.git';
|
|
712
|
+
if (cmdStr.includes('git ls-remote')) return 'abc123\trefs/heads/my-branch';
|
|
713
|
+
if (cmdStr.includes('symbolic-ref')) return 'refs/remotes/origin/main\n';
|
|
714
|
+
if (cmdStr.includes('which claude')) throw new Error('not found');
|
|
715
|
+
if (cmdStr.includes('gh pr create')) throw new Error('PR already exists');
|
|
716
|
+
return '';
|
|
717
|
+
});
|
|
718
|
+
|
|
719
|
+
mockExistsSync.mockReturnValue(false);
|
|
720
|
+
|
|
721
|
+
await createPullRequest('my-branch', '/path/to/RAF/acbfhg-project');
|
|
722
|
+
|
|
723
|
+
expect(mockUnlinkSync).toHaveBeenCalledWith(
|
|
724
|
+
expect.stringContaining('/tmp/raf-pr-body-'),
|
|
725
|
+
);
|
|
726
|
+
});
|
|
727
|
+
|
|
728
|
+
it('should create PR successfully with all checks passing', async () => {
|
|
729
|
+
mockExecSync.mockImplementation((cmd: unknown) => {
|
|
730
|
+
const cmdStr = cmd as string;
|
|
731
|
+
if (cmdStr.includes('gh --version')) return 'gh version 2.40.0';
|
|
732
|
+
if (cmdStr.includes('gh auth status')) return 'Logged in';
|
|
733
|
+
if (cmdStr.includes('git remote get-url')) return 'git@github.com:user/repo.git';
|
|
734
|
+
if (cmdStr.includes('git ls-remote')) return 'abc123\trefs/heads/my-branch'; // already pushed
|
|
735
|
+
if (cmdStr.includes('symbolic-ref')) return 'refs/remotes/origin/main\n';
|
|
736
|
+
if (cmdStr.includes('which claude')) throw new Error('not found');
|
|
737
|
+
if (cmdStr.includes('gh pr create')) return 'https://github.com/user/repo/pull/42\n';
|
|
738
|
+
return '';
|
|
739
|
+
});
|
|
740
|
+
|
|
741
|
+
// Mock fs for generatePrBody fallback
|
|
742
|
+
mockExistsSync.mockReturnValue(false);
|
|
743
|
+
|
|
744
|
+
const result = await createPullRequest('my-branch', '/path/to/RAF/acbfhg-project');
|
|
745
|
+
|
|
746
|
+
expect(result.success).toBe(true);
|
|
747
|
+
expect(result.prUrl).toBe('https://github.com/user/repo/pull/42');
|
|
748
|
+
});
|
|
749
|
+
|
|
750
|
+
it('should use explicit base branch when provided', async () => {
|
|
751
|
+
let ghPrCmd = '';
|
|
752
|
+
mockExecSync.mockImplementation((cmd: unknown) => {
|
|
753
|
+
const cmdStr = cmd as string;
|
|
754
|
+
if (cmdStr.includes('gh --version')) return 'gh version 2.40.0';
|
|
755
|
+
if (cmdStr.includes('gh auth status')) return 'Logged in';
|
|
756
|
+
if (cmdStr.includes('git remote get-url')) return 'git@github.com:user/repo.git';
|
|
757
|
+
if (cmdStr.includes('git ls-remote')) return 'abc123\trefs/heads/my-branch';
|
|
758
|
+
if (cmdStr.includes('which claude')) throw new Error('not found');
|
|
759
|
+
if (cmdStr.includes('gh pr create')) {
|
|
760
|
+
ghPrCmd = cmdStr;
|
|
761
|
+
return 'https://github.com/user/repo/pull/1\n';
|
|
762
|
+
}
|
|
763
|
+
return '';
|
|
764
|
+
});
|
|
765
|
+
|
|
766
|
+
mockExistsSync.mockReturnValue(false);
|
|
767
|
+
|
|
768
|
+
await createPullRequest('my-branch', '/path/to/RAF/acbfhg-project', {
|
|
769
|
+
baseBranch: 'develop',
|
|
770
|
+
});
|
|
771
|
+
|
|
772
|
+
expect(ghPrCmd).toContain('--base "develop"');
|
|
773
|
+
});
|
|
774
|
+
|
|
775
|
+
it('should use custom title when provided', async () => {
|
|
776
|
+
let ghPrCmd = '';
|
|
777
|
+
mockExecSync.mockImplementation((cmd: unknown) => {
|
|
778
|
+
const cmdStr = cmd as string;
|
|
779
|
+
if (cmdStr.includes('gh --version')) return 'gh version 2.40.0';
|
|
780
|
+
if (cmdStr.includes('gh auth status')) return 'Logged in';
|
|
781
|
+
if (cmdStr.includes('git remote get-url')) return 'git@github.com:user/repo.git';
|
|
782
|
+
if (cmdStr.includes('git ls-remote')) return 'abc123\trefs/heads/my-branch';
|
|
783
|
+
if (cmdStr.includes('symbolic-ref')) return 'refs/remotes/origin/main\n';
|
|
784
|
+
if (cmdStr.includes('which claude')) throw new Error('not found');
|
|
785
|
+
if (cmdStr.includes('gh pr create')) {
|
|
786
|
+
ghPrCmd = cmdStr;
|
|
787
|
+
return 'https://github.com/user/repo/pull/1\n';
|
|
788
|
+
}
|
|
789
|
+
return '';
|
|
790
|
+
});
|
|
791
|
+
|
|
792
|
+
mockExistsSync.mockReturnValue(false);
|
|
793
|
+
|
|
794
|
+
await createPullRequest('my-branch', '/path/to/RAF/acbfhg-project', {
|
|
795
|
+
title: 'My custom PR title',
|
|
796
|
+
});
|
|
797
|
+
|
|
798
|
+
expect(ghPrCmd).toContain('--title "My custom PR title"');
|
|
799
|
+
});
|
|
800
|
+
|
|
801
|
+
it('should handle gh pr create failure', async () => {
|
|
802
|
+
mockExecSync.mockImplementation((cmd: unknown) => {
|
|
803
|
+
const cmdStr = cmd as string;
|
|
804
|
+
if (cmdStr.includes('gh --version')) return 'gh version 2.40.0';
|
|
805
|
+
if (cmdStr.includes('gh auth status')) return 'Logged in';
|
|
806
|
+
if (cmdStr.includes('git remote get-url')) return 'git@github.com:user/repo.git';
|
|
807
|
+
if (cmdStr.includes('git ls-remote')) return 'abc123\trefs/heads/my-branch';
|
|
808
|
+
if (cmdStr.includes('symbolic-ref')) return 'refs/remotes/origin/main\n';
|
|
809
|
+
if (cmdStr.includes('which claude')) throw new Error('not found');
|
|
810
|
+
if (cmdStr.includes('gh pr create')) throw new Error('a]pull request already exists');
|
|
811
|
+
return '';
|
|
812
|
+
});
|
|
813
|
+
|
|
814
|
+
mockExistsSync.mockReturnValue(false);
|
|
815
|
+
|
|
816
|
+
const result = await createPullRequest('my-branch', '/path/to/RAF/acbfhg-project');
|
|
817
|
+
|
|
818
|
+
expect(result.success).toBe(false);
|
|
819
|
+
expect(result.error).toContain('Failed to create PR');
|
|
820
|
+
});
|
|
821
|
+
|
|
822
|
+
it('should pass cwd to git commands', async () => {
|
|
823
|
+
const cwdCalls: string[] = [];
|
|
824
|
+
mockExecSync.mockImplementation((cmd: unknown, opts: unknown) => {
|
|
825
|
+
const cmdStr = cmd as string;
|
|
826
|
+
const options = opts as { cwd?: string } | undefined;
|
|
827
|
+
if (options?.cwd) {
|
|
828
|
+
cwdCalls.push(cmdStr);
|
|
829
|
+
}
|
|
830
|
+
if (cmdStr.includes('gh --version')) return 'gh version 2.40.0';
|
|
831
|
+
if (cmdStr.includes('gh auth status')) return 'Logged in';
|
|
832
|
+
if (cmdStr.includes('git remote get-url')) return 'git@github.com:user/repo.git';
|
|
833
|
+
if (cmdStr.includes('git ls-remote')) return 'abc123\trefs/heads/my-branch';
|
|
834
|
+
if (cmdStr.includes('symbolic-ref')) return 'refs/remotes/origin/main\n';
|
|
835
|
+
if (cmdStr.includes('which claude')) throw new Error('not found');
|
|
836
|
+
if (cmdStr.includes('gh pr create')) return 'https://github.com/user/repo/pull/1\n';
|
|
837
|
+
return '';
|
|
838
|
+
});
|
|
839
|
+
|
|
840
|
+
mockExistsSync.mockReturnValue(false);
|
|
841
|
+
|
|
842
|
+
await createPullRequest('my-branch', '/path/to/RAF/acbfhg-project', {
|
|
843
|
+
cwd: '/worktree/path',
|
|
844
|
+
});
|
|
845
|
+
|
|
846
|
+
// Git commands that accept cwd should have received it
|
|
847
|
+
expect(cwdCalls.some(c => c.includes('git remote get-url'))).toBe(true);
|
|
848
|
+
expect(cwdCalls.some(c => c.includes('git ls-remote'))).toBe(true);
|
|
849
|
+
expect(cwdCalls.some(c => c.includes('gh pr create'))).toBe(true);
|
|
850
|
+
});
|
|
851
|
+
});
|
|
852
|
+
});
|