agex-cli 0.23.0__tar.gz → 0.24.0__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 (214) hide show
  1. {agex_cli-0.23.0 → agex_cli-0.24.0}/CHANGELOG.md +11 -0
  2. {agex_cli-0.23.0 → agex_cli-0.24.0}/CLAUDE.md +2 -2
  3. {agex_cli-0.23.0 → agex_cli-0.24.0}/PKG-INFO +1 -1
  4. {agex_cli-0.23.0 → agex_cli-0.24.0}/pyproject.toml +1 -1
  5. {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/cli.py +25 -0
  6. {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/commands/pr/SKILL.md +24 -3
  7. {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/commands/pr/assets/backends/acp.yaml +1 -0
  8. {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/commands/pr/assets/backends/claude-code.yaml +1 -0
  9. {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/commands/pr/assets/backends/codex.yaml +1 -0
  10. {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/commands/pr/assets/backends/copilot.yaml +1 -0
  11. {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/commands/pr/assets/rules/next_step_rules.py +4 -0
  12. {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/commands/pr/assets/templates/pr_open_result.md.j2 +5 -1
  13. agex_cli-0.24.0/src/agent_experience/commands/pr/assets/templates/pr_review_result.md.j2 +5 -0
  14. {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/commands/pr/scripts/await_.py +1 -10
  15. {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/commands/pr/scripts/open_.py +23 -1
  16. {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/commands/pr/scripts/read.py +1 -10
  17. agex_cli-0.24.0/src/agent_experience/commands/pr/scripts/review.py +59 -0
  18. {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/core/github.py +15 -0
  19. {agex_cli-0.23.0 → agex_cli-0.24.0}/tests/commands/pr/test_open.py +90 -0
  20. agex_cli-0.24.0/tests/commands/pr/test_review.py +97 -0
  21. {agex_cli-0.23.0 → agex_cli-0.24.0}/uv.lock +1 -1
  22. {agex_cli-0.23.0 → agex_cli-0.24.0}/.claude/skills/agent-config/SKILL.md +0 -0
  23. {agex_cli-0.23.0 → agex_cli-0.24.0}/.claude/skills/agent-config/data/backend-fingerprints.yaml +0 -0
  24. {agex_cli-0.23.0 → agex_cli-0.24.0}/.claude/skills/agent-config/scripts/show.sh +0 -0
  25. {agex_cli-0.23.0 → agex_cli-0.24.0}/.claude/skills/assign-to-workforce/SKILL.md +0 -0
  26. {agex_cli-0.23.0 → agex_cli-0.24.0}/.claude/skills/assign-to-workforce/scripts/assign-to-workforce.sh +0 -0
  27. {agex_cli-0.23.0 → agex_cli-0.24.0}/.claude/skills/cicd/SKILL.md +0 -0
  28. {agex_cli-0.23.0 → agex_cli-0.24.0}/.claude/skills/cicd/scripts/workflow.sh +0 -0
  29. {agex_cli-0.23.0 → agex_cli-0.24.0}/.claude/skills/communicate/SKILL.md +0 -0
  30. {agex_cli-0.23.0 → agex_cli-0.24.0}/.claude/skills/communicate/scripts/fetch-issues.sh +0 -0
  31. {agex_cli-0.23.0 → agex_cli-0.24.0}/.claude/skills/communicate/scripts/mesh-message.sh +0 -0
  32. {agex_cli-0.23.0 → agex_cli-0.24.0}/.claude/skills/communicate/scripts/post-comment.sh +0 -0
  33. {agex_cli-0.23.0 → agex_cli-0.24.0}/.claude/skills/communicate/scripts/post-issue.sh +0 -0
  34. {agex_cli-0.23.0 → agex_cli-0.24.0}/.claude/skills/doc-test-alignment/SKILL.md +0 -0
  35. {agex_cli-0.23.0 → agex_cli-0.24.0}/.claude/skills/doc-test-alignment/scripts/check.sh +0 -0
  36. {agex_cli-0.23.0 → agex_cli-0.24.0}/.claude/skills/pypi-maintainer/SKILL.md +0 -0
  37. {agex_cli-0.23.0 → agex_cli-0.24.0}/.claude/skills/pypi-maintainer/scripts/switch-source.sh +0 -0
  38. {agex_cli-0.23.0 → agex_cli-0.24.0}/.claude/skills/run-tests/SKILL.md +0 -0
  39. {agex_cli-0.23.0 → agex_cli-0.24.0}/.claude/skills/run-tests/scripts/test.sh +0 -0
  40. {agex_cli-0.23.0 → agex_cli-0.24.0}/.claude/skills/sonarclaude/SKILL.md +0 -0
  41. {agex_cli-0.23.0 → agex_cli-0.24.0}/.claude/skills/sonarclaude/scripts/sonar.sh +0 -0
  42. {agex_cli-0.23.0 → agex_cli-0.24.0}/.claude/skills/spec-to-plan/SKILL.md +0 -0
  43. {agex_cli-0.23.0 → agex_cli-0.24.0}/.claude/skills/spec-to-plan/scripts/spec-to-plan.sh +0 -0
  44. {agex_cli-0.23.0 → agex_cli-0.24.0}/.claude/skills/think/SKILL.md +0 -0
  45. {agex_cli-0.23.0 → agex_cli-0.24.0}/.claude/skills/think/scripts/think.sh +0 -0
  46. {agex_cli-0.23.0 → agex_cli-0.24.0}/.claude/skills/version-bump/SKILL.md +0 -0
  47. {agex_cli-0.23.0 → agex_cli-0.24.0}/.claude/skills/version-bump/scripts/bump.py +0 -0
  48. {agex_cli-0.23.0 → agex_cli-0.24.0}/.flake8 +0 -0
  49. {agex_cli-0.23.0 → agex_cli-0.24.0}/.github/workflows/publish.yml +0 -0
  50. {agex_cli-0.23.0 → agex_cli-0.24.0}/.github/workflows/test.yml +0 -0
  51. {agex_cli-0.23.0 → agex_cli-0.24.0}/.gitignore +0 -0
  52. {agex_cli-0.23.0 → agex_cli-0.24.0}/.python-version +0 -0
  53. {agex_cli-0.23.0 → agex_cli-0.24.0}/LICENSE +0 -0
  54. {agex_cli-0.23.0 → agex_cli-0.24.0}/README.md +0 -0
  55. {agex_cli-0.23.0 → agex_cli-0.24.0}/culture.yaml +0 -0
  56. {agex_cli-0.23.0 → agex_cli-0.24.0}/docs/skill-sources.md +0 -0
  57. {agex_cli-0.23.0 → agex_cli-0.24.0}/docs/superpowers/plans/2026-04-18-agex-v0.1.md +0 -0
  58. {agex_cli-0.23.0 → agex_cli-0.24.0}/docs/superpowers/plans/2026-05-10-agex-pr.md +0 -0
  59. {agex_cli-0.23.0 → agex_cli-0.24.0}/docs/superpowers/specs/2026-04-18-agex-design.md +0 -0
  60. {agex_cli-0.23.0 → agex_cli-0.24.0}/docs/superpowers/specs/2026-04-26-agex-doctor.md +0 -0
  61. {agex_cli-0.23.0 → agex_cli-0.24.0}/docs/superpowers/specs/2026-05-10-agex-pr-design.md +0 -0
  62. {agex_cli-0.23.0 → agex_cli-0.24.0}/sonar-project.properties +0 -0
  63. {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/__init__.py +0 -0
  64. {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/__main__.py +0 -0
  65. {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/backends/__init__.py +0 -0
  66. {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/backends/acp/__init__.py +0 -0
  67. {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/backends/acp/probe.py +0 -0
  68. {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/backends/capabilities/acp.yaml +0 -0
  69. {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/backends/capabilities/claude-code.yaml +0 -0
  70. {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/backends/capabilities/codex.yaml +0 -0
  71. {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/backends/capabilities/copilot.yaml +0 -0
  72. {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/backends/claude_code/__init__.py +0 -0
  73. {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/backends/claude_code/probe.py +0 -0
  74. {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/backends/codex/__init__.py +0 -0
  75. {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/backends/codex/probe.py +0 -0
  76. {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/backends/copilot/__init__.py +0 -0
  77. {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/backends/copilot/probe.py +0 -0
  78. {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/commands/__init__.py +0 -0
  79. {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/commands/doctor/SKILL.md +0 -0
  80. {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/commands/doctor/__init__.py +0 -0
  81. {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/commands/doctor/assets/report.md.j2 +0 -0
  82. {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/commands/doctor/references/design.md +0 -0
  83. {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/commands/doctor/scripts/__init__.py +0 -0
  84. {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/commands/doctor/scripts/doctor.py +0 -0
  85. {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/commands/explain/SKILL.md +0 -0
  86. {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/commands/explain/__init__.py +0 -0
  87. {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/commands/explain/assets/topics/agex.md +0 -0
  88. {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/commands/explain/references/.gitkeep +0 -0
  89. {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/commands/explain/scripts/__init__.py +0 -0
  90. {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/commands/explain/scripts/explain.py +0 -0
  91. {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/commands/gamify/SKILL.md +0 -0
  92. {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/commands/gamify/__init__.py +0 -0
  93. {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/commands/gamify/assets/hooks/claude-code.json +0 -0
  94. {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/commands/gamify/references/.gitkeep +0 -0
  95. {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/commands/gamify/scripts/__init__.py +0 -0
  96. {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/commands/gamify/scripts/install.py +0 -0
  97. {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/commands/hook/SKILL.md +0 -0
  98. {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/commands/hook/__init__.py +0 -0
  99. {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/commands/hook/assets/table.md.j2 +0 -0
  100. {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/commands/hook/references/.gitkeep +0 -0
  101. {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/commands/hook/scripts/__init__.py +0 -0
  102. {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/commands/hook/scripts/read.py +0 -0
  103. {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/commands/hook/scripts/write.py +0 -0
  104. {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/commands/learn/SKILL.md +0 -0
  105. {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/commands/learn/__init__.py +0 -0
  106. {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/commands/learn/assets/menu.md.j2 +0 -0
  107. {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/commands/learn/assets/topics/cicd/SKILL.md +0 -0
  108. {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/commands/learn/assets/topics/gamify/SKILL.md +0 -0
  109. {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/commands/learn/assets/topics/gamify/assets/skill-template/claude-code/SKILL.md +0 -0
  110. {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/commands/learn/assets/topics/introspect/SKILL.md +0 -0
  111. {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/commands/learn/assets/topics/introspect/assets/skill-template/claude-code/SKILL.md +0 -0
  112. {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/commands/learn/assets/topics/levelup/SKILL.md +0 -0
  113. {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/commands/learn/assets/topics/levelup/assets/skill-template/claude-code/SKILL.md +0 -0
  114. {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/commands/learn/assets/topics/visualize/SKILL.md +0 -0
  115. {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/commands/learn/assets/topics/visualize/assets/skill-template/claude-code/SKILL.md +0 -0
  116. {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/commands/learn/references/.gitkeep +0 -0
  117. {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/commands/learn/scripts/__init__.py +0 -0
  118. {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/commands/learn/scripts/learn.py +0 -0
  119. {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/commands/overview/SKILL.md +0 -0
  120. {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/commands/overview/__init__.py +0 -0
  121. {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/commands/overview/assets/backends/acp.yaml +0 -0
  122. {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/commands/overview/assets/backends/claude-code.yaml +0 -0
  123. {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/commands/overview/assets/backends/codex.yaml +0 -0
  124. {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/commands/overview/assets/backends/copilot.yaml +0 -0
  125. {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/commands/overview/assets/sections.md.j2 +0 -0
  126. {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/commands/overview/references/.gitkeep +0 -0
  127. {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/commands/overview/scripts/__init__.py +0 -0
  128. {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/commands/overview/scripts/overview.py +0 -0
  129. {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/commands/pr/__init__.py +0 -0
  130. {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/commands/pr/assets/__init__.py +0 -0
  131. {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/commands/pr/assets/backends/__init__.py +0 -0
  132. {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/commands/pr/assets/rules/__init__.py +0 -0
  133. {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/commands/pr/assets/rules/lint_rules.py +0 -0
  134. {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/commands/pr/assets/templates/__init__.py +0 -0
  135. {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/commands/pr/assets/templates/delta.md.j2 +0 -0
  136. {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/commands/pr/assets/templates/footer.md.j2 +0 -0
  137. {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/commands/pr/assets/templates/lint_result.md.j2 +0 -0
  138. {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/commands/pr/assets/templates/pr_briefing.md.j2 +0 -0
  139. {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/commands/pr/assets/templates/pr_reply_result.md.j2 +0 -0
  140. {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/commands/pr/scripts/__init__.py +0 -0
  141. {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/commands/pr/scripts/_footer.py +0 -0
  142. {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/commands/pr/scripts/_journal.py +0 -0
  143. {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/commands/pr/scripts/_qodo.py +0 -0
  144. {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/commands/pr/scripts/_readiness.py +0 -0
  145. {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/commands/pr/scripts/_sonar.py +0 -0
  146. {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/commands/pr/scripts/delta.py +0 -0
  147. {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/commands/pr/scripts/lint.py +0 -0
  148. {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/commands/pr/scripts/reply.py +0 -0
  149. {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/core/__init__.py +0 -0
  150. {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/core/backend.py +0 -0
  151. {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/core/capabilities.py +0 -0
  152. {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/core/config.py +0 -0
  153. {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/core/hook_io.py +0 -0
  154. {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/core/journal.py +0 -0
  155. {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/core/paths.py +0 -0
  156. {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/core/prog.py +0 -0
  157. {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/core/render.py +0 -0
  158. {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/core/skill_loader.py +0 -0
  159. {agex_cli-0.23.0 → agex_cli-0.24.0}/tester-agents/claude/.claude/settings.json +0 -0
  160. {agex_cli-0.23.0 → agex_cli-0.24.0}/tester-agents/claude/CLAUDE.md +0 -0
  161. {agex_cli-0.23.0 → agex_cli-0.24.0}/tester-agents/claude/README.md +0 -0
  162. {agex_cli-0.23.0 → agex_cli-0.24.0}/tester-agents/claude/culture.yaml +0 -0
  163. {agex_cli-0.23.0 → agex_cli-0.24.0}/tests/__init__.py +0 -0
  164. {agex_cli-0.23.0 → agex_cli-0.24.0}/tests/backends/__init__.py +0 -0
  165. {agex_cli-0.23.0 → agex_cli-0.24.0}/tests/backends/test_claude_code_probe.py +0 -0
  166. {agex_cli-0.23.0 → agex_cli-0.24.0}/tests/backends/test_stub_probes.py +0 -0
  167. {agex_cli-0.23.0 → agex_cli-0.24.0}/tests/commands/__init__.py +0 -0
  168. {agex_cli-0.23.0 → agex_cli-0.24.0}/tests/commands/pr/__init__.py +0 -0
  169. {agex_cli-0.23.0 → agex_cli-0.24.0}/tests/commands/pr/fixtures/gh/.gitkeep +0 -0
  170. {agex_cli-0.23.0 → agex_cli-0.24.0}/tests/commands/pr/fixtures/gh/pr_checks_42.json +0 -0
  171. {agex_cli-0.23.0 → agex_cli-0.24.0}/tests/commands/pr/fixtures/gh/pr_comments_42.json +0 -0
  172. {agex_cli-0.23.0 → agex_cli-0.24.0}/tests/commands/pr/fixtures/gh/qodo_summary_comment.html +0 -0
  173. {agex_cli-0.23.0 → agex_cli-0.24.0}/tests/commands/pr/fixtures/journals/dogfood_40.jsonl +0 -0
  174. {agex_cli-0.23.0 → agex_cli-0.24.0}/tests/commands/pr/test_await.py +0 -0
  175. {agex_cli-0.23.0 → agex_cli-0.24.0}/tests/commands/pr/test_delta.py +0 -0
  176. {agex_cli-0.23.0 → agex_cli-0.24.0}/tests/commands/pr/test_footer.py +0 -0
  177. {agex_cli-0.23.0 → agex_cli-0.24.0}/tests/commands/pr/test_lint.py +0 -0
  178. {agex_cli-0.23.0 → agex_cli-0.24.0}/tests/commands/pr/test_lint_rules.py +0 -0
  179. {agex_cli-0.23.0 → agex_cli-0.24.0}/tests/commands/pr/test_qodo.py +0 -0
  180. {agex_cli-0.23.0 → agex_cli-0.24.0}/tests/commands/pr/test_read.py +0 -0
  181. {agex_cli-0.23.0 → agex_cli-0.24.0}/tests/commands/pr/test_reply.py +0 -0
  182. {agex_cli-0.23.0 → agex_cli-0.24.0}/tests/commands/test_doctor.py +0 -0
  183. {agex_cli-0.23.0 → agex_cli-0.24.0}/tests/commands/test_explain.py +0 -0
  184. {agex_cli-0.23.0 → agex_cli-0.24.0}/tests/commands/test_gamify.py +0 -0
  185. {agex_cli-0.23.0 → agex_cli-0.24.0}/tests/commands/test_hook.py +0 -0
  186. {agex_cli-0.23.0 → agex_cli-0.24.0}/tests/commands/test_learn.py +0 -0
  187. {agex_cli-0.23.0 → agex_cli-0.24.0}/tests/commands/test_overview.py +0 -0
  188. {agex_cli-0.23.0 → agex_cli-0.24.0}/tests/commands/test_prog_propagation.py +0 -0
  189. {agex_cli-0.23.0 → agex_cli-0.24.0}/tests/core/__init__.py +0 -0
  190. {agex_cli-0.23.0 → agex_cli-0.24.0}/tests/core/test_backend.py +0 -0
  191. {agex_cli-0.23.0 → agex_cli-0.24.0}/tests/core/test_capabilities.py +0 -0
  192. {agex_cli-0.23.0 → agex_cli-0.24.0}/tests/core/test_config.py +0 -0
  193. {agex_cli-0.23.0 → agex_cli-0.24.0}/tests/core/test_github.py +0 -0
  194. {agex_cli-0.23.0 → agex_cli-0.24.0}/tests/core/test_hook_io.py +0 -0
  195. {agex_cli-0.23.0 → agex_cli-0.24.0}/tests/core/test_journal.py +0 -0
  196. {agex_cli-0.23.0 → agex_cli-0.24.0}/tests/core/test_paths.py +0 -0
  197. {agex_cli-0.23.0 → agex_cli-0.24.0}/tests/core/test_prog.py +0 -0
  198. {agex_cli-0.23.0 → agex_cli-0.24.0}/tests/core/test_render.py +0 -0
  199. {agex_cli-0.23.0 → agex_cli-0.24.0}/tests/core/test_resolve_backend.py +0 -0
  200. {agex_cli-0.23.0 → agex_cli-0.24.0}/tests/core/test_skill_loader.py +0 -0
  201. {agex_cli-0.23.0 → agex_cli-0.24.0}/tests/core/test_version_lookup.py +0 -0
  202. {agex_cli-0.23.0 → agex_cli-0.24.0}/tests/fixtures/claude-code/empty/.gitkeep +0 -0
  203. {agex_cli-0.23.0 → agex_cli-0.24.0}/tests/fixtures/claude-code/malformed/.claude/hooks.json +0 -0
  204. {agex_cli-0.23.0 → agex_cli-0.24.0}/tests/fixtures/claude-code/malformed/.claude/settings.json +0 -0
  205. {agex_cli-0.23.0 → agex_cli-0.24.0}/tests/fixtures/claude-code/malformed/.claude/skills/bad/SKILL.md +0 -0
  206. {agex_cli-0.23.0 → agex_cli-0.24.0}/tests/fixtures/claude-code/malformed/.claude/skills/broken-yaml/SKILL.md +0 -0
  207. {agex_cli-0.23.0 → agex_cli-0.24.0}/tests/fixtures/claude-code/typical/.claude/hooks.json +0 -0
  208. {agex_cli-0.23.0 → agex_cli-0.24.0}/tests/fixtures/claude-code/typical/.claude/settings.json +0 -0
  209. {agex_cli-0.23.0 → agex_cli-0.24.0}/tests/fixtures/claude-code/typical/.claude/skills/example/SKILL.md +0 -0
  210. {agex_cli-0.23.0 → agex_cli-0.24.0}/tests/fixtures/claude-code/typical/CLAUDE.md +0 -0
  211. {agex_cli-0.23.0 → agex_cli-0.24.0}/tests/test_cli_dispatch.py +0 -0
  212. {agex_cli-0.23.0 → agex_cli-0.24.0}/tests/test_cli_errors.py +0 -0
  213. {agex_cli-0.23.0 → agex_cli-0.24.0}/tests/test_cli_smoke.py +0 -0
  214. {agex_cli-0.23.0 → agex_cli-0.24.0}/tests/test_skill_md_consistency.py +0 -0
@@ -7,6 +7,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.24.0] - 2026-05-28
11
+
12
+ ### Added
13
+
14
+ - `agex pr review [<PR>]` verb — posts the Qodo agentic-review trigger (`/agentic_review`) on demand; re-triggers a review on an existing PR or triggers a draft once ready.
15
+ - `agex pr open` now auto-posts `/agentic_review` out of the box for a freshly created, non-draft PR (skipped for drafts and already-open PRs) so the review trigger is never hand-typed (#60).
16
+
17
+ ### Changed
18
+
19
+ - Centralized the Qodo review-trigger command string in one place (`commands/pr/scripts/review.QODO_REVIEW_TRIGGER`). agex emits the current `/agentic_review`; the legacy `/improve` command is deprecated by Qodo and is never emitted by agex (#60).
20
+
10
21
  ## [0.23.0] - 2026-05-28
11
22
 
12
23
  ### Added
@@ -19,7 +19,7 @@ Read the spec before any non-trivial change — the design invariants below are
19
19
  1. **Zero LLM calls inside agex.** All output is deterministic markdown from Jinja templates + Python.
20
20
  2. **Markdown is the only output format.** No `--json` flag.
21
21
  3. **`--agent <backend>` is required** on backend-sensitive commands. The CLI never auto-detects.
22
- 4. **Side effects only in** `gamify`, `gamify --uninstall`, `hook write`, `pr open`, `pr reply`, `pr read` (journal writes), and first-run `.agex/` init. Everything else is read-only. The `agex pr` namespace allows scoped network I/O (via `gh`) and bounded `--wait` sleep — a deliberate carve-out from the no-network/no-sleep invariants.
22
+ 4. **Side effects only in** `gamify`, `gamify --uninstall`, `hook write`, `pr open`, `pr reply`, `pr review`, `pr read` (journal writes), and first-run `.agex/` init. Everything else is read-only. The `agex pr` namespace allows scoped network I/O (via `gh`) and bounded `--wait` sleep — a deliberate carve-out from the no-network/no-sleep invariants.
23
23
  5. **"Unsupported" is success** — exit 0 with a markdown notice that links to the issue tracker, not a non-zero exit.
24
24
  6. **Skills are authored by the agent, not shipped by agex.** `agex learn <topic>` teaches; `agex explain <topic>` describes; agex never writes a user skill file on the agent's behalf in v0.1.
25
25
 
@@ -43,7 +43,7 @@ cli.py ──► commands/<name>/scripts/<name>.py ──► core/render.py
43
43
 
44
44
  ## `agex pr` namespace (v0.17.0+)
45
45
 
46
- `lint`, `open`, `read`, `reply`, `delta`. Each command ends with a deterministic "Next step:" footer. The `pr` namespace allows scoped network I/O (via `gh`) and bounded `--wait` sleep — a deliberate carve-out from the no-network/no-sleep invariants. Key modules:
46
+ `lint`, `open`, `read`, `reply`, `review`, `await`, `delta`. Each command ends with a deterministic "Next step:" footer. The `pr` namespace allows scoped network I/O (via `gh`) and bounded `--wait` sleep — a deliberate carve-out from the no-network/no-sleep invariants. `pr open` (non-draft, new PR) and `pr review` post the Qodo `/agentic_review` trigger comment; the legacy `/improve` is deprecated and never emitted. The trigger string lives in one place: `commands/pr/scripts/review.QODO_REVIEW_TRIGGER`. Key modules:
47
47
 
48
48
  - `core/github.py` — thin `gh` shellout wrapper; future zero-trust httpx swap touches only this file.
49
49
  - `core/journal.py` — nested-stream JSONL append/load for `.agex/data/<dir>/<stream>.jsonl`.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: agex-cli
3
- Version: 0.23.0
3
+ Version: 0.24.0
4
4
  Summary: Agent-operated developer-experience CLI — deterministic per-backend markdown briefings for autonomous agents.
5
5
  Project-URL: Homepage, https://culture.dev/agex/
6
6
  Project-URL: Repository, https://github.com/agentculture/devex
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "agex-cli"
3
- version = "0.23.0"
3
+ version = "0.24.0"
4
4
  description = "Agent-operated developer-experience CLI — deterministic per-backend markdown briefings for autonomous agents."
5
5
  authors = [{name = "Ori Nachum"}]
6
6
  license = {text = "MIT"}
@@ -27,6 +27,7 @@ from agent_experience.commands.pr.scripts import lint as pr_lint_script
27
27
  from agent_experience.commands.pr.scripts import open_ as pr_open_script
28
28
  from agent_experience.commands.pr.scripts import read as pr_read_script
29
29
  from agent_experience.commands.pr.scripts import reply as pr_reply_script
30
+ from agent_experience.commands.pr.scripts import review as pr_review_script
30
31
  from agent_experience.core.backend import parse_backend
31
32
  from agent_experience.core.prog import prog_name
32
33
 
@@ -237,6 +238,22 @@ def _cmd_pr_await(args: argparse.Namespace) -> int:
237
238
  return exit_code
238
239
 
239
240
 
241
+ def _cmd_pr_review(args: argparse.Namespace) -> int:
242
+ try:
243
+ stdout, exit_code, stderr = pr_review_script.run(
244
+ agent=args.agent, project_dir=Path.cwd(), pr=args.pr
245
+ )
246
+ except ValueError as exc:
247
+ print(f"{prog_name()}: {exc}", file=sys.stderr)
248
+ return 2
249
+ except RuntimeError as exc:
250
+ print(str(exc), file=sys.stderr)
251
+ print(_gh_rerun_hint(), file=sys.stderr)
252
+ return 1
253
+ _emit(stdout, stderr)
254
+ return exit_code
255
+
256
+
240
257
  def _cmd_pr_delta(args: argparse.Namespace) -> int:
241
258
  try:
242
259
  stdout, exit_code, stderr = pr_delta_script.run(agent=args.agent, project_dir=Path.cwd())
@@ -403,6 +420,14 @@ def _register_pr(sub: argparse._SubParsersAction) -> None:
403
420
  _add_agent_option(p_await, required=False, help_text=_AGENT_HELP)
404
421
  p_await.set_defaults(func=_cmd_pr_await)
405
422
 
423
+ p_review = pr_sub.add_parser(
424
+ "review",
425
+ help="Post the Qodo agentic-review trigger (/agentic_review) on a PR.",
426
+ )
427
+ p_review.add_argument("pr", type=int, nargs="?", default=None)
428
+ _add_agent_option(p_review, required=False, help_text=_AGENT_HELP)
429
+ p_review.set_defaults(func=_cmd_pr_review)
430
+
406
431
  p_delta = pr_sub.add_parser("delta", help="Show the delta since the last PR read.")
407
432
  _add_agent_option(p_delta, required=False, help_text=_AGENT_HELP)
408
433
  p_delta.set_defaults(func=_cmd_pr_delta)
@@ -1,12 +1,12 @@
1
1
  ---
2
2
  name: pr
3
- description: GitHub PR lifecycle commands for agents — lint, open, read, reply, await, delta. Each command ends with a deterministic "Next step:" footer so the agent never has to guess what to chain.
3
+ description: GitHub PR lifecycle commands for agents — lint, open, read, reply, review, await, delta. Each command ends with a deterministic "Next step:" footer so the agent never has to guess what to chain.
4
4
  type: command
5
5
  ---
6
6
 
7
7
  # `agex pr` — PR lifecycle for agents
8
8
 
9
- Six verbs, in roughly the order an agent uses them on a PR:
9
+ Seven verbs, in roughly the order an agent uses them on a PR:
10
10
 
11
11
  | Verb | Purpose |
12
12
  |---|---|
@@ -14,6 +14,7 @@ Six verbs, in roughly the order an agent uses them on a PR:
14
14
  | `agex pr open --title T [--body-file F] [--draft] [--delayed-read]` | `gh pr create` with auto-signed body; with `--delayed-read` chains to `read --wait 180`. |
15
15
  | `agex pr read [<PR>] [--wait SECS]` | Unified briefing: CI checks, SonarCloud quality gate, all comments, reviewer readiness. With `--wait`, polls until required reviewers are ready or timeout. Always exits 0. |
16
16
  | `agex pr reply <PR>` | Read JSONL replies on stdin, post each, resolve threads. |
17
+ | `agex pr review [<PR>]` | Post the Qodo agentic-review trigger (`/agentic_review`) on a PR. Re-triggers on an already-open PR; `pr open` posts it automatically for a new non-draft PR. |
17
18
  | `agex pr await [<PR>] [--max-wait SECS]` | "Wake me when this is triage-able" — polls readiness, runs CI + Sonar gate, dumps briefing. **Exits 1 on quality-gate `ERROR`, unresolved threads, or failing CI checks**, 0 on clean state or timeout. Default `--max-wait 1800` (30 min). |
18
19
  | `agex pr delta` | Dump sibling-project `CLAUDE.md` heads + `culture.yaml` for alignment review. |
19
20
 
@@ -40,6 +41,24 @@ PR health (e.g., in scripts that should fail if Sonar errors).
40
41
  gates on top of the same readiness loop and **exits non-zero** when the PR
41
42
  isn't clean.
42
43
 
44
+ ## Qodo agentic review
45
+
46
+ When a PR is opened, Qodo runs an agentic code review on demand when it sees a
47
+ trigger comment. The current command is **`/agentic_review`**. The legacy
48
+ `/improve` command is **deprecated** (Qodo replies with a deprecation banner and
49
+ no committed removal date), so agex never posts `/improve`.
50
+
51
+ - `agex pr open` posts `/agentic_review` **automatically** for a freshly created,
52
+ non-draft PR — out of the box, no flag. Drafts are skipped (not review-ready)
53
+ and already-open PRs are skipped (idempotent re-opens shouldn't spam the
54
+ thread).
55
+ - `agex pr review [<PR>]` posts the same trigger on demand — use it to re-trigger
56
+ a review on an existing PR (e.g. after pushing fixes) or to trigger a draft
57
+ once it's ready.
58
+
59
+ The command string lives in one place (`scripts/review.QODO_REVIEW_TRIGGER`), so
60
+ a future Qodo rename is a one-line change for every consumer of `agex pr`.
61
+
43
62
  ## SonarCloud project key
44
63
 
45
64
  `pr read` and `pr await` query the SonarCloud quality gate for the current
@@ -57,7 +76,9 @@ Every command ends with a `**Next step:**` footer — chase the chain without gu
57
76
  ## Side effects
58
77
 
59
78
  Network: every command except `lint` and `delta` talks to GitHub via `gh`.
60
- Disk: `pr open`, `pr read`, and `pr reply` append events to
79
+ `pr open` (non-draft, new PR) and `pr review` post the `/agentic_review` trigger
80
+ comment.
81
+ Disk: `pr open`, `pr read`, `pr reply`, and `pr review` append events to
61
82
  `.agex/data/pr/events.jsonl`.
62
83
 
63
84
  ## Prerequisites
@@ -12,6 +12,7 @@ hints:
12
12
  reply_with_failures: "Resubmit the failed lines (see table above) to `{{ prog }} pr reply {{ pr }}`."
13
13
  reply_clean: "Push fixes (if any), then `{{ prog }} pr read {{ pr }} --wait 180`."
14
14
  delta_done: "Triage each sibling — open follow-up PRs where alignment drifted; mention them in your reply."
15
+ review_posted: "Posted `/agentic_review` — `{{ prog }} pr read {{ pr }} --wait 180` once Qodo responds."
15
16
  await_clean: "PR #{{ pr }} is ready — wait for human merge."
16
17
  await_gate_error: "SonarCloud quality gate is ERROR — fix the new issues above, push, then `{{ prog }} pr await {{ pr }}` again."
17
18
  await_gate_unknown: "SonarCloud quality gate is UNKNOWN — analysis is pending or the project isn't analyzed yet; wait and re-run `{{ prog }} pr await {{ pr }}`."
@@ -12,6 +12,7 @@ hints:
12
12
  reply_with_failures: "Resubmit the failed lines (see table above) to `{{ prog }} pr reply {{ pr }}`."
13
13
  reply_clean: "Push fixes (if any), then `{{ prog }} pr read {{ pr }} --wait 180`."
14
14
  delta_done: "Triage each sibling — open follow-up PRs where alignment drifted; mention them in your reply."
15
+ review_posted: "Posted `/agentic_review` — `{{ prog }} pr read {{ pr }} --wait 180` once Qodo responds."
15
16
  await_clean: "PR #{{ pr }} is ready — wait for human merge."
16
17
  await_gate_error: "SonarCloud quality gate is ERROR — fix the new issues above, push, then `{{ prog }} pr await {{ pr }}` again."
17
18
  await_gate_unknown: "SonarCloud quality gate is UNKNOWN — analysis is pending or the project isn't analyzed yet; wait and re-run `{{ prog }} pr await {{ pr }}`."
@@ -12,6 +12,7 @@ hints:
12
12
  reply_with_failures: "Resubmit the failed lines (see table above) to `{{ prog }} pr reply {{ pr }}`."
13
13
  reply_clean: "Push fixes (if any), then `{{ prog }} pr read {{ pr }} --wait 180`."
14
14
  delta_done: "Triage each sibling — open follow-up PRs where alignment drifted; mention them in your reply."
15
+ review_posted: "Posted `/agentic_review` — `{{ prog }} pr read {{ pr }} --wait 180` once Qodo responds."
15
16
  await_clean: "PR #{{ pr }} is ready — wait for human merge."
16
17
  await_gate_error: "SonarCloud quality gate is ERROR — fix the new issues above, push, then `{{ prog }} pr await {{ pr }}` again."
17
18
  await_gate_unknown: "SonarCloud quality gate is UNKNOWN — analysis is pending or the project isn't analyzed yet; wait and re-run `{{ prog }} pr await {{ pr }}`."
@@ -12,6 +12,7 @@ hints:
12
12
  reply_with_failures: "Resubmit the failed lines (see table above) to `{{ prog }} pr reply {{ pr }}`."
13
13
  reply_clean: "Push fixes (if any), then `{{ prog }} pr read {{ pr }} --wait 180`."
14
14
  delta_done: "Triage each sibling — open follow-up PRs where alignment drifted; mention them in your reply."
15
+ review_posted: "Posted `/agentic_review` — `{{ prog }} pr read {{ pr }} --wait 180` once Qodo responds."
15
16
  await_clean: "PR #{{ pr }} is ready — wait for human merge."
16
17
  await_gate_error: "SonarCloud quality gate is ERROR — fix the new issues above, push, then `{{ prog }} pr await {{ pr }}` again."
17
18
  await_gate_unknown: "SonarCloud quality gate is UNKNOWN — analysis is pending or the project isn't analyzed yet; wait and re-run `{{ prog }} pr await {{ pr }}`."
@@ -70,5 +70,9 @@ def reply_next_step(pr: int, failure_count: int) -> tuple[str, dict[str, Any]]:
70
70
  return "reply_clean", {"pr": pr}
71
71
 
72
72
 
73
+ def review_next_step(pr: int) -> tuple[str, dict[str, Any]]:
74
+ return "review_posted", {"pr": pr}
75
+
76
+
73
77
  def delta_next_step() -> tuple[str, dict[str, Any]]:
74
78
  return "delta_done", {}
@@ -9,5 +9,9 @@ PR opened: **#{{ pr }}**{% if url %} ({{ url }}){% endif %}.
9
9
  - Title: `{{ title }}`
10
10
  - Signed: {{ "yes" if signed else "no" }}
11
11
  - Draft: {{ "yes" if draft else "no" }}
12
-
12
+ {% if review_posted %}- Posted `{{ review_command }}` to start a Qodo agentic review.
13
+ {% endif %}
14
+ {%- if review_failed %}
15
+ > ⚠️ PR created, but posting `{{ review_command }}` failed (network/`gh`). The PR is open — run `{{ prog }} pr review {{ pr }}` to post the Qodo trigger once reachable.
16
+ {% endif %}
13
17
  {{ footer }}
@@ -0,0 +1,5 @@
1
+ # `{{ prog }} pr review`
2
+
3
+ Posted `{{ command }}` on PR **#{{ pr }}** to start a Qodo agentic review.
4
+
5
+ {{ footer }}
@@ -29,15 +29,6 @@ from agent_experience.core.render import render_string
29
29
  _TEMPLATES_PKG = "agent_experience.commands.pr.assets.templates"
30
30
 
31
31
 
32
- def _resolve_pr(pr: int | None) -> int:
33
- if pr is not None:
34
- return pr
35
- view = github.pr_view(None)
36
- if view is None:
37
- raise ValueError("no PR found for current branch; pass <PR> explicitly")
38
- return int(view["number"])
39
-
40
-
41
32
  def _gate_status(gate: Mapping[str, Any] | None) -> str | None:
42
33
  """Normalized SonarCloud quality-gate status, or None when there's no gate.
43
34
 
@@ -57,7 +48,7 @@ def run(
57
48
  max_wait: int,
58
49
  ) -> tuple[str, int, str]:
59
50
  backend = resolve_backend(agent, project_dir)
60
- pr_number = _resolve_pr(pr)
51
+ pr_number = github.resolve_pr_number(pr)
61
52
 
62
53
  waited_secs = 0
63
54
  waiting_for: list[str] = []
@@ -7,7 +7,7 @@ from importlib.resources import files
7
7
  from pathlib import Path
8
8
 
9
9
  from agent_experience.commands.pr.assets.rules.next_step_rules import open_next_step
10
- from agent_experience.commands.pr.scripts import _journal
10
+ from agent_experience.commands.pr.scripts import _journal, review
11
11
  from agent_experience.commands.pr.scripts._footer import render_footer
12
12
  from agent_experience.core import github
13
13
  from agent_experience.core.backend import resolve_backend
@@ -55,6 +55,25 @@ def run(
55
55
  was_already_open = False
56
56
  _journal.append({"type": "pr_opened", "pr": pr, "title": title})
57
57
 
58
+ # Auto-post the Qodo agentic-review trigger out of the box for a freshly
59
+ # created, non-draft PR. Skipped for drafts (not review-ready yet — use
60
+ # `pr review` to trigger later) and for already-open PRs (idempotent
61
+ # re-opens shouldn't spam the thread).
62
+ #
63
+ # The trigger post is best-effort: PR creation is the primary side effect
64
+ # and has already succeeded, so a transient `gh` failure here must NOT abort
65
+ # the command (which would tell the user to rerun `pr open`, only to skip
66
+ # the trigger forever as an already-open PR). On failure we keep exit 0 and
67
+ # point the user at `pr review` to retry just the trigger.
68
+ review_posted = False
69
+ review_failed = False
70
+ if not was_already_open and not draft:
71
+ try:
72
+ review.post_trigger(pr)
73
+ review_posted = True
74
+ except RuntimeError:
75
+ review_failed = True
76
+
58
77
  footer_key, footer_ctx = open_next_step(pr, was_already_open)
59
78
  footer = render_footer(footer_key, backend, footer_ctx)
60
79
 
@@ -68,6 +87,9 @@ def run(
68
87
  "signed": signed,
69
88
  "draft": draft,
70
89
  "was_already_open": was_already_open,
90
+ "review_posted": review_posted,
91
+ "review_failed": review_failed,
92
+ "review_command": review.QODO_REVIEW_TRIGGER,
71
93
  "footer": footer,
72
94
  },
73
95
  )
@@ -25,15 +25,6 @@ from agent_experience.core.render import render_string
25
25
  _TEMPLATES_PKG = "agent_experience.commands.pr.assets.templates"
26
26
 
27
27
 
28
- def _resolve_pr(pr: int | None) -> int:
29
- if pr is not None:
30
- return pr
31
- view = github.pr_view(None)
32
- if view is None:
33
- raise ValueError("no PR found for current branch; pass <PR> explicitly")
34
- return int(view["number"])
35
-
36
-
37
28
  def _has_recent_local_commits(journal_events: list[dict[str, Any]], pr: int) -> bool:
38
29
  """True if `git log` shows commits authored after the most recent
39
30
  `pr_read` event for this PR. No event yet → False (first read)."""
@@ -60,7 +51,7 @@ def run(
60
51
  wait: int | None,
61
52
  ) -> tuple[str, int, str]:
62
53
  backend = resolve_backend(agent, project_dir)
63
- pr_number = _resolve_pr(pr)
54
+ pr_number = github.resolve_pr_number(pr)
64
55
 
65
56
  waited_secs = 0
66
57
  waiting_for: list[str] = []
@@ -0,0 +1,59 @@
1
+ """`agex pr review` — post the Qodo agentic-review trigger comment.
2
+
3
+ Qodo's current command to start an agentic PR review is ``/agentic_review``.
4
+ The legacy ``/improve`` command is deprecated (Qodo emits a deprecation banner
5
+ when it is used), so agex never posts ``/improve``. ``QODO_REVIEW_TRIGGER`` is
6
+ the single source of truth for the command string — any future Qodo rename is a
7
+ one-line change here, picked up by both this verb and the auto-post on
8
+ ``pr open``.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ from importlib.resources import files
14
+ from pathlib import Path
15
+
16
+ from agent_experience.commands.pr.assets.rules.next_step_rules import review_next_step
17
+ from agent_experience.commands.pr.scripts import _journal
18
+ from agent_experience.commands.pr.scripts._footer import render_footer
19
+ from agent_experience.core import github
20
+ from agent_experience.core.backend import resolve_backend
21
+ from agent_experience.core.render import render_string
22
+
23
+ _TEMPLATES_PKG = "agent_experience.commands.pr.assets.templates"
24
+
25
+ # The non-deprecated Qodo command to start an agentic code review. Single
26
+ # source of truth — referenced here and by the `pr open` auto-post.
27
+ QODO_REVIEW_TRIGGER = "/agentic_review"
28
+
29
+
30
+ def post_trigger(pr: int) -> int:
31
+ """Post the Qodo review-trigger comment on ``pr``; return the comment ID.
32
+
33
+ Also appends a ``pr_review_triggered`` journal event. Shared by the
34
+ ``pr review`` verb and the ``pr open`` auto-post.
35
+ """
36
+ comment_id = github.pr_post_comment(pr, QODO_REVIEW_TRIGGER, None)
37
+ _journal.append({"type": "pr_review_triggered", "pr": pr, "command": QODO_REVIEW_TRIGGER})
38
+ return comment_id
39
+
40
+
41
+ def run(agent: str | None, project_dir: Path, pr: int | None) -> tuple[str, int, str]:
42
+ backend = resolve_backend(agent, project_dir)
43
+ pr_number = github.resolve_pr_number(pr)
44
+
45
+ post_trigger(pr_number)
46
+
47
+ footer_key, footer_ctx = review_next_step(pr_number)
48
+ footer = render_footer(footer_key, backend, footer_ctx)
49
+
50
+ template = files(_TEMPLATES_PKG).joinpath("pr_review_result.md.j2").read_text(encoding="utf-8")
51
+ stdout = render_string(
52
+ template,
53
+ {
54
+ "pr": pr_number,
55
+ "command": QODO_REVIEW_TRIGGER,
56
+ "footer": footer,
57
+ },
58
+ )
59
+ return stdout, 0, ""
@@ -93,6 +93,21 @@ def pr_view(pr_or_branch: str | None) -> dict[str, Any] | None:
93
93
  return json.loads(stdout)
94
94
 
95
95
 
96
+ def resolve_pr_number(pr: int | None) -> int:
97
+ """Return ``pr`` if given, else the open PR number for the current branch.
98
+
99
+ Raises ``ValueError`` when no number is given and the branch has no PR —
100
+ the shared resolution used by every `agex pr` verb that takes an optional
101
+ ``<PR>`` positional (``read``, ``await``, ``review``).
102
+ """
103
+ if pr is not None:
104
+ return pr
105
+ view = pr_view(None)
106
+ if view is None:
107
+ raise ValueError("no PR found for current branch; pass <PR> explicitly")
108
+ return int(view["number"])
109
+
110
+
96
111
  def _repo_slug() -> str:
97
112
  """Return 'owner/repo' from `gh repo view --json owner,name`."""
98
113
  out = _run_gh(["repo", "view", "--json", "owner,name"])
@@ -6,6 +6,7 @@ from agent_experience.core import github
6
6
  def _patch(monkeypatch, *, view_returns, create_returns_pr=42, captured=None):
7
7
  if captured is None:
8
8
  captured = {}
9
+ captured.setdefault("posts", [])
9
10
  monkeypatch.setattr(github, "pr_view", lambda branch=None: view_returns)
10
11
  monkeypatch.setattr(github, "resolve_nick", lambda d: "agex-cli")
11
12
 
@@ -15,7 +16,12 @@ def _patch(monkeypatch, *, view_returns, create_returns_pr=42, captured=None):
15
16
  captured["draft"] = draft
16
17
  return create_returns_pr
17
18
 
19
+ def fake_post(pr, body, in_reply_to):
20
+ captured["posts"].append({"pr": pr, "body": body, "in_reply_to": in_reply_to})
21
+ return 1000 + len(captured["posts"])
22
+
18
23
  monkeypatch.setattr(github, "pr_create", fake_create)
24
+ monkeypatch.setattr(github, "pr_post_comment", fake_post)
19
25
  return captured
20
26
 
21
27
 
@@ -123,6 +129,90 @@ def test_pr_open_draft_flag(monkeypatch, tmp_path, capsys):
123
129
  assert captured["draft"] is True
124
130
 
125
131
 
132
+ def test_pr_open_auto_posts_agentic_review(monkeypatch, tmp_path, capsys):
133
+ monkeypatch.chdir(tmp_path)
134
+ captured = _patch(monkeypatch, view_returns=None)
135
+ body_file = tmp_path / "body.md"
136
+ body_file.write_text("b\n", encoding="utf-8")
137
+ code = cli.main(
138
+ ["pr", "open", "--agent", "claude-code", "--title", "t", "--body-file", str(body_file)]
139
+ )
140
+ out = capsys.readouterr()
141
+ assert code == 0
142
+ # Exactly one auto-posted trigger, top-level, with the non-deprecated command.
143
+ assert captured["posts"] == [{"pr": 42, "body": "/agentic_review", "in_reply_to": None}]
144
+ assert "/agentic_review" in out.out
145
+ assert "/improve" not in out.out
146
+ # Journal records the trigger alongside pr_opened.
147
+ events = _journal.load()
148
+ assert "pr_review_triggered" in [e["type"] for e in events]
149
+
150
+
151
+ def test_pr_open_draft_does_not_post_trigger(monkeypatch, tmp_path, capsys):
152
+ monkeypatch.chdir(tmp_path)
153
+ captured = _patch(monkeypatch, view_returns=None)
154
+ body_file = tmp_path / "body.md"
155
+ body_file.write_text("b\n", encoding="utf-8")
156
+ cli.main(
157
+ [
158
+ "pr",
159
+ "open",
160
+ "--agent",
161
+ "claude-code",
162
+ "--title",
163
+ "t",
164
+ "--body-file",
165
+ str(body_file),
166
+ "--draft",
167
+ ]
168
+ )
169
+ capsys.readouterr()
170
+ assert captured["posts"] == []
171
+
172
+
173
+ def test_pr_open_already_open_does_not_post_trigger(monkeypatch, tmp_path, capsys):
174
+ monkeypatch.chdir(tmp_path)
175
+ posts: list = []
176
+ monkeypatch.setattr(github, "pr_view", lambda branch=None: {"number": 7, "state": "OPEN"})
177
+ monkeypatch.setattr(github, "resolve_nick", lambda d: "agex-cli")
178
+ monkeypatch.setattr(github, "pr_create", lambda **kw: 999)
179
+ monkeypatch.setattr(
180
+ github, "pr_post_comment", lambda pr, body, in_reply_to: posts.append(body) or 1
181
+ )
182
+ body_file = tmp_path / "body.md"
183
+ body_file.write_text("b\n", encoding="utf-8")
184
+ cli.main(
185
+ ["pr", "open", "--agent", "claude-code", "--title", "t", "--body-file", str(body_file)]
186
+ )
187
+ capsys.readouterr()
188
+ assert posts == []
189
+
190
+
191
+ def test_pr_open_survives_trigger_post_failure(monkeypatch, tmp_path, capsys):
192
+ # PR creation is the primary side effect; a failed trigger post must not
193
+ # abort the command (which would tell the user to rerun `pr open` and skip
194
+ # the trigger forever). Exit 0, PR reported open, and point at `pr review`.
195
+ monkeypatch.chdir(tmp_path)
196
+ _patch(monkeypatch, view_returns=None)
197
+
198
+ def boom(pr, body, in_reply_to):
199
+ raise RuntimeError("gh failed: network unreachable")
200
+
201
+ monkeypatch.setattr(github, "pr_post_comment", boom)
202
+ body_file = tmp_path / "body.md"
203
+ body_file.write_text("b\n", encoding="utf-8")
204
+ code = cli.main(
205
+ ["pr", "open", "--agent", "claude-code", "--title", "t", "--body-file", str(body_file)]
206
+ )
207
+ out = capsys.readouterr()
208
+ assert code == 0
209
+ assert "PR opened" in out.out
210
+ assert "#42" in out.out
211
+ # Surfaces the recovery path without claiming the trigger was posted.
212
+ assert "pr review 42" in out.out
213
+ assert "Posted `/agentic_review`" not in out.out
214
+
215
+
126
216
  def test_pr_open_with_delayed_read_chains(monkeypatch, tmp_path, capsys):
127
217
  monkeypatch.chdir(tmp_path)
128
218
  _patch(monkeypatch, view_returns=None)
@@ -0,0 +1,97 @@
1
+ from pathlib import Path
2
+
3
+ import agent_experience
4
+ import agent_experience.cli as cli
5
+ from agent_experience.commands.pr.scripts import _journal, review
6
+ from agent_experience.core import github
7
+
8
+
9
+ def test_package_never_emits_deprecated_improve_command():
10
+ """Acceptance criterion: no code path posts the deprecated `/improve`.
11
+
12
+ Scans shipped Python and template/asset files for `/improve` as a *posted*
13
+ command — i.e. a quoted string literal or a Jinja/YAML value — guarding
14
+ against a regression that reintroduces it. Prose that *names* `/improve`
15
+ only to say it's deprecated (e.g. SKILL.md, the review.py docstring) is
16
+ allowed; what's forbidden is emitting it as a value.
17
+ """
18
+ pkg_root = Path(agent_experience.__file__).parent
19
+ forbidden = ('"/improve"', "'/improve'")
20
+ offenders = []
21
+ for p in pkg_root.rglob("*"):
22
+ if not (p.is_file() and p.suffix in {".py", ".j2", ".yaml", ".yml"}):
23
+ continue
24
+ text = p.read_text(encoding="utf-8", errors="ignore")
25
+ if any(token in text for token in forbidden):
26
+ offenders.append(p.relative_to(pkg_root))
27
+ assert offenders == [], f"deprecated Qodo command `/improve` emitted in: {offenders}"
28
+
29
+
30
+ def _patch_post(monkeypatch, captured):
31
+ def fake_post(pr, body, in_reply_to):
32
+ captured.append({"pr": pr, "body": body, "in_reply_to": in_reply_to})
33
+ return 1234
34
+
35
+ monkeypatch.setattr(github, "pr_post_comment", fake_post)
36
+
37
+
38
+ def test_trigger_constant_is_agentic_review():
39
+ # Single source of truth — the non-deprecated Qodo command.
40
+ assert review.QODO_REVIEW_TRIGGER == "/agentic_review"
41
+
42
+
43
+ def test_pr_review_posts_agentic_review_on_explicit_pr(monkeypatch, tmp_path, capsys):
44
+ monkeypatch.chdir(tmp_path)
45
+ posts: list = []
46
+ _patch_post(monkeypatch, posts)
47
+
48
+ code = cli.main(["pr", "review", "42", "--agent", "claude-code"])
49
+ out = capsys.readouterr()
50
+
51
+ assert code == 0
52
+ assert posts == [{"pr": 42, "body": "/agentic_review", "in_reply_to": None}]
53
+ assert "/agentic_review" in out.out
54
+ assert "/improve" not in out.out
55
+ # Next-step footer present.
56
+ assert "Next step:" in out.out
57
+ assert "pr read 42" in out.out
58
+
59
+
60
+ def test_pr_review_resolves_current_branch_pr(monkeypatch, tmp_path, capsys):
61
+ monkeypatch.chdir(tmp_path)
62
+ posts: list = []
63
+ _patch_post(monkeypatch, posts)
64
+ monkeypatch.setattr(github, "pr_view", lambda branch=None: {"number": 7, "state": "OPEN"})
65
+
66
+ code = cli.main(["pr", "review", "--agent", "claude-code"])
67
+ capsys.readouterr()
68
+
69
+ assert code == 0
70
+ assert posts == [{"pr": 7, "body": "/agentic_review", "in_reply_to": None}]
71
+
72
+
73
+ def test_pr_review_errors_when_no_pr_for_branch(monkeypatch, tmp_path, capsys):
74
+ monkeypatch.chdir(tmp_path)
75
+ posts: list = []
76
+ _patch_post(monkeypatch, posts)
77
+ monkeypatch.setattr(github, "pr_view", lambda branch=None: None)
78
+
79
+ code = cli.main(["pr", "review", "--agent", "claude-code"])
80
+ err = capsys.readouterr().err
81
+
82
+ assert code == 2
83
+ assert "no PR found for current branch" in err
84
+ assert posts == []
85
+
86
+
87
+ def test_pr_review_writes_journal_event(monkeypatch, tmp_path, capsys):
88
+ monkeypatch.chdir(tmp_path)
89
+ _patch_post(monkeypatch, [])
90
+
91
+ cli.main(["pr", "review", "99", "--agent", "claude-code"])
92
+ capsys.readouterr()
93
+
94
+ events = _journal.load()
95
+ triggered = next(e for e in events if e["type"] == "pr_review_triggered")
96
+ assert triggered["pr"] == 99
97
+ assert triggered["command"] == "/agentic_review"
@@ -4,7 +4,7 @@ requires-python = ">=3.10"
4
4
 
5
5
  [[package]]
6
6
  name = "agex-cli"
7
- version = "0.23.0"
7
+ version = "0.24.0"
8
8
  source = { editable = "." }
9
9
  dependencies = [
10
10
  { name = "jinja2" },