@openhands/extensions 0.0.1-alpha → 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.
Files changed (347) hide show
  1. package/.agents/skills/custom-codereview-guide.md +25 -0
  2. package/.github/pull_request_template.md +38 -0
  3. package/.github/release.yml +14 -0
  4. package/.github/workflows/check-extensions.yml +72 -0
  5. package/.github/workflows/npm-publish.yml +89 -0
  6. package/.github/workflows/pr.yml +30 -0
  7. package/.github/workflows/release.yml +24 -0
  8. package/.github/workflows/tests.yml +25 -0
  9. package/.github/workflows/vulnerability-scan.yml +87 -0
  10. package/.release-please-manifest.json +3 -0
  11. package/AGENTS.md +132 -0
  12. package/README.md +10 -0
  13. package/analysis_results.md +162 -0
  14. package/marketplaces/large-codebase.json +66 -0
  15. package/marketplaces/openhands-extensions.json +682 -0
  16. package/package.json +4 -10
  17. package/plugins/README.md +30 -0
  18. package/plugins/city-weather/.plugin/plugin.json +13 -0
  19. package/plugins/city-weather/README.md +145 -0
  20. package/plugins/city-weather/commands/now.md +56 -0
  21. package/plugins/cobol-modernization/.plugin/plugin.json +19 -0
  22. package/plugins/cobol-modernization/README.md +201 -0
  23. package/plugins/cobol-modernization/references/troubleshooting.md +18 -0
  24. package/plugins/cobol-modernization/skills/build-setup/SKILL.md +78 -0
  25. package/plugins/cobol-modernization/skills/build-setup/scripts/install-gnucobol.sh +32 -0
  26. package/plugins/cobol-modernization/skills/cobol-modernization-overview/SKILL.md +113 -0
  27. package/plugins/cobol-modernization/skills/mainfraime-removal/SKILL.md +62 -0
  28. package/plugins/cobol-modernization/skills/mainfraime-removal/references/cics-transformation-examples.md +45 -0
  29. package/plugins/cobol-modernization/skills/mainframe-planning/SKILL.md +78 -0
  30. package/plugins/cobol-modernization/skills/to-java-migration/SKILL.md +59 -0
  31. package/plugins/cobol-modernization/skills/to-java-migration/references/cobol-to-java-example.md +58 -0
  32. package/plugins/cobol-modernization/skills/to-java-migration/references/datatype-mappings.md +19 -0
  33. package/plugins/issue-duplicate-checker/.plugin/plugin.json +13 -0
  34. package/plugins/issue-duplicate-checker/README.md +51 -0
  35. package/plugins/issue-duplicate-checker/action.yml +349 -0
  36. package/plugins/issue-duplicate-checker/scripts/auto_close_duplicate_issues.py +569 -0
  37. package/plugins/issue-duplicate-checker/scripts/issue_duplicate_check_openhands.py +681 -0
  38. package/plugins/issue-duplicate-checker/scripts/post_duplicate_notice.js +220 -0
  39. package/plugins/issue-duplicate-checker/scripts/remove_duplicate_candidate_label.js +27 -0
  40. package/plugins/magic-test/.plugin/plugin.json +13 -0
  41. package/plugins/magic-test/skills/magic-word/SKILL.md +33 -0
  42. package/plugins/migration-scoring/.plugin/plugin.json +19 -0
  43. package/plugins/migration-scoring/README.md +244 -0
  44. package/plugins/migration-scoring/skills/migration-mapping/SKILL.md +72 -0
  45. package/plugins/migration-scoring/skills/migration-report/SKILL.md +118 -0
  46. package/plugins/migration-scoring/skills/migration-scoring-overview/SKILL.md +126 -0
  47. package/plugins/migration-scoring/skills/score-quality/SKILL.md +54 -0
  48. package/plugins/migration-scoring/skills/score-quality/references/scoring-criteria.md +30 -0
  49. package/plugins/migration-scoring/skills/score-style/SKILL.md +106 -0
  50. package/plugins/onboarding/.plugin/plugin.json +20 -0
  51. package/plugins/onboarding/README.md +30 -0
  52. package/plugins/onboarding/references/criteria.md +144 -0
  53. package/plugins/onboarding/skills/agent-readiness-report/README.md +23 -0
  54. package/plugins/onboarding/skills/agent-readiness-report/SKILL.md +122 -0
  55. package/plugins/onboarding/skills/agent-readiness-report/scripts/scan_agent_instructions.sh +88 -0
  56. package/plugins/onboarding/skills/agent-readiness-report/scripts/scan_build_env.sh +114 -0
  57. package/plugins/onboarding/skills/agent-readiness-report/scripts/scan_feedback_loops.sh +133 -0
  58. package/plugins/onboarding/skills/agent-readiness-report/scripts/scan_policy.sh +113 -0
  59. package/plugins/onboarding/skills/agent-readiness-report/scripts/scan_workflows.sh +127 -0
  60. package/plugins/onboarding/skills/improve-agent-readiness/README.md +19 -0
  61. package/plugins/onboarding/skills/improve-agent-readiness/SKILL.md +167 -0
  62. package/plugins/onboarding/skills/setup-agents-md/README.md +15 -0
  63. package/plugins/onboarding/skills/setup-agents-md/SKILL.md +150 -0
  64. package/plugins/onboarding/skills/setup-openhands/README.md +20 -0
  65. package/plugins/onboarding/skills/setup-openhands/SKILL.md +56 -0
  66. package/plugins/onboarding/skills/setup-pr-review/README.md +23 -0
  67. package/plugins/onboarding/skills/setup-pr-review/SKILL.md +72 -0
  68. package/plugins/openhands/.plugin/plugin.json +13 -0
  69. package/plugins/openhands/README.md +52 -0
  70. package/plugins/openhands/SKILL.md +61 -0
  71. package/plugins/openhands/commands/create.md +55 -0
  72. package/plugins/openhands/commands/openhands-cloud.md +8 -0
  73. package/plugins/openhands/scripts/run.sh +69 -0
  74. package/plugins/pr-review/.plugin/plugin.json +13 -0
  75. package/plugins/pr-review/README.md +393 -0
  76. package/plugins/pr-review/action.yml +298 -0
  77. package/plugins/pr-review/scripts/agent_script.py +1282 -0
  78. package/plugins/pr-review/scripts/evaluate_review.py +655 -0
  79. package/plugins/pr-review/scripts/prompt.py +260 -0
  80. package/plugins/pr-review/workflows/pr-review-by-openhands.yml +51 -0
  81. package/plugins/pr-review/workflows/pr-review-evaluation.yml +85 -0
  82. package/plugins/qa-changes/.plugin/plugin.json +11 -0
  83. package/plugins/qa-changes/README.md +185 -0
  84. package/plugins/qa-changes/action.yml +181 -0
  85. package/plugins/qa-changes/scripts/agent_script.py +406 -0
  86. package/plugins/qa-changes/scripts/evaluate_qa_changes.py +385 -0
  87. package/plugins/qa-changes/scripts/prompt.py +174 -0
  88. package/plugins/qa-changes/workflows/qa-changes-by-openhands.yml +50 -0
  89. package/plugins/qa-changes/workflows/qa-changes-evaluation.yml +85 -0
  90. package/plugins/release-notes/.plugin/plugin.json +19 -0
  91. package/plugins/release-notes/README.md +283 -0
  92. package/plugins/release-notes/SKILL.md +83 -0
  93. package/plugins/release-notes/action.yml +117 -0
  94. package/plugins/release-notes/commands/release-notes.md +8 -0
  95. package/plugins/release-notes/scripts/agent_script.py +292 -0
  96. package/plugins/release-notes/scripts/generate_release_notes.py +733 -0
  97. package/plugins/release-notes/scripts/prompt.py +90 -0
  98. package/plugins/release-notes/scripts/validate_release_notes.py +328 -0
  99. package/plugins/release-notes/workflows/release-notes.yml +76 -0
  100. package/plugins/vulnerability-remediation/.plugin/plugin.json +19 -0
  101. package/plugins/vulnerability-remediation/README.md +217 -0
  102. package/plugins/vulnerability-remediation/action.yml +187 -0
  103. package/plugins/vulnerability-remediation/scripts/scan_and_remediate.py +561 -0
  104. package/plugins/vulnerability-remediation/workflows/vulnerability-scan.yml +87 -0
  105. package/pyproject.toml +12 -0
  106. package/release-please-config.json +16 -0
  107. package/scripts/sync_extensions.py +494 -0
  108. package/scripts/sync_openhands_sdk_skill.py +264 -0
  109. package/skills/README.md +159 -0
  110. package/skills/add-javadoc/.plugin/plugin.json +18 -0
  111. package/skills/add-javadoc/README.md +40 -0
  112. package/skills/add-javadoc/SKILL.md +35 -0
  113. package/skills/add-javadoc/references/example.md +32 -0
  114. package/skills/add-skill/.plugin/plugin.json +18 -0
  115. package/skills/add-skill/README.md +67 -0
  116. package/skills/add-skill/SKILL.md +47 -0
  117. package/skills/add-skill/scripts/fetch_skill.py +259 -0
  118. package/skills/agent-creator/.plugin/plugin.json +20 -0
  119. package/skills/agent-creator/README.md +104 -0
  120. package/skills/agent-creator/SKILL.md +190 -0
  121. package/skills/agent-creator/commands/agent-creator.md +8 -0
  122. package/skills/agent-creator/references/fallback.md +117 -0
  123. package/skills/agent-memory/.plugin/plugin.json +18 -0
  124. package/skills/agent-memory/README.md +35 -0
  125. package/skills/agent-memory/SKILL.md +30 -0
  126. package/skills/agent-memory/commands/remember.md +8 -0
  127. package/skills/agent-sdk-builder/.plugin/plugin.json +18 -0
  128. package/skills/agent-sdk-builder/README.md +40 -0
  129. package/skills/agent-sdk-builder/SKILL.md +37 -0
  130. package/skills/agent-sdk-builder/commands/agent-builder.md +8 -0
  131. package/skills/azure-devops/.plugin/plugin.json +18 -0
  132. package/skills/azure-devops/README.md +55 -0
  133. package/skills/azure-devops/SKILL.md +50 -0
  134. package/skills/bitbucket/.plugin/plugin.json +17 -0
  135. package/skills/bitbucket/README.md +50 -0
  136. package/skills/bitbucket/SKILL.md +45 -0
  137. package/skills/code-review/.plugin/plugin.json +19 -0
  138. package/skills/code-review/README.md +18 -0
  139. package/skills/code-review/SKILL.md +208 -0
  140. package/skills/code-review/commands/codereview-roasted.md +8 -0
  141. package/skills/code-review/commands/codereview.md +8 -0
  142. package/skills/code-review/references/risk-evaluation.md +41 -0
  143. package/skills/code-review/references/supply-chain-security.md +31 -0
  144. package/skills/code-simplifier/.plugin/plugin.json +21 -0
  145. package/skills/code-simplifier/README.md +30 -0
  146. package/skills/code-simplifier/SKILL.md +91 -0
  147. package/skills/code-simplifier/commands/simplify.md +8 -0
  148. package/skills/code-simplifier/references/code-quality-review.md +86 -0
  149. package/skills/code-simplifier/references/code-reuse-review.md +63 -0
  150. package/skills/code-simplifier/references/efficiency-review.md +81 -0
  151. package/skills/datadog/.plugin/plugin.json +19 -0
  152. package/skills/datadog/README.md +100 -0
  153. package/skills/datadog/SKILL.md +95 -0
  154. package/skills/deno/.plugin/plugin.json +18 -0
  155. package/skills/deno/README.md +5 -0
  156. package/skills/deno/SKILL.md +99 -0
  157. package/skills/deno/references/README.md +6 -0
  158. package/skills/discord/.plugin/plugin.json +18 -0
  159. package/skills/discord/README.md +31 -0
  160. package/skills/discord/SKILL.md +109 -0
  161. package/skills/discord/__init__.py +0 -0
  162. package/skills/discord/references/REFERENCE.md +78 -0
  163. package/skills/discord/scripts/__init__.py +0 -0
  164. package/skills/discord/scripts/_http.py +127 -0
  165. package/skills/discord/scripts/post_webhook.py +106 -0
  166. package/skills/discord/scripts/send_message.py +102 -0
  167. package/skills/docker/.plugin/plugin.json +17 -0
  168. package/skills/docker/README.md +34 -0
  169. package/skills/docker/SKILL.md +29 -0
  170. package/skills/evidence-based-citations/.plugin/plugin.json +20 -0
  171. package/skills/evidence-based-citations/README.md +31 -0
  172. package/skills/evidence-based-citations/SKILL.md +59 -0
  173. package/skills/flarglebargle/.plugin/plugin.json +16 -0
  174. package/skills/flarglebargle/README.md +14 -0
  175. package/skills/flarglebargle/SKILL.md +9 -0
  176. package/skills/frontend-design/.plugin/plugin.json +21 -0
  177. package/skills/frontend-design/LICENSE.txt +177 -0
  178. package/skills/frontend-design/README.md +42 -0
  179. package/skills/frontend-design/SKILL.md +42 -0
  180. package/skills/github/.plugin/plugin.json +19 -0
  181. package/skills/github/README.md +42 -0
  182. package/skills/github/SKILL.md +106 -0
  183. package/skills/github-pr-review/.plugin/plugin.json +18 -0
  184. package/skills/github-pr-review/README.md +145 -0
  185. package/skills/github-pr-review/SKILL.md +148 -0
  186. package/skills/github-pr-review/commands/github-pr-review.md +8 -0
  187. package/skills/github-pr-reviewer/.plugin/plugin.json +20 -0
  188. package/skills/github-pr-reviewer/README.md +34 -0
  189. package/skills/github-pr-reviewer/SKILL.md +89 -0
  190. package/skills/github-pr-reviewer/commands/pr-reviewer:setup.md +8 -0
  191. package/skills/github-repo-monitor/.plugin/plugin.json +22 -0
  192. package/skills/github-repo-monitor/README.md +70 -0
  193. package/skills/github-repo-monitor/SKILL.md +316 -0
  194. package/skills/github-repo-monitor/commands/github-monitor:poll.md +8 -0
  195. package/skills/github-repo-monitor/references/github-api.md +241 -0
  196. package/skills/github-repo-monitor/references/state-schema.md +160 -0
  197. package/skills/github-repo-monitor/scripts/main.py +915 -0
  198. package/skills/github-repo-monitor/tests/test_main.py +400 -0
  199. package/skills/gitlab/.plugin/plugin.json +17 -0
  200. package/skills/gitlab/README.md +37 -0
  201. package/skills/gitlab/SKILL.md +32 -0
  202. package/skills/incident-retrospective/.plugin/plugin.json +21 -0
  203. package/skills/incident-retrospective/README.md +34 -0
  204. package/skills/incident-retrospective/SKILL.md +98 -0
  205. package/skills/incident-retrospective/commands/incident-retro:setup.md +8 -0
  206. package/skills/iterate/.plugin/plugin.json +13 -0
  207. package/skills/iterate/README.md +25 -0
  208. package/skills/iterate/SKILL.md +399 -0
  209. package/skills/iterate/commands/babysit.md +8 -0
  210. package/skills/iterate/commands/iterate.md +8 -0
  211. package/skills/iterate/commands/verify.md +8 -0
  212. package/skills/iterate/references/heuristics.md +58 -0
  213. package/skills/iterate/references/verification.md +96 -0
  214. package/skills/jupyter/.plugin/plugin.json +18 -0
  215. package/skills/jupyter/README.md +55 -0
  216. package/skills/jupyter/SKILL.md +50 -0
  217. package/skills/kubernetes/.plugin/plugin.json +18 -0
  218. package/skills/kubernetes/README.md +53 -0
  219. package/skills/kubernetes/SKILL.md +48 -0
  220. package/skills/learn-from-code-review/.plugin/plugin.json +19 -0
  221. package/skills/learn-from-code-review/README.md +64 -0
  222. package/skills/learn-from-code-review/SKILL.md +186 -0
  223. package/skills/learn-from-code-review/commands/learn-from-reviews.md +8 -0
  224. package/skills/linear/.plugin/plugin.json +19 -0
  225. package/skills/linear/README.md +58 -0
  226. package/skills/linear/SKILL.md +213 -0
  227. package/skills/linear-triage/.plugin/plugin.json +21 -0
  228. package/skills/linear-triage/README.md +34 -0
  229. package/skills/linear-triage/SKILL.md +91 -0
  230. package/skills/linear-triage/commands/linear-triage:setup.md +8 -0
  231. package/skills/notion/.plugin/plugin.json +17 -0
  232. package/skills/notion/README.md +114 -0
  233. package/skills/notion/SKILL.md +109 -0
  234. package/skills/npm/.plugin/plugin.json +17 -0
  235. package/skills/npm/README.md +14 -0
  236. package/skills/npm/SKILL.md +9 -0
  237. package/skills/openhands-api/.plugin/plugin.json +22 -0
  238. package/skills/openhands-api/README.md +48 -0
  239. package/skills/openhands-api/SKILL.md +399 -0
  240. package/skills/openhands-api/references/README.md +33 -0
  241. package/skills/openhands-api/references/TROUBLESHOOTING.md +81 -0
  242. package/skills/openhands-api/references/example_prompt.md +12 -0
  243. package/skills/openhands-api/scripts/openhands_api.py +606 -0
  244. package/skills/openhands-api/scripts/openhands_api.ts +252 -0
  245. package/skills/openhands-automation/.plugin/plugin.json +19 -0
  246. package/skills/openhands-automation/README.md +89 -0
  247. package/skills/openhands-automation/SKILL.md +875 -0
  248. package/skills/openhands-automation/commands/automation:create.md +8 -0
  249. package/skills/openhands-automation/references/ab-testing.md +185 -0
  250. package/skills/openhands-automation/references/custom-automation.md +644 -0
  251. package/skills/openhands-sdk/.plugin/plugin.json +20 -0
  252. package/skills/openhands-sdk/README.md +22 -0
  253. package/skills/openhands-sdk/SKILL.md +229 -0
  254. package/skills/openhands-sdk/commands/sdk.md +8 -0
  255. package/skills/pdflatex/.plugin/plugin.json +18 -0
  256. package/skills/pdflatex/README.md +39 -0
  257. package/skills/pdflatex/SKILL.md +34 -0
  258. package/skills/prd/.plugin/plugin.json +19 -0
  259. package/skills/prd/README.md +28 -0
  260. package/skills/prd/SKILL.md +237 -0
  261. package/skills/prd/commands/prd.md +8 -0
  262. package/skills/qa-changes/README.md +18 -0
  263. package/skills/qa-changes/SKILL.md +229 -0
  264. package/skills/qa-changes/commands/qa-changes.md +8 -0
  265. package/skills/release-notes/README.md +24 -0
  266. package/skills/release-notes/SKILL.md +19 -0
  267. package/skills/release-notes/commands/release-notes.md +8 -0
  268. package/skills/research-brief/.plugin/plugin.json +20 -0
  269. package/skills/research-brief/README.md +34 -0
  270. package/skills/research-brief/SKILL.md +99 -0
  271. package/skills/research-brief/commands/research-brief:setup.md +8 -0
  272. package/skills/security/.plugin/plugin.json +18 -0
  273. package/skills/security/README.md +38 -0
  274. package/skills/security/SKILL.md +33 -0
  275. package/skills/skill-creator/.plugin/plugin.json +17 -0
  276. package/skills/skill-creator/LICENSE.txt +202 -0
  277. package/skills/skill-creator/README.md +182 -0
  278. package/skills/skill-creator/SKILL.md +545 -0
  279. package/skills/skill-creator/references/output-patterns.md +82 -0
  280. package/skills/skill-creator/references/workflows.md +28 -0
  281. package/skills/skill-creator/scripts/init_skill.py +303 -0
  282. package/skills/skill-creator/scripts/quick_validate.py +95 -0
  283. package/skills/slack-channel-monitor/.plugin/plugin.json +21 -0
  284. package/skills/slack-channel-monitor/README.md +91 -0
  285. package/skills/slack-channel-monitor/SKILL.md +276 -0
  286. package/skills/slack-channel-monitor/commands/slack-monitor:poll.md +8 -0
  287. package/skills/slack-channel-monitor/references/slack-api.md +207 -0
  288. package/skills/slack-channel-monitor/references/state-schema.md +180 -0
  289. package/skills/slack-channel-monitor/scripts/main.py +962 -0
  290. package/skills/slack-standup-digest/.plugin/plugin.json +21 -0
  291. package/skills/slack-standup-digest/README.md +34 -0
  292. package/skills/slack-standup-digest/SKILL.md +92 -0
  293. package/skills/slack-standup-digest/commands/standup-digest:setup.md +8 -0
  294. package/skills/spark-version-upgrade/.plugin/plugin.json +20 -0
  295. package/skills/spark-version-upgrade/README.md +54 -0
  296. package/skills/spark-version-upgrade/SKILL.md +233 -0
  297. package/skills/ssh/.plugin/plugin.json +18 -0
  298. package/skills/ssh/README.md +140 -0
  299. package/skills/ssh/SKILL.md +135 -0
  300. package/skills/swift-linux/.plugin/plugin.json +17 -0
  301. package/skills/swift-linux/README.md +86 -0
  302. package/skills/swift-linux/SKILL.md +81 -0
  303. package/skills/theme-factory/.plugin/plugin.json +19 -0
  304. package/skills/theme-factory/LICENSE.txt +202 -0
  305. package/skills/theme-factory/README.md +58 -0
  306. package/skills/theme-factory/SKILL.md +59 -0
  307. package/skills/theme-factory/theme-showcase.pdf +0 -0
  308. package/skills/theme-factory/themes/arctic-frost.md +19 -0
  309. package/skills/theme-factory/themes/botanical-garden.md +19 -0
  310. package/skills/theme-factory/themes/desert-rose.md +19 -0
  311. package/skills/theme-factory/themes/forest-canopy.md +19 -0
  312. package/skills/theme-factory/themes/golden-hour.md +19 -0
  313. package/skills/theme-factory/themes/midnight-galaxy.md +19 -0
  314. package/skills/theme-factory/themes/modern-minimalist.md +19 -0
  315. package/skills/theme-factory/themes/ocean-depths.md +19 -0
  316. package/skills/theme-factory/themes/sunset-boulevard.md +19 -0
  317. package/skills/theme-factory/themes/tech-innovation.md +19 -0
  318. package/skills/uv/.plugin/plugin.json +18 -0
  319. package/skills/uv/README.md +5 -0
  320. package/skills/uv/SKILL.md +95 -0
  321. package/skills/uv/references/README.md +5 -0
  322. package/skills/vercel/.plugin/plugin.json +18 -0
  323. package/skills/vercel/README.md +108 -0
  324. package/skills/vercel/SKILL.md +103 -0
  325. package/tests/test_add_skill_installs_to_agents_dir.py +42 -0
  326. package/tests/test_catalogs.py +109 -0
  327. package/tests/test_code_review_risk_evaluation.py +94 -0
  328. package/tests/test_issue_duplicate_checker.py +240 -0
  329. package/tests/test_openhands_api_python.py +152 -0
  330. package/tests/test_plugin_manifest.py +83 -0
  331. package/tests/test_pr_review_diff_payload.py +202 -0
  332. package/tests/test_pr_review_feedback.py +263 -0
  333. package/tests/test_pr_review_prompt.py +152 -0
  334. package/tests/test_pr_review_review_context.py +253 -0
  335. package/tests/test_qa_changes.py +232 -0
  336. package/tests/test_qa_changes_evaluation.py +259 -0
  337. package/tests/test_release_notes_generator.py +990 -0
  338. package/tests/test_sdk_loading.py +150 -0
  339. package/tests/test_skill_plugin_loading.py +149 -0
  340. package/tests/test_skills_have_readme.py +66 -0
  341. package/tests/test_sync_extensions.py +292 -0
  342. package/tests/test_workflow_sync.py +46 -0
  343. package/utils/analysis/README.md +7 -0
  344. package/utils/analysis/laminar_signals/README.md +211 -0
  345. package/utils/analysis/laminar_signals/analyze.py +780 -0
  346. package/utils/analysis/laminar_signals/templates/default.j2 +49 -0
  347. 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
+ )