wade-cli 0.35.0__tar.gz → 0.35.3__tar.gz
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.
- {wade_cli-0.35.0 → wade_cli-0.35.3}/CHANGELOG.md +12 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/KNOWLEDGE.md +6 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/KNOWLEDGE.ratings.yml +5 -1
- {wade_cli-0.35.0 → wade_cli-0.35.3}/PKG-INFO +2 -2
- {wade_cli-0.35.0 → wade_cli-0.35.3}/docs/dev/architecture.md +1 -1
- {wade_cli-0.35.0 → wade_cli-0.35.3}/pyproject.toml +2 -2
- {wade_cli-0.35.0 → wade_cli-0.35.3}/scripts/changelog.py +3 -2
- {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/__init__.py +1 -1
- {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/git/repo.py +16 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/services/implementation_service/lifecycle.py +28 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/unit/test_git/test_repo.py +30 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/unit/test_services/test_post_implementation_lifecycle.py +208 -1
- {wade_cli-0.35.0 → wade_cli-0.35.3}/.github/workflows/auto-version.yml +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/.github/workflows/ci.yml +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/.github/workflows/pr-title-lint.yml +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/.github/workflows/publish.yml +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/.github/workflows/release.yml +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/.gitignore +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/.pre-commit-config.yaml +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/.wade.yml +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/AGENTS.md +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/CONTRIBUTING.md +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/README.md +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/assets/wade.png +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/docker-compose.e2e.yml +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/docs/dev/documentation-policies.md +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/docs/dev/extending.md +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/docs/dev/skills-system.md +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/docs/dev/testing.md +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/install.sh +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/scripts/auto_version.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/scripts/check-all.sh +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/scripts/check.sh +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/scripts/fmt.sh +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/scripts/hooks/pre-push +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/scripts/install-hooks.sh +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/scripts/release.sh +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/scripts/setup-worktree.sh +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/scripts/test-e2e-docker.sh +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/scripts/test-e2e.sh +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/scripts/test-live-ai-taskr.sh +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/scripts/test-live-ai.sh +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/scripts/test-live-gh.sh +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/scripts/test.sh +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/__main__.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/cli/__init__.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/cli/admin.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/cli/autocomplete.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/cli/implementation_session.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/cli/knowledge.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/cli/main.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/cli/plan_session.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/cli/review.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/cli/review_pr_comments_session.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/cli/session_shared.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/cli/task.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/cli/worktree.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/config/__init__.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/config/loader.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/config/migrations.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/config/schema.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/db/__init__.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/db/engine.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/db/repositories.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/db/tables.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/git/__init__.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/git/branch.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/git/pr.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/git/stash.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/git/sync.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/git/worktree.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/hooks/__init__.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/hooks/plan_write_guard.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/hooks/worktree_guard.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/logging/__init__.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/logging/context.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/logging/setup.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/models/__init__.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/models/batch.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/models/config.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/models/delegation.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/models/deps.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/models/events.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/models/permission.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/models/review.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/models/session.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/models/task.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/providers/__init__.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/providers/_pr_delegate.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/providers/base.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/providers/clickup.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/providers/github.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/providers/markdown.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/providers/registry.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/services/__init__.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/services/ai_resolution.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/services/batch_review_service.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/services/check_service.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/services/delegation_service.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/services/deps_service.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/services/implementation_service/__init__.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/services/implementation_service/_shared.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/services/implementation_service/batch.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/services/implementation_service/bootstrap.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/services/implementation_service/cleanup.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/services/implementation_service/core.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/services/implementation_service/done.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/services/implementation_service/draft_pr.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/services/implementation_service/sync.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/services/implementation_service/usage_tracking.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/services/init_service/__init__.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/services/init_service/auth.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/services/init_service/commands.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/services/init_service/config_io.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/services/init_service/manifest.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/services/init_service/migrations.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/services/init_service/prompts_ai.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/services/init_service/prompts_setup.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/services/init_service/shell.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/services/knowledge_search.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/services/knowledge_service.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/services/plan_service.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/services/prompt_delivery.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/services/review_delegation_service.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/services/review_service.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/services/review_settle.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/services/smart_start.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/services/task_service.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/skills/__init__.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/skills/installer.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/skills/pointer.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/ui/__init__.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/ui/console.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/ui/prompts.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/utils/__init__.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/utils/clipboard.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/utils/http.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/utils/install.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/utils/markdown.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/utils/process.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/utils/slug.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/utils/terminal.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/utils/token_usage_markdown.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/utils/update_check.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/templates/agents-pointer.md +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/templates/prompts/deps-analysis.md +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/templates/prompts/implement-context.md +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/templates/prompts/plan-session.md +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/templates/prompts/review-batch.md +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/templates/prompts/review-code.md +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/templates/prompts/review-plan.md +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/templates/prompts/review-pr-comments.md +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/templates/setup-worktree.sh.example +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/templates/skills/_partials/review-enforcement-rule.md +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/templates/skills/_partials/review-implementation-closing-step.md +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/templates/skills/_partials/review-plan-step.md +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/templates/skills/_partials/user-interaction.md +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/templates/skills/deps/SKILL.md +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/templates/skills/implementation-session/SKILL.md +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/templates/skills/knowledge/SKILL.md +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/templates/skills/plan-session/SKILL.md +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/templates/skills/review-pr-comments-session/SKILL.md +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/templates/skills/task/SKILL.md +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/templates/skills/task/examples.md +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/templates/skills/task/plan-format.md +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/templates/statusline-command.sh +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/__init__.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/conftest.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/e2e/__init__.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/e2e/_support.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/e2e/conftest.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/e2e/mock_gh_script.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/e2e/test_admin_contract.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/e2e/test_check_contract.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/e2e/test_implement_work_done_contract.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/e2e/test_knowledge_contract.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/e2e/test_mock_gh_contract.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/e2e/test_plan_contract.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/e2e/test_review_contract.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/e2e/test_smart_start_contract.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/e2e/test_task_contract.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/e2e/test_work_sync_list_contract.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/fixtures/live/taskr_header_hi_body.md +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/fixtures/live/taskr_header_howdy_body.md +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/fixtures/transcripts/claude_session.txt +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/fixtures/transcripts/codex_session.txt +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/fixtures/transcripts/copilot_session.txt +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/fixtures/transcripts/generic_session.txt +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/integration/__init__.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/integration/test_autostash.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/integration/test_check.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/integration/test_git.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/integration/test_implementation_lifecycle.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/integration/test_init.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/integration/test_knowledge.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/integration/test_markdown_provider.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/integration/test_skill_install.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/live/__init__.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/live/test_wade_live_ai.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/live/test_wade_live_ai_taskr.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/live/test_wade_live_gh.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/test_cli_basics.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/unit/__init__.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/unit/conftest.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/unit/test_cli/__init__.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/unit/test_cli/test_done_review_hint.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/unit/test_cli/test_knowledge_cli.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/unit/test_cli/test_review_cli.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/unit/test_cli/test_session_subapps.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/unit/test_cli/test_shell_init.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/unit/test_cli/test_task_cli.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/unit/test_cli/test_work_ai_flags.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/unit/test_cli/test_work_cli.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/unit/test_config/__init__.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/unit/test_config/test_loader.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/unit/test_config/test_migrations.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/unit/test_config/test_migrations_atomic.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/unit/test_console.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/unit/test_db/__init__.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/unit/test_db/test_repository_transactions.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/unit/test_effort.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/unit/test_git/__init__.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/unit/test_git/test_branch_slugify.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/unit/test_git/test_detached_worktree.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/unit/test_git/test_pr_base.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/unit/test_git/test_scaffold_commit.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/unit/test_git/test_sync_conflicted_files.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/unit/test_git/test_worktree_remove.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/unit/test_hooks/__init__.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/unit/test_hooks/test_plan_write_guard.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/unit/test_hooks/test_worktree_guard.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/unit/test_models/__init__.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/unit/test_models/test_batch.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/unit/test_models/test_config_models.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/unit/test_models/test_db.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/unit/test_models/test_delegation.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/unit/test_models/test_deps_models.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/unit/test_models/test_deps_partition.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/unit/test_models/test_review.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/unit/test_models/test_task_models.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/unit/test_permission_mode.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/unit/test_providers/__init__.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/unit/test_providers/test_clickup.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/unit/test_providers/test_github.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/unit/test_providers/test_github_pr.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/unit/test_providers/test_markdown.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/unit/test_services/__init__.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/unit/test_services/test_ai_resolution.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/unit/test_services/test_batch_review_service.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/unit/test_services/test_bootstrap_allowlist.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/unit/test_services/test_bootstrap_hook_timeout.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/unit/test_services/test_catchup.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/unit/test_services/test_check.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/unit/test_services/test_delegation_service.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/unit/test_services/test_deps_service.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/unit/test_services/test_done_plan_flag.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/unit/test_services/test_done_via_direct_cleanup.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/unit/test_services/test_implementation_done_sync.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/unit/test_services/test_implementation_service.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/unit/test_services/test_implementation_service_resume.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/unit/test_services/test_init.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/unit/test_services/test_init_shell_integration.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/unit/test_services/test_knowledge_search.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/unit/test_services/test_knowledge_service.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/unit/test_services/test_plan_service.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/unit/test_services/test_poll_for_reviews.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/unit/test_services/test_pr_summary_path.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/unit/test_services/test_preserve_session_data.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/unit/test_services/test_prompt_delivery.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/unit/test_services/test_review_delegation_service.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/unit/test_services/test_review_service.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/unit/test_services/test_smart_start.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/unit/test_services/test_smart_start_resume.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/unit/test_services/test_sync_base_branch.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/unit/test_services/test_task_service.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/unit/test_services/test_tracked_managed_files.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/unit/test_services/test_worktree_gitignore.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/unit/test_skills/test_managed_gitignore.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/unit/test_utils/__init__.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/unit/test_utils/test_http.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/unit/test_utils/test_install.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/unit/test_utils/test_markdown.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/unit/test_utils/test_process.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/unit/test_utils/test_prompts.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/unit/test_utils/test_slug.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/unit/test_utils/test_terminal.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/unit/test_utils/test_terminal_launch.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/unit/test_utils/test_update_check.py +0 -0
- {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/unit/test_yolo.py +0 -0
|
@@ -4,6 +4,18 @@ All notable changes to this project will be documented in this file.
|
|
|
4
4
|
|
|
5
5
|
The format is based on [Conventional Commits](https://conventionalcommits.org/).
|
|
6
6
|
|
|
7
|
+
## [v0.35.3] — 2026-07-25
|
|
8
|
+
|
|
9
|
+
### Chores
|
|
10
|
+
|
|
11
|
+
- bump crossby to v0.11.0 and adopt newer models (Opus 5) (#345) (d9ac8a5)
|
|
12
|
+
|
|
13
|
+
## [v0.35.1] — 2026-07-24
|
|
14
|
+
|
|
15
|
+
### Bug Fixes
|
|
16
|
+
|
|
17
|
+
- PR merge fails with 'could not determine current branch' when repo_root HEAD is detached (#334) (7c79e88)
|
|
18
|
+
|
|
7
19
|
## [v0.35.0] — 2026-07-24
|
|
8
20
|
|
|
9
21
|
### Features
|
|
@@ -231,3 +231,9 @@ wade never branches on crossby's AIToolType/tool_type (only re-exported in wade.
|
|
|
231
231
|
To unit-test a crossby GUI adapter launch command (Antigravity IDE, VS Code), patch `crossby.ai_tools.<module>.run_with_transcript` (e.g. crossby.ai_tools.antigravity.run_with_transcript) and read call_args[0][0] — GUI adapters build the cmd and call crossby's run_with_transcript directly, so patching wade.utils.process/subprocess never fires. The Antigravity IDE cmd is exactly ["antigravity", str(working_dir)] (explicit dir, not ".").
|
|
232
232
|
|
|
233
233
|
---
|
|
234
|
+
|
|
235
|
+
## ae5df2be | 2026-07-24 | plan | tags: crossby, ai-tools, model-registry, dependencies
|
|
236
|
+
|
|
237
|
+
To add or refresh supported AI models in wade (e.g. a new Opus), bump the crossby dependency — there is no wade-side model list to edit. The model registry (crossby.data.MODELS / get_models_for_tool), the complexity-tier defaults used by `wade init` (crossby.config.defaults.get_defaults), and EffortLevel all live in crossby; wade's _resolve_models and _suggest_model_for_tool (init_service/config_io.py, prompts_ai.py), ai_resolution.py, and cli/autocomplete.py are thin pass-throughs. Because the pyproject pin uses an exclusive upper bound (e.g. crossby<0.11.0), each new crossby minor needs an explicit pin bump plus a verification pass across those four import sites.
|
|
238
|
+
|
|
239
|
+
---
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: wade-cli
|
|
3
|
-
Version: 0.35.
|
|
3
|
+
Version: 0.35.3
|
|
4
4
|
Summary: WADE — Workflow for AI-Driven Engineering
|
|
5
5
|
Project-URL: Homepage, https://github.com/ivanviragine/wade
|
|
6
6
|
Project-URL: Repository, https://github.com/ivanviragine/wade
|
|
@@ -16,7 +16,7 @@ Classifier: Programming Language :: Python :: 3.12
|
|
|
16
16
|
Classifier: Programming Language :: Python :: 3.13
|
|
17
17
|
Classifier: Topic :: Software Development :: Version Control :: Git
|
|
18
18
|
Requires-Python: >=3.11
|
|
19
|
-
Requires-Dist: crossby<0.
|
|
19
|
+
Requires-Dist: crossby<0.12.0,>=0.11.0
|
|
20
20
|
Requires-Dist: httpx<1.0,>=0.27
|
|
21
21
|
Requires-Dist: pydantic-settings>=2.0
|
|
22
22
|
Requires-Dist: pydantic>=2.0
|
|
@@ -127,7 +127,7 @@ src/wade/
|
|
|
127
127
|
|
|
128
128
|
## AI Tool Layer (external: crossby)
|
|
129
129
|
|
|
130
|
-
AI tool adapters, model/effort resolution primitives, the model registry, and per-tool config (allowlists, hooks, defaults) are **not** part of this repo. They live in the external [`crossby`](https://github.com/ivanviragine/crossby) package, pinned in `pyproject.toml` (`crossby>=0.
|
|
130
|
+
AI tool adapters, model/effort resolution primitives, the model registry, and per-tool config (allowlists, hooks, defaults) are **not** part of this repo. They live in the external [`crossby`](https://github.com/ivanviragine/crossby) package, pinned in `pyproject.toml` (`crossby>=0.11.0,<0.12.0`). This replaced wade's formerly-internal `ai_tools/` and parts of `config/` and `data/` (see `feat: replace internal AI tool layer with the crossby dependency (#215)`).
|
|
131
131
|
|
|
132
132
|
| What | Lives in crossby | Used from wade via |
|
|
133
133
|
|------|-------------------|---------------------|
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "wade-cli"
|
|
7
|
-
version = "0.35.
|
|
7
|
+
version = "0.35.3"
|
|
8
8
|
description = "WADE — Workflow for AI-Driven Engineering"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = "MIT"
|
|
@@ -24,7 +24,7 @@ classifiers = [
|
|
|
24
24
|
"Topic :: Software Development :: Version Control :: Git",
|
|
25
25
|
]
|
|
26
26
|
dependencies = [
|
|
27
|
-
"crossby>=0.
|
|
27
|
+
"crossby>=0.11.0,<0.12.0",
|
|
28
28
|
"typer>=0.12,<0.26",
|
|
29
29
|
"pydantic>=2.0",
|
|
30
30
|
"pydantic-settings>=2.0",
|
|
@@ -66,7 +66,8 @@ def get_tags() -> list[str]:
|
|
|
66
66
|
def get_commits(commit_range: str) -> list[tuple[str, str]]:
|
|
67
67
|
"""Get commits in a range as (subject, hash) pairs.
|
|
68
68
|
|
|
69
|
-
Filters out version-bump noise
|
|
69
|
+
Filters out version-bump and branch-scaffold noise — both are
|
|
70
|
+
wade-generated commits that carry no user-facing release content.
|
|
70
71
|
"""
|
|
71
72
|
raw = git("log", commit_range, "--pretty=format:%s|%h", "--no-merges")
|
|
72
73
|
if not raw:
|
|
@@ -76,7 +77,7 @@ def get_commits(commit_range: str) -> list[tuple[str, str]]:
|
|
|
76
77
|
if "|" not in line:
|
|
77
78
|
continue
|
|
78
79
|
subject, sha = line.rsplit("|", 1)
|
|
79
|
-
if subject.startswith("chore: bump version"):
|
|
80
|
+
if subject.startswith(("chore: bump version", "chore: scaffold branch")):
|
|
80
81
|
continue
|
|
81
82
|
commits.append((subject.strip(), sha.strip()))
|
|
82
83
|
return commits
|
|
@@ -186,6 +186,22 @@ def get_current_branch(path: Path) -> str:
|
|
|
186
186
|
return branch
|
|
187
187
|
|
|
188
188
|
|
|
189
|
+
def is_head_attached(path: Path) -> bool:
|
|
190
|
+
"""Check whether HEAD is attached to a branch (i.e. not detached).
|
|
191
|
+
|
|
192
|
+
Uses ``git symbolic-ref -q HEAD``: returncode 0 means HEAD points at a
|
|
193
|
+
branch ref (attached); non-zero means a detached HEAD (or not a repo).
|
|
194
|
+
|
|
195
|
+
Args:
|
|
196
|
+
path: Any directory inside the repo.
|
|
197
|
+
|
|
198
|
+
Returns:
|
|
199
|
+
True if HEAD is on a branch, False if detached or on failure.
|
|
200
|
+
"""
|
|
201
|
+
result = _run_git("symbolic-ref", "-q", "HEAD", cwd=path, check=False)
|
|
202
|
+
return result.returncode == 0
|
|
203
|
+
|
|
204
|
+
|
|
189
205
|
def detect_main_branch(path: Path) -> str:
|
|
190
206
|
"""Detect the main branch name — checks for 'main' then 'master'.
|
|
191
207
|
|
|
@@ -258,6 +258,34 @@ def _merge_pr(
|
|
|
258
258
|
if not prompts.confirm("Proceed anyway? Uncommitted work will be lost.", default=False):
|
|
259
259
|
return MergeStatus.NOT_MERGED
|
|
260
260
|
|
|
261
|
+
# Guard repo_root's HEAD state BEFORE the irreversible merge. `gh pr merge
|
|
262
|
+
# --delete-branch` runs with cwd=repo_root (the main checkout, a *different*
|
|
263
|
+
# directory from the worktree) and resolves repo_root's current branch during
|
|
264
|
+
# its post-merge --delete-branch bookkeeping. If repo_root has a detached HEAD
|
|
265
|
+
# — e.g. from unrelated manual git activity — gh aborts with "could not
|
|
266
|
+
# determine current branch", but only AFTER it has already squash-merged the
|
|
267
|
+
# PR on GitHub, leaving a "merged-on-GitHub-but-reported-failed" state.
|
|
268
|
+
# Re-attaching to the default branch here (mirroring the post-merge
|
|
269
|
+
# `git pull` that already targets it) lets us fail fast if we cannot proceed
|
|
270
|
+
# safely, before any GitHub-side merge happens.
|
|
271
|
+
if not git_repo.is_head_attached(repo_root):
|
|
272
|
+
try:
|
|
273
|
+
default_branch = git_repo.detect_main_branch(repo_root)
|
|
274
|
+
git_repo.checkout(repo_root, default_branch)
|
|
275
|
+
except GitError as e:
|
|
276
|
+
logger.error("repo_root.reattach_failed", error=str(e))
|
|
277
|
+
console.error(
|
|
278
|
+
f"Cannot merge: '{repo_root.name}' has a detached HEAD and could "
|
|
279
|
+
f"not be re-attached to a branch ({e})."
|
|
280
|
+
)
|
|
281
|
+
console.hint(
|
|
282
|
+
f"Check out a branch in {repo_root} "
|
|
283
|
+
f"(e.g. `git -C {repo_root} checkout main`), then retry the merge."
|
|
284
|
+
)
|
|
285
|
+
return MergeStatus.MERGE_FAILED
|
|
286
|
+
console.step(f"Re-attached {repo_root.name} to '{default_branch}' (was detached).")
|
|
287
|
+
logger.info("repo_root.reattached", branch=default_branch)
|
|
288
|
+
|
|
261
289
|
# Detach HEAD in the worktree so git no longer considers the branch
|
|
262
290
|
# "checked out", which unblocks `gh pr merge --delete-branch`.
|
|
263
291
|
if worktree_path and worktree_path.is_dir():
|
|
@@ -12,6 +12,7 @@ from wade.git.repo import (
|
|
|
12
12
|
_run_git_with_retry,
|
|
13
13
|
diff_between,
|
|
14
14
|
get_main_worktree_path,
|
|
15
|
+
is_head_attached,
|
|
15
16
|
stash,
|
|
16
17
|
stash_pop,
|
|
17
18
|
)
|
|
@@ -155,3 +156,32 @@ class TestGetMainWorktreePath:
|
|
|
155
156
|
mock_run.return_value.stdout = "HEAD abc123\nbranch refs/heads/main\n"
|
|
156
157
|
result = get_main_worktree_path(tmp_path)
|
|
157
158
|
assert result is None
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
class TestIsHeadAttached:
|
|
162
|
+
"""Tests for is_head_attached against real temp repos."""
|
|
163
|
+
|
|
164
|
+
def test_returns_true_on_attached_branch(self, tmp_git_repo: Path) -> None:
|
|
165
|
+
# tmp_git_repo has an initial commit and HEAD on a branch.
|
|
166
|
+
assert is_head_attached(tmp_git_repo) is True
|
|
167
|
+
|
|
168
|
+
def test_returns_false_on_detached_head(self, tmp_git_repo: Path) -> None:
|
|
169
|
+
import subprocess
|
|
170
|
+
|
|
171
|
+
subprocess.run(
|
|
172
|
+
["git", "checkout", "--detach", "HEAD"],
|
|
173
|
+
cwd=tmp_git_repo,
|
|
174
|
+
capture_output=True,
|
|
175
|
+
check=True,
|
|
176
|
+
)
|
|
177
|
+
assert is_head_attached(tmp_git_repo) is False
|
|
178
|
+
|
|
179
|
+
def test_returns_false_outside_git_repo(self, tmp_path: Path) -> None:
|
|
180
|
+
# A real directory with no repo (git symbolic-ref exits non-zero) and a
|
|
181
|
+
# non-existent path (git invocation raises FileNotFoundError, which
|
|
182
|
+
# _run_git turns into a synthetic non-zero result) both resolve to
|
|
183
|
+
# "not attached" rather than raising.
|
|
184
|
+
non_git = tmp_path / "plain"
|
|
185
|
+
non_git.mkdir()
|
|
186
|
+
assert is_head_attached(non_git) is False
|
|
187
|
+
assert is_head_attached(tmp_path / "does-not-exist") is False
|
{wade_cli-0.35.0 → wade_cli-0.35.3}/tests/unit/test_services/test_post_implementation_lifecycle.py
RENAMED
|
@@ -4,14 +4,17 @@ import subprocess
|
|
|
4
4
|
from pathlib import Path
|
|
5
5
|
from unittest.mock import MagicMock, patch
|
|
6
6
|
|
|
7
|
+
from wade.git.repo import GitError
|
|
7
8
|
from wade.models.config import AIConfig, ProjectConfig, ProjectSettings
|
|
8
|
-
from wade.models.session import MergeStrategy
|
|
9
|
+
from wade.models.session import MergeStatus, MergeStrategy
|
|
9
10
|
from wade.models.task import Task
|
|
10
11
|
from wade.services.implementation_service import _post_implementation_lifecycle, start
|
|
11
12
|
|
|
12
13
|
_PULL_FF = "wade.services.implementation_service.lifecycle.git_repo.pull_ff_only"
|
|
13
14
|
_CHECKOUT = "wade.services.implementation_service.lifecycle.git_repo.checkout"
|
|
14
15
|
_CHECKOUT_DETACH = "wade.services.implementation_service.lifecycle.git_repo.checkout_detach"
|
|
16
|
+
_IS_HEAD_ATTACHED = "wade.services.implementation_service.lifecycle.git_repo.is_head_attached"
|
|
17
|
+
_DETECT_MAIN = "wade.services.implementation_service.lifecycle.git_repo.detect_main_branch"
|
|
15
18
|
_MERGE_SQUASH = "wade.services.implementation_service.lifecycle.git_repo.merge_squash"
|
|
16
19
|
_COMMIT_NO_EDIT = "wade.services.implementation_service.lifecycle.git_repo.commit_no_edit"
|
|
17
20
|
_PUSH = "wade.services.implementation_service.lifecycle.git_repo.push"
|
|
@@ -24,6 +27,7 @@ def _config(strategy: MergeStrategy) -> ProjectConfig:
|
|
|
24
27
|
)
|
|
25
28
|
|
|
26
29
|
|
|
30
|
+
@patch(_IS_HEAD_ATTACHED, return_value=True)
|
|
27
31
|
@patch(_PULL_FF)
|
|
28
32
|
@patch(_CHECKOUT_DETACH)
|
|
29
33
|
@patch("wade.services.implementation_service.lifecycle.git_worktree.prune_worktrees")
|
|
@@ -48,6 +52,7 @@ def test_pr_strategy_prompts_merge_on_existing_pr(
|
|
|
48
52
|
_mock_prune: MagicMock,
|
|
49
53
|
_mock_checkout_detach: MagicMock,
|
|
50
54
|
mock_pull_ff: MagicMock,
|
|
55
|
+
_mock_is_head_attached: MagicMock,
|
|
51
56
|
tmp_path: Path,
|
|
52
57
|
) -> None:
|
|
53
58
|
mock_pull_ff.return_value = MagicMock(returncode=0)
|
|
@@ -130,6 +135,7 @@ def test_pr_strategy_user_declines_merge(
|
|
|
130
135
|
mock_poll.assert_called_once()
|
|
131
136
|
|
|
132
137
|
|
|
138
|
+
@patch(_IS_HEAD_ATTACHED, return_value=True)
|
|
133
139
|
@patch(_CHECKOUT)
|
|
134
140
|
@patch("wade.services.implementation_service.lifecycle.git_pr.merge_pr")
|
|
135
141
|
@patch("wade.services.implementation_service.lifecycle.prompts.select", return_value=0)
|
|
@@ -146,6 +152,7 @@ def test_pr_strategy_merge_failure_preserves_branch(
|
|
|
146
152
|
_mock_select: MagicMock,
|
|
147
153
|
mock_merge_pr: MagicMock,
|
|
148
154
|
_mock_checkout: MagicMock,
|
|
155
|
+
_mock_is_head_attached: MagicMock,
|
|
149
156
|
tmp_path: Path,
|
|
150
157
|
) -> None:
|
|
151
158
|
provider = MagicMock()
|
|
@@ -165,6 +172,7 @@ def test_pr_strategy_merge_failure_preserves_branch(
|
|
|
165
172
|
mock_merge_pr.assert_called_once()
|
|
166
173
|
|
|
167
174
|
|
|
175
|
+
@patch(_IS_HEAD_ATTACHED, return_value=True)
|
|
168
176
|
@patch(_CHECKOUT)
|
|
169
177
|
@patch(_CHECKOUT_DETACH)
|
|
170
178
|
@patch("wade.services.implementation_service.lifecycle.git_pr.merge_pr")
|
|
@@ -185,6 +193,7 @@ def test_pr_strategy_merge_failure_restores_branch(
|
|
|
185
193
|
mock_merge_pr: MagicMock,
|
|
186
194
|
_mock_checkout_detach: MagicMock,
|
|
187
195
|
mock_checkout: MagicMock,
|
|
196
|
+
_mock_is_head_attached: MagicMock,
|
|
188
197
|
tmp_path: Path,
|
|
189
198
|
) -> None:
|
|
190
199
|
"""On merge failure, HEAD should be restored from detached state to the branch."""
|
|
@@ -207,6 +216,7 @@ def test_pr_strategy_merge_failure_restores_branch(
|
|
|
207
216
|
mock_checkout.assert_called_once_with(wt_path, "feat/42-test")
|
|
208
217
|
|
|
209
218
|
|
|
219
|
+
@patch(_IS_HEAD_ATTACHED, return_value=True)
|
|
210
220
|
@patch(_PULL_FF)
|
|
211
221
|
@patch(_CHECKOUT_DETACH)
|
|
212
222
|
@patch("wade.services.implementation_service.lifecycle.git_worktree.prune_worktrees")
|
|
@@ -231,6 +241,7 @@ def test_pr_strategy_cleanup_and_pull_after_merge(
|
|
|
231
241
|
_mock_prune: MagicMock,
|
|
232
242
|
_mock_checkout_detach: MagicMock,
|
|
233
243
|
mock_pull_ff: MagicMock,
|
|
244
|
+
_mock_is_head_attached: MagicMock,
|
|
234
245
|
tmp_path: Path,
|
|
235
246
|
) -> None:
|
|
236
247
|
mock_pull_ff.return_value = MagicMock(returncode=0)
|
|
@@ -249,6 +260,202 @@ def test_pr_strategy_cleanup_and_pull_after_merge(
|
|
|
249
260
|
mock_pull_ff.assert_called_once_with(repo_root)
|
|
250
261
|
|
|
251
262
|
|
|
263
|
+
@patch(_PULL_FF)
|
|
264
|
+
@patch(_CHECKOUT_DETACH)
|
|
265
|
+
@patch(_CHECKOUT)
|
|
266
|
+
@patch(_DETECT_MAIN, return_value="main")
|
|
267
|
+
@patch(_IS_HEAD_ATTACHED, return_value=False)
|
|
268
|
+
@patch("wade.services.implementation_service.lifecycle.git_worktree.prune_worktrees")
|
|
269
|
+
@patch("wade.services.implementation_service.lifecycle.git_worktree.remove_worktree")
|
|
270
|
+
@patch("wade.services.implementation_service.lifecycle.git_pr.merge_pr")
|
|
271
|
+
@patch("wade.services.implementation_service.lifecycle.git_repo.is_clean", return_value=True)
|
|
272
|
+
@patch("wade.services.implementation_service.lifecycle.prompts.select", return_value=0)
|
|
273
|
+
@patch("wade.services.implementation_service.lifecycle.prompts.confirm", return_value=True)
|
|
274
|
+
@patch("wade.services.implementation_service.lifecycle.webbrowser.open")
|
|
275
|
+
@patch(
|
|
276
|
+
"wade.services.implementation_service.lifecycle.git_pr.get_pr_for_branch",
|
|
277
|
+
return_value={"number": 99, "url": "https://example/pr/99"},
|
|
278
|
+
)
|
|
279
|
+
def test_pr_strategy_detached_repo_root_reattaches_then_merges(
|
|
280
|
+
_mock_get_pr: MagicMock,
|
|
281
|
+
_mock_webbrowser_open: MagicMock,
|
|
282
|
+
_mock_confirm: MagicMock,
|
|
283
|
+
_mock_select: MagicMock,
|
|
284
|
+
_mock_is_clean: MagicMock,
|
|
285
|
+
mock_merge_pr: MagicMock,
|
|
286
|
+
mock_remove_worktree: MagicMock,
|
|
287
|
+
_mock_prune: MagicMock,
|
|
288
|
+
_mock_is_head_attached: MagicMock,
|
|
289
|
+
mock_detect_main: MagicMock,
|
|
290
|
+
mock_checkout: MagicMock,
|
|
291
|
+
_mock_checkout_detach: MagicMock,
|
|
292
|
+
mock_pull_ff: MagicMock,
|
|
293
|
+
tmp_path: Path,
|
|
294
|
+
) -> None:
|
|
295
|
+
"""Detached repo_root HEAD is re-attached before the merge, then merge proceeds."""
|
|
296
|
+
mock_pull_ff.return_value = MagicMock(returncode=0)
|
|
297
|
+
provider = MagicMock()
|
|
298
|
+
repo_root = tmp_path / "repo"
|
|
299
|
+
wt_path = tmp_path / "wt"
|
|
300
|
+
wt_path.mkdir()
|
|
301
|
+
|
|
302
|
+
with patch("wade.services.implementation_service.lifecycle.prompts.is_tty", return_value=True):
|
|
303
|
+
status = _post_implementation_lifecycle(
|
|
304
|
+
repo_root, "feat/42-test", 42, wt_path, _config(MergeStrategy.PR), provider
|
|
305
|
+
)
|
|
306
|
+
|
|
307
|
+
# Re-attached repo_root to the detected default branch BEFORE merging.
|
|
308
|
+
mock_detect_main.assert_called_once_with(repo_root)
|
|
309
|
+
mock_checkout.assert_called_once_with(repo_root, "main")
|
|
310
|
+
# Merge and normal cleanup still ran.
|
|
311
|
+
mock_merge_pr.assert_called_once_with(repo_root=repo_root, pr_number=99, strategy="squash")
|
|
312
|
+
mock_remove_worktree.assert_called_once_with(repo_root, wt_path)
|
|
313
|
+
mock_pull_ff.assert_called_once_with(repo_root)
|
|
314
|
+
assert status == MergeStatus.MERGED
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
@patch(_CHECKOUT_DETACH)
|
|
318
|
+
@patch(_CHECKOUT)
|
|
319
|
+
@patch(_DETECT_MAIN, side_effect=GitError("Neither 'main' nor 'master' branch found"))
|
|
320
|
+
@patch(_IS_HEAD_ATTACHED, return_value=False)
|
|
321
|
+
@patch("wade.services.implementation_service.lifecycle.git_pr.merge_pr")
|
|
322
|
+
@patch("wade.services.implementation_service.lifecycle.git_repo.is_clean", return_value=True)
|
|
323
|
+
@patch("wade.services.implementation_service.lifecycle.prompts.select", return_value=0)
|
|
324
|
+
@patch("wade.services.implementation_service.lifecycle.prompts.confirm", return_value=True)
|
|
325
|
+
@patch("wade.services.implementation_service.lifecycle.webbrowser.open")
|
|
326
|
+
@patch(
|
|
327
|
+
"wade.services.implementation_service.lifecycle.git_pr.get_pr_for_branch",
|
|
328
|
+
return_value={"number": 99, "url": "https://example/pr/99"},
|
|
329
|
+
)
|
|
330
|
+
def test_pr_strategy_detached_repo_root_detect_fails_no_merge(
|
|
331
|
+
_mock_get_pr: MagicMock,
|
|
332
|
+
_mock_webbrowser_open: MagicMock,
|
|
333
|
+
_mock_confirm: MagicMock,
|
|
334
|
+
_mock_select: MagicMock,
|
|
335
|
+
_mock_is_clean: MagicMock,
|
|
336
|
+
mock_merge_pr: MagicMock,
|
|
337
|
+
_mock_is_head_attached: MagicMock,
|
|
338
|
+
_mock_detect_main: MagicMock,
|
|
339
|
+
mock_checkout: MagicMock,
|
|
340
|
+
mock_checkout_detach: MagicMock,
|
|
341
|
+
tmp_path: Path,
|
|
342
|
+
) -> None:
|
|
343
|
+
"""When re-attach detection fails (no main/master), fail fast without merging."""
|
|
344
|
+
provider = MagicMock()
|
|
345
|
+
repo_root = tmp_path / "repo"
|
|
346
|
+
wt_path = tmp_path / "wt"
|
|
347
|
+
wt_path.mkdir()
|
|
348
|
+
|
|
349
|
+
with patch("wade.services.implementation_service.lifecycle.prompts.is_tty", return_value=True):
|
|
350
|
+
status = _post_implementation_lifecycle(
|
|
351
|
+
repo_root, "feat/42-test", 42, wt_path, _config(MergeStrategy.PR), provider
|
|
352
|
+
)
|
|
353
|
+
|
|
354
|
+
# No GitHub-side merge and the worktree is left untouched (never detached).
|
|
355
|
+
mock_merge_pr.assert_not_called()
|
|
356
|
+
mock_checkout.assert_not_called()
|
|
357
|
+
mock_checkout_detach.assert_not_called()
|
|
358
|
+
provider.close_task.assert_not_called()
|
|
359
|
+
assert status == MergeStatus.MERGE_FAILED
|
|
360
|
+
|
|
361
|
+
|
|
362
|
+
@patch(_CHECKOUT_DETACH)
|
|
363
|
+
@patch(_DETECT_MAIN, return_value="main")
|
|
364
|
+
@patch(_IS_HEAD_ATTACHED, return_value=False)
|
|
365
|
+
@patch("wade.services.implementation_service.lifecycle.git_pr.merge_pr")
|
|
366
|
+
@patch("wade.services.implementation_service.lifecycle.git_repo.is_clean", return_value=True)
|
|
367
|
+
@patch("wade.services.implementation_service.lifecycle.prompts.select", return_value=0)
|
|
368
|
+
@patch("wade.services.implementation_service.lifecycle.prompts.confirm", return_value=True)
|
|
369
|
+
@patch("wade.services.implementation_service.lifecycle.webbrowser.open")
|
|
370
|
+
@patch(
|
|
371
|
+
"wade.services.implementation_service.lifecycle.git_pr.get_pr_for_branch",
|
|
372
|
+
return_value={"number": 99, "url": "https://example/pr/99"},
|
|
373
|
+
)
|
|
374
|
+
def test_pr_strategy_detached_repo_root_checkout_fails_no_merge(
|
|
375
|
+
_mock_get_pr: MagicMock,
|
|
376
|
+
_mock_webbrowser_open: MagicMock,
|
|
377
|
+
_mock_confirm: MagicMock,
|
|
378
|
+
_mock_select: MagicMock,
|
|
379
|
+
_mock_is_clean: MagicMock,
|
|
380
|
+
mock_merge_pr: MagicMock,
|
|
381
|
+
_mock_is_head_attached: MagicMock,
|
|
382
|
+
_mock_detect_main: MagicMock,
|
|
383
|
+
mock_checkout_detach: MagicMock,
|
|
384
|
+
tmp_path: Path,
|
|
385
|
+
) -> None:
|
|
386
|
+
"""When re-attach checkout is refused (e.g. conflicting changes), fail fast."""
|
|
387
|
+
provider = MagicMock()
|
|
388
|
+
repo_root = tmp_path / "repo"
|
|
389
|
+
wt_path = tmp_path / "wt"
|
|
390
|
+
wt_path.mkdir()
|
|
391
|
+
|
|
392
|
+
checkout_target = "wade.services.implementation_service.lifecycle.git_repo.checkout"
|
|
393
|
+
with (
|
|
394
|
+
patch(checkout_target, side_effect=GitError("checkout refused: local changes")),
|
|
395
|
+
patch("wade.services.implementation_service.lifecycle.prompts.is_tty", return_value=True),
|
|
396
|
+
):
|
|
397
|
+
status = _post_implementation_lifecycle(
|
|
398
|
+
repo_root, "feat/42-test", 42, wt_path, _config(MergeStrategy.PR), provider
|
|
399
|
+
)
|
|
400
|
+
|
|
401
|
+
# Re-attach checkout raised → no merge, worktree never detached.
|
|
402
|
+
mock_merge_pr.assert_not_called()
|
|
403
|
+
mock_checkout_detach.assert_not_called()
|
|
404
|
+
provider.close_task.assert_not_called()
|
|
405
|
+
assert status == MergeStatus.MERGE_FAILED
|
|
406
|
+
|
|
407
|
+
|
|
408
|
+
@patch(_PULL_FF)
|
|
409
|
+
@patch(_CHECKOUT_DETACH)
|
|
410
|
+
@patch(_CHECKOUT)
|
|
411
|
+
@patch(_DETECT_MAIN)
|
|
412
|
+
@patch(_IS_HEAD_ATTACHED, return_value=True)
|
|
413
|
+
@patch("wade.services.implementation_service.lifecycle.git_worktree.prune_worktrees")
|
|
414
|
+
@patch("wade.services.implementation_service.lifecycle.git_worktree.remove_worktree")
|
|
415
|
+
@patch("wade.services.implementation_service.lifecycle.git_pr.merge_pr")
|
|
416
|
+
@patch("wade.services.implementation_service.lifecycle.git_repo.is_clean", return_value=True)
|
|
417
|
+
@patch("wade.services.implementation_service.lifecycle.prompts.select", return_value=0)
|
|
418
|
+
@patch("wade.services.implementation_service.lifecycle.prompts.confirm", return_value=True)
|
|
419
|
+
@patch("wade.services.implementation_service.lifecycle.webbrowser.open")
|
|
420
|
+
@patch(
|
|
421
|
+
"wade.services.implementation_service.lifecycle.git_pr.get_pr_for_branch",
|
|
422
|
+
return_value={"number": 99, "url": "https://example/pr/99"},
|
|
423
|
+
)
|
|
424
|
+
def test_pr_strategy_attached_repo_root_skips_reattach(
|
|
425
|
+
_mock_get_pr: MagicMock,
|
|
426
|
+
_mock_webbrowser_open: MagicMock,
|
|
427
|
+
_mock_confirm: MagicMock,
|
|
428
|
+
_mock_select: MagicMock,
|
|
429
|
+
_mock_is_clean: MagicMock,
|
|
430
|
+
mock_merge_pr: MagicMock,
|
|
431
|
+
_mock_remove_worktree: MagicMock,
|
|
432
|
+
_mock_prune: MagicMock,
|
|
433
|
+
_mock_is_head_attached: MagicMock,
|
|
434
|
+
mock_detect_main: MagicMock,
|
|
435
|
+
mock_checkout: MagicMock,
|
|
436
|
+
_mock_checkout_detach: MagicMock,
|
|
437
|
+
mock_pull_ff: MagicMock,
|
|
438
|
+
tmp_path: Path,
|
|
439
|
+
) -> None:
|
|
440
|
+
"""Attached repo_root HEAD: no detection, no re-attach checkout — happy path intact."""
|
|
441
|
+
mock_pull_ff.return_value = MagicMock(returncode=0)
|
|
442
|
+
provider = MagicMock()
|
|
443
|
+
repo_root = tmp_path / "repo"
|
|
444
|
+
wt_path = tmp_path / "wt"
|
|
445
|
+
wt_path.mkdir()
|
|
446
|
+
|
|
447
|
+
with patch("wade.services.implementation_service.lifecycle.prompts.is_tty", return_value=True):
|
|
448
|
+
status = _post_implementation_lifecycle(
|
|
449
|
+
repo_root, "feat/42-test", 42, wt_path, _config(MergeStrategy.PR), provider
|
|
450
|
+
)
|
|
451
|
+
|
|
452
|
+
# Attached: never detected the default branch, never re-attached via checkout.
|
|
453
|
+
mock_detect_main.assert_not_called()
|
|
454
|
+
mock_checkout.assert_not_called()
|
|
455
|
+
mock_merge_pr.assert_called_once_with(repo_root=repo_root, pr_number=99, strategy="squash")
|
|
456
|
+
assert status == MergeStatus.MERGED
|
|
457
|
+
|
|
458
|
+
|
|
252
459
|
@patch("wade.services.implementation_service.lifecycle.prompts.confirm", return_value=False)
|
|
253
460
|
@patch("wade.services.implementation_service.lifecycle.git_branch.commits_ahead", return_value=0)
|
|
254
461
|
def test_direct_strategy_zero_ahead_offers_delete(
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|