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.
Files changed (289) hide show
  1. {wade_cli-0.35.0 → wade_cli-0.35.3}/CHANGELOG.md +12 -0
  2. {wade_cli-0.35.0 → wade_cli-0.35.3}/KNOWLEDGE.md +6 -0
  3. {wade_cli-0.35.0 → wade_cli-0.35.3}/KNOWLEDGE.ratings.yml +5 -1
  4. {wade_cli-0.35.0 → wade_cli-0.35.3}/PKG-INFO +2 -2
  5. {wade_cli-0.35.0 → wade_cli-0.35.3}/docs/dev/architecture.md +1 -1
  6. {wade_cli-0.35.0 → wade_cli-0.35.3}/pyproject.toml +2 -2
  7. {wade_cli-0.35.0 → wade_cli-0.35.3}/scripts/changelog.py +3 -2
  8. {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/__init__.py +1 -1
  9. {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/git/repo.py +16 -0
  10. {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/services/implementation_service/lifecycle.py +28 -0
  11. {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/unit/test_git/test_repo.py +30 -0
  12. {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/unit/test_services/test_post_implementation_lifecycle.py +208 -1
  13. {wade_cli-0.35.0 → wade_cli-0.35.3}/.github/workflows/auto-version.yml +0 -0
  14. {wade_cli-0.35.0 → wade_cli-0.35.3}/.github/workflows/ci.yml +0 -0
  15. {wade_cli-0.35.0 → wade_cli-0.35.3}/.github/workflows/pr-title-lint.yml +0 -0
  16. {wade_cli-0.35.0 → wade_cli-0.35.3}/.github/workflows/publish.yml +0 -0
  17. {wade_cli-0.35.0 → wade_cli-0.35.3}/.github/workflows/release.yml +0 -0
  18. {wade_cli-0.35.0 → wade_cli-0.35.3}/.gitignore +0 -0
  19. {wade_cli-0.35.0 → wade_cli-0.35.3}/.pre-commit-config.yaml +0 -0
  20. {wade_cli-0.35.0 → wade_cli-0.35.3}/.wade.yml +0 -0
  21. {wade_cli-0.35.0 → wade_cli-0.35.3}/AGENTS.md +0 -0
  22. {wade_cli-0.35.0 → wade_cli-0.35.3}/CONTRIBUTING.md +0 -0
  23. {wade_cli-0.35.0 → wade_cli-0.35.3}/README.md +0 -0
  24. {wade_cli-0.35.0 → wade_cli-0.35.3}/assets/wade.png +0 -0
  25. {wade_cli-0.35.0 → wade_cli-0.35.3}/docker-compose.e2e.yml +0 -0
  26. {wade_cli-0.35.0 → wade_cli-0.35.3}/docs/dev/documentation-policies.md +0 -0
  27. {wade_cli-0.35.0 → wade_cli-0.35.3}/docs/dev/extending.md +0 -0
  28. {wade_cli-0.35.0 → wade_cli-0.35.3}/docs/dev/skills-system.md +0 -0
  29. {wade_cli-0.35.0 → wade_cli-0.35.3}/docs/dev/testing.md +0 -0
  30. {wade_cli-0.35.0 → wade_cli-0.35.3}/install.sh +0 -0
  31. {wade_cli-0.35.0 → wade_cli-0.35.3}/scripts/auto_version.py +0 -0
  32. {wade_cli-0.35.0 → wade_cli-0.35.3}/scripts/check-all.sh +0 -0
  33. {wade_cli-0.35.0 → wade_cli-0.35.3}/scripts/check.sh +0 -0
  34. {wade_cli-0.35.0 → wade_cli-0.35.3}/scripts/fmt.sh +0 -0
  35. {wade_cli-0.35.0 → wade_cli-0.35.3}/scripts/hooks/pre-push +0 -0
  36. {wade_cli-0.35.0 → wade_cli-0.35.3}/scripts/install-hooks.sh +0 -0
  37. {wade_cli-0.35.0 → wade_cli-0.35.3}/scripts/release.sh +0 -0
  38. {wade_cli-0.35.0 → wade_cli-0.35.3}/scripts/setup-worktree.sh +0 -0
  39. {wade_cli-0.35.0 → wade_cli-0.35.3}/scripts/test-e2e-docker.sh +0 -0
  40. {wade_cli-0.35.0 → wade_cli-0.35.3}/scripts/test-e2e.sh +0 -0
  41. {wade_cli-0.35.0 → wade_cli-0.35.3}/scripts/test-live-ai-taskr.sh +0 -0
  42. {wade_cli-0.35.0 → wade_cli-0.35.3}/scripts/test-live-ai.sh +0 -0
  43. {wade_cli-0.35.0 → wade_cli-0.35.3}/scripts/test-live-gh.sh +0 -0
  44. {wade_cli-0.35.0 → wade_cli-0.35.3}/scripts/test.sh +0 -0
  45. {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/__main__.py +0 -0
  46. {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/cli/__init__.py +0 -0
  47. {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/cli/admin.py +0 -0
  48. {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/cli/autocomplete.py +0 -0
  49. {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/cli/implementation_session.py +0 -0
  50. {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/cli/knowledge.py +0 -0
  51. {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/cli/main.py +0 -0
  52. {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/cli/plan_session.py +0 -0
  53. {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/cli/review.py +0 -0
  54. {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/cli/review_pr_comments_session.py +0 -0
  55. {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/cli/session_shared.py +0 -0
  56. {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/cli/task.py +0 -0
  57. {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/cli/worktree.py +0 -0
  58. {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/config/__init__.py +0 -0
  59. {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/config/loader.py +0 -0
  60. {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/config/migrations.py +0 -0
  61. {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/config/schema.py +0 -0
  62. {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/db/__init__.py +0 -0
  63. {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/db/engine.py +0 -0
  64. {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/db/repositories.py +0 -0
  65. {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/db/tables.py +0 -0
  66. {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/git/__init__.py +0 -0
  67. {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/git/branch.py +0 -0
  68. {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/git/pr.py +0 -0
  69. {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/git/stash.py +0 -0
  70. {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/git/sync.py +0 -0
  71. {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/git/worktree.py +0 -0
  72. {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/hooks/__init__.py +0 -0
  73. {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/hooks/plan_write_guard.py +0 -0
  74. {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/hooks/worktree_guard.py +0 -0
  75. {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/logging/__init__.py +0 -0
  76. {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/logging/context.py +0 -0
  77. {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/logging/setup.py +0 -0
  78. {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/models/__init__.py +0 -0
  79. {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/models/batch.py +0 -0
  80. {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/models/config.py +0 -0
  81. {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/models/delegation.py +0 -0
  82. {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/models/deps.py +0 -0
  83. {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/models/events.py +0 -0
  84. {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/models/permission.py +0 -0
  85. {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/models/review.py +0 -0
  86. {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/models/session.py +0 -0
  87. {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/models/task.py +0 -0
  88. {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/providers/__init__.py +0 -0
  89. {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/providers/_pr_delegate.py +0 -0
  90. {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/providers/base.py +0 -0
  91. {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/providers/clickup.py +0 -0
  92. {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/providers/github.py +0 -0
  93. {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/providers/markdown.py +0 -0
  94. {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/providers/registry.py +0 -0
  95. {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/services/__init__.py +0 -0
  96. {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/services/ai_resolution.py +0 -0
  97. {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/services/batch_review_service.py +0 -0
  98. {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/services/check_service.py +0 -0
  99. {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/services/delegation_service.py +0 -0
  100. {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/services/deps_service.py +0 -0
  101. {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/services/implementation_service/__init__.py +0 -0
  102. {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/services/implementation_service/_shared.py +0 -0
  103. {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/services/implementation_service/batch.py +0 -0
  104. {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/services/implementation_service/bootstrap.py +0 -0
  105. {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/services/implementation_service/cleanup.py +0 -0
  106. {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/services/implementation_service/core.py +0 -0
  107. {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/services/implementation_service/done.py +0 -0
  108. {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/services/implementation_service/draft_pr.py +0 -0
  109. {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/services/implementation_service/sync.py +0 -0
  110. {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/services/implementation_service/usage_tracking.py +0 -0
  111. {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/services/init_service/__init__.py +0 -0
  112. {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/services/init_service/auth.py +0 -0
  113. {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/services/init_service/commands.py +0 -0
  114. {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/services/init_service/config_io.py +0 -0
  115. {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/services/init_service/manifest.py +0 -0
  116. {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/services/init_service/migrations.py +0 -0
  117. {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/services/init_service/prompts_ai.py +0 -0
  118. {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/services/init_service/prompts_setup.py +0 -0
  119. {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/services/init_service/shell.py +0 -0
  120. {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/services/knowledge_search.py +0 -0
  121. {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/services/knowledge_service.py +0 -0
  122. {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/services/plan_service.py +0 -0
  123. {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/services/prompt_delivery.py +0 -0
  124. {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/services/review_delegation_service.py +0 -0
  125. {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/services/review_service.py +0 -0
  126. {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/services/review_settle.py +0 -0
  127. {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/services/smart_start.py +0 -0
  128. {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/services/task_service.py +0 -0
  129. {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/skills/__init__.py +0 -0
  130. {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/skills/installer.py +0 -0
  131. {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/skills/pointer.py +0 -0
  132. {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/ui/__init__.py +0 -0
  133. {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/ui/console.py +0 -0
  134. {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/ui/prompts.py +0 -0
  135. {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/utils/__init__.py +0 -0
  136. {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/utils/clipboard.py +0 -0
  137. {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/utils/http.py +0 -0
  138. {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/utils/install.py +0 -0
  139. {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/utils/markdown.py +0 -0
  140. {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/utils/process.py +0 -0
  141. {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/utils/slug.py +0 -0
  142. {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/utils/terminal.py +0 -0
  143. {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/utils/token_usage_markdown.py +0 -0
  144. {wade_cli-0.35.0 → wade_cli-0.35.3}/src/wade/utils/update_check.py +0 -0
  145. {wade_cli-0.35.0 → wade_cli-0.35.3}/templates/agents-pointer.md +0 -0
  146. {wade_cli-0.35.0 → wade_cli-0.35.3}/templates/prompts/deps-analysis.md +0 -0
  147. {wade_cli-0.35.0 → wade_cli-0.35.3}/templates/prompts/implement-context.md +0 -0
  148. {wade_cli-0.35.0 → wade_cli-0.35.3}/templates/prompts/plan-session.md +0 -0
  149. {wade_cli-0.35.0 → wade_cli-0.35.3}/templates/prompts/review-batch.md +0 -0
  150. {wade_cli-0.35.0 → wade_cli-0.35.3}/templates/prompts/review-code.md +0 -0
  151. {wade_cli-0.35.0 → wade_cli-0.35.3}/templates/prompts/review-plan.md +0 -0
  152. {wade_cli-0.35.0 → wade_cli-0.35.3}/templates/prompts/review-pr-comments.md +0 -0
  153. {wade_cli-0.35.0 → wade_cli-0.35.3}/templates/setup-worktree.sh.example +0 -0
  154. {wade_cli-0.35.0 → wade_cli-0.35.3}/templates/skills/_partials/review-enforcement-rule.md +0 -0
  155. {wade_cli-0.35.0 → wade_cli-0.35.3}/templates/skills/_partials/review-implementation-closing-step.md +0 -0
  156. {wade_cli-0.35.0 → wade_cli-0.35.3}/templates/skills/_partials/review-plan-step.md +0 -0
  157. {wade_cli-0.35.0 → wade_cli-0.35.3}/templates/skills/_partials/user-interaction.md +0 -0
  158. {wade_cli-0.35.0 → wade_cli-0.35.3}/templates/skills/deps/SKILL.md +0 -0
  159. {wade_cli-0.35.0 → wade_cli-0.35.3}/templates/skills/implementation-session/SKILL.md +0 -0
  160. {wade_cli-0.35.0 → wade_cli-0.35.3}/templates/skills/knowledge/SKILL.md +0 -0
  161. {wade_cli-0.35.0 → wade_cli-0.35.3}/templates/skills/plan-session/SKILL.md +0 -0
  162. {wade_cli-0.35.0 → wade_cli-0.35.3}/templates/skills/review-pr-comments-session/SKILL.md +0 -0
  163. {wade_cli-0.35.0 → wade_cli-0.35.3}/templates/skills/task/SKILL.md +0 -0
  164. {wade_cli-0.35.0 → wade_cli-0.35.3}/templates/skills/task/examples.md +0 -0
  165. {wade_cli-0.35.0 → wade_cli-0.35.3}/templates/skills/task/plan-format.md +0 -0
  166. {wade_cli-0.35.0 → wade_cli-0.35.3}/templates/statusline-command.sh +0 -0
  167. {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/__init__.py +0 -0
  168. {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/conftest.py +0 -0
  169. {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/e2e/__init__.py +0 -0
  170. {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/e2e/_support.py +0 -0
  171. {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/e2e/conftest.py +0 -0
  172. {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/e2e/mock_gh_script.py +0 -0
  173. {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/e2e/test_admin_contract.py +0 -0
  174. {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/e2e/test_check_contract.py +0 -0
  175. {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/e2e/test_implement_work_done_contract.py +0 -0
  176. {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/e2e/test_knowledge_contract.py +0 -0
  177. {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/e2e/test_mock_gh_contract.py +0 -0
  178. {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/e2e/test_plan_contract.py +0 -0
  179. {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/e2e/test_review_contract.py +0 -0
  180. {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/e2e/test_smart_start_contract.py +0 -0
  181. {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/e2e/test_task_contract.py +0 -0
  182. {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/e2e/test_work_sync_list_contract.py +0 -0
  183. {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/fixtures/live/taskr_header_hi_body.md +0 -0
  184. {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/fixtures/live/taskr_header_howdy_body.md +0 -0
  185. {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/fixtures/transcripts/claude_session.txt +0 -0
  186. {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/fixtures/transcripts/codex_session.txt +0 -0
  187. {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/fixtures/transcripts/copilot_session.txt +0 -0
  188. {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/fixtures/transcripts/generic_session.txt +0 -0
  189. {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/integration/__init__.py +0 -0
  190. {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/integration/test_autostash.py +0 -0
  191. {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/integration/test_check.py +0 -0
  192. {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/integration/test_git.py +0 -0
  193. {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/integration/test_implementation_lifecycle.py +0 -0
  194. {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/integration/test_init.py +0 -0
  195. {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/integration/test_knowledge.py +0 -0
  196. {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/integration/test_markdown_provider.py +0 -0
  197. {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/integration/test_skill_install.py +0 -0
  198. {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/live/__init__.py +0 -0
  199. {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/live/test_wade_live_ai.py +0 -0
  200. {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/live/test_wade_live_ai_taskr.py +0 -0
  201. {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/live/test_wade_live_gh.py +0 -0
  202. {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/test_cli_basics.py +0 -0
  203. {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/unit/__init__.py +0 -0
  204. {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/unit/conftest.py +0 -0
  205. {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/unit/test_cli/__init__.py +0 -0
  206. {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/unit/test_cli/test_done_review_hint.py +0 -0
  207. {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/unit/test_cli/test_knowledge_cli.py +0 -0
  208. {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/unit/test_cli/test_review_cli.py +0 -0
  209. {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/unit/test_cli/test_session_subapps.py +0 -0
  210. {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/unit/test_cli/test_shell_init.py +0 -0
  211. {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/unit/test_cli/test_task_cli.py +0 -0
  212. {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/unit/test_cli/test_work_ai_flags.py +0 -0
  213. {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/unit/test_cli/test_work_cli.py +0 -0
  214. {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/unit/test_config/__init__.py +0 -0
  215. {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/unit/test_config/test_loader.py +0 -0
  216. {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/unit/test_config/test_migrations.py +0 -0
  217. {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/unit/test_config/test_migrations_atomic.py +0 -0
  218. {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/unit/test_console.py +0 -0
  219. {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/unit/test_db/__init__.py +0 -0
  220. {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/unit/test_db/test_repository_transactions.py +0 -0
  221. {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/unit/test_effort.py +0 -0
  222. {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/unit/test_git/__init__.py +0 -0
  223. {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/unit/test_git/test_branch_slugify.py +0 -0
  224. {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/unit/test_git/test_detached_worktree.py +0 -0
  225. {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/unit/test_git/test_pr_base.py +0 -0
  226. {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/unit/test_git/test_scaffold_commit.py +0 -0
  227. {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/unit/test_git/test_sync_conflicted_files.py +0 -0
  228. {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/unit/test_git/test_worktree_remove.py +0 -0
  229. {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/unit/test_hooks/__init__.py +0 -0
  230. {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/unit/test_hooks/test_plan_write_guard.py +0 -0
  231. {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/unit/test_hooks/test_worktree_guard.py +0 -0
  232. {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/unit/test_models/__init__.py +0 -0
  233. {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/unit/test_models/test_batch.py +0 -0
  234. {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/unit/test_models/test_config_models.py +0 -0
  235. {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/unit/test_models/test_db.py +0 -0
  236. {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/unit/test_models/test_delegation.py +0 -0
  237. {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/unit/test_models/test_deps_models.py +0 -0
  238. {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/unit/test_models/test_deps_partition.py +0 -0
  239. {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/unit/test_models/test_review.py +0 -0
  240. {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/unit/test_models/test_task_models.py +0 -0
  241. {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/unit/test_permission_mode.py +0 -0
  242. {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/unit/test_providers/__init__.py +0 -0
  243. {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/unit/test_providers/test_clickup.py +0 -0
  244. {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/unit/test_providers/test_github.py +0 -0
  245. {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/unit/test_providers/test_github_pr.py +0 -0
  246. {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/unit/test_providers/test_markdown.py +0 -0
  247. {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/unit/test_services/__init__.py +0 -0
  248. {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/unit/test_services/test_ai_resolution.py +0 -0
  249. {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/unit/test_services/test_batch_review_service.py +0 -0
  250. {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/unit/test_services/test_bootstrap_allowlist.py +0 -0
  251. {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/unit/test_services/test_bootstrap_hook_timeout.py +0 -0
  252. {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/unit/test_services/test_catchup.py +0 -0
  253. {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/unit/test_services/test_check.py +0 -0
  254. {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/unit/test_services/test_delegation_service.py +0 -0
  255. {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/unit/test_services/test_deps_service.py +0 -0
  256. {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/unit/test_services/test_done_plan_flag.py +0 -0
  257. {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/unit/test_services/test_done_via_direct_cleanup.py +0 -0
  258. {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/unit/test_services/test_implementation_done_sync.py +0 -0
  259. {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/unit/test_services/test_implementation_service.py +0 -0
  260. {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/unit/test_services/test_implementation_service_resume.py +0 -0
  261. {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/unit/test_services/test_init.py +0 -0
  262. {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/unit/test_services/test_init_shell_integration.py +0 -0
  263. {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/unit/test_services/test_knowledge_search.py +0 -0
  264. {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/unit/test_services/test_knowledge_service.py +0 -0
  265. {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/unit/test_services/test_plan_service.py +0 -0
  266. {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/unit/test_services/test_poll_for_reviews.py +0 -0
  267. {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/unit/test_services/test_pr_summary_path.py +0 -0
  268. {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/unit/test_services/test_preserve_session_data.py +0 -0
  269. {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/unit/test_services/test_prompt_delivery.py +0 -0
  270. {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/unit/test_services/test_review_delegation_service.py +0 -0
  271. {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/unit/test_services/test_review_service.py +0 -0
  272. {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/unit/test_services/test_smart_start.py +0 -0
  273. {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/unit/test_services/test_smart_start_resume.py +0 -0
  274. {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/unit/test_services/test_sync_base_branch.py +0 -0
  275. {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/unit/test_services/test_task_service.py +0 -0
  276. {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/unit/test_services/test_tracked_managed_files.py +0 -0
  277. {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/unit/test_services/test_worktree_gitignore.py +0 -0
  278. {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/unit/test_skills/test_managed_gitignore.py +0 -0
  279. {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/unit/test_utils/__init__.py +0 -0
  280. {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/unit/test_utils/test_http.py +0 -0
  281. {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/unit/test_utils/test_install.py +0 -0
  282. {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/unit/test_utils/test_markdown.py +0 -0
  283. {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/unit/test_utils/test_process.py +0 -0
  284. {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/unit/test_utils/test_prompts.py +0 -0
  285. {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/unit/test_utils/test_slug.py +0 -0
  286. {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/unit/test_utils/test_terminal.py +0 -0
  287. {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/unit/test_utils/test_terminal_launch.py +0 -0
  288. {wade_cli-0.35.0 → wade_cli-0.35.3}/tests/unit/test_utils/test_update_check.py +0 -0
  289. {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
+ ---
@@ -36,7 +36,7 @@
36
36
  up: 4
37
37
  54326c98:
38
38
  down: 1
39
- stale: 0
39
+ stale: 1
40
40
  up: 0
41
41
  6e38b3aa:
42
42
  down: 0
@@ -68,6 +68,10 @@ b6a3a637:
68
68
  down: 0
69
69
  stale: 0
70
70
  up: 1
71
+ b6ca74e5:
72
+ down: 0
73
+ stale: 0
74
+ up: 1
71
75
  cae0f47a:
72
76
  down: 0
73
77
  stale: 0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: wade-cli
3
- Version: 0.35.0
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.11.0,>=0.10.2
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.10.2,<0.11.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)`).
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.0"
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.10.2,<0.11.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
@@ -1,3 +1,3 @@
1
1
  """WADE (Workflow for AI-Driven Engineering) — AI-agent-driven git workflow management CLI."""
2
2
 
3
- __version__ = "0.35.0"
3
+ __version__ = "0.35.3"
@@ -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
@@ -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