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.
- {agex_cli-0.23.0 → agex_cli-0.24.0}/CHANGELOG.md +11 -0
- {agex_cli-0.23.0 → agex_cli-0.24.0}/CLAUDE.md +2 -2
- {agex_cli-0.23.0 → agex_cli-0.24.0}/PKG-INFO +1 -1
- {agex_cli-0.23.0 → agex_cli-0.24.0}/pyproject.toml +1 -1
- {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/cli.py +25 -0
- {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/commands/pr/SKILL.md +24 -3
- {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/commands/pr/assets/backends/acp.yaml +1 -0
- {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/commands/pr/assets/backends/claude-code.yaml +1 -0
- {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/commands/pr/assets/backends/codex.yaml +1 -0
- {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/commands/pr/assets/backends/copilot.yaml +1 -0
- {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/commands/pr/assets/rules/next_step_rules.py +4 -0
- {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/commands/pr/assets/templates/pr_open_result.md.j2 +5 -1
- agex_cli-0.24.0/src/agent_experience/commands/pr/assets/templates/pr_review_result.md.j2 +5 -0
- {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/commands/pr/scripts/await_.py +1 -10
- {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/commands/pr/scripts/open_.py +23 -1
- {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/commands/pr/scripts/read.py +1 -10
- agex_cli-0.24.0/src/agent_experience/commands/pr/scripts/review.py +59 -0
- {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/core/github.py +15 -0
- {agex_cli-0.23.0 → agex_cli-0.24.0}/tests/commands/pr/test_open.py +90 -0
- agex_cli-0.24.0/tests/commands/pr/test_review.py +97 -0
- {agex_cli-0.23.0 → agex_cli-0.24.0}/uv.lock +1 -1
- {agex_cli-0.23.0 → agex_cli-0.24.0}/.claude/skills/agent-config/SKILL.md +0 -0
- {agex_cli-0.23.0 → agex_cli-0.24.0}/.claude/skills/agent-config/data/backend-fingerprints.yaml +0 -0
- {agex_cli-0.23.0 → agex_cli-0.24.0}/.claude/skills/agent-config/scripts/show.sh +0 -0
- {agex_cli-0.23.0 → agex_cli-0.24.0}/.claude/skills/assign-to-workforce/SKILL.md +0 -0
- {agex_cli-0.23.0 → agex_cli-0.24.0}/.claude/skills/assign-to-workforce/scripts/assign-to-workforce.sh +0 -0
- {agex_cli-0.23.0 → agex_cli-0.24.0}/.claude/skills/cicd/SKILL.md +0 -0
- {agex_cli-0.23.0 → agex_cli-0.24.0}/.claude/skills/cicd/scripts/workflow.sh +0 -0
- {agex_cli-0.23.0 → agex_cli-0.24.0}/.claude/skills/communicate/SKILL.md +0 -0
- {agex_cli-0.23.0 → agex_cli-0.24.0}/.claude/skills/communicate/scripts/fetch-issues.sh +0 -0
- {agex_cli-0.23.0 → agex_cli-0.24.0}/.claude/skills/communicate/scripts/mesh-message.sh +0 -0
- {agex_cli-0.23.0 → agex_cli-0.24.0}/.claude/skills/communicate/scripts/post-comment.sh +0 -0
- {agex_cli-0.23.0 → agex_cli-0.24.0}/.claude/skills/communicate/scripts/post-issue.sh +0 -0
- {agex_cli-0.23.0 → agex_cli-0.24.0}/.claude/skills/doc-test-alignment/SKILL.md +0 -0
- {agex_cli-0.23.0 → agex_cli-0.24.0}/.claude/skills/doc-test-alignment/scripts/check.sh +0 -0
- {agex_cli-0.23.0 → agex_cli-0.24.0}/.claude/skills/pypi-maintainer/SKILL.md +0 -0
- {agex_cli-0.23.0 → agex_cli-0.24.0}/.claude/skills/pypi-maintainer/scripts/switch-source.sh +0 -0
- {agex_cli-0.23.0 → agex_cli-0.24.0}/.claude/skills/run-tests/SKILL.md +0 -0
- {agex_cli-0.23.0 → agex_cli-0.24.0}/.claude/skills/run-tests/scripts/test.sh +0 -0
- {agex_cli-0.23.0 → agex_cli-0.24.0}/.claude/skills/sonarclaude/SKILL.md +0 -0
- {agex_cli-0.23.0 → agex_cli-0.24.0}/.claude/skills/sonarclaude/scripts/sonar.sh +0 -0
- {agex_cli-0.23.0 → agex_cli-0.24.0}/.claude/skills/spec-to-plan/SKILL.md +0 -0
- {agex_cli-0.23.0 → agex_cli-0.24.0}/.claude/skills/spec-to-plan/scripts/spec-to-plan.sh +0 -0
- {agex_cli-0.23.0 → agex_cli-0.24.0}/.claude/skills/think/SKILL.md +0 -0
- {agex_cli-0.23.0 → agex_cli-0.24.0}/.claude/skills/think/scripts/think.sh +0 -0
- {agex_cli-0.23.0 → agex_cli-0.24.0}/.claude/skills/version-bump/SKILL.md +0 -0
- {agex_cli-0.23.0 → agex_cli-0.24.0}/.claude/skills/version-bump/scripts/bump.py +0 -0
- {agex_cli-0.23.0 → agex_cli-0.24.0}/.flake8 +0 -0
- {agex_cli-0.23.0 → agex_cli-0.24.0}/.github/workflows/publish.yml +0 -0
- {agex_cli-0.23.0 → agex_cli-0.24.0}/.github/workflows/test.yml +0 -0
- {agex_cli-0.23.0 → agex_cli-0.24.0}/.gitignore +0 -0
- {agex_cli-0.23.0 → agex_cli-0.24.0}/.python-version +0 -0
- {agex_cli-0.23.0 → agex_cli-0.24.0}/LICENSE +0 -0
- {agex_cli-0.23.0 → agex_cli-0.24.0}/README.md +0 -0
- {agex_cli-0.23.0 → agex_cli-0.24.0}/culture.yaml +0 -0
- {agex_cli-0.23.0 → agex_cli-0.24.0}/docs/skill-sources.md +0 -0
- {agex_cli-0.23.0 → agex_cli-0.24.0}/docs/superpowers/plans/2026-04-18-agex-v0.1.md +0 -0
- {agex_cli-0.23.0 → agex_cli-0.24.0}/docs/superpowers/plans/2026-05-10-agex-pr.md +0 -0
- {agex_cli-0.23.0 → agex_cli-0.24.0}/docs/superpowers/specs/2026-04-18-agex-design.md +0 -0
- {agex_cli-0.23.0 → agex_cli-0.24.0}/docs/superpowers/specs/2026-04-26-agex-doctor.md +0 -0
- {agex_cli-0.23.0 → agex_cli-0.24.0}/docs/superpowers/specs/2026-05-10-agex-pr-design.md +0 -0
- {agex_cli-0.23.0 → agex_cli-0.24.0}/sonar-project.properties +0 -0
- {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/__init__.py +0 -0
- {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/__main__.py +0 -0
- {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/backends/__init__.py +0 -0
- {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/backends/acp/__init__.py +0 -0
- {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/backends/acp/probe.py +0 -0
- {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/backends/capabilities/acp.yaml +0 -0
- {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/backends/capabilities/claude-code.yaml +0 -0
- {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/backends/capabilities/codex.yaml +0 -0
- {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/backends/capabilities/copilot.yaml +0 -0
- {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/backends/claude_code/__init__.py +0 -0
- {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/backends/claude_code/probe.py +0 -0
- {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/backends/codex/__init__.py +0 -0
- {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/backends/codex/probe.py +0 -0
- {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/backends/copilot/__init__.py +0 -0
- {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/backends/copilot/probe.py +0 -0
- {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/commands/__init__.py +0 -0
- {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/commands/doctor/SKILL.md +0 -0
- {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/commands/doctor/__init__.py +0 -0
- {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/commands/doctor/assets/report.md.j2 +0 -0
- {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/commands/doctor/references/design.md +0 -0
- {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/commands/doctor/scripts/__init__.py +0 -0
- {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/commands/doctor/scripts/doctor.py +0 -0
- {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/commands/explain/SKILL.md +0 -0
- {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/commands/explain/__init__.py +0 -0
- {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/commands/explain/assets/topics/agex.md +0 -0
- {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/commands/explain/references/.gitkeep +0 -0
- {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/commands/explain/scripts/__init__.py +0 -0
- {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/commands/explain/scripts/explain.py +0 -0
- {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/commands/gamify/SKILL.md +0 -0
- {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/commands/gamify/__init__.py +0 -0
- {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/commands/gamify/assets/hooks/claude-code.json +0 -0
- {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/commands/gamify/references/.gitkeep +0 -0
- {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/commands/gamify/scripts/__init__.py +0 -0
- {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/commands/gamify/scripts/install.py +0 -0
- {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/commands/hook/SKILL.md +0 -0
- {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/commands/hook/__init__.py +0 -0
- {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/commands/hook/assets/table.md.j2 +0 -0
- {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/commands/hook/references/.gitkeep +0 -0
- {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/commands/hook/scripts/__init__.py +0 -0
- {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/commands/hook/scripts/read.py +0 -0
- {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/commands/hook/scripts/write.py +0 -0
- {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/commands/learn/SKILL.md +0 -0
- {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/commands/learn/__init__.py +0 -0
- {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/commands/learn/assets/menu.md.j2 +0 -0
- {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/commands/learn/assets/topics/cicd/SKILL.md +0 -0
- {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/commands/learn/assets/topics/gamify/SKILL.md +0 -0
- {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
- {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/commands/learn/assets/topics/introspect/SKILL.md +0 -0
- {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
- {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/commands/learn/assets/topics/levelup/SKILL.md +0 -0
- {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
- {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/commands/learn/assets/topics/visualize/SKILL.md +0 -0
- {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
- {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/commands/learn/references/.gitkeep +0 -0
- {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/commands/learn/scripts/__init__.py +0 -0
- {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/commands/learn/scripts/learn.py +0 -0
- {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/commands/overview/SKILL.md +0 -0
- {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/commands/overview/__init__.py +0 -0
- {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/commands/overview/assets/backends/acp.yaml +0 -0
- {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/commands/overview/assets/backends/claude-code.yaml +0 -0
- {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/commands/overview/assets/backends/codex.yaml +0 -0
- {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/commands/overview/assets/backends/copilot.yaml +0 -0
- {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/commands/overview/assets/sections.md.j2 +0 -0
- {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/commands/overview/references/.gitkeep +0 -0
- {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/commands/overview/scripts/__init__.py +0 -0
- {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/commands/overview/scripts/overview.py +0 -0
- {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/commands/pr/__init__.py +0 -0
- {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/commands/pr/assets/__init__.py +0 -0
- {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/commands/pr/assets/backends/__init__.py +0 -0
- {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/commands/pr/assets/rules/__init__.py +0 -0
- {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/commands/pr/assets/rules/lint_rules.py +0 -0
- {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/commands/pr/assets/templates/__init__.py +0 -0
- {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/commands/pr/assets/templates/delta.md.j2 +0 -0
- {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/commands/pr/assets/templates/footer.md.j2 +0 -0
- {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/commands/pr/assets/templates/lint_result.md.j2 +0 -0
- {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/commands/pr/assets/templates/pr_briefing.md.j2 +0 -0
- {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/commands/pr/assets/templates/pr_reply_result.md.j2 +0 -0
- {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/commands/pr/scripts/__init__.py +0 -0
- {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/commands/pr/scripts/_footer.py +0 -0
- {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/commands/pr/scripts/_journal.py +0 -0
- {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/commands/pr/scripts/_qodo.py +0 -0
- {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/commands/pr/scripts/_readiness.py +0 -0
- {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/commands/pr/scripts/_sonar.py +0 -0
- {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/commands/pr/scripts/delta.py +0 -0
- {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/commands/pr/scripts/lint.py +0 -0
- {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/commands/pr/scripts/reply.py +0 -0
- {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/core/__init__.py +0 -0
- {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/core/backend.py +0 -0
- {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/core/capabilities.py +0 -0
- {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/core/config.py +0 -0
- {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/core/hook_io.py +0 -0
- {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/core/journal.py +0 -0
- {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/core/paths.py +0 -0
- {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/core/prog.py +0 -0
- {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/core/render.py +0 -0
- {agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/core/skill_loader.py +0 -0
- {agex_cli-0.23.0 → agex_cli-0.24.0}/tester-agents/claude/.claude/settings.json +0 -0
- {agex_cli-0.23.0 → agex_cli-0.24.0}/tester-agents/claude/CLAUDE.md +0 -0
- {agex_cli-0.23.0 → agex_cli-0.24.0}/tester-agents/claude/README.md +0 -0
- {agex_cli-0.23.0 → agex_cli-0.24.0}/tester-agents/claude/culture.yaml +0 -0
- {agex_cli-0.23.0 → agex_cli-0.24.0}/tests/__init__.py +0 -0
- {agex_cli-0.23.0 → agex_cli-0.24.0}/tests/backends/__init__.py +0 -0
- {agex_cli-0.23.0 → agex_cli-0.24.0}/tests/backends/test_claude_code_probe.py +0 -0
- {agex_cli-0.23.0 → agex_cli-0.24.0}/tests/backends/test_stub_probes.py +0 -0
- {agex_cli-0.23.0 → agex_cli-0.24.0}/tests/commands/__init__.py +0 -0
- {agex_cli-0.23.0 → agex_cli-0.24.0}/tests/commands/pr/__init__.py +0 -0
- {agex_cli-0.23.0 → agex_cli-0.24.0}/tests/commands/pr/fixtures/gh/.gitkeep +0 -0
- {agex_cli-0.23.0 → agex_cli-0.24.0}/tests/commands/pr/fixtures/gh/pr_checks_42.json +0 -0
- {agex_cli-0.23.0 → agex_cli-0.24.0}/tests/commands/pr/fixtures/gh/pr_comments_42.json +0 -0
- {agex_cli-0.23.0 → agex_cli-0.24.0}/tests/commands/pr/fixtures/gh/qodo_summary_comment.html +0 -0
- {agex_cli-0.23.0 → agex_cli-0.24.0}/tests/commands/pr/fixtures/journals/dogfood_40.jsonl +0 -0
- {agex_cli-0.23.0 → agex_cli-0.24.0}/tests/commands/pr/test_await.py +0 -0
- {agex_cli-0.23.0 → agex_cli-0.24.0}/tests/commands/pr/test_delta.py +0 -0
- {agex_cli-0.23.0 → agex_cli-0.24.0}/tests/commands/pr/test_footer.py +0 -0
- {agex_cli-0.23.0 → agex_cli-0.24.0}/tests/commands/pr/test_lint.py +0 -0
- {agex_cli-0.23.0 → agex_cli-0.24.0}/tests/commands/pr/test_lint_rules.py +0 -0
- {agex_cli-0.23.0 → agex_cli-0.24.0}/tests/commands/pr/test_qodo.py +0 -0
- {agex_cli-0.23.0 → agex_cli-0.24.0}/tests/commands/pr/test_read.py +0 -0
- {agex_cli-0.23.0 → agex_cli-0.24.0}/tests/commands/pr/test_reply.py +0 -0
- {agex_cli-0.23.0 → agex_cli-0.24.0}/tests/commands/test_doctor.py +0 -0
- {agex_cli-0.23.0 → agex_cli-0.24.0}/tests/commands/test_explain.py +0 -0
- {agex_cli-0.23.0 → agex_cli-0.24.0}/tests/commands/test_gamify.py +0 -0
- {agex_cli-0.23.0 → agex_cli-0.24.0}/tests/commands/test_hook.py +0 -0
- {agex_cli-0.23.0 → agex_cli-0.24.0}/tests/commands/test_learn.py +0 -0
- {agex_cli-0.23.0 → agex_cli-0.24.0}/tests/commands/test_overview.py +0 -0
- {agex_cli-0.23.0 → agex_cli-0.24.0}/tests/commands/test_prog_propagation.py +0 -0
- {agex_cli-0.23.0 → agex_cli-0.24.0}/tests/core/__init__.py +0 -0
- {agex_cli-0.23.0 → agex_cli-0.24.0}/tests/core/test_backend.py +0 -0
- {agex_cli-0.23.0 → agex_cli-0.24.0}/tests/core/test_capabilities.py +0 -0
- {agex_cli-0.23.0 → agex_cli-0.24.0}/tests/core/test_config.py +0 -0
- {agex_cli-0.23.0 → agex_cli-0.24.0}/tests/core/test_github.py +0 -0
- {agex_cli-0.23.0 → agex_cli-0.24.0}/tests/core/test_hook_io.py +0 -0
- {agex_cli-0.23.0 → agex_cli-0.24.0}/tests/core/test_journal.py +0 -0
- {agex_cli-0.23.0 → agex_cli-0.24.0}/tests/core/test_paths.py +0 -0
- {agex_cli-0.23.0 → agex_cli-0.24.0}/tests/core/test_prog.py +0 -0
- {agex_cli-0.23.0 → agex_cli-0.24.0}/tests/core/test_render.py +0 -0
- {agex_cli-0.23.0 → agex_cli-0.24.0}/tests/core/test_resolve_backend.py +0 -0
- {agex_cli-0.23.0 → agex_cli-0.24.0}/tests/core/test_skill_loader.py +0 -0
- {agex_cli-0.23.0 → agex_cli-0.24.0}/tests/core/test_version_lookup.py +0 -0
- {agex_cli-0.23.0 → agex_cli-0.24.0}/tests/fixtures/claude-code/empty/.gitkeep +0 -0
- {agex_cli-0.23.0 → agex_cli-0.24.0}/tests/fixtures/claude-code/malformed/.claude/hooks.json +0 -0
- {agex_cli-0.23.0 → agex_cli-0.24.0}/tests/fixtures/claude-code/malformed/.claude/settings.json +0 -0
- {agex_cli-0.23.0 → agex_cli-0.24.0}/tests/fixtures/claude-code/malformed/.claude/skills/bad/SKILL.md +0 -0
- {agex_cli-0.23.0 → agex_cli-0.24.0}/tests/fixtures/claude-code/malformed/.claude/skills/broken-yaml/SKILL.md +0 -0
- {agex_cli-0.23.0 → agex_cli-0.24.0}/tests/fixtures/claude-code/typical/.claude/hooks.json +0 -0
- {agex_cli-0.23.0 → agex_cli-0.24.0}/tests/fixtures/claude-code/typical/.claude/settings.json +0 -0
- {agex_cli-0.23.0 → agex_cli-0.24.0}/tests/fixtures/claude-code/typical/.claude/skills/example/SKILL.md +0 -0
- {agex_cli-0.23.0 → agex_cli-0.24.0}/tests/fixtures/claude-code/typical/CLAUDE.md +0 -0
- {agex_cli-0.23.0 → agex_cli-0.24.0}/tests/test_cli_dispatch.py +0 -0
- {agex_cli-0.23.0 → agex_cli-0.24.0}/tests/test_cli_errors.py +0 -0
- {agex_cli-0.23.0 → agex_cli-0.24.0}/tests/test_cli_smoke.py +0 -0
- {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.
|
|
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
|
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
{agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/commands/pr/assets/backends/acp.yaml
RENAMED
|
@@ -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 }}`."
|
{agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/commands/pr/assets/backends/codex.yaml
RENAMED
|
@@ -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 }}`."
|
{agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/commands/pr/assets/backends/copilot.yaml
RENAMED
|
@@ -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 }}`."
|
{agex_cli-0.23.0 → agex_cli-0.24.0}/src/agent_experience/commands/pr/assets/rules/next_step_rules.py
RENAMED
|
@@ -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 }}
|
|
@@ -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 =
|
|
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 =
|
|
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"
|
|
File without changes
|
{agex_cli-0.23.0 → agex_cli-0.24.0}/.claude/skills/agent-config/data/backend-fingerprints.yaml
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|