@openhands/extensions 0.1.0 → 0.2.0
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.
- package/.agents/skills/custom-codereview-guide.md +25 -0
- package/.github/pull_request_template.md +38 -0
- package/.github/release.yml +14 -0
- package/.github/workflows/check-extensions.yml +72 -0
- package/.github/workflows/npm-publish.yml +89 -0
- package/.github/workflows/pr.yml +30 -0
- package/.github/workflows/release.yml +24 -0
- package/.github/workflows/tests.yml +25 -0
- package/.github/workflows/vulnerability-scan.yml +87 -0
- package/.release-please-manifest.json +3 -0
- package/AGENTS.md +132 -0
- package/README.md +10 -0
- package/analysis_results.md +162 -0
- package/marketplaces/large-codebase.json +66 -0
- package/marketplaces/openhands-extensions.json +682 -0
- package/package.json +4 -10
- package/plugins/README.md +30 -0
- package/plugins/city-weather/.plugin/plugin.json +13 -0
- package/plugins/city-weather/README.md +145 -0
- package/plugins/city-weather/commands/now.md +56 -0
- package/plugins/cobol-modernization/.plugin/plugin.json +19 -0
- package/plugins/cobol-modernization/README.md +201 -0
- package/plugins/cobol-modernization/references/troubleshooting.md +18 -0
- package/plugins/cobol-modernization/skills/build-setup/SKILL.md +78 -0
- package/plugins/cobol-modernization/skills/build-setup/scripts/install-gnucobol.sh +32 -0
- package/plugins/cobol-modernization/skills/cobol-modernization-overview/SKILL.md +113 -0
- package/plugins/cobol-modernization/skills/mainfraime-removal/SKILL.md +62 -0
- package/plugins/cobol-modernization/skills/mainfraime-removal/references/cics-transformation-examples.md +45 -0
- package/plugins/cobol-modernization/skills/mainframe-planning/SKILL.md +78 -0
- package/plugins/cobol-modernization/skills/to-java-migration/SKILL.md +59 -0
- package/plugins/cobol-modernization/skills/to-java-migration/references/cobol-to-java-example.md +58 -0
- package/plugins/cobol-modernization/skills/to-java-migration/references/datatype-mappings.md +19 -0
- package/plugins/issue-duplicate-checker/.plugin/plugin.json +13 -0
- package/plugins/issue-duplicate-checker/README.md +51 -0
- package/plugins/issue-duplicate-checker/action.yml +349 -0
- package/plugins/issue-duplicate-checker/scripts/auto_close_duplicate_issues.py +569 -0
- package/plugins/issue-duplicate-checker/scripts/issue_duplicate_check_openhands.py +681 -0
- package/plugins/issue-duplicate-checker/scripts/post_duplicate_notice.js +220 -0
- package/plugins/issue-duplicate-checker/scripts/remove_duplicate_candidate_label.js +27 -0
- package/plugins/magic-test/.plugin/plugin.json +13 -0
- package/plugins/magic-test/skills/magic-word/SKILL.md +33 -0
- package/plugins/migration-scoring/.plugin/plugin.json +19 -0
- package/plugins/migration-scoring/README.md +244 -0
- package/plugins/migration-scoring/skills/migration-mapping/SKILL.md +72 -0
- package/plugins/migration-scoring/skills/migration-report/SKILL.md +118 -0
- package/plugins/migration-scoring/skills/migration-scoring-overview/SKILL.md +126 -0
- package/plugins/migration-scoring/skills/score-quality/SKILL.md +54 -0
- package/plugins/migration-scoring/skills/score-quality/references/scoring-criteria.md +30 -0
- package/plugins/migration-scoring/skills/score-style/SKILL.md +106 -0
- package/plugins/onboarding/.plugin/plugin.json +20 -0
- package/plugins/onboarding/README.md +30 -0
- package/plugins/onboarding/references/criteria.md +144 -0
- package/plugins/onboarding/skills/agent-readiness-report/README.md +23 -0
- package/plugins/onboarding/skills/agent-readiness-report/SKILL.md +122 -0
- package/plugins/onboarding/skills/agent-readiness-report/scripts/scan_agent_instructions.sh +88 -0
- package/plugins/onboarding/skills/agent-readiness-report/scripts/scan_build_env.sh +114 -0
- package/plugins/onboarding/skills/agent-readiness-report/scripts/scan_feedback_loops.sh +133 -0
- package/plugins/onboarding/skills/agent-readiness-report/scripts/scan_policy.sh +113 -0
- package/plugins/onboarding/skills/agent-readiness-report/scripts/scan_workflows.sh +127 -0
- package/plugins/onboarding/skills/improve-agent-readiness/README.md +19 -0
- package/plugins/onboarding/skills/improve-agent-readiness/SKILL.md +167 -0
- package/plugins/onboarding/skills/setup-agents-md/README.md +15 -0
- package/plugins/onboarding/skills/setup-agents-md/SKILL.md +150 -0
- package/plugins/onboarding/skills/setup-openhands/README.md +20 -0
- package/plugins/onboarding/skills/setup-openhands/SKILL.md +56 -0
- package/plugins/onboarding/skills/setup-pr-review/README.md +23 -0
- package/plugins/onboarding/skills/setup-pr-review/SKILL.md +72 -0
- package/plugins/openhands/.plugin/plugin.json +13 -0
- package/plugins/openhands/README.md +52 -0
- package/plugins/openhands/SKILL.md +61 -0
- package/plugins/openhands/commands/create.md +55 -0
- package/plugins/openhands/commands/openhands-cloud.md +8 -0
- package/plugins/openhands/scripts/run.sh +69 -0
- package/plugins/pr-review/.plugin/plugin.json +13 -0
- package/plugins/pr-review/README.md +393 -0
- package/plugins/pr-review/action.yml +298 -0
- package/plugins/pr-review/scripts/agent_script.py +1282 -0
- package/plugins/pr-review/scripts/evaluate_review.py +655 -0
- package/plugins/pr-review/scripts/prompt.py +260 -0
- package/plugins/pr-review/workflows/pr-review-by-openhands.yml +51 -0
- package/plugins/pr-review/workflows/pr-review-evaluation.yml +85 -0
- package/plugins/qa-changes/.plugin/plugin.json +11 -0
- package/plugins/qa-changes/README.md +185 -0
- package/plugins/qa-changes/action.yml +181 -0
- package/plugins/qa-changes/scripts/agent_script.py +406 -0
- package/plugins/qa-changes/scripts/evaluate_qa_changes.py +385 -0
- package/plugins/qa-changes/scripts/prompt.py +174 -0
- package/plugins/qa-changes/workflows/qa-changes-by-openhands.yml +50 -0
- package/plugins/qa-changes/workflows/qa-changes-evaluation.yml +85 -0
- package/plugins/release-notes/.plugin/plugin.json +19 -0
- package/plugins/release-notes/README.md +283 -0
- package/plugins/release-notes/SKILL.md +83 -0
- package/plugins/release-notes/action.yml +117 -0
- package/plugins/release-notes/commands/release-notes.md +8 -0
- package/plugins/release-notes/scripts/agent_script.py +292 -0
- package/plugins/release-notes/scripts/generate_release_notes.py +733 -0
- package/plugins/release-notes/scripts/prompt.py +90 -0
- package/plugins/release-notes/scripts/validate_release_notes.py +328 -0
- package/plugins/release-notes/workflows/release-notes.yml +76 -0
- package/plugins/vulnerability-remediation/.plugin/plugin.json +19 -0
- package/plugins/vulnerability-remediation/README.md +217 -0
- package/plugins/vulnerability-remediation/action.yml +187 -0
- package/plugins/vulnerability-remediation/scripts/scan_and_remediate.py +561 -0
- package/plugins/vulnerability-remediation/workflows/vulnerability-scan.yml +87 -0
- package/pyproject.toml +12 -0
- package/release-please-config.json +16 -0
- package/scripts/sync_extensions.py +494 -0
- package/scripts/sync_openhands_sdk_skill.py +264 -0
- package/skills/README.md +159 -0
- package/skills/add-javadoc/.plugin/plugin.json +18 -0
- package/skills/add-javadoc/README.md +40 -0
- package/skills/add-javadoc/SKILL.md +35 -0
- package/skills/add-javadoc/references/example.md +32 -0
- package/skills/add-skill/.plugin/plugin.json +18 -0
- package/skills/add-skill/README.md +67 -0
- package/skills/add-skill/SKILL.md +47 -0
- package/skills/add-skill/scripts/fetch_skill.py +259 -0
- package/skills/agent-creator/.plugin/plugin.json +20 -0
- package/skills/agent-creator/README.md +104 -0
- package/skills/agent-creator/SKILL.md +190 -0
- package/skills/agent-creator/commands/agent-creator.md +8 -0
- package/skills/agent-creator/references/fallback.md +117 -0
- package/skills/agent-memory/.plugin/plugin.json +18 -0
- package/skills/agent-memory/README.md +35 -0
- package/skills/agent-memory/SKILL.md +30 -0
- package/skills/agent-memory/commands/remember.md +8 -0
- package/skills/agent-sdk-builder/.plugin/plugin.json +18 -0
- package/skills/agent-sdk-builder/README.md +40 -0
- package/skills/agent-sdk-builder/SKILL.md +37 -0
- package/skills/agent-sdk-builder/commands/agent-builder.md +8 -0
- package/skills/azure-devops/.plugin/plugin.json +18 -0
- package/skills/azure-devops/README.md +55 -0
- package/skills/azure-devops/SKILL.md +50 -0
- package/skills/bitbucket/.plugin/plugin.json +17 -0
- package/skills/bitbucket/README.md +50 -0
- package/skills/bitbucket/SKILL.md +45 -0
- package/skills/code-review/.plugin/plugin.json +19 -0
- package/skills/code-review/README.md +18 -0
- package/skills/code-review/SKILL.md +208 -0
- package/skills/code-review/commands/codereview-roasted.md +8 -0
- package/skills/code-review/commands/codereview.md +8 -0
- package/skills/code-review/references/risk-evaluation.md +41 -0
- package/skills/code-review/references/supply-chain-security.md +31 -0
- package/skills/code-simplifier/.plugin/plugin.json +21 -0
- package/skills/code-simplifier/README.md +30 -0
- package/skills/code-simplifier/SKILL.md +91 -0
- package/skills/code-simplifier/commands/simplify.md +8 -0
- package/skills/code-simplifier/references/code-quality-review.md +86 -0
- package/skills/code-simplifier/references/code-reuse-review.md +63 -0
- package/skills/code-simplifier/references/efficiency-review.md +81 -0
- package/skills/datadog/.plugin/plugin.json +19 -0
- package/skills/datadog/README.md +100 -0
- package/skills/datadog/SKILL.md +95 -0
- package/skills/deno/.plugin/plugin.json +18 -0
- package/skills/deno/README.md +5 -0
- package/skills/deno/SKILL.md +99 -0
- package/skills/deno/references/README.md +6 -0
- package/skills/discord/.plugin/plugin.json +18 -0
- package/skills/discord/README.md +31 -0
- package/skills/discord/SKILL.md +109 -0
- package/skills/discord/__init__.py +0 -0
- package/skills/discord/references/REFERENCE.md +78 -0
- package/skills/discord/scripts/__init__.py +0 -0
- package/skills/discord/scripts/_http.py +127 -0
- package/skills/discord/scripts/post_webhook.py +106 -0
- package/skills/discord/scripts/send_message.py +102 -0
- package/skills/docker/.plugin/plugin.json +17 -0
- package/skills/docker/README.md +34 -0
- package/skills/docker/SKILL.md +29 -0
- package/skills/evidence-based-citations/.plugin/plugin.json +20 -0
- package/skills/evidence-based-citations/README.md +31 -0
- package/skills/evidence-based-citations/SKILL.md +59 -0
- package/skills/flarglebargle/.plugin/plugin.json +16 -0
- package/skills/flarglebargle/README.md +14 -0
- package/skills/flarglebargle/SKILL.md +9 -0
- package/skills/frontend-design/.plugin/plugin.json +21 -0
- package/skills/frontend-design/LICENSE.txt +177 -0
- package/skills/frontend-design/README.md +42 -0
- package/skills/frontend-design/SKILL.md +42 -0
- package/skills/github/.plugin/plugin.json +19 -0
- package/skills/github/README.md +42 -0
- package/skills/github/SKILL.md +106 -0
- package/skills/github-pr-review/.plugin/plugin.json +18 -0
- package/skills/github-pr-review/README.md +145 -0
- package/skills/github-pr-review/SKILL.md +148 -0
- package/skills/github-pr-review/commands/github-pr-review.md +8 -0
- package/skills/github-pr-reviewer/.plugin/plugin.json +20 -0
- package/skills/github-pr-reviewer/README.md +34 -0
- package/skills/github-pr-reviewer/SKILL.md +89 -0
- package/skills/github-pr-reviewer/commands/pr-reviewer:setup.md +8 -0
- package/skills/github-repo-monitor/.plugin/plugin.json +22 -0
- package/skills/github-repo-monitor/README.md +70 -0
- package/skills/github-repo-monitor/SKILL.md +316 -0
- package/skills/github-repo-monitor/commands/github-monitor:poll.md +8 -0
- package/skills/github-repo-monitor/references/github-api.md +241 -0
- package/skills/github-repo-monitor/references/state-schema.md +160 -0
- package/skills/github-repo-monitor/scripts/main.py +915 -0
- package/skills/github-repo-monitor/tests/test_main.py +400 -0
- package/skills/gitlab/.plugin/plugin.json +17 -0
- package/skills/gitlab/README.md +37 -0
- package/skills/gitlab/SKILL.md +32 -0
- package/skills/incident-retrospective/.plugin/plugin.json +21 -0
- package/skills/incident-retrospective/README.md +34 -0
- package/skills/incident-retrospective/SKILL.md +98 -0
- package/skills/incident-retrospective/commands/incident-retro:setup.md +8 -0
- package/skills/iterate/.plugin/plugin.json +13 -0
- package/skills/iterate/README.md +25 -0
- package/skills/iterate/SKILL.md +399 -0
- package/skills/iterate/commands/babysit.md +8 -0
- package/skills/iterate/commands/iterate.md +8 -0
- package/skills/iterate/commands/verify.md +8 -0
- package/skills/iterate/references/heuristics.md +58 -0
- package/skills/iterate/references/verification.md +96 -0
- package/skills/jupyter/.plugin/plugin.json +18 -0
- package/skills/jupyter/README.md +55 -0
- package/skills/jupyter/SKILL.md +50 -0
- package/skills/kubernetes/.plugin/plugin.json +18 -0
- package/skills/kubernetes/README.md +53 -0
- package/skills/kubernetes/SKILL.md +48 -0
- package/skills/learn-from-code-review/.plugin/plugin.json +19 -0
- package/skills/learn-from-code-review/README.md +64 -0
- package/skills/learn-from-code-review/SKILL.md +186 -0
- package/skills/learn-from-code-review/commands/learn-from-reviews.md +8 -0
- package/skills/linear/.plugin/plugin.json +19 -0
- package/skills/linear/README.md +58 -0
- package/skills/linear/SKILL.md +213 -0
- package/skills/linear-triage/.plugin/plugin.json +21 -0
- package/skills/linear-triage/README.md +34 -0
- package/skills/linear-triage/SKILL.md +91 -0
- package/skills/linear-triage/commands/linear-triage:setup.md +8 -0
- package/skills/notion/.plugin/plugin.json +17 -0
- package/skills/notion/README.md +114 -0
- package/skills/notion/SKILL.md +109 -0
- package/skills/npm/.plugin/plugin.json +17 -0
- package/skills/npm/README.md +14 -0
- package/skills/npm/SKILL.md +9 -0
- package/skills/openhands-api/.plugin/plugin.json +22 -0
- package/skills/openhands-api/README.md +48 -0
- package/skills/openhands-api/SKILL.md +399 -0
- package/skills/openhands-api/references/README.md +33 -0
- package/skills/openhands-api/references/TROUBLESHOOTING.md +81 -0
- package/skills/openhands-api/references/example_prompt.md +12 -0
- package/skills/openhands-api/scripts/openhands_api.py +606 -0
- package/skills/openhands-api/scripts/openhands_api.ts +252 -0
- package/skills/openhands-automation/.plugin/plugin.json +19 -0
- package/skills/openhands-automation/README.md +89 -0
- package/skills/openhands-automation/SKILL.md +875 -0
- package/skills/openhands-automation/commands/automation:create.md +8 -0
- package/skills/openhands-automation/references/ab-testing.md +185 -0
- package/skills/openhands-automation/references/custom-automation.md +644 -0
- package/skills/openhands-sdk/.plugin/plugin.json +20 -0
- package/skills/openhands-sdk/README.md +22 -0
- package/skills/openhands-sdk/SKILL.md +229 -0
- package/skills/openhands-sdk/commands/sdk.md +8 -0
- package/skills/pdflatex/.plugin/plugin.json +18 -0
- package/skills/pdflatex/README.md +39 -0
- package/skills/pdflatex/SKILL.md +34 -0
- package/skills/prd/.plugin/plugin.json +19 -0
- package/skills/prd/README.md +28 -0
- package/skills/prd/SKILL.md +237 -0
- package/skills/prd/commands/prd.md +8 -0
- package/skills/qa-changes/README.md +18 -0
- package/skills/qa-changes/SKILL.md +229 -0
- package/skills/qa-changes/commands/qa-changes.md +8 -0
- package/skills/release-notes/README.md +24 -0
- package/skills/release-notes/SKILL.md +19 -0
- package/skills/release-notes/commands/release-notes.md +8 -0
- package/skills/research-brief/.plugin/plugin.json +20 -0
- package/skills/research-brief/README.md +34 -0
- package/skills/research-brief/SKILL.md +99 -0
- package/skills/research-brief/commands/research-brief:setup.md +8 -0
- package/skills/security/.plugin/plugin.json +18 -0
- package/skills/security/README.md +38 -0
- package/skills/security/SKILL.md +33 -0
- package/skills/skill-creator/.plugin/plugin.json +17 -0
- package/skills/skill-creator/LICENSE.txt +202 -0
- package/skills/skill-creator/README.md +182 -0
- package/skills/skill-creator/SKILL.md +545 -0
- package/skills/skill-creator/references/output-patterns.md +82 -0
- package/skills/skill-creator/references/workflows.md +28 -0
- package/skills/skill-creator/scripts/init_skill.py +303 -0
- package/skills/skill-creator/scripts/quick_validate.py +95 -0
- package/skills/slack-channel-monitor/.plugin/plugin.json +21 -0
- package/skills/slack-channel-monitor/README.md +91 -0
- package/skills/slack-channel-monitor/SKILL.md +276 -0
- package/skills/slack-channel-monitor/commands/slack-monitor:poll.md +8 -0
- package/skills/slack-channel-monitor/references/slack-api.md +207 -0
- package/skills/slack-channel-monitor/references/state-schema.md +180 -0
- package/skills/slack-channel-monitor/scripts/main.py +962 -0
- package/skills/slack-standup-digest/.plugin/plugin.json +21 -0
- package/skills/slack-standup-digest/README.md +34 -0
- package/skills/slack-standup-digest/SKILL.md +92 -0
- package/skills/slack-standup-digest/commands/standup-digest:setup.md +8 -0
- package/skills/spark-version-upgrade/.plugin/plugin.json +20 -0
- package/skills/spark-version-upgrade/README.md +54 -0
- package/skills/spark-version-upgrade/SKILL.md +233 -0
- package/skills/ssh/.plugin/plugin.json +18 -0
- package/skills/ssh/README.md +140 -0
- package/skills/ssh/SKILL.md +135 -0
- package/skills/swift-linux/.plugin/plugin.json +17 -0
- package/skills/swift-linux/README.md +86 -0
- package/skills/swift-linux/SKILL.md +81 -0
- package/skills/theme-factory/.plugin/plugin.json +19 -0
- package/skills/theme-factory/LICENSE.txt +202 -0
- package/skills/theme-factory/README.md +58 -0
- package/skills/theme-factory/SKILL.md +59 -0
- package/skills/theme-factory/theme-showcase.pdf +0 -0
- package/skills/theme-factory/themes/arctic-frost.md +19 -0
- package/skills/theme-factory/themes/botanical-garden.md +19 -0
- package/skills/theme-factory/themes/desert-rose.md +19 -0
- package/skills/theme-factory/themes/forest-canopy.md +19 -0
- package/skills/theme-factory/themes/golden-hour.md +19 -0
- package/skills/theme-factory/themes/midnight-galaxy.md +19 -0
- package/skills/theme-factory/themes/modern-minimalist.md +19 -0
- package/skills/theme-factory/themes/ocean-depths.md +19 -0
- package/skills/theme-factory/themes/sunset-boulevard.md +19 -0
- package/skills/theme-factory/themes/tech-innovation.md +19 -0
- package/skills/uv/.plugin/plugin.json +18 -0
- package/skills/uv/README.md +5 -0
- package/skills/uv/SKILL.md +95 -0
- package/skills/uv/references/README.md +5 -0
- package/skills/vercel/.plugin/plugin.json +18 -0
- package/skills/vercel/README.md +108 -0
- package/skills/vercel/SKILL.md +103 -0
- package/tests/test_add_skill_installs_to_agents_dir.py +42 -0
- package/tests/test_catalogs.py +109 -0
- package/tests/test_code_review_risk_evaluation.py +94 -0
- package/tests/test_issue_duplicate_checker.py +240 -0
- package/tests/test_openhands_api_python.py +152 -0
- package/tests/test_plugin_manifest.py +83 -0
- package/tests/test_pr_review_diff_payload.py +202 -0
- package/tests/test_pr_review_feedback.py +263 -0
- package/tests/test_pr_review_prompt.py +152 -0
- package/tests/test_pr_review_review_context.py +253 -0
- package/tests/test_qa_changes.py +232 -0
- package/tests/test_qa_changes_evaluation.py +259 -0
- package/tests/test_release_notes_generator.py +990 -0
- package/tests/test_sdk_loading.py +150 -0
- package/tests/test_skill_plugin_loading.py +149 -0
- package/tests/test_skills_have_readme.py +66 -0
- package/tests/test_sync_extensions.py +292 -0
- package/tests/test_workflow_sync.py +46 -0
- package/utils/analysis/README.md +7 -0
- package/utils/analysis/laminar_signals/README.md +211 -0
- package/utils/analysis/laminar_signals/analyze.py +780 -0
- package/utils/analysis/laminar_signals/templates/default.j2 +49 -0
- package/utils/analysis/laminar_signals/templates/pr_review.j2 +61 -0
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
"""Tests for the per-file diff payload introduced to replace the global byte-
|
|
2
|
+
slice truncation in agent_script.py.
|
|
3
|
+
|
|
4
|
+
The legacy `truncate_text()` cut the raw diff at byte 100,000, which silently
|
|
5
|
+
dropped files whose patches lived past that point (issue #233). These tests
|
|
6
|
+
exercise the replacement: a manifest that lists every file plus per-file
|
|
7
|
+
budgeted patches that mark abbreviation/omission explicitly.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
# Re-use the module loader from the review-context test file so we don't
|
|
13
|
+
# duplicate ~130 lines of openhands-SDK stubbing.
|
|
14
|
+
from test_pr_review_review_context import _load_agent_script_module
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _file(
|
|
18
|
+
filename: str,
|
|
19
|
+
*,
|
|
20
|
+
status: str = "modified",
|
|
21
|
+
additions: int = 0,
|
|
22
|
+
deletions: int = 0,
|
|
23
|
+
patch: str | None = "",
|
|
24
|
+
previous_filename: str | None = None,
|
|
25
|
+
) -> dict:
|
|
26
|
+
return {
|
|
27
|
+
"filename": filename,
|
|
28
|
+
"status": status,
|
|
29
|
+
"additions": additions,
|
|
30
|
+
"deletions": deletions,
|
|
31
|
+
"patch": patch,
|
|
32
|
+
"previous_filename": previous_filename,
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
# --- manifest -----------------------------------------------------------
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def test_manifest_lists_every_file():
|
|
40
|
+
module = _load_agent_script_module()
|
|
41
|
+
files = [
|
|
42
|
+
_file("a.py", status="added", additions=10),
|
|
43
|
+
_file("b.py", status="modified", additions=2, deletions=3),
|
|
44
|
+
_file("c.py", status="removed", deletions=15),
|
|
45
|
+
]
|
|
46
|
+
manifest = module.format_files_manifest(files)
|
|
47
|
+
assert "`a.py`" in manifest
|
|
48
|
+
assert "`b.py`" in manifest
|
|
49
|
+
assert "`c.py`" in manifest
|
|
50
|
+
assert "[added]" in manifest
|
|
51
|
+
assert "[modified]" in manifest
|
|
52
|
+
assert "[removed]" in manifest
|
|
53
|
+
assert "+10" in manifest
|
|
54
|
+
assert "+2/-3" in manifest
|
|
55
|
+
assert "-15" in manifest
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def test_manifest_includes_totals_in_header():
|
|
59
|
+
module = _load_agent_script_module()
|
|
60
|
+
files = [
|
|
61
|
+
_file("a.py", additions=10, deletions=2),
|
|
62
|
+
_file("b.py", additions=5, deletions=1),
|
|
63
|
+
]
|
|
64
|
+
manifest = module.format_files_manifest(files)
|
|
65
|
+
assert "2 files" in manifest
|
|
66
|
+
assert "+15" in manifest
|
|
67
|
+
assert "-3" in manifest
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def test_manifest_handles_renames():
|
|
71
|
+
module = _load_agent_script_module()
|
|
72
|
+
files = [
|
|
73
|
+
_file(
|
|
74
|
+
"new.py",
|
|
75
|
+
status="renamed",
|
|
76
|
+
additions=1,
|
|
77
|
+
deletions=1,
|
|
78
|
+
previous_filename="old.py",
|
|
79
|
+
),
|
|
80
|
+
]
|
|
81
|
+
manifest = module.format_files_manifest(files)
|
|
82
|
+
assert "`new.py`" in manifest
|
|
83
|
+
assert "renamed from old.py" in manifest
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def test_manifest_flags_binary_files():
|
|
87
|
+
module = _load_agent_script_module()
|
|
88
|
+
files = [
|
|
89
|
+
_file("logo.png", status="modified", additions=1, deletions=1, patch=""),
|
|
90
|
+
]
|
|
91
|
+
manifest = module.format_files_manifest(files)
|
|
92
|
+
assert "`logo.png`" in manifest
|
|
93
|
+
assert "binary or unavailable" in manifest
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
# --- patch formatting ---------------------------------------------------
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def test_patches_include_header_for_every_file():
|
|
100
|
+
"""Every file gets a diff header even when only a stub fits."""
|
|
101
|
+
module = _load_agent_script_module()
|
|
102
|
+
files = [
|
|
103
|
+
_file("a.py", status="added", additions=1, patch="@@ -0,0 +1 @@\n+x\n"),
|
|
104
|
+
_file("b.py", status="added", additions=1, patch="@@ -0,0 +1 @@\n+y\n"),
|
|
105
|
+
]
|
|
106
|
+
out = module.format_patches(files)
|
|
107
|
+
assert "diff --git a/a.py b/a.py" in out
|
|
108
|
+
assert "diff --git a/b.py b/b.py" in out
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def test_patches_below_budget_passthrough():
|
|
112
|
+
module = _load_agent_script_module()
|
|
113
|
+
patch = "@@ -1,2 +1,2 @@\n-foo\n+bar\n"
|
|
114
|
+
files = [_file("a.py", patch=patch)]
|
|
115
|
+
out = module.format_patches(files, max_total=10000, max_per_file=10000)
|
|
116
|
+
assert patch in out
|
|
117
|
+
assert "[patch abbreviated" not in out
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def test_per_file_cap_abbreviates_huge_single_file():
|
|
121
|
+
"""A single large patch is abbreviated, not dropped."""
|
|
122
|
+
module = _load_agent_script_module()
|
|
123
|
+
big_patch = "@@ -0,0 +1,1000 @@\n" + "".join(
|
|
124
|
+
f"+line {i}\n" for i in range(1000)
|
|
125
|
+
)
|
|
126
|
+
files = [_file("big.py", status="added", patch=big_patch)]
|
|
127
|
+
out = module.format_patches(files, max_total=100000, max_per_file=500)
|
|
128
|
+
assert "diff --git a/big.py b/big.py" in out
|
|
129
|
+
# The marker is the contract — the agent knows the patch was cut.
|
|
130
|
+
assert "[patch abbreviated" in out
|
|
131
|
+
# Should reference the actual file path so the agent knows where to look.
|
|
132
|
+
assert "`big.py`" in out
|
|
133
|
+
# Must not contain the full patch text.
|
|
134
|
+
assert len(out) < len(big_patch)
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def test_total_budget_omits_late_files_with_marker_not_silence():
|
|
138
|
+
"""When total budget is exhausted, later files become header-only stubs
|
|
139
|
+
with an explicit `[patch omitted]` marker — never silently dropped."""
|
|
140
|
+
module = _load_agent_script_module()
|
|
141
|
+
fat_patch = "@@ -0,0 +1,100 @@\n" + "".join(f"+x{i}\n" for i in range(100))
|
|
142
|
+
files = [
|
|
143
|
+
_file("first.py", status="added", patch=fat_patch),
|
|
144
|
+
_file("second.py", status="added", patch=fat_patch),
|
|
145
|
+
_file("third.py", status="added", patch=fat_patch),
|
|
146
|
+
]
|
|
147
|
+
# Budget that comfortably fits one full patch but not three.
|
|
148
|
+
out = module.format_patches(files, max_total=800, max_per_file=10000)
|
|
149
|
+
|
|
150
|
+
# All three files appear in the patch block — at minimum as headers.
|
|
151
|
+
assert "diff --git a/first.py b/first.py" in out
|
|
152
|
+
assert "diff --git a/second.py b/second.py" in out
|
|
153
|
+
assert "diff --git a/third.py b/third.py" in out
|
|
154
|
+
# At least one of the later files is marked omitted.
|
|
155
|
+
assert "[patch omitted" in out
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def test_no_patch_field_is_annotated_not_silent():
|
|
159
|
+
"""A file with no patch text (binary, rename, etc) gets a clear note."""
|
|
160
|
+
module = _load_agent_script_module()
|
|
161
|
+
files = [_file("logo.png", additions=1, deletions=1, patch="")]
|
|
162
|
+
out = module.format_patches(files)
|
|
163
|
+
assert "diff --git a/logo.png b/logo.png" in out
|
|
164
|
+
assert "no patch available" in out
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def test_smoking_gun_pr_14401_shape():
|
|
168
|
+
"""Regression for issue #233 — the specific failure mode that triggered
|
|
169
|
+
the redesign. Earlier files have huge patches; the implementation file
|
|
170
|
+
of interest sits past the byte-100K mark of the original raw diff. With
|
|
171
|
+
a per-file budget the late file's *patch* may still be abbreviated, but
|
|
172
|
+
its *presence* must always be visible in both manifest and patch block."""
|
|
173
|
+
module = _load_agent_script_module()
|
|
174
|
+
|
|
175
|
+
# Two heavy files dominate the byte budget.
|
|
176
|
+
bulk_patch = "@@ -1 +1,2000 @@\n" + "".join(
|
|
177
|
+
f"+filler {i}\n" for i in range(2000)
|
|
178
|
+
)
|
|
179
|
+
# The implementation file the bot kept missing.
|
|
180
|
+
target = (
|
|
181
|
+
"@@ -0,0 +1,55 @@\n"
|
|
182
|
+
+ "".join(f"+line {i}\n" for i in range(55))
|
|
183
|
+
)
|
|
184
|
+
files = [
|
|
185
|
+
_file("enterprise/poetry.lock", patch=bulk_patch),
|
|
186
|
+
_file("openhands/long_module.py", patch=bulk_patch),
|
|
187
|
+
_file(
|
|
188
|
+
"frontend/src/utils/shell-tokenize.ts",
|
|
189
|
+
status="added",
|
|
190
|
+
additions=55,
|
|
191
|
+
patch=target,
|
|
192
|
+
),
|
|
193
|
+
]
|
|
194
|
+
|
|
195
|
+
manifest = module.format_files_manifest(files)
|
|
196
|
+
patches = module.format_patches(files, max_total=2000, max_per_file=600)
|
|
197
|
+
|
|
198
|
+
# Manifest always names the target file — this is the property that
|
|
199
|
+
# prevents the bot from claiming the file is missing.
|
|
200
|
+
assert "`frontend/src/utils/shell-tokenize.ts`" in manifest
|
|
201
|
+
# Patch block lists it too, even when its content is abbreviated/omitted.
|
|
202
|
+
assert "diff --git a/frontend/src/utils/shell-tokenize.ts" in patches
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
"""Tests for PR review feedback collection."""
|
|
2
|
+
|
|
3
|
+
import importlib.util
|
|
4
|
+
import sys
|
|
5
|
+
import types
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
import yaml
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
_ROOT = Path(__file__).parent.parent
|
|
12
|
+
_PR_REVIEW_SCRIPTS = _ROOT / "plugins" / "pr-review" / "scripts"
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _load_prompt_module():
|
|
16
|
+
path = _PR_REVIEW_SCRIPTS / "prompt.py"
|
|
17
|
+
spec = importlib.util.spec_from_file_location("pr_review_prompt_feedback", path)
|
|
18
|
+
module = importlib.util.module_from_spec(spec)
|
|
19
|
+
sys.modules[spec.name] = module
|
|
20
|
+
spec.loader.exec_module(module)
|
|
21
|
+
return module
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _load_eval_module():
|
|
26
|
+
"""Load evaluate_review.py with Laminar stubbed."""
|
|
27
|
+
lmnr_mod = types.ModuleType("lmnr")
|
|
28
|
+
|
|
29
|
+
class _FakeLaminar:
|
|
30
|
+
@staticmethod
|
|
31
|
+
def initialize():
|
|
32
|
+
return None
|
|
33
|
+
|
|
34
|
+
@staticmethod
|
|
35
|
+
def get_trace_id():
|
|
36
|
+
return None
|
|
37
|
+
|
|
38
|
+
@staticmethod
|
|
39
|
+
def get_laminar_span_context():
|
|
40
|
+
return None
|
|
41
|
+
|
|
42
|
+
@staticmethod
|
|
43
|
+
def set_trace_metadata(metadata):
|
|
44
|
+
return None
|
|
45
|
+
|
|
46
|
+
@staticmethod
|
|
47
|
+
def set_span_output(output):
|
|
48
|
+
return None
|
|
49
|
+
|
|
50
|
+
@staticmethod
|
|
51
|
+
def flush():
|
|
52
|
+
return None
|
|
53
|
+
|
|
54
|
+
@staticmethod
|
|
55
|
+
def start_as_current_span(**kwargs):
|
|
56
|
+
import contextlib
|
|
57
|
+
|
|
58
|
+
return contextlib.nullcontext()
|
|
59
|
+
|
|
60
|
+
class _FakeClient:
|
|
61
|
+
class evaluators:
|
|
62
|
+
@staticmethod
|
|
63
|
+
def score(**kwargs):
|
|
64
|
+
return None
|
|
65
|
+
|
|
66
|
+
class tags:
|
|
67
|
+
@staticmethod
|
|
68
|
+
def tag(trace_id, tags):
|
|
69
|
+
return None
|
|
70
|
+
|
|
71
|
+
lmnr_mod.Laminar = _FakeLaminar
|
|
72
|
+
lmnr_mod.LaminarClient = _FakeClient
|
|
73
|
+
|
|
74
|
+
saved = sys.modules.get("lmnr")
|
|
75
|
+
sys.modules["lmnr"] = lmnr_mod
|
|
76
|
+
try:
|
|
77
|
+
path = _PR_REVIEW_SCRIPTS / "evaluate_review.py"
|
|
78
|
+
spec = importlib.util.spec_from_file_location("pr_review_evaluate", path)
|
|
79
|
+
module = importlib.util.module_from_spec(spec)
|
|
80
|
+
sys.modules[spec.name] = module
|
|
81
|
+
spec.loader.exec_module(module)
|
|
82
|
+
return module
|
|
83
|
+
finally:
|
|
84
|
+
if saved is None:
|
|
85
|
+
sys.modules.pop("lmnr", None)
|
|
86
|
+
else:
|
|
87
|
+
sys.modules["lmnr"] = saved
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def _format_prompt(*, collect_feedback: bool, review_run_url: str = "") -> str:
|
|
92
|
+
module = _load_prompt_module()
|
|
93
|
+
return module.format_prompt(
|
|
94
|
+
skill_trigger="/codereview",
|
|
95
|
+
title="Add review feedback footer",
|
|
96
|
+
body="## Summary\nCapture review reactions without extra PR spam.",
|
|
97
|
+
repo_name="OpenHands/extensions",
|
|
98
|
+
base_branch="main",
|
|
99
|
+
head_branch="feature/feedback-footer",
|
|
100
|
+
pr_number="249",
|
|
101
|
+
commit_id="abc123",
|
|
102
|
+
diff="diff --git a/file b/file",
|
|
103
|
+
review_context="",
|
|
104
|
+
require_evidence=False,
|
|
105
|
+
collect_feedback=collect_feedback,
|
|
106
|
+
review_run_url=review_run_url,
|
|
107
|
+
use_sub_agents=False,
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def test_action_collect_feedback_defaults_to_true_and_uses_agent_prompt():
|
|
113
|
+
action_yml = _ROOT / "plugins" / "pr-review" / "action.yml"
|
|
114
|
+
with open(action_yml) as f:
|
|
115
|
+
action = yaml.safe_load(f)
|
|
116
|
+
|
|
117
|
+
collect_feedback = action["inputs"]["collect-feedback"]
|
|
118
|
+
assert collect_feedback["default"] == "true"
|
|
119
|
+
|
|
120
|
+
run_step = next(
|
|
121
|
+
step for step in action["runs"]["steps"] if step["name"] == "Run PR review"
|
|
122
|
+
)
|
|
123
|
+
assert run_step["env"]["COLLECT_FEEDBACK"] == "${{ inputs.collect-feedback }}"
|
|
124
|
+
assert run_step["env"]["REVIEW_RUN_URL"] == (
|
|
125
|
+
"${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"
|
|
126
|
+
)
|
|
127
|
+
assert all(
|
|
128
|
+
step["name"] != "Post PR review feedback prompt"
|
|
129
|
+
for step in action["runs"]["steps"]
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def test_prompt_omits_feedback_footer_by_default():
|
|
135
|
+
prompt = _format_prompt(collect_feedback=False)
|
|
136
|
+
|
|
137
|
+
assert "## Review Feedback Footer" not in prompt
|
|
138
|
+
assert "React with 👍 or 👎 to this review" not in prompt
|
|
139
|
+
assert "<!-- openhands-pr-review-feedback -->" not in prompt
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def test_prompt_includes_feedback_footer_when_enabled():
|
|
144
|
+
prompt = _format_prompt(
|
|
145
|
+
collect_feedback=True,
|
|
146
|
+
review_run_url="https://github.com/OpenHands/extensions/actions/runs/123",
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
assert "## Review Feedback Footer" in prompt
|
|
150
|
+
assert "main review body, not in a separate issue comment" in prompt
|
|
151
|
+
assert "React with 👍 or 👎 to this review" in prompt
|
|
152
|
+
assert "https://github.com/OpenHands/extensions/actions/runs/123" in prompt
|
|
153
|
+
assert "<!-- openhands-pr-review-feedback -->" in prompt
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def test_extract_review_feedback_counts_thumbs_reactions_from_reviews():
|
|
158
|
+
module = _load_eval_module()
|
|
159
|
+
|
|
160
|
+
reviews = [
|
|
161
|
+
{
|
|
162
|
+
"id": 101,
|
|
163
|
+
"user": {"login": "openhands-agent"},
|
|
164
|
+
"body": "Automated review\n<!-- openhands-pr-review-feedback -->",
|
|
165
|
+
"submitted_at": "2026-05-19T12:00:00Z",
|
|
166
|
+
"reactions": {"+1": 3, "-1": 1, "total_count": 4},
|
|
167
|
+
},
|
|
168
|
+
{
|
|
169
|
+
"id": 102,
|
|
170
|
+
"user": {"login": "openhands-agent"},
|
|
171
|
+
"body": "Regular review comment",
|
|
172
|
+
"reactions": {"+1": 100, "-1": 100},
|
|
173
|
+
},
|
|
174
|
+
{
|
|
175
|
+
"id": 103,
|
|
176
|
+
"user": {"login": "human-dev"},
|
|
177
|
+
"body": "<!-- openhands-pr-review-feedback -->",
|
|
178
|
+
"reactions": {"+1": 5, "-1": 0},
|
|
179
|
+
},
|
|
180
|
+
]
|
|
181
|
+
|
|
182
|
+
assert module.extract_review_feedback([], reviews) == [
|
|
183
|
+
{
|
|
184
|
+
"comment_id": 101,
|
|
185
|
+
"created_at": "2026-05-19T12:00:00Z",
|
|
186
|
+
"thumbs_up": 3,
|
|
187
|
+
"thumbs_down": 1,
|
|
188
|
+
"total": 4,
|
|
189
|
+
}
|
|
190
|
+
]
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def test_extract_review_feedback_keeps_legacy_issue_comment_support():
|
|
195
|
+
module = _load_eval_module()
|
|
196
|
+
|
|
197
|
+
result = module.extract_review_feedback(
|
|
198
|
+
[
|
|
199
|
+
{
|
|
200
|
+
"id": 201,
|
|
201
|
+
"user": {"login": "all-hands-bot"},
|
|
202
|
+
"body": "<!-- openhands-pr-review-feedback -->",
|
|
203
|
+
"created_at": "2026-05-19T12:00:00Z",
|
|
204
|
+
"reactions": {"+1": 2, "-1": 0},
|
|
205
|
+
}
|
|
206
|
+
]
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
assert result == [
|
|
210
|
+
{
|
|
211
|
+
"comment_id": 201,
|
|
212
|
+
"created_at": "2026-05-19T12:00:00Z",
|
|
213
|
+
"thumbs_up": 2,
|
|
214
|
+
"thumbs_down": 0,
|
|
215
|
+
"total": 2,
|
|
216
|
+
}
|
|
217
|
+
]
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
def test_extract_review_feedback_handles_missing_reactions():
|
|
222
|
+
module = _load_eval_module()
|
|
223
|
+
|
|
224
|
+
result = module.extract_review_feedback(
|
|
225
|
+
[],
|
|
226
|
+
[
|
|
227
|
+
{
|
|
228
|
+
"id": 301,
|
|
229
|
+
"user": {"login": "all-hands-bot"},
|
|
230
|
+
"body": "<!-- openhands-pr-review-feedback -->",
|
|
231
|
+
}
|
|
232
|
+
],
|
|
233
|
+
)
|
|
234
|
+
|
|
235
|
+
assert result == [
|
|
236
|
+
{
|
|
237
|
+
"comment_id": 301,
|
|
238
|
+
"created_at": None,
|
|
239
|
+
"thumbs_up": 0,
|
|
240
|
+
"thumbs_down": 0,
|
|
241
|
+
"total": 0,
|
|
242
|
+
}
|
|
243
|
+
]
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
def test_extract_review_feedback_accepts_github_actions_bot():
|
|
248
|
+
module = _load_eval_module()
|
|
249
|
+
|
|
250
|
+
result = module.extract_review_feedback(
|
|
251
|
+
[],
|
|
252
|
+
[
|
|
253
|
+
{
|
|
254
|
+
"id": 401,
|
|
255
|
+
"user": {"login": "github-actions[bot]"},
|
|
256
|
+
"body": "<!-- openhands-pr-review-feedback -->",
|
|
257
|
+
"reactions": {"+1": 1, "-1": 2},
|
|
258
|
+
}
|
|
259
|
+
],
|
|
260
|
+
)
|
|
261
|
+
|
|
262
|
+
assert result[0]["thumbs_up"] == 1
|
|
263
|
+
assert result[0]["thumbs_down"] == 2
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import importlib.util
|
|
2
|
+
import sys
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
import yaml
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def _load_prompt_module():
|
|
9
|
+
script_path = (
|
|
10
|
+
Path(__file__).parent.parent
|
|
11
|
+
/ "plugins"
|
|
12
|
+
/ "pr-review"
|
|
13
|
+
/ "scripts"
|
|
14
|
+
/ "prompt.py"
|
|
15
|
+
)
|
|
16
|
+
spec = importlib.util.spec_from_file_location("pr_review_prompt", script_path)
|
|
17
|
+
module = importlib.util.module_from_spec(spec)
|
|
18
|
+
sys.modules["pr_review_prompt"] = module
|
|
19
|
+
spec.loader.exec_module(module)
|
|
20
|
+
return module
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _format_prompt(
|
|
24
|
+
*, require_evidence: bool, use_sub_agents: bool = False
|
|
25
|
+
) -> str:
|
|
26
|
+
module = _load_prompt_module()
|
|
27
|
+
return module.format_prompt(
|
|
28
|
+
skill_trigger="/codereview",
|
|
29
|
+
title="Add evidence enforcement",
|
|
30
|
+
body="## Summary\nAdds stricter review guidance.",
|
|
31
|
+
repo_name="OpenHands/extensions",
|
|
32
|
+
base_branch="main",
|
|
33
|
+
head_branch="feature/evidence",
|
|
34
|
+
pr_number="102",
|
|
35
|
+
commit_id="abc123",
|
|
36
|
+
diff="diff --git a/file b/file",
|
|
37
|
+
review_context="",
|
|
38
|
+
require_evidence=require_evidence,
|
|
39
|
+
use_sub_agents=use_sub_agents,
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def test_prompt_with_roasted_trigger():
|
|
44
|
+
"""Verify the backward-compatibility trigger alias works."""
|
|
45
|
+
module = _load_prompt_module()
|
|
46
|
+
prompt = module.format_prompt(
|
|
47
|
+
skill_trigger="/codereview-roasted",
|
|
48
|
+
title="Test PR",
|
|
49
|
+
body="Test body",
|
|
50
|
+
repo_name="owner/repo",
|
|
51
|
+
base_branch="main",
|
|
52
|
+
head_branch="feature",
|
|
53
|
+
pr_number="1",
|
|
54
|
+
commit_id="abc123",
|
|
55
|
+
diff="test diff",
|
|
56
|
+
review_context="",
|
|
57
|
+
require_evidence=False,
|
|
58
|
+
)
|
|
59
|
+
assert "/codereview-roasted" in prompt
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def test_format_prompt_omits_evidence_requirements_by_default():
|
|
63
|
+
prompt = _format_prompt(require_evidence=False)
|
|
64
|
+
|
|
65
|
+
assert "## PR Description Evidence Requirement" not in prompt
|
|
66
|
+
assert "real code path end-to-end" not in prompt
|
|
67
|
+
assert "https://app.all-hands.dev/conversations/{conversation_id}" not in prompt
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def test_format_prompt_includes_evidence_requirements_when_enabled():
|
|
71
|
+
prompt = _format_prompt(require_evidence=True)
|
|
72
|
+
|
|
73
|
+
assert "## PR Description Evidence Requirement" in prompt
|
|
74
|
+
assert "`Evidence` section" in prompt
|
|
75
|
+
assert "screenshot or video" in prompt
|
|
76
|
+
assert "real code path end-to-end" in prompt
|
|
77
|
+
assert "unit test output" in prompt
|
|
78
|
+
assert "https://app.all-hands.dev/conversations/{conversation_id}" in prompt
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
# --- Sub-agent delegation prompt tests ---
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def test_format_prompt_uses_standard_prompt_by_default():
|
|
85
|
+
prompt = _format_prompt(require_evidence=False, use_sub_agents=False)
|
|
86
|
+
|
|
87
|
+
# Standard prompt should NOT mention delegation or sub-agents
|
|
88
|
+
assert "review coordinator" not in prompt
|
|
89
|
+
assert "TaskToolSet" not in prompt
|
|
90
|
+
assert "file_reviewer" not in prompt
|
|
91
|
+
# Standard prompt should contain the normal review instruction
|
|
92
|
+
assert "Analyze the changes and post your review" in prompt
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def test_format_prompt_appends_delegation_suffix_when_enabled():
|
|
96
|
+
prompt = _format_prompt(require_evidence=False, use_sub_agents=True)
|
|
97
|
+
|
|
98
|
+
# Should still include the base prompt content
|
|
99
|
+
assert "Add evidence enforcement" in prompt
|
|
100
|
+
assert "OpenHands/extensions" in prompt
|
|
101
|
+
assert "abc123" in prompt
|
|
102
|
+
assert "diff --git a/file b/file" in prompt
|
|
103
|
+
assert "Analyze the changes and post your review" in prompt
|
|
104
|
+
# Delegation suffix appended
|
|
105
|
+
assert "Sub-agent Delegation" in prompt
|
|
106
|
+
assert "file_reviewer" in prompt
|
|
107
|
+
assert "task" in prompt.lower()
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def test_delegation_suffix_with_evidence():
|
|
111
|
+
prompt = _format_prompt(require_evidence=True, use_sub_agents=True)
|
|
112
|
+
|
|
113
|
+
assert "Sub-agent Delegation" in prompt
|
|
114
|
+
assert "## PR Description Evidence Requirement" in prompt
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def test_file_reviewer_skill_content():
|
|
118
|
+
module = _load_prompt_module()
|
|
119
|
+
content = module.FILE_REVIEWER_SKILL
|
|
120
|
+
|
|
121
|
+
assert "file-level code reviewer" in content
|
|
122
|
+
# Unified review style
|
|
123
|
+
assert "pragmatic" in content
|
|
124
|
+
# JSON schema documented
|
|
125
|
+
assert "path" in content
|
|
126
|
+
assert "line" in content
|
|
127
|
+
assert "severity" in content
|
|
128
|
+
assert "body" in content
|
|
129
|
+
assert "critical" in content
|
|
130
|
+
# Tool access documented
|
|
131
|
+
assert "terminal" in content
|
|
132
|
+
assert "file_editor" in content
|
|
133
|
+
# Sub-agent returns results via finish tool
|
|
134
|
+
assert "finish" in content
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
# --- action.yml default guard ---
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def test_use_sub_agents_defaults_to_false():
|
|
141
|
+
"""Guard: use-sub-agents must default to 'false' until cost issues are resolved (#208)."""
|
|
142
|
+
action_yml = (
|
|
143
|
+
Path(__file__).parent.parent / "plugins" / "pr-review" / "action.yml"
|
|
144
|
+
)
|
|
145
|
+
with open(action_yml) as f:
|
|
146
|
+
action = yaml.safe_load(f)
|
|
147
|
+
|
|
148
|
+
default = action["inputs"]["use-sub-agents"]["default"]
|
|
149
|
+
assert default == "false", (
|
|
150
|
+
f"use-sub-agents default should be 'false' (got {default!r}). "
|
|
151
|
+
"See https://github.com/OpenHands/extensions/issues/208"
|
|
152
|
+
)
|