wade-cli 1.1.1__tar.gz → 1.1.2__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 (420) hide show
  1. {wade_cli-1.1.1 → wade_cli-1.1.2}/AGENTS.md +7 -0
  2. {wade_cli-1.1.1 → wade_cli-1.1.2}/CHANGELOG.md +6 -0
  3. {wade_cli-1.1.1 → wade_cli-1.1.2}/PKG-INFO +19 -2
  4. {wade_cli-1.1.1 → wade_cli-1.1.2}/README.md +18 -1
  5. {wade_cli-1.1.1 → wade_cli-1.1.2}/docs/dev/architecture.md +18 -1
  6. {wade_cli-1.1.1 → wade_cli-1.1.2}/docs/dev/workflows-and-skills.md +27 -1
  7. {wade_cli-1.1.1 → wade_cli-1.1.2}/pyproject.toml +1 -1
  8. {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/__init__.py +1 -1
  9. {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/git/repo.py +12 -0
  10. wade_cli-1.1.2/src/wade/models/review_cycle.py +69 -0
  11. {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/models/workflow.py +4 -4
  12. {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/services/implementation_service/done.py +13 -0
  13. wade_cli-1.1.2/src/wade/services/review_cycle_service.py +192 -0
  14. {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/services/review_delegation_service.py +212 -16
  15. {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/services/review_record_service.py +45 -0
  16. {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/services/review_service.py +108 -0
  17. {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/skills/materializer.py +9 -3
  18. {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/utils/safe_state.py +46 -0
  19. wade_cli-1.1.2/templates/prompts/review-code.md +9 -0
  20. {wade_cli-1.1.1 → wade_cli-1.1.2}/templates/workflows/implementation.md +3 -3
  21. {wade_cli-1.1.1 → wade_cli-1.1.2}/templates/workflows/review-pr-comments.md +3 -3
  22. {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/integration/test_skill_install.py +18 -1
  23. {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_services/test_done_gates.py +2 -2
  24. {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_services/test_done_idempotent.py +26 -6
  25. wade_cli-1.1.2/tests/unit/test_services/test_review_cycle_service.py +235 -0
  26. {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_services/test_review_delegation_service.py +173 -0
  27. {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_services/test_review_records.py +82 -3
  28. {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_services/test_review_status_block.py +2 -2
  29. wade_cli-1.1.1/templates/prompts/review-code.md +0 -7
  30. {wade_cli-1.1.1 → wade_cli-1.1.2}/.gitattributes +0 -0
  31. {wade_cli-1.1.1 → wade_cli-1.1.2}/.github/workflows/auto-version.yml +0 -0
  32. {wade_cli-1.1.1 → wade_cli-1.1.2}/.github/workflows/ci.yml +0 -0
  33. {wade_cli-1.1.1 → wade_cli-1.1.2}/.github/workflows/pr-title-lint.yml +0 -0
  34. {wade_cli-1.1.1 → wade_cli-1.1.2}/.github/workflows/publish.yml +0 -0
  35. {wade_cli-1.1.1 → wade_cli-1.1.2}/.github/workflows/release.yml +0 -0
  36. {wade_cli-1.1.1 → wade_cli-1.1.2}/.gitignore +0 -0
  37. {wade_cli-1.1.1 → wade_cli-1.1.2}/.pre-commit-config.yaml +0 -0
  38. {wade_cli-1.1.1 → wade_cli-1.1.2}/.wade.yml +0 -0
  39. {wade_cli-1.1.1 → wade_cli-1.1.2}/CONTRIBUTING.md +0 -0
  40. {wade_cli-1.1.1 → wade_cli-1.1.2}/KNOWLEDGE.md +0 -0
  41. {wade_cli-1.1.1 → wade_cli-1.1.2}/KNOWLEDGE.ratings.jsonl +0 -0
  42. {wade_cli-1.1.1 → wade_cli-1.1.2}/assets/wade.png +0 -0
  43. {wade_cli-1.1.1 → wade_cli-1.1.2}/docker-compose.e2e.yml +0 -0
  44. {wade_cli-1.1.1 → wade_cli-1.1.2}/docs/dev/documentation-policies.md +0 -0
  45. {wade_cli-1.1.1 → wade_cli-1.1.2}/docs/dev/extending.md +0 -0
  46. {wade_cli-1.1.1 → wade_cli-1.1.2}/docs/dev/skills-system.md +0 -0
  47. {wade_cli-1.1.1 → wade_cli-1.1.2}/docs/dev/testing.md +0 -0
  48. {wade_cli-1.1.1 → wade_cli-1.1.2}/install.sh +0 -0
  49. {wade_cli-1.1.1 → wade_cli-1.1.2}/scripts/auto_version.py +0 -0
  50. {wade_cli-1.1.1 → wade_cli-1.1.2}/scripts/changelog.py +0 -0
  51. {wade_cli-1.1.1 → wade_cli-1.1.2}/scripts/check-all.sh +0 -0
  52. {wade_cli-1.1.1 → wade_cli-1.1.2}/scripts/check.sh +0 -0
  53. {wade_cli-1.1.1 → wade_cli-1.1.2}/scripts/fmt.sh +0 -0
  54. {wade_cli-1.1.1 → wade_cli-1.1.2}/scripts/hooks/pre-push +0 -0
  55. {wade_cli-1.1.1 → wade_cli-1.1.2}/scripts/install-hooks.sh +0 -0
  56. {wade_cli-1.1.1 → wade_cli-1.1.2}/scripts/release.sh +0 -0
  57. {wade_cli-1.1.1 → wade_cli-1.1.2}/scripts/setup-worktree.sh +0 -0
  58. {wade_cli-1.1.1 → wade_cli-1.1.2}/scripts/test-concurrency.sh +0 -0
  59. {wade_cli-1.1.1 → wade_cli-1.1.2}/scripts/test-e2e-docker.sh +0 -0
  60. {wade_cli-1.1.1 → wade_cli-1.1.2}/scripts/test-e2e.sh +0 -0
  61. {wade_cli-1.1.1 → wade_cli-1.1.2}/scripts/test-live-ai-taskr.sh +0 -0
  62. {wade_cli-1.1.1 → wade_cli-1.1.2}/scripts/test-live-ai.sh +0 -0
  63. {wade_cli-1.1.1 → wade_cli-1.1.2}/scripts/test-live-gh.sh +0 -0
  64. {wade_cli-1.1.1 → wade_cli-1.1.2}/scripts/test.sh +0 -0
  65. {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/__main__.py +0 -0
  66. {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/cli/__init__.py +0 -0
  67. {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/cli/admin.py +0 -0
  68. {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/cli/autocomplete.py +0 -0
  69. {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/cli/deps_session.py +0 -0
  70. {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/cli/hook.py +0 -0
  71. {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/cli/implementation_session.py +0 -0
  72. {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/cli/knowledge.py +0 -0
  73. {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/cli/main.py +0 -0
  74. {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/cli/plan_session.py +0 -0
  75. {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/cli/review.py +0 -0
  76. {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/cli/review_pr_comments_session.py +0 -0
  77. {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/cli/session.py +0 -0
  78. {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/cli/session_shared.py +0 -0
  79. {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/cli/skills.py +0 -0
  80. {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/cli/task.py +0 -0
  81. {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/cli/worktree.py +0 -0
  82. {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/config/__init__.py +0 -0
  83. {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/config/loader.py +0 -0
  84. {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/config/migrations.py +0 -0
  85. {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/config/schema.py +0 -0
  86. {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/git/__init__.py +0 -0
  87. {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/git/branch.py +0 -0
  88. {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/git/hooks.py +0 -0
  89. {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/git/pr.py +0 -0
  90. {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/git/stash.py +0 -0
  91. {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/git/sync.py +0 -0
  92. {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/git/worktree.py +0 -0
  93. {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/hooks/__init__.py +0 -0
  94. {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/hooks/cli.py +0 -0
  95. {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/hooks/policies.py +0 -0
  96. {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/logging/__init__.py +0 -0
  97. {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/logging/context.py +0 -0
  98. {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/logging/setup.py +0 -0
  99. {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/models/__init__.py +0 -0
  100. {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/models/batch.py +0 -0
  101. {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/models/config.py +0 -0
  102. {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/models/delegation.py +0 -0
  103. {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/models/deps.py +0 -0
  104. {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/models/events.py +0 -0
  105. {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/models/hooks.py +0 -0
  106. {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/models/permission.py +0 -0
  107. {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/models/readiness.py +0 -0
  108. {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/models/review.py +0 -0
  109. {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/models/session.py +0 -0
  110. {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/models/session_manifest.py +0 -0
  111. {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/models/skill.py +0 -0
  112. {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/models/task.py +0 -0
  113. {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/models/worktree.py +0 -0
  114. {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/providers/__init__.py +0 -0
  115. {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/providers/_pr_delegate.py +0 -0
  116. {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/providers/base.py +0 -0
  117. {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/providers/clickup.py +0 -0
  118. {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/providers/github.py +0 -0
  119. {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/providers/markdown.py +0 -0
  120. {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/providers/registry.py +0 -0
  121. {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/services/__init__.py +0 -0
  122. {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/services/ai_resolution.py +0 -0
  123. {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/services/batch_review_service.py +0 -0
  124. {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/services/bot_trigger.py +0 -0
  125. {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/services/check_service.py +0 -0
  126. {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/services/delegation_service.py +0 -0
  127. {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/services/deps_service.py +0 -0
  128. {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/services/documentation_receipt_service.py +0 -0
  129. {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/services/implementation_service/__init__.py +0 -0
  130. {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/services/implementation_service/_shared.py +0 -0
  131. {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/services/implementation_service/batch.py +0 -0
  132. {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/services/implementation_service/bootstrap.py +0 -0
  133. {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/services/implementation_service/cleanup.py +0 -0
  134. {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/services/implementation_service/core.py +0 -0
  135. {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/services/implementation_service/draft_pr.py +0 -0
  136. {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/services/implementation_service/lifecycle.py +0 -0
  137. {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/services/implementation_service/sync.py +0 -0
  138. {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/services/implementation_service/usage_tracking.py +0 -0
  139. {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/services/init_service/__init__.py +0 -0
  140. {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/services/init_service/auth.py +0 -0
  141. {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/services/init_service/commands.py +0 -0
  142. {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/services/init_service/config_io.py +0 -0
  143. {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/services/init_service/manifest.py +0 -0
  144. {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/services/init_service/migrations.py +0 -0
  145. {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/services/init_service/prompts_ai.py +0 -0
  146. {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/services/init_service/prompts_setup.py +0 -0
  147. {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/services/init_service/shell.py +0 -0
  148. {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/services/knowledge_recovery.py +0 -0
  149. {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/services/knowledge_search.py +0 -0
  150. {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/services/knowledge_service.py +0 -0
  151. {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/services/plan_service.py +0 -0
  152. {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/services/prompt_delivery.py +0 -0
  153. {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/services/review_settle.py +0 -0
  154. {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/services/session_composition_service.py +0 -0
  155. {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/services/skill_diagnostics_service.py +0 -0
  156. {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/services/skill_invocation_service.py +0 -0
  157. {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/services/smart_start.py +0 -0
  158. {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/services/task_service.py +0 -0
  159. {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/skills/__init__.py +0 -0
  160. {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/skills/catalog.py +0 -0
  161. {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/skills/discovery.py +0 -0
  162. {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/skills/doc_targets.py +0 -0
  163. {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/skills/installer.py +0 -0
  164. {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/skills/pointer.py +0 -0
  165. {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/skills/resolver.py +0 -0
  166. {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/skills/validation.py +0 -0
  167. {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/ui/__init__.py +0 -0
  168. {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/ui/console.py +0 -0
  169. {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/ui/prompts.py +0 -0
  170. {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/utils/__init__.py +0 -0
  171. {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/utils/body_markers.py +0 -0
  172. {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/utils/clipboard.py +0 -0
  173. {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/utils/conventional.py +0 -0
  174. {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/utils/filelock.py +0 -0
  175. {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/utils/gitref.py +0 -0
  176. {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/utils/http.py +0 -0
  177. {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/utils/install.py +0 -0
  178. {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/utils/knowledge_file.py +0 -0
  179. {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/utils/markdown.py +0 -0
  180. {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/utils/markers.py +0 -0
  181. {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/utils/paths.py +0 -0
  182. {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/utils/plan_validation.py +0 -0
  183. {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/utils/process.py +0 -0
  184. {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/utils/runtime_env.py +0 -0
  185. {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/utils/slug.py +0 -0
  186. {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/utils/stale_base.py +0 -0
  187. {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/utils/templates.py +0 -0
  188. {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/utils/terminal.py +0 -0
  189. {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/utils/token_usage_markdown.py +0 -0
  190. {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/utils/update_check.py +0 -0
  191. {wade_cli-1.1.1 → wade_cli-1.1.2}/templates/agents-pointer.md +0 -0
  192. {wade_cli-1.1.1 → wade_cli-1.1.2}/templates/hooks/commit-msg +0 -0
  193. {wade_cli-1.1.1 → wade_cli-1.1.2}/templates/hooks/pre-commit +0 -0
  194. {wade_cli-1.1.1 → wade_cli-1.1.2}/templates/hooks/pre-push +0 -0
  195. {wade_cli-1.1.1 → wade_cli-1.1.2}/templates/prompts/deps-analysis.md +0 -0
  196. {wade_cli-1.1.1 → wade_cli-1.1.2}/templates/prompts/implement-context.md +0 -0
  197. {wade_cli-1.1.1 → wade_cli-1.1.2}/templates/prompts/plan-session.md +0 -0
  198. {wade_cli-1.1.1 → wade_cli-1.1.2}/templates/prompts/review-batch.md +0 -0
  199. {wade_cli-1.1.1 → wade_cli-1.1.2}/templates/prompts/review-plan.md +0 -0
  200. {wade_cli-1.1.1 → wade_cli-1.1.2}/templates/prompts/review-pr-comments.md +0 -0
  201. {wade_cli-1.1.1 → wade_cli-1.1.2}/templates/prompts/session-start-implement.md +0 -0
  202. {wade_cli-1.1.1 → wade_cli-1.1.2}/templates/prompts/session-start-plan.md +0 -0
  203. {wade_cli-1.1.1 → wade_cli-1.1.2}/templates/prompts/session-start-review.md +0 -0
  204. {wade_cli-1.1.1 → wade_cli-1.1.2}/templates/setup-worktree.sh.example +0 -0
  205. {wade_cli-1.1.1 → wade_cli-1.1.2}/templates/skills/batch-review/SKILL.md +0 -0
  206. {wade_cli-1.1.1 → wade_cli-1.1.2}/templates/skills/code-review/SKILL.md +0 -0
  207. {wade_cli-1.1.1 → wade_cli-1.1.2}/templates/skills/dependency-analysis/SKILL.md +0 -0
  208. {wade_cli-1.1.1 → wade_cli-1.1.2}/templates/skills/implementation/SKILL.md +0 -0
  209. {wade_cli-1.1.1 → wade_cli-1.1.2}/templates/skills/knowledge/SKILL.md +0 -0
  210. {wade_cli-1.1.1 → wade_cli-1.1.2}/templates/skills/plan-review/SKILL.md +0 -0
  211. {wade_cli-1.1.1 → wade_cli-1.1.2}/templates/skills/planning/SKILL.md +0 -0
  212. {wade_cli-1.1.1 → wade_cli-1.1.2}/templates/skills/review-comments/SKILL.md +0 -0
  213. {wade_cli-1.1.1 → wade_cli-1.1.2}/templates/skills/task/SKILL.md +0 -0
  214. {wade_cli-1.1.1 → wade_cli-1.1.2}/templates/skills/task/examples.md +0 -0
  215. {wade_cli-1.1.1 → wade_cli-1.1.2}/templates/skills/task/plan-format.md +0 -0
  216. {wade_cli-1.1.1 → wade_cli-1.1.2}/templates/skills/task/reference/session-summary-format.md +0 -0
  217. {wade_cli-1.1.1 → wade_cli-1.1.2}/templates/statusline-command.sh +0 -0
  218. {wade_cli-1.1.1 → wade_cli-1.1.2}/templates/workflows/_partials/completion.md +0 -0
  219. {wade_cli-1.1.1 → wade_cli-1.1.2}/templates/workflows/_partials/documentation-step.md +0 -0
  220. {wade_cli-1.1.1 → wade_cli-1.1.2}/templates/workflows/_partials/interaction-policy.md +0 -0
  221. {wade_cli-1.1.1 → wade_cli-1.1.2}/templates/workflows/_partials/knowledge-step.md +0 -0
  222. {wade_cli-1.1.1 → wade_cli-1.1.2}/templates/workflows/_partials/review-budget.md +0 -0
  223. {wade_cli-1.1.1 → wade_cli-1.1.2}/templates/workflows/plan.md +0 -0
  224. {wade_cli-1.1.1 → wade_cli-1.1.2}/templates/workflows/reference/documentation.md +0 -0
  225. {wade_cli-1.1.1 → wade_cli-1.1.2}/templates/workflows/reference/new-plan.md +0 -0
  226. {wade_cli-1.1.1 → wade_cli-1.1.2}/templates/workflows/reference/plan-output-contract.md +0 -0
  227. {wade_cli-1.1.1 → wade_cli-1.1.2}/templates/workflows/reference/pr-summary-format.md +0 -0
  228. {wade_cli-1.1.1 → wade_cli-1.1.2}/templates/workflows/reference/recovery.md +0 -0
  229. {wade_cli-1.1.1 → wade_cli-1.1.2}/templates/workflows/reference/session-summary-format.md +0 -0
  230. {wade_cli-1.1.1 → wade_cli-1.1.2}/templates/workflows/reference/tracking-issues.md +0 -0
  231. {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/__init__.py +0 -0
  232. {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/concurrency/__init__.py +0 -0
  233. {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/concurrency/test_batch_multi_pr.py +0 -0
  234. {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/concurrency/test_knowledge_merge.py +0 -0
  235. {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/concurrency/test_lock_retry.py +0 -0
  236. {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/concurrency/test_stash_race.py +0 -0
  237. {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/conftest.py +0 -0
  238. {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/e2e/__init__.py +0 -0
  239. {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/e2e/_support.py +0 -0
  240. {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/e2e/conftest.py +0 -0
  241. {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/e2e/mock_gh_script.py +0 -0
  242. {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/e2e/test_admin_contract.py +0 -0
  243. {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/e2e/test_check_contract.py +0 -0
  244. {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/e2e/test_implement_work_done_contract.py +0 -0
  245. {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/e2e/test_knowledge_contract.py +0 -0
  246. {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/e2e/test_mock_gh_contract.py +0 -0
  247. {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/e2e/test_plan_contract.py +0 -0
  248. {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/e2e/test_review_contract.py +0 -0
  249. {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/e2e/test_smart_start_contract.py +0 -0
  250. {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/e2e/test_task_contract.py +0 -0
  251. {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/e2e/test_work_sync_list_contract.py +0 -0
  252. {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/fixtures/live/taskr_header_hi_body.md +0 -0
  253. {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/fixtures/live/taskr_header_howdy_body.md +0 -0
  254. {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/fixtures/transcripts/claude_session.txt +0 -0
  255. {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/fixtures/transcripts/codex_session.txt +0 -0
  256. {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/fixtures/transcripts/copilot_session.txt +0 -0
  257. {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/fixtures/transcripts/generic_session.txt +0 -0
  258. {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/integration/__init__.py +0 -0
  259. {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/integration/test_autostash.py +0 -0
  260. {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/integration/test_check.py +0 -0
  261. {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/integration/test_dynamic_skill_sessions.py +0 -0
  262. {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/integration/test_git.py +0 -0
  263. {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/integration/test_implementation_lifecycle.py +0 -0
  264. {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/integration/test_init.py +0 -0
  265. {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/integration/test_knowledge.py +0 -0
  266. {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/integration/test_markdown_provider.py +0 -0
  267. {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/integration/test_skill_context_budget.py +0 -0
  268. {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/live/__init__.py +0 -0
  269. {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/live/test_wade_live_ai.py +0 -0
  270. {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/live/test_wade_live_ai_taskr.py +0 -0
  271. {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/live/test_wade_live_gh.py +0 -0
  272. {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/test_cli_basics.py +0 -0
  273. {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/__init__.py +0 -0
  274. {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/conftest.py +0 -0
  275. {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_cli/__init__.py +0 -0
  276. {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_cli/test_autocomplete.py +0 -0
  277. {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_cli/test_done_doc_pass_advisory.py +0 -0
  278. {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_cli/test_done_review_hint.py +0 -0
  279. {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_cli/test_knowledge_cli.py +0 -0
  280. {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_cli/test_review_cli.py +0 -0
  281. {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_cli/test_session_subapps.py +0 -0
  282. {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_cli/test_shell_init.py +0 -0
  283. {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_cli/test_skills_session_cli.py +0 -0
  284. {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_cli/test_task_cli.py +0 -0
  285. {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_cli/test_work_ai_flags.py +0 -0
  286. {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_cli/test_work_cli.py +0 -0
  287. {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_config/__init__.py +0 -0
  288. {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_config/test_bot_review_config.py +0 -0
  289. {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_config/test_dynamic_skill_config.py +0 -0
  290. {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_config/test_loader.py +0 -0
  291. {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_config/test_merge_strategy_migration.py +0 -0
  292. {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_config/test_migrations.py +0 -0
  293. {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_config/test_migrations_atomic.py +0 -0
  294. {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_console.py +0 -0
  295. {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_effort.py +0 -0
  296. {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_git/__init__.py +0 -0
  297. {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_git/test_all_patches_present.py +0 -0
  298. {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_git/test_branch_list.py +0 -0
  299. {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_git/test_branch_slugify.py +0 -0
  300. {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_git/test_detached_worktree.py +0 -0
  301. {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_git/test_is_merged_into.py +0 -0
  302. {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_git/test_main_checkout_root.py +0 -0
  303. {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_git/test_merge_count.py +0 -0
  304. {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_git/test_pr_base.py +0 -0
  305. {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_git/test_pr_lookup.py +0 -0
  306. {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_git/test_pr_retry.py +0 -0
  307. {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_git/test_pr_title.py +0 -0
  308. {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_git/test_repo.py +0 -0
  309. {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_git/test_reset_branch.py +0 -0
  310. {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_git/test_scaffold_commit.py +0 -0
  311. {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_git/test_stash_drop.py +0 -0
  312. {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_git/test_sync_conflicted_files.py +0 -0
  313. {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_git/test_tip_commit_is_empty.py +0 -0
  314. {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_git/test_worktree_remove.py +0 -0
  315. {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_hooks/__init__.py +0 -0
  316. {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_hooks/test_hook_cli.py +0 -0
  317. {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_hooks/test_policies.py +0 -0
  318. {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_hooks/test_post_tool_use.py +0 -0
  319. {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_hooks/test_session_start.py +0 -0
  320. {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_models/__init__.py +0 -0
  321. {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_models/test_batch.py +0 -0
  322. {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_models/test_config_models.py +0 -0
  323. {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_models/test_delegation.py +0 -0
  324. {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_models/test_deps_models.py +0 -0
  325. {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_models/test_deps_partition.py +0 -0
  326. {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_models/test_review.py +0 -0
  327. {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_models/test_review_expectation.py +0 -0
  328. {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_models/test_task_models.py +0 -0
  329. {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_models/test_workflow_models.py +0 -0
  330. {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_permission_mode.py +0 -0
  331. {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_providers/__init__.py +0 -0
  332. {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_providers/test_clickup.py +0 -0
  333. {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_providers/test_github.py +0 -0
  334. {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_providers/test_github_pr.py +0 -0
  335. {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_providers/test_markdown.py +0 -0
  336. {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_scripts/__init__.py +0 -0
  337. {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_scripts/test_changelog.py +0 -0
  338. {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_services/__init__.py +0 -0
  339. {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_services/test_ai_resolution.py +0 -0
  340. {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_services/test_batch_build_cmd.py +0 -0
  341. {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_services/test_batch_pr_index.py +0 -0
  342. {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_services/test_batch_review_service.py +0 -0
  343. {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_services/test_bootstrap_allowlist.py +0 -0
  344. {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_services/test_bootstrap_guard_matcher.py +0 -0
  345. {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_services/test_bootstrap_hook_timeout.py +0 -0
  346. {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_services/test_bootstrap_post_tool_use.py +0 -0
  347. {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_services/test_bootstrap_pre_push.py +0 -0
  348. {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_services/test_bootstrap_session_start.py +0 -0
  349. {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_services/test_bot_trigger.py +0 -0
  350. {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_services/test_catchup.py +0 -0
  351. {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_services/test_catchup_reconcile.py +0 -0
  352. {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_services/test_check.py +0 -0
  353. {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_services/test_cleanup_loss_guard.py +0 -0
  354. {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_services/test_codex_worktree_launch_context.py +0 -0
  355. {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_services/test_delegation_service.py +0 -0
  356. {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_services/test_deps_service.py +0 -0
  357. {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_services/test_documentation_receipt.py +0 -0
  358. {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_services/test_done_plan_flag.py +0 -0
  359. {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_services/test_done_pr_title_sync.py +0 -0
  360. {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_services/test_draft_pr_base_fetch.py +0 -0
  361. {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_services/test_draft_pr_retarget.py +0 -0
  362. {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_services/test_implementation_done_sync.py +0 -0
  363. {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_services/test_implementation_launch_context.py +0 -0
  364. {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_services/test_implementation_service.py +0 -0
  365. {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_services/test_implementation_service_resume.py +0 -0
  366. {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_services/test_init.py +0 -0
  367. {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_services/test_init_shell_integration.py +0 -0
  368. {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_services/test_knowledge_recovery.py +0 -0
  369. {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_services/test_knowledge_search.py +0 -0
  370. {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_services/test_knowledge_service.py +0 -0
  371. {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_services/test_managed_git_hooks.py +0 -0
  372. {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_services/test_no_direct_git.py +0 -0
  373. {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_services/test_plan_service.py +0 -0
  374. {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_services/test_poll_for_reviews.py +0 -0
  375. {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_services/test_post_implementation_lifecycle.py +0 -0
  376. {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_services/test_pr_lookup_guards.py +0 -0
  377. {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_services/test_pr_summary_path.py +0 -0
  378. {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_services/test_pre_push_backstop.py +0 -0
  379. {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_services/test_preserve_session_data.py +0 -0
  380. {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_services/test_prompt_delivery.py +0 -0
  381. {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_services/test_push_recovery.py +0 -0
  382. {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_services/test_resolve_task_branch.py +0 -0
  383. {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_services/test_resume_autonomy.py +0 -0
  384. {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_services/test_review_receipts.py +0 -0
  385. {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_services/test_review_service.py +0 -0
  386. {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_services/test_skill_diagnostics_service.py +0 -0
  387. {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_services/test_smart_start.py +0 -0
  388. {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_services/test_smart_start_resume.py +0 -0
  389. {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_services/test_stale_base_surface.py +0 -0
  390. {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_services/test_sync_base_branch.py +0 -0
  391. {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_services/test_task_service.py +0 -0
  392. {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_services/test_tracked_managed_files.py +0 -0
  393. {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_services/test_worktree_gitignore.py +0 -0
  394. {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_skills/test_crossby_contract.py +0 -0
  395. {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_skills/test_doc_targets.py +0 -0
  396. {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_skills/test_dynamic_skills.py +0 -0
  397. {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_skills/test_generic_builtin_boundaries.py +0 -0
  398. {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_skills/test_knowledge_attributes.py +0 -0
  399. {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_skills/test_managed_gitignore.py +0 -0
  400. {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_skills/test_pointer.py +0 -0
  401. {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_skills/test_review_budget_partial.py +0 -0
  402. {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_utils/__init__.py +0 -0
  403. {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_utils/test_body_markers.py +0 -0
  404. {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_utils/test_conventional.py +0 -0
  405. {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_utils/test_filelock.py +0 -0
  406. {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_utils/test_gitref.py +0 -0
  407. {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_utils/test_http.py +0 -0
  408. {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_utils/test_install.py +0 -0
  409. {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_utils/test_markdown.py +0 -0
  410. {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_utils/test_markers.py +0 -0
  411. {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_utils/test_plan_validation.py +0 -0
  412. {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_utils/test_process.py +0 -0
  413. {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_utils/test_prompts.py +0 -0
  414. {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_utils/test_runtime_env.py +0 -0
  415. {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_utils/test_slug.py +0 -0
  416. {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_utils/test_stale_base.py +0 -0
  417. {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_utils/test_terminal.py +0 -0
  418. {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_utils/test_terminal_launch.py +0 -0
  419. {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_utils/test_update_check.py +0 -0
  420. {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_yolo.py +0 -0
@@ -72,6 +72,13 @@ No circular dependencies. Models are pure data. Services orchestrate. **Never im
72
72
  > GitHub (PR/issue body markers, labels) and worktree files, **not** SQLite. The
73
73
  > code still exists but is inert; removal is tracked as #357 C5.
74
74
 
75
+ > **Closing-review state** lives in worktree files, not the immutable session
76
+ > bundle: binding-aware receipts are under `.wade/reviews/`, and an active
77
+ > PR-comment feedback cycle is under `.wade/review-cycles/`. The cycle preserves
78
+ > its pre-edit baseline and feedback history across refreshes and is cleared
79
+ > only after a successful PR-comment completion; unsafe state must fall back to
80
+ > a full review.
81
+
75
82
  > **Deterministic git-hook install/reconcile/build logic lives in `git/hooks.py`**
76
83
  > (git layer), not `skills/installer.py`. Packaged template-asset loaders
77
84
  > (prompt/skill/hook templates) live in `utils/templates.py`, a leaf module.
@@ -4,6 +4,12 @@ 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
+ ## [v1.1.2] — 2026-09-08
8
+
9
+ ### Bug Fixes
10
+
11
+ - make closing reviews converge on final incremental changes (#503) (5599fb1)
12
+
7
13
  ## [v1.1.1] — 2026-09-08
8
14
 
9
15
  ### Bug Fixes
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: wade-cli
3
- Version: 1.1.1
3
+ Version: 1.1.2
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
@@ -325,7 +325,7 @@ WADE splits review into an **AI review pass** you or the agent can invoke, and
325
325
  | Command | What it reviews |
326
326
  |---------|-----------------|
327
327
  | `wade review plan <file>` | A plan file, before it becomes tasks |
328
- | `wade review implementation` | The current implementation diff (mandatory gate before `done`) |
328
+ | `wade review implementation` | The current implementation change (mandatory gate before `done`) |
329
329
  | `wade review batch <N>` | Coherence across parallel implementation branches |
330
330
  | `wade review pr-comments <N>` | Starts a session to address human/bot PR comments |
331
331
  | `wade review trigger <N>` | Posts configured bot-review trigger comments on the task's PR |
@@ -341,10 +341,27 @@ implementation` exits successfully without launching another reviewer and direct
341
341
  you to `wade implementation-session done`. The dispatch cap does not block a
342
342
  valid `--ack-self-review`, which still writes its active-binding receipt.
343
343
 
344
+ The first implementation review for a frozen reviewer binding receives the
345
+ complete branch change. After a successful review and a new fix commit, a
346
+ re-review receives only the committed delta since that successful same-binding
347
+ review, plus any staged or unstaged changes and the local `PLAN.md` when safely
348
+ available. This narrows repeated review work without weakening `done`: the final
349
+ receipt is still written for the exact current commit. An unreadable record,
350
+ changed binding, unreachable baseline, or merge on the baseline-to-HEAD path
351
+ falls back to the complete branch change.
352
+
344
353
  To fetch the unresolved comments themselves during a review session, the agent
345
354
  runs `wade review-pr-comments-session fetch <N>`; it resolves individual threads with
346
355
  `wade review-pr-comments-session resolve`.
347
356
 
357
+ When a PR-comment session starts, WADE records its pre-edit commit and a feedback
358
+ snapshot under local `.wade/review-cycles/` state, outside the immutable session
359
+ bundle. Re-fetching retains prior feedback while adding new feedback, but never
360
+ advances that baseline. Its closing review receives the accumulated feedback and
361
+ the cycle delta; unsafe, mismatched, stale, or merged lineage uses the full
362
+ branch change instead. A successful `review-pr-comments-session done` clears
363
+ the cycle so later feedback starts from the then-current PR head.
364
+
348
365
  ### The auto-launched review session
349
366
 
350
367
  The auto-launched **review session** — the one started when you pick **"Wait
@@ -289,7 +289,7 @@ WADE splits review into an **AI review pass** you or the agent can invoke, and
289
289
  | Command | What it reviews |
290
290
  |---------|-----------------|
291
291
  | `wade review plan <file>` | A plan file, before it becomes tasks |
292
- | `wade review implementation` | The current implementation diff (mandatory gate before `done`) |
292
+ | `wade review implementation` | The current implementation change (mandatory gate before `done`) |
293
293
  | `wade review batch <N>` | Coherence across parallel implementation branches |
294
294
  | `wade review pr-comments <N>` | Starts a session to address human/bot PR comments |
295
295
  | `wade review trigger <N>` | Posts configured bot-review trigger comments on the task's PR |
@@ -305,10 +305,27 @@ implementation` exits successfully without launching another reviewer and direct
305
305
  you to `wade implementation-session done`. The dispatch cap does not block a
306
306
  valid `--ack-self-review`, which still writes its active-binding receipt.
307
307
 
308
+ The first implementation review for a frozen reviewer binding receives the
309
+ complete branch change. After a successful review and a new fix commit, a
310
+ re-review receives only the committed delta since that successful same-binding
311
+ review, plus any staged or unstaged changes and the local `PLAN.md` when safely
312
+ available. This narrows repeated review work without weakening `done`: the final
313
+ receipt is still written for the exact current commit. An unreadable record,
314
+ changed binding, unreachable baseline, or merge on the baseline-to-HEAD path
315
+ falls back to the complete branch change.
316
+
308
317
  To fetch the unresolved comments themselves during a review session, the agent
309
318
  runs `wade review-pr-comments-session fetch <N>`; it resolves individual threads with
310
319
  `wade review-pr-comments-session resolve`.
311
320
 
321
+ When a PR-comment session starts, WADE records its pre-edit commit and a feedback
322
+ snapshot under local `.wade/review-cycles/` state, outside the immutable session
323
+ bundle. Re-fetching retains prior feedback while adding new feedback, but never
324
+ advances that baseline. Its closing review receives the accumulated feedback and
325
+ the cycle delta; unsafe, mismatched, stale, or merged lineage uses the full
326
+ branch change instead. A successful `review-pr-comments-session done` clears
327
+ the cycle so later feedback starts from the then-current PR head.
328
+
312
329
  ### The auto-launched review session
313
330
 
314
331
  The auto-launched **review session** — the one started when you pick **"Wait
@@ -87,6 +87,7 @@ src/wade/
87
87
  │ ├── skill_invocation_service.py # Bind methods to bounded operation contracts
88
88
  │ ├── skill_diagnostics_service.py # Discovery and precedence diagnostics
89
89
  │ ├── review_record_service.py # Binding-aware durable review outcomes
90
+ │ ├── review_cycle_service.py # Safe PR-comment feedback-cycle context
90
91
  │ ├── documentation_receipt_service.py # Current-commit documentation decision
91
92
  │ ├── batch_review_service.py # Batch issue review
92
93
  │ ├── deps_service.py # Dependency analysis
@@ -809,6 +810,22 @@ still records the current binding receipt because it acknowledges work already
809
810
  performed rather than dispatching a reviewer. Plan and PR-comment reviews are
810
811
  uncapped.
811
812
 
813
+ Review input converges without weakening completion evidence. The first mapped
814
+ implementation review uses the full branch diff; later reviews may select the
815
+ nearest satisfying record for the same frozen REVIEW binding that is an ancestor
816
+ of `HEAD`, then inspect only the delta from that commit. A Git error, malformed
817
+ or foreign record, non-ancestor, or merge on that ancestry path forces the full
818
+ branch diff. Staged and unstaged changes remain separate inputs in either case,
819
+ and every review writes its ordinary exact-HEAD receipt.
820
+
821
+ `review_cycle_service.py` stores the PR-comment session's issue/PR identity,
822
+ pre-edit baseline, and normalized feedback in `.wade/review-cycles/`, outside
823
+ the immutable session bundle. Re-fetching retains prior feedback and adds new
824
+ feedback while preserving the baseline; a successful PR-comment `done` removes
825
+ it. The closing review receives the accumulated feedback and cycle delta only
826
+ after safe state, identity, ancestry, and merge-path checks. Any failed check
827
+ emits a diagnostic and reviews the complete branch diff instead.
828
+
812
829
  The capability remediation is intentionally tool-neutral: retain the sandbox
813
830
  and grant only the worktree Git metadata paths, GitHub credential/API route, or
814
831
  local staging path named by the failure. Codex uses the resolved `ai.sandbox`
@@ -918,7 +935,7 @@ includes the active binding's pass count. The structured worktree records under
918
935
  `.wade/reviews/` provide deterministic gate evidence; the PR-body projection is
919
936
  the durable human-readable outcome.
920
937
 
921
- Binding-aware review records and documentation receipts use the descriptor-safe
938
+ Binding-aware review records, review-cycle contexts, and documentation receipts use the descriptor-safe
922
939
  state primitives in `utils/safe_state.py`: `.wade` and intermediate directories
923
940
  must be real directories, final files are opened relative to trusted directory
924
941
  handles, and malformed/symlinked/unreadable state is treated as absent. Record
@@ -175,6 +175,14 @@ An implementation-to-PR-comment transition is a new session and replaces the
175
175
  manifest/workflow. Durable review records survive outside the session bundle,
176
176
  but apply only if the new session's review binding is identical.
177
177
 
178
+ The implementation and PR-comment definitions currently use workflow revision
179
+ 2. Their authoritative order is verification, documentation decision, knowledge,
180
+ then closing method review; an old frozen revision is rejected until an explicit
181
+ refresh replaces it. Expected tracked documentation and knowledge changes are
182
+ therefore normally present in the final review. If a review finding creates a
183
+ new tracked commit, the session repeats affected verification/documentation work
184
+ and reviews that new final commit.
185
+
178
186
  ## Bounded invocation contract
179
187
 
180
188
  `skill_invocation_service.py` composes bounded prompts structurally in this
@@ -228,7 +236,25 @@ active binding. Switching A to B preserves A's history but makes it inapplicable
228
236
  switching back to byte-identical A at the same commit reuses its record. Refresh
229
237
  of only WORK does not invalidate a REVIEW record.
230
238
 
231
- Manifest, review, and documentation-gate state uses descriptor-relative,
239
+ The first implementation review for a REVIEW binding uses the full branch diff.
240
+ A later one may use the nearest same-binding satisfying record that is a safe
241
+ ancestor of `HEAD`, reviewing only its committed delta while retaining staged and
242
+ unstaged sections. Git errors, malformed or other-binding records, non-ancestors,
243
+ and any merge on the selected ancestry path fail open to the full branch diff.
244
+ The record is only an input baseline: every completed review still writes the
245
+ normal exact-`HEAD`, binding-aware receipt.
246
+
247
+ PR-comment cycles persist separately at
248
+ `.wade/review-cycles/review-cycle@<issue>.json`, rather than in immutable
249
+ `.wade/session/` methodology snapshots. A cycle captures the open PR identity,
250
+ the pre-edit baseline, and normalized fetched feedback. Starting or resuming a
251
+ cycle preserves its baseline and retains prior feedback while adding newly
252
+ fetched feedback; successful `review-pr-comments-session done` removes the
253
+ context. The closing review uses that accumulated feedback plus the cycle delta
254
+ only after identity, ancestry, and merge-path validation; unreadable, mismatched,
255
+ malformed, or unsafe state falls back to a full diff.
256
+
257
+ Manifest, review, review-cycle, and documentation-gate state uses descriptor-relative,
232
258
  no-follow filesystem operations. Unsafe/malformed/unreadable state is absent for
233
259
  gate purposes and fails toward re-review, never toward completion. SHA marker
234
260
  files are never accepted as review evidence. The completion classifier also
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "wade-cli"
7
- version = "1.1.1"
7
+ version = "1.1.2"
8
8
  description = "WADE — Workflow for AI-Driven Engineering"
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -1,3 +1,3 @@
1
1
  """WADE (Workflow for AI-Driven Engineering) — AI-agent-driven git workflow management CLI."""
2
2
 
3
- __version__ = "1.1.1"
3
+ __version__ = "1.1.2"
@@ -686,6 +686,18 @@ def diff_between_checked(repo_root: Path, base: str, head: str) -> str:
686
686
  return _run_git("diff", f"{base}...{head}", cwd=repo_root).stdout
687
687
 
688
688
 
689
+ def has_merge_commit_between(repo_root: Path, base: str, head: str) -> bool:
690
+ """Return whether the ancestry path from *base* to *head* contains a merge.
691
+
692
+ Callers first establish that ``base`` is an ancestor of ``head``. Keeping
693
+ this query in the git layer lets review-input selection fail open when git
694
+ cannot establish a trustworthy incremental lineage.
695
+ """
696
+
697
+ result = _run_git("rev-list", "--merges", f"{base}..{head}", cwd=repo_root)
698
+ return bool(result.stdout.strip())
699
+
700
+
689
701
  def diff_worktree(repo_root: Path, *, staged: bool = False) -> str:
690
702
  """Return ``git diff`` output for the working tree (or the staged index).
691
703
 
@@ -0,0 +1,69 @@
1
+ """Persisted PR-comment review-cycle context."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ import re
7
+
8
+ from pydantic import BaseModel, Field, field_validator, model_validator
9
+
10
+ REVIEW_CYCLE_SCHEMA_VERSION = 1
11
+ # A persisted context includes JSON syntax and other fields, so its actual
12
+ # serialized byte length remains the authoritative limit.
13
+ MAX_REVIEW_CYCLE_PAYLOAD_BYTES = 256 * 1024
14
+ MAX_REVIEW_FEEDBACK_CHARS = MAX_REVIEW_CYCLE_PAYLOAD_BYTES
15
+ _COMMIT_RE = re.compile(r"^[0-9a-f]{7,64}$")
16
+
17
+
18
+ class ReviewCycleContext(BaseModel, frozen=True):
19
+ """The immutable baseline and mutable feedback snapshot for one PR cycle."""
20
+
21
+ schema_version: int = Field(default=REVIEW_CYCLE_SCHEMA_VERSION)
22
+ issue_number: str
23
+ pr_number: int = Field(gt=0)
24
+ baseline_commit: str
25
+ feedback: str = Field(min_length=1, max_length=MAX_REVIEW_FEEDBACK_CHARS)
26
+
27
+ @field_validator("schema_version")
28
+ @classmethod
29
+ def _schema(cls, value: int) -> int:
30
+ if value != REVIEW_CYCLE_SCHEMA_VERSION:
31
+ raise ValueError(f"Unsupported review-cycle schema {value}")
32
+ return value
33
+
34
+ @field_validator("issue_number")
35
+ @classmethod
36
+ def _issue_number(cls, value: str) -> str:
37
+ normalized = value.strip().lstrip("#")
38
+ if not normalized.isdigit() or int(normalized) <= 0:
39
+ raise ValueError("Review-cycle issue number must be positive")
40
+ return str(int(normalized))
41
+
42
+ @field_validator("baseline_commit")
43
+ @classmethod
44
+ def _commit(cls, value: str) -> str:
45
+ if not _COMMIT_RE.fullmatch(value):
46
+ raise ValueError("Review-cycle baseline must be a lowercase hexadecimal git object id")
47
+ return value
48
+
49
+ @field_validator("feedback")
50
+ @classmethod
51
+ def _feedback(cls, value: str) -> str:
52
+ normalized = value.replace("\r\n", "\n").replace("\r", "\n").strip()
53
+ if not normalized:
54
+ raise ValueError("Review-cycle feedback cannot be empty")
55
+ return normalized
56
+
57
+ def serialized_payload(self) -> bytes:
58
+ """Return the exact UTF-8 state-file representation for this context."""
59
+
60
+ return (
61
+ json.dumps(self.model_dump(mode="json"), indent=2, sort_keys=True, ensure_ascii=True)
62
+ + "\n"
63
+ ).encode("utf-8")
64
+
65
+ @model_validator(mode="after")
66
+ def _serialized_payload_fits_state_file(self) -> ReviewCycleContext:
67
+ if len(self.serialized_payload()) > MAX_REVIEW_CYCLE_PAYLOAD_BYTES:
68
+ raise ValueError("Review-cycle serialized payload exceeds the 256 KiB limit")
69
+ return self
@@ -178,7 +178,7 @@ SESSION_DEFINITIONS: dict[SessionKind, SessionDefinition] = {
178
178
  kind=SessionKind.IMPLEMENTATION,
179
179
  ai_command=AICommandKey.IMPLEMENT,
180
180
  workflow_template="implementation.md",
181
- workflow_revision=1,
181
+ workflow_revision=2,
182
182
  launch_prompt="implement-context.md",
183
183
  session_phase=SessionPhase.IMPLEMENT,
184
184
  steps=(
@@ -187,9 +187,9 @@ SESSION_DEFINITIONS: dict[SessionKind, SessionDefinition] = {
187
187
  "understand-plan",
188
188
  "implement",
189
189
  "verify",
190
- "method-review",
191
190
  "documentation",
192
191
  "knowledge",
192
+ "method-review",
193
193
  "pr-summary",
194
194
  "sync",
195
195
  "done",
@@ -214,7 +214,7 @@ SESSION_DEFINITIONS: dict[SessionKind, SessionDefinition] = {
214
214
  kind=SessionKind.REVIEW_PR_COMMENTS,
215
215
  ai_command=AICommandKey.REVIEW_PR_COMMENTS,
216
216
  workflow_template="review-pr-comments.md",
217
- workflow_revision=1,
217
+ workflow_revision=2,
218
218
  launch_prompt="review-pr-comments.md",
219
219
  session_phase=SessionPhase.REVIEW,
220
220
  steps=(
@@ -223,9 +223,9 @@ SESSION_DEFINITIONS: dict[SessionKind, SessionDefinition] = {
223
223
  "verify-feedback",
224
224
  "address-feedback",
225
225
  "verify",
226
- "method-review",
227
226
  "documentation",
228
227
  "knowledge",
228
+ "method-review",
229
229
  "pr-summary",
230
230
  "sync",
231
231
  "resolve-threads",
@@ -46,6 +46,7 @@ from wade.services.implementation_service.lifecycle import (
46
46
  _strip_summary_section,
47
47
  )
48
48
  from wade.services.implementation_service.usage_tracking import IMPL_USAGE_MARKER_START
49
+ from wade.services.review_cycle_service import clear_review_cycle
49
50
  from wade.services.review_record_service import (
50
51
  count_binding_passes,
51
52
  has_other_satisfying_binding,
@@ -387,6 +388,18 @@ def done(
387
388
  # block + skip-worktree still in place) so the user can fix and re-run.
388
389
  return False
389
390
 
391
+ if session_type == "review-pr-comments" and not clear_review_cycle(
392
+ worktree_root, issue_number=issue_number
393
+ ):
394
+ # The PR may already be finalized, but completion remains retryable
395
+ # until the active review-cycle state is gone or durably invalidated.
396
+ # Otherwise a later feedback cycle could inherit an obsolete baseline.
397
+ console.error(
398
+ "Could not clear the completed PR-comment review-cycle state safely; "
399
+ "restore filesystem access and re-run done."
400
+ )
401
+ return False
402
+
390
403
  # Success only: strip the worktree gitignore block and restore .gitignore
391
404
  # visibility now that there is nothing left to retry. The PR is already
392
405
  # updated at this point, so a filesystem cleanup failure (read-only file,
@@ -0,0 +1,192 @@
1
+ """Safe PR-comment review-cycle state outside immutable session bundles."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import re
6
+ from dataclasses import dataclass
7
+ from pathlib import Path
8
+
9
+ from pydantic import ValidationError
10
+
11
+ from wade.models.review_cycle import ReviewCycleContext
12
+ from wade.utils.safe_state import (
13
+ MAX_STATE_FILE_BYTES,
14
+ atomic_write_state_file,
15
+ delete_state_file,
16
+ read_state_file,
17
+ state_file_present,
18
+ )
19
+
20
+ _DIRECTORIES = ("review-cycles",)
21
+ _INVALIDATION_DIRECTORIES = ("review-cycle-invalidations",)
22
+ _PREFIX = "review-cycle@"
23
+ _FEEDBACK_SECTION = re.compile(r"(?m)(?=^### )")
24
+
25
+
26
+ @dataclass(frozen=True)
27
+ class ReviewCycleLookup:
28
+ """A context lookup that preserves an unsafe-state diagnostic."""
29
+
30
+ context: ReviewCycleContext | None
31
+ diagnostic: str | None = None
32
+
33
+
34
+ def review_cycle_filename(issue_number: str) -> str:
35
+ """Return the one canonical context filename for an issue's active cycle."""
36
+
37
+ normalized = issue_number.strip().lstrip("#")
38
+ if not normalized.isdigit() or int(normalized) <= 0:
39
+ raise ValueError("Review-cycle issue number must be positive")
40
+ return f"{_PREFIX}{int(normalized)}.json"
41
+
42
+
43
+ def read_review_cycle(
44
+ root: Path,
45
+ *,
46
+ issue_number: str,
47
+ pr_number: int,
48
+ ) -> ReviewCycleLookup:
49
+ """Read a current identity-matched cycle, never trusting malformed state."""
50
+
51
+ try:
52
+ filename = review_cycle_filename(issue_number)
53
+ except ValueError as exc:
54
+ return ReviewCycleLookup(None, str(exc))
55
+ if state_file_present(root, _INVALIDATION_DIRECTORIES, filename):
56
+ return ReviewCycleLookup(
57
+ None,
58
+ "PR-comment review-cycle state was invalidated after an unsafe update",
59
+ )
60
+ raw = read_state_file(root, _DIRECTORIES, filename)
61
+ if raw is None:
62
+ if state_file_present(root, _DIRECTORIES, filename):
63
+ return ReviewCycleLookup(
64
+ None,
65
+ "PR-comment review-cycle state is unreadable or unsafe",
66
+ )
67
+ return ReviewCycleLookup(None)
68
+ try:
69
+ context = ReviewCycleContext.model_validate_json(raw)
70
+ except (ValidationError, ValueError):
71
+ return ReviewCycleLookup(None, "PR-comment review-cycle state is malformed")
72
+ if context.issue_number != str(int(issue_number.lstrip("#"))):
73
+ return ReviewCycleLookup(None, "PR-comment review-cycle issue identity does not match")
74
+ if context.pr_number != pr_number:
75
+ return ReviewCycleLookup(None, "PR-comment review-cycle PR identity does not match")
76
+ return ReviewCycleLookup(context)
77
+
78
+
79
+ def _feedback_sections(feedback: str) -> tuple[str, ...]:
80
+ """Return individually rendered review entries from a feedback snapshot."""
81
+
82
+ return tuple(
83
+ section.strip() for section in _FEEDBACK_SECTION.split(feedback)[1:] if section.strip()
84
+ )
85
+
86
+
87
+ def _merge_feedback(existing_feedback: str, refreshed_feedback: str) -> str:
88
+ """Retain feedback already addressed while adding new feedback from a refresh."""
89
+
90
+ if refreshed_feedback in existing_feedback:
91
+ return existing_feedback
92
+
93
+ refreshed_sections = _feedback_sections(refreshed_feedback)
94
+ if refreshed_sections:
95
+ existing_sections = set(_feedback_sections(existing_feedback))
96
+ additions = [section for section in refreshed_sections if section not in existing_sections]
97
+ if not additions:
98
+ return existing_feedback
99
+ return f"{existing_feedback}\n\n## Feedback added during refresh\n\n" + "\n\n".join(
100
+ additions
101
+ )
102
+
103
+ return f"{existing_feedback}\n\n{refreshed_feedback}"
104
+
105
+
106
+ def initialize_or_refresh_review_cycle(
107
+ root: Path,
108
+ *,
109
+ issue_number: str,
110
+ pr_number: int,
111
+ baseline_commit: str,
112
+ feedback: str,
113
+ ) -> ReviewCycleLookup:
114
+ """Create a cycle or retain its baseline and all feedback across refreshes."""
115
+
116
+ try:
117
+ filename = review_cycle_filename(issue_number)
118
+ except ValueError as exc:
119
+ return ReviewCycleLookup(None, f"Could not validate PR-comment review-cycle state: {exc}")
120
+ invalidation = _discard_invalidated_review_cycle(root, filename)
121
+ if invalidation is not None:
122
+ return ReviewCycleLookup(None, invalidation)
123
+
124
+ existing = read_review_cycle(root, issue_number=issue_number, pr_number=pr_number)
125
+ if existing.diagnostic is not None:
126
+ return existing
127
+ try:
128
+ context = (
129
+ ReviewCycleContext(
130
+ **(
131
+ existing.context.model_dump()
132
+ | {"feedback": _merge_feedback(existing.context.feedback, feedback)}
133
+ )
134
+ )
135
+ if existing.context is not None
136
+ else ReviewCycleContext(
137
+ issue_number=issue_number,
138
+ pr_number=pr_number,
139
+ baseline_commit=baseline_commit,
140
+ feedback=feedback,
141
+ )
142
+ )
143
+ except (ValidationError, ValueError) as exc:
144
+ return ReviewCycleLookup(None, f"Could not validate PR-comment review-cycle state: {exc}")
145
+ payload = context.serialized_payload()
146
+ if len(payload) > MAX_STATE_FILE_BYTES:
147
+ return ReviewCycleLookup(
148
+ None,
149
+ "Could not persist PR-comment review-cycle state: serialized payload exceeds "
150
+ "the 256 KiB limit",
151
+ )
152
+ if not atomic_write_state_file(root, _DIRECTORIES, filename, payload):
153
+ if clear_review_cycle(root, issue_number=issue_number):
154
+ return ReviewCycleLookup(
155
+ None,
156
+ "Could not persist PR-comment review-cycle state safely; the prior state was "
157
+ "invalidated",
158
+ )
159
+ return ReviewCycleLookup(None, "Could not persist PR-comment review-cycle state safely")
160
+ return ReviewCycleLookup(context)
161
+
162
+
163
+ def clear_review_cycle(root: Path, *, issue_number: str) -> bool:
164
+ """Remove an active cycle, or durably invalidate it when removal is unsafe."""
165
+
166
+ try:
167
+ filename = review_cycle_filename(issue_number)
168
+ except ValueError:
169
+ return False
170
+ if not state_file_present(root, _DIRECTORIES, filename):
171
+ return True
172
+ if delete_state_file(root, _DIRECTORIES, filename):
173
+ return True
174
+ # A successful marker makes every later read fail closed, even if the old
175
+ # regular JSON file remains readable after a failed deletion. It lives in
176
+ # a sibling directory so a read-only review-cycles directory can still be
177
+ # invalidated through its writable .wade parent.
178
+ return atomic_write_state_file(root, _INVALIDATION_DIRECTORIES, filename, b"")
179
+
180
+
181
+ def _discard_invalidated_review_cycle(root: Path, filename: str) -> str | None:
182
+ """Clear a previous invalidation before beginning a fresh feedback cycle."""
183
+
184
+ if not state_file_present(root, _INVALIDATION_DIRECTORIES, filename):
185
+ return None
186
+ if state_file_present(root, _DIRECTORIES, filename) and not delete_state_file(
187
+ root, _DIRECTORIES, filename
188
+ ):
189
+ return "Could not discard invalidated PR-comment review-cycle state safely"
190
+ if not delete_state_file(root, _INVALIDATION_DIRECTORIES, filename):
191
+ return "Could not clear PR-comment review-cycle invalidation safely"
192
+ return None