claude-dev-env 2.3.0 → 2.5.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 (290) hide show
  1. package/CLAUDE.md +53 -48
  2. package/_shared/pr-loop/scripts/_claude_permissions_common.py +84 -0
  3. package/_shared/pr-loop/scripts/code_rules_gate.py +4 -2
  4. package/_shared/pr-loop/scripts/grant_project_claude_permissions.py +306 -306
  5. package/_shared/pr-loop/scripts/pr_loop_shared_constants/claude_permissions_constants.py +44 -0
  6. package/_shared/pr-loop/scripts/pr_loop_shared_constants/copilot_quota_constants.py +24 -24
  7. package/_shared/pr-loop/scripts/pr_loop_shared_constants/stale_worktree_rule_sweep_constants.py +107 -107
  8. package/_shared/pr-loop/scripts/revoke_project_claude_permissions.py +290 -48
  9. package/_shared/pr-loop/scripts/tests/test_claude_permissions_common.py +42 -2
  10. package/_shared/pr-loop/scripts/tests/test_claude_permissions_constants.py +36 -0
  11. package/_shared/pr-loop/scripts/tests/test_code_rules_gate.py +100 -1
  12. package/_shared/pr-loop/scripts/tests/test_fix_hookspath.py +497 -497
  13. package/_shared/pr-loop/scripts/tests/test_revoke_project_claude_permissions.py +311 -2
  14. package/_shared/pr-loop/scripts/tests/test_stale_worktree_rule_sweep.py +301 -301
  15. package/_shared/pr-loop/scripts/tests/test_stale_worktree_rule_sweep_constants.py +85 -85
  16. package/_shared/pr-loop/worker-spawn.md +1 -1
  17. package/agents/CLAUDE.md +3 -1
  18. package/agents/caveman.md +0 -1
  19. package/agents/clasp-deployment-orchestrator.md +0 -1
  20. package/agents/clean-coder.md +0 -1
  21. package/agents/code-advisor.md +0 -1
  22. package/agents/code-quality-agent.md +1 -2
  23. package/agents/code-verifier.md +3 -4
  24. package/agents/deep-research.md +0 -1
  25. package/agents/docs-agent.md +0 -1
  26. package/agents/git-commit-crafter.md +0 -1
  27. package/agents/issue-tracker.md +42 -0
  28. package/agents/plan-packet-validator.md +0 -1
  29. package/agents/pr-description-writer.md +0 -1
  30. package/agents/skill-writer-agent.md +84 -0
  31. package/agents/test_agent_frontmatter.py +67 -18
  32. package/audit-rubrics/category_rubrics/category-o-docstring-vs-impl-drift.md +105 -3
  33. package/audit-rubrics/prompts/category-o-docstring-vs-impl-drift.md +29 -13
  34. package/bin/CLAUDE.md +68 -5
  35. package/bin/ever-shipped-skills.mjs +1 -0
  36. package/bin/install-constants.mjs +88 -0
  37. package/bin/install.mjs +1138 -114
  38. package/bin/install.prune.test.mjs +869 -19
  39. package/bin/install.test.mjs +906 -2
  40. package/commands/implement.md +1 -1
  41. package/commands/right-size.md +1 -1
  42. package/docs/CLAUDE.md +2 -0
  43. package/docs/host-pool-health-monitor.md +102 -0
  44. package/docs/references/CLAUDE.md +5 -2
  45. package/docs/references/advisor-tool.md +13 -0
  46. package/docs/references/code-review-enforcement.md +107 -0
  47. package/docs/references/team-advisor-skill.md +14 -0
  48. package/docs/wsl-docker-cowork-starter-matrix.md +89 -0
  49. package/hooks/blocking/CLAUDE.md +9 -1
  50. package/hooks/blocking/code_review_enforcement_config_bootstrap.py +53 -0
  51. package/hooks/blocking/code_review_gate_deny.py +74 -0
  52. package/hooks/blocking/code_review_pr_create_gate.py +198 -0
  53. package/hooks/blocking/code_review_push_gate.py +145 -0
  54. package/hooks/blocking/code_review_stamp_directory_write_blocker.py +348 -0
  55. package/hooks/blocking/code_review_stamp_store.py +233 -0
  56. package/hooks/blocking/code_review_stamp_write_blocker_parts/__init__.py +7 -0
  57. package/hooks/blocking/code_review_stamp_write_blocker_parts/conftest.py +15 -0
  58. package/hooks/blocking/code_review_stamp_write_blocker_parts/obfuscated_stamp_path_reference.py +212 -0
  59. package/hooks/blocking/code_review_stamp_write_blocker_parts/split_directory_change_into_stamp.py +138 -0
  60. package/hooks/blocking/code_review_stamp_write_blocker_parts/test_obfuscated_stamp_path_reference.py +49 -0
  61. package/hooks/blocking/code_review_stamp_write_blocker_parts/test_split_directory_change_into_stamp.py +38 -0
  62. package/hooks/blocking/code_verifier_spawn_preflight_gate.py +39 -27
  63. package/hooks/blocking/config/__init__.py +5 -5
  64. package/hooks/blocking/config/code_review_enforcement_constants.py +113 -0
  65. package/hooks/blocking/config/test_code_review_enforcement_constants.py +113 -0
  66. package/hooks/blocking/config/verified_commit_constants.py +160 -155
  67. package/hooks/blocking/conftest.py +2 -0
  68. package/hooks/blocking/convergence_gate_blocker.py +112 -23
  69. package/hooks/blocking/destructive_command_blocker.py +19 -6
  70. package/hooks/blocking/orchestrator_refresh_reschedule_gate.py +256 -0
  71. package/hooks/blocking/pr_description_proof_of_work.py +52 -34
  72. package/hooks/blocking/pre_tool_use_dispatcher.py +24 -24
  73. package/hooks/blocking/test_bash_pre_tool_use_dispatcher.py +4 -1
  74. package/hooks/blocking/test_code_review_enforcement_config_bootstrap.py +62 -0
  75. package/hooks/blocking/test_code_review_gate_deny.py +54 -0
  76. package/hooks/blocking/test_code_review_pr_create_gate.py +199 -0
  77. package/hooks/blocking/test_code_review_push_gate.py +205 -0
  78. package/hooks/blocking/test_code_review_stamp_directory_write_blocker.py +199 -0
  79. package/hooks/blocking/test_code_review_stamp_store.py +205 -0
  80. package/hooks/blocking/test_code_verifier_spawn_preflight_gate.py +124 -2
  81. package/hooks/blocking/test_convergence_gate_blocker.py +153 -5
  82. package/hooks/blocking/test_destructive_command_blocker.py +1 -1
  83. package/hooks/blocking/test_destructive_command_blocker_deny_mode.py +45 -0
  84. package/hooks/blocking/test_orchestrator_refresh_reschedule_gate.py +231 -0
  85. package/hooks/blocking/test_pr_description_proof_of_work.py +151 -0
  86. package/hooks/blocking/test_pre_tool_use_dispatcher.py +17 -8
  87. package/hooks/blocking/test_verdict_directory_write_blocker.py +808 -808
  88. package/hooks/blocking/test_verification_verdict_store.py +974 -903
  89. package/hooks/blocking/test_verified_commit_gate.py +581 -581
  90. package/hooks/blocking/test_verified_commit_message_accuracy_blocker.py +131 -131
  91. package/hooks/blocking/test_volatile_path_in_post_blocker.py +114 -2
  92. package/hooks/blocking/verdict_directory_write_blocker.py +687 -687
  93. package/hooks/blocking/verification_verdict_store.py +1039 -1014
  94. package/hooks/blocking/verified_commit_gate_parts/gated_invocations.py +29 -17
  95. package/hooks/blocking/verified_commit_gate_parts/tests/test_gated_invocations.py +35 -0
  96. package/hooks/blocking/verified_commit_message_accuracy_blocker.py +167 -167
  97. package/hooks/blocking/verifier_verdict_minter.py +280 -280
  98. package/hooks/blocking/volatile_path_in_post_blocker.py +69 -8
  99. package/hooks/git-hooks/git_hooks_constants/__init__.py +6 -0
  100. package/hooks/git-hooks/pre_push.py +89 -2
  101. package/hooks/git-hooks/test_pre_push.py +128 -0
  102. package/hooks/hooks.json +26 -1
  103. package/hooks/hooks_constants/CLAUDE.md +3 -1
  104. package/hooks/hooks_constants/bash_pre_tool_use_dispatcher_constants.py +8 -0
  105. package/hooks/hooks_constants/code_rules_path_utils_constants.py +1 -0
  106. package/hooks/hooks_constants/code_verifier_spawn_preflight_gate_constants.py +26 -11
  107. package/hooks/hooks_constants/convergence_gate_blocker_constants.py +20 -3
  108. package/hooks/hooks_constants/destructive_command_segment_constants.py +3 -1
  109. package/hooks/hooks_constants/enter_worktree_prefetch_constants.py +18 -18
  110. package/hooks/hooks_constants/orchestrator_refresh_reschedule_gate_constants.py +48 -0
  111. package/hooks/hooks_constants/pr_description_proof_of_work_constants.py +0 -4
  112. package/hooks/hooks_constants/pre_tool_use_dispatcher_constants.py +4 -0
  113. package/hooks/hooks_constants/pyproject_config_discovery_constants.py +16 -0
  114. package/hooks/hooks_constants/ruff_integration_constants.py +16 -0
  115. package/hooks/hooks_constants/test_bash_pre_tool_use_dispatcher_constants.py +24 -0
  116. package/hooks/hooks_constants/test_pre_tool_use_dispatcher_constants.py +6 -0
  117. package/hooks/hooks_constants/volatile_path_in_post_blocker_constants.py +8 -1
  118. package/hooks/lifecycle/enter_worktree_origin_prefetch.py +163 -146
  119. package/hooks/lifecycle/test_enter_worktree_origin_prefetch.py +185 -178
  120. package/hooks/pyproject.toml +1 -0
  121. package/hooks/validators/CLAUDE.md +2 -0
  122. package/hooks/validators/config/__init__.py +0 -0
  123. package/hooks/validators/config/directory_exemption_constants.py +183 -0
  124. package/hooks/validators/config/test_directory_exemption_constants.py +21 -0
  125. package/hooks/validators/conftest.py +4 -0
  126. package/hooks/validators/mypy_integration.py +63 -50
  127. package/hooks/validators/pyproject_config_discovery.py +101 -0
  128. package/hooks/validators/ruff_integration.py +257 -25
  129. package/hooks/validators/run_all_validators.py +223 -19
  130. package/hooks/validators/test_directory_exemption_constants.py +185 -0
  131. package/hooks/validators/test_mypy_integration.py +32 -0
  132. package/hooks/validators/test_pyproject_config_discovery.py +94 -0
  133. package/hooks/validators/test_python_antipattern_checks.py +110 -5
  134. package/hooks/validators/test_ruff_integration.py +160 -2
  135. package/hooks/validators/test_run_all_validators.py +140 -68
  136. package/hooks/validators/test_run_all_validators_config_discovery.py +123 -0
  137. package/hooks/validators/test_run_all_validators_pretooluse.py +159 -1
  138. package/package.json +10 -2
  139. package/rules/CLAUDE.md +1 -0
  140. package/rules/docstring-prose-matches-implementation.md +45 -67
  141. package/rules/durable-post-artifacts.md +7 -0
  142. package/rules/state-what-is.md +25 -0
  143. package/rules/verified-commit-gate-skip.md +1 -1
  144. package/scripts/CLAUDE.md +1 -0
  145. package/scripts/Capture-PoolHealth.ps1 +410 -0
  146. package/scripts/_code_review_test_support.py +404 -0
  147. package/scripts/claude_chain_runner.py +141 -1
  148. package/scripts/codec_forwarding_test_support.py +83 -0
  149. package/scripts/conftest.py +23 -0
  150. package/scripts/dev_env_scripts_constants/CLAUDE.md +2 -2
  151. package/scripts/dev_env_scripts_constants/claude_chain_constants.py +53 -1
  152. package/scripts/dev_env_scripts_constants/code_review_constants.py +129 -12
  153. package/scripts/dev_env_scripts_constants/test_code_review_constants.py +55 -0
  154. package/scripts/invoke_code_review.py +550 -38
  155. package/scripts/resolve_worker_spawn.py +626 -619
  156. package/scripts/spawn_grok_batch.py +672 -672
  157. package/scripts/test_claude_chain_runner.py +131 -0
  158. package/scripts/test_invoke_code_review_chain.py +70 -0
  159. package/scripts/test_invoke_code_review_cli.py +192 -0
  160. package/scripts/test_invoke_code_review_codec.py +77 -0
  161. package/scripts/test_invoke_code_review_contract.py +256 -0
  162. package/scripts/test_invoke_code_review_git.py +123 -0
  163. package/scripts/test_invoke_code_review_mode.py +99 -0
  164. package/scripts/test_resolve_worker_spawn.py +1014 -1014
  165. package/scripts/test_resolve_worker_spawn_codec.py +101 -0
  166. package/skills/CLAUDE.md +2 -0
  167. package/skills/auditing-claude-config/SKILL.md +114 -114
  168. package/skills/autoconverge/SKILL.md +427 -421
  169. package/skills/autoconverge/reference/convergence.md +26 -4
  170. package/skills/autoconverge/reference/multi-pr.md +6 -1
  171. package/skills/autoconverge/reference/stop-conditions.md +16 -10
  172. package/skills/autoconverge/workflow/CLAUDE.md +1 -0
  173. package/skills/autoconverge/workflow/converge.clean-audit.test.mjs +4 -4
  174. package/skills/autoconverge/workflow/converge.codex-gate.test.mjs +175 -3
  175. package/skills/autoconverge/workflow/converge.contract.test.mjs +1263 -1244
  176. package/skills/autoconverge/workflow/converge.mjs +191 -8
  177. package/skills/autoconverge/workflow/converge.p2-advance.test.mjs +202 -0
  178. package/skills/autoconverge/workflow/converge_multi.mjs +7 -3
  179. package/skills/autoconverge/workflow/converge_multi.run-input.test.mjs +5 -0
  180. package/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-a11d903476b803493.jsonl +2 -2
  181. package/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-a26213978adeef6fb.jsonl +2 -2
  182. package/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-a3def0d15ed9d9110.jsonl +2 -2
  183. package/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-a41f41b1b708ee3b7.jsonl +2 -2
  184. package/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-a758b880abecc3ff7.jsonl +2 -2
  185. package/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-a8897b89656b1bd16.jsonl +2 -2
  186. package/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-abd463d744a1437bc.jsonl +2 -2
  187. package/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-ad19d027ae8ee1816.jsonl +2 -2
  188. package/skills/autoconverge/workflow/fixtures/wf_run/workflows/wf_881252e6-700.json +265 -265
  189. package/skills/closeout/SKILL.md +33 -50
  190. package/skills/codex-review/scripts/codex_review_scripts_constants/run_constants.py +8 -0
  191. package/skills/codex-review/scripts/run_codex_review.py +233 -1
  192. package/skills/codex-review/scripts/test_run_codex_review.py +189 -0
  193. package/skills/condensing-instructions/SKILL.md +81 -0
  194. package/skills/copilot-review/SKILL.md +119 -119
  195. package/skills/e-code-review/SKILL.md +52 -0
  196. package/skills/e-code-review/reference/fix.md +54 -0
  197. package/skills/e-code-review/reference/loop.md +43 -0
  198. package/skills/e-code-review/reference/low.md +57 -0
  199. package/skills/e-code-review/reference/medium.md +153 -0
  200. package/skills/e-code-review/reference/xhigh.md +182 -0
  201. package/skills/e-simplify/SKILL.md +97 -0
  202. package/skills/fresh-branch/CLAUDE.md +2 -0
  203. package/skills/fresh-branch/SKILL.md +2 -0
  204. package/skills/fresh-branch/scripts/create_fresh_branch.py +78 -180
  205. package/skills/fresh-branch/scripts/fresh_branch_git_commands.py +285 -0
  206. package/skills/fresh-branch/scripts/fresh_branch_scripts_constants/fresh_branch_cli_constants.py +1 -0
  207. package/skills/fresh-branch/scripts/pytest.ini +4 -0
  208. package/skills/fresh-branch/scripts/test_create_fresh_branch.py +98 -0
  209. package/skills/fresh-branch/scripts/test_fresh_branch_git_commands.py +310 -0
  210. package/skills/issue-tracker/SKILL.md +92 -0
  211. package/skills/issue-tracker/reference/epic-and-sub-issue-model.md +55 -0
  212. package/skills/issue-tracker/reference/handoff-schema.md +64 -0
  213. package/skills/issue-tracker/reference/operation-matrix.md +41 -0
  214. package/skills/orchestrator/SKILL.md +162 -21
  215. package/skills/orchestrator/scripts/status_gate.py +625 -0
  216. package/skills/orchestrator/scripts/status_gate_constants/__init__.py +1 -0
  217. package/skills/orchestrator/scripts/status_gate_constants/config/__init__.py +1 -0
  218. package/skills/orchestrator/scripts/status_gate_constants/config/constants.py +47 -0
  219. package/skills/orchestrator/scripts/test_status_gate.py +439 -0
  220. package/skills/orchestrator-refresh/SKILL.md +110 -35
  221. package/skills/plan-to-pr/SKILL.md +155 -0
  222. package/skills/plan-to-pr/reference/final-validation-tasks.md +15 -0
  223. package/skills/plan-to-pr/reference/model-routing.md +36 -0
  224. package/skills/plan-to-pr/reference/packet-contract.md +43 -0
  225. package/skills/plan-to-pr/reference/packet-schema.json +57 -0
  226. package/skills/plan-to-pr/reference/process-inventory.md +22 -0
  227. package/skills/plan-to-pr/reference/review-loop.md +33 -0
  228. package/skills/plan-to-pr/reference/run-record.schema.json +27 -0
  229. package/skills/plan-to-pr/reference/self-audit-tasks.md +15 -0
  230. package/skills/plan-to-pr/reference/task-seeds.md +14 -0
  231. package/skills/plan-to-pr/reference/task-ticket.md +38 -0
  232. package/skills/plan-to-pr/scripts/config/__init__.py +1 -0
  233. package/skills/plan-to-pr/scripts/config/constants.py +193 -0
  234. package/skills/plan-to-pr/scripts/create_packet.py +173 -0
  235. package/skills/plan-to-pr/scripts/test_create_packet.py +102 -0
  236. package/skills/plan-to-pr/scripts/test_validate_packet.py +256 -0
  237. package/skills/plan-to-pr/scripts/test_validate_protocol.py +135 -0
  238. package/skills/plan-to-pr/scripts/test_validate_run.py +158 -0
  239. package/skills/plan-to-pr/scripts/validate_packet.py +655 -0
  240. package/skills/plan-to-pr/scripts/validate_protocol.py +622 -0
  241. package/skills/plan-to-pr/scripts/validate_run.py +173 -0
  242. package/skills/plan-to-pr/test_skill_contract.py +207 -0
  243. package/skills/plan-to-pr/test_task_ticket_contract.py +151 -0
  244. package/skills/pr-converge/SKILL.md +472 -469
  245. package/skills/pr-converge/reference/examples.md +3 -3
  246. package/skills/pr-converge/reference/fix-protocol.md +1 -1
  247. package/skills/pr-converge/reference/ground-rules.md +7 -4
  248. package/skills/pr-converge/reference/multi-pr-orchestration.md +4 -1
  249. package/skills/pr-converge/reference/per-tick.md +5 -5
  250. package/skills/pr-converge/reference/progress-checklist.md +1 -1
  251. package/skills/pr-converge/scripts/check_convergence_gates.py +279 -279
  252. package/skills/pr-converge/scripts/test_check_convergence_codex.py +507 -507
  253. package/skills/pr-converge/scripts/test_check_convergence_gates.py +84 -84
  254. package/skills/pr-converge/test_step5_host_branch.py +1 -1
  255. package/skills/pr-fix-protocol/SKILL.md +1 -1
  256. package/skills/privacy-hygiene/SKILL.md +68 -68
  257. package/skills/prototype/SKILL.md +86 -0
  258. package/skills/prototype/reference/honest-limitations.md +23 -0
  259. package/skills/prototype/reference/promotion-tasks.md +23 -0
  260. package/skills/prototype/scripts/build_sandbox_settings.py +249 -0
  261. package/skills/prototype/scripts/conftest.py +15 -0
  262. package/skills/prototype/scripts/launch_sandbox.py +205 -0
  263. package/skills/prototype/scripts/probe_sandbox_safety.py +311 -0
  264. package/skills/prototype/scripts/prototype_scripts_constants/__init__.py +1 -0
  265. package/skills/prototype/scripts/prototype_scripts_constants/config/__init__.py +0 -0
  266. package/skills/prototype/scripts/prototype_scripts_constants/config/build_sandbox_settings_constants.py +41 -0
  267. package/skills/prototype/scripts/prototype_scripts_constants/config/launch_sandbox_constants.py +23 -0
  268. package/skills/prototype/scripts/prototype_scripts_constants/config/probe_sandbox_safety_constants.py +45 -0
  269. package/skills/prototype/scripts/prototype_scripts_constants/config/prototype_common_constants.py +10 -0
  270. package/skills/prototype/scripts/test_build_sandbox_settings.py +275 -0
  271. package/skills/prototype/scripts/test_launch_sandbox.py +303 -0
  272. package/skills/prototype/scripts/test_probe_sandbox_safety.py +284 -0
  273. package/skills/prototype/workflows/promotion.md +27 -0
  274. package/skills/prototype/workflows/sandbox.md +35 -0
  275. package/skills/release-notes-html/SKILL.md +164 -0
  276. package/skills/skill-builder/CLAUDE.md +3 -3
  277. package/skills/skill-builder/SKILL.md +5 -5
  278. package/skills/skill-builder/references/CLAUDE.md +1 -1
  279. package/skills/skill-builder/references/delegation-map.md +3 -3
  280. package/skills/skill-builder/references/description-field.md +1 -1
  281. package/skills/skill-builder/references/skill-modularity.md +2 -3
  282. package/skills/skill-builder/workflows/CLAUDE.md +1 -1
  283. package/skills/skill-builder/workflows/improve-skill.md +1 -1
  284. package/skills/skill-builder/workflows/new-skill.md +2 -2
  285. package/skills/task-build/CLAUDE.md +8 -7
  286. package/skills/task-build/SKILL.md +16 -8
  287. package/skills/task-build/reference/tool-routing.md +19 -0
  288. package/skills/team-advisor/SKILL.md +2 -2
  289. package/scripts/test_invoke_code_review.py +0 -672
  290. package/skills/closeout/reference/issue-body-templates.md +0 -108
@@ -30,6 +30,7 @@ from dev_env_scripts_constants.claude_chain_constants import ( # noqa: E402
30
30
  CLAUDE_HOME_SUBDIRECTORY,
31
31
  CLI_ARGUMENTS_SEPARATOR,
32
32
  CLI_TIMEOUT_FLAG,
33
+ CODEC_ERROR_STRATEGY,
33
34
  CONFIG_CHAIN_EMPTY_REASON,
34
35
  CONFIG_CHAIN_KEY,
35
36
  CONFIG_CHAIN_NOT_LIST_REASON,
@@ -50,6 +51,14 @@ from dev_env_scripts_constants.claude_chain_constants import ( # noqa: E402
50
51
  UTF8_ENCODING,
51
52
  )
52
53
 
54
+ _LARGE_CAPTURE_BYTE_COUNT = 400_000
55
+ _LARGE_CAPTURE_MARKER = "X"
56
+ _STDIN_ECHO_PAYLOAD = "charter body for spool path"
57
+ _UNDECODABLE_STDOUT_BYTES = b"ok \x90 end"
58
+ _DECODED_UNDECODABLE_STDOUT = "ok \ufffd end"
59
+ _CRLF_CHILD_STDOUT_BYTES = b"a\r\nb\rc\n"
60
+ _CRLF_CHILD_STDOUT_NORMALIZED = "a\nb\nc\n"
61
+
53
62
  _A_SIGNATURE = ALL_USAGE_LIMIT_SIGNATURES[0]
54
63
  _PROMPT_ARGUMENTS = ["-p", "hello"]
55
64
  _EQUAL_WEEKLY_REMAINING_PERCENT = 50.0
@@ -955,6 +964,128 @@ def test_real_subprocess_capture_preserves_utf8_text(
955
964
  assert chain_result.stdout == "report ✅ done"
956
965
 
957
966
 
967
+ def test_run_captured_subprocess_spools_large_stdout_and_stderr() -> None:
968
+ child_code = (
969
+ "import sys;"
970
+ f"sys.stdout.write({_LARGE_CAPTURE_MARKER!r} * {_LARGE_CAPTURE_BYTE_COUNT});"
971
+ "sys.stderr.write('err-marker');"
972
+ "sys.exit(3)"
973
+ )
974
+ completion = runner._run_captured_subprocess(
975
+ [sys.executable, "-c", child_code],
976
+ encoding=UTF8_ENCODING,
977
+ errors=CODEC_ERROR_STRATEGY,
978
+ timeout=60,
979
+ check=False,
980
+ input=None,
981
+ )
982
+ assert completion.returncode == 3
983
+ assert len(completion.stdout) == _LARGE_CAPTURE_BYTE_COUNT
984
+ assert completion.stdout == _LARGE_CAPTURE_MARKER * _LARGE_CAPTURE_BYTE_COUNT
985
+ assert completion.stderr == "err-marker"
986
+
987
+
988
+ def test_run_captured_subprocess_forwards_stdin_input() -> None:
989
+ child_code = "import sys; sys.stdout.write(sys.stdin.read())"
990
+ completion = runner._run_captured_subprocess(
991
+ [sys.executable, "-c", child_code],
992
+ encoding=UTF8_ENCODING,
993
+ errors=CODEC_ERROR_STRATEGY,
994
+ timeout=60,
995
+ check=False,
996
+ input=_STDIN_ECHO_PAYLOAD,
997
+ )
998
+ assert completion.returncode == 0
999
+ assert completion.stdout == _STDIN_ECHO_PAYLOAD
1000
+ assert completion.stderr == ""
1001
+
1002
+
1003
+ def test_run_captured_subprocess_replaces_undecodable_stdout_bytes() -> None:
1004
+ child_code = (
1005
+ "import sys;"
1006
+ f"sys.stdout.buffer.write({_UNDECODABLE_STDOUT_BYTES!r})"
1007
+ )
1008
+ completion = runner._run_captured_subprocess(
1009
+ [sys.executable, "-c", child_code],
1010
+ encoding=UTF8_ENCODING,
1011
+ errors=CODEC_ERROR_STRATEGY,
1012
+ timeout=60,
1013
+ check=False,
1014
+ input=None,
1015
+ )
1016
+ assert completion.returncode == 0
1017
+ assert completion.stdout == _DECODED_UNDECODABLE_STDOUT
1018
+
1019
+
1020
+ def test_run_captured_subprocess_normalizes_crlf_to_lf() -> None:
1021
+ """Spool decode matches subprocess text=True universal-newline translation."""
1022
+ child_code = (
1023
+ "import sys;"
1024
+ f"sys.stdout.buffer.write({_CRLF_CHILD_STDOUT_BYTES!r})"
1025
+ )
1026
+ completion = runner._run_captured_subprocess(
1027
+ [sys.executable, "-c", child_code],
1028
+ encoding=UTF8_ENCODING,
1029
+ errors=CODEC_ERROR_STRATEGY,
1030
+ timeout=60,
1031
+ check=False,
1032
+ input=None,
1033
+ )
1034
+ assert completion.returncode == 0
1035
+ assert completion.stdout == _CRLF_CHILD_STDOUT_NORMALIZED
1036
+
1037
+
1038
+ def test_run_captured_subprocess_honors_cwd(tmp_path: Path) -> None:
1039
+ child_code = "import os, sys; sys.stdout.write(os.getcwd())"
1040
+ completion = runner._run_captured_subprocess(
1041
+ [sys.executable, "-c", child_code],
1042
+ encoding=UTF8_ENCODING,
1043
+ errors=CODEC_ERROR_STRATEGY,
1044
+ timeout=60,
1045
+ check=False,
1046
+ cwd=str(tmp_path),
1047
+ )
1048
+ assert completion.returncode == 0
1049
+ assert Path(completion.stdout).resolve() == tmp_path.resolve()
1050
+
1051
+
1052
+ def test_run_captured_subprocess_timeout_keeps_partial_stdout() -> None:
1053
+ child_code = (
1054
+ "import sys, time;"
1055
+ "sys.stdout.write('partial-before-timeout');"
1056
+ "sys.stdout.flush();"
1057
+ "time.sleep(30)"
1058
+ )
1059
+ with pytest.raises(subprocess.TimeoutExpired) as raised:
1060
+ runner._run_captured_subprocess(
1061
+ [sys.executable, "-c", child_code],
1062
+ encoding=UTF8_ENCODING,
1063
+ errors=CODEC_ERROR_STRATEGY,
1064
+ timeout=1,
1065
+ check=False,
1066
+ input=None,
1067
+ )
1068
+ assert raised.value.stdout == "partial-before-timeout"
1069
+ assert isinstance(raised.value.stdout, str)
1070
+
1071
+
1072
+ def test_run_captured_subprocess_reads_stdin_stream(tmp_path: Path) -> None:
1073
+ prompt_path = tmp_path / "prompt_body.txt"
1074
+ prompt_path.write_text(_STDIN_ECHO_PAYLOAD, encoding=UTF8_ENCODING)
1075
+ child_code = "import sys; sys.stdout.write(sys.stdin.read())"
1076
+ with prompt_path.open(encoding=UTF8_ENCODING) as prompt_stream:
1077
+ completion = runner._run_captured_subprocess(
1078
+ [sys.executable, "-c", child_code],
1079
+ encoding=UTF8_ENCODING,
1080
+ errors=CODEC_ERROR_STRATEGY,
1081
+ timeout=60,
1082
+ check=False,
1083
+ stdin=prompt_stream,
1084
+ )
1085
+ assert completion.returncode == 0
1086
+ assert completion.stdout == _STDIN_ECHO_PAYLOAD
1087
+
1088
+
958
1089
  def test_cli_emits_utf8_when_console_encoding_is_legacy(tmp_path: Path) -> None:
959
1090
  tmp_home = tmp_path / "home"
960
1091
  claude_directory = tmp_home / CLAUDE_HOME_SUBDIRECTORY
@@ -0,0 +1,70 @@
1
+ """Chain-invocation argv assembly, empty stdin, and working directory."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from pathlib import Path
6
+
7
+ import pytest
8
+
9
+ import invoke_code_review as invoker
10
+ from _code_review_test_support import (
11
+ FIXTURE_SESSION_OPUS,
12
+ FIXTURE_SESSION_SONNET,
13
+ HOST_PROFILE_THIRD_PARTY,
14
+ claude_served,
15
+ init_git_repository,
16
+ install_seams,
17
+ run_review,
18
+ )
19
+ from dev_env_scripts_constants.code_review_constants import (
20
+ CODE_REVIEW_MODEL_ALIAS,
21
+ DEFAULT_CODE_REVIEW_EFFORT,
22
+ PERMISSION_MODE_BYPASS,
23
+ PERMISSION_MODE_FLAG,
24
+ )
25
+ from dev_env_scripts_constants.grok_worker_constants import (
26
+ MODEL_FLAG,
27
+ OUTPUT_FORMAT_FLAG,
28
+ OUTPUT_FORMAT_JSON,
29
+ SINGLE_TURN_FLAG,
30
+ )
31
+
32
+
33
+ def test_chain_argv_assembly(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None:
34
+ working_directory = init_git_repository(tmp_path / "repo")
35
+ call_log = install_seams(
36
+ monkeypatch,
37
+ host_profile=HOST_PROFILE_THIRD_PARTY,
38
+ claude_outcome=claude_served(),
39
+ working_directory=working_directory,
40
+ )
41
+
42
+ run_review(working_directory, session_model=FIXTURE_SESSION_SONNET)
43
+
44
+ assert call_log.claude_arguments == [
45
+ SINGLE_TURN_FLAG,
46
+ invoker.build_code_review_prompt(DEFAULT_CODE_REVIEW_EFFORT),
47
+ MODEL_FLAG,
48
+ CODE_REVIEW_MODEL_ALIAS,
49
+ OUTPUT_FORMAT_FLAG,
50
+ OUTPUT_FORMAT_JSON,
51
+ PERMISSION_MODE_FLAG,
52
+ PERMISSION_MODE_BYPASS,
53
+ ]
54
+
55
+
56
+ def test_chain_redirects_empty_stdin_and_sets_cwd(
57
+ monkeypatch: pytest.MonkeyPatch, tmp_path: Path
58
+ ) -> None:
59
+ working_directory = init_git_repository(tmp_path / "repo")
60
+ call_log = install_seams(
61
+ monkeypatch,
62
+ host_profile=HOST_PROFILE_THIRD_PARTY,
63
+ claude_outcome=claude_served(),
64
+ working_directory=working_directory,
65
+ )
66
+
67
+ run_review(working_directory, session_model=FIXTURE_SESSION_OPUS)
68
+
69
+ assert call_log.is_stdin_empty is True
70
+ assert call_log.claude_working_directory == working_directory
@@ -0,0 +1,192 @@
1
+ """CLI JSON output for in-session, error, effort, and record-stamp paths."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ from pathlib import Path
7
+
8
+ import pytest
9
+
10
+ import invoke_code_review as invoker
11
+ from _code_review_test_support import (
12
+ DriftingReview,
13
+ EFFORT_LOW,
14
+ FIXTURE_CHAIN_CONFIG_ERROR_MESSAGE,
15
+ FIXTURE_HOST_PROFILE_ERROR_MESSAGE,
16
+ FIXTURE_SESSION_OPUS,
17
+ HOST_PROFILE_CLAUDE,
18
+ HOST_PROFILE_THIRD_PARTY,
19
+ REJECTED_ULTRA_EFFORT,
20
+ init_git_repository,
21
+ install_seams,
22
+ prepared_surface_repo,
23
+ run_record_stamp_cli,
24
+ run_review_cli,
25
+ )
26
+ from claude_chain_runner import ChainConfigurationError
27
+ from dev_env_scripts_constants.claude_chain_constants import (
28
+ CHAIN_CONFIG_ERROR_EXIT_CODE,
29
+ )
30
+ from dev_env_scripts_constants.code_review_constants import (
31
+ CLI_SESSION_MODEL_FLAG,
32
+ HOST_PROFILE_ERROR_RETURNCODE,
33
+ IN_SESSION_RETURNCODE,
34
+ INVALID_EFFORT_RETURNCODE,
35
+ MAXIMUM_STAMP_MINT_PASSES,
36
+ MODE_CHAIN,
37
+ MODE_IN_SESSION,
38
+ RESULT_KEY_BOUND_HASH,
39
+ RESULT_KEY_DIRTY_TREE,
40
+ RESULT_KEY_MODE,
41
+ RESULT_KEY_PASS_COUNT,
42
+ RESULT_KEY_RETURNCODE,
43
+ RESULT_KEY_SERVED_COMMAND,
44
+ RESULT_KEY_STAMP_MINTED,
45
+ STAMP_DID_NOT_CONVERGE_RETURNCODE,
46
+ )
47
+ from dev_env_scripts_constants.grok_worker_constants import CWD_FLAG
48
+
49
+
50
+ def test_cli_prints_result_json_only(
51
+ monkeypatch: pytest.MonkeyPatch,
52
+ tmp_path: Path,
53
+ capsys: pytest.CaptureFixture[str],
54
+ ) -> None:
55
+ working_directory = init_git_repository(tmp_path / "repo")
56
+ install_seams(
57
+ monkeypatch,
58
+ host_profile=HOST_PROFILE_CLAUDE,
59
+ claude_outcome=None,
60
+ working_directory=working_directory,
61
+ )
62
+
63
+ exit_code = run_review_cli(working_directory, session_model=FIXTURE_SESSION_OPUS)
64
+
65
+ assert exit_code == IN_SESSION_RETURNCODE
66
+ captured = capsys.readouterr()
67
+ assert captured.err == ""
68
+ parsed_payload = json.loads(captured.out)
69
+ assert parsed_payload == {
70
+ RESULT_KEY_MODE: MODE_IN_SESSION,
71
+ RESULT_KEY_SERVED_COMMAND: None,
72
+ RESULT_KEY_RETURNCODE: IN_SESSION_RETURNCODE,
73
+ RESULT_KEY_DIRTY_TREE: False,
74
+ }
75
+
76
+
77
+ def test_cli_emits_json_on_chain_configuration_error(
78
+ monkeypatch: pytest.MonkeyPatch,
79
+ tmp_path: Path,
80
+ capsys: pytest.CaptureFixture[str],
81
+ ) -> None:
82
+ working_directory = init_git_repository(tmp_path / "repo")
83
+ install_seams(
84
+ monkeypatch,
85
+ host_profile=HOST_PROFILE_THIRD_PARTY,
86
+ claude_outcome=ChainConfigurationError(FIXTURE_CHAIN_CONFIG_ERROR_MESSAGE),
87
+ working_directory=working_directory,
88
+ )
89
+ exit_code = run_review_cli(working_directory, session_model=FIXTURE_SESSION_OPUS)
90
+
91
+ assert exit_code == CHAIN_CONFIG_ERROR_EXIT_CODE
92
+ captured = capsys.readouterr()
93
+ parsed_payload = json.loads(captured.out)
94
+ assert parsed_payload == {
95
+ RESULT_KEY_MODE: MODE_CHAIN,
96
+ RESULT_KEY_SERVED_COMMAND: None,
97
+ RESULT_KEY_RETURNCODE: CHAIN_CONFIG_ERROR_EXIT_CODE,
98
+ RESULT_KEY_DIRTY_TREE: False,
99
+ }
100
+ config_error_outcome = invoker.CodeReviewOutcome(
101
+ mode=MODE_CHAIN,
102
+ served_command=None,
103
+ returncode=CHAIN_CONFIG_ERROR_EXIT_CODE,
104
+ is_dirty_tree=False,
105
+ )
106
+ assert invoker.is_code_review_clean_stamp_allowed(config_error_outcome) is False
107
+
108
+
109
+ def test_cli_emits_json_on_host_profile_value_error(
110
+ monkeypatch: pytest.MonkeyPatch,
111
+ tmp_path: Path,
112
+ capsys: pytest.CaptureFixture[str],
113
+ ) -> None:
114
+ working_directory = init_git_repository(tmp_path / "repo")
115
+
116
+ def fake_host_profile_raises(setting_by_name: object | None = None) -> str:
117
+ del setting_by_name
118
+ raise ValueError(FIXTURE_HOST_PROFILE_ERROR_MESSAGE)
119
+
120
+ monkeypatch.setattr(
121
+ invoker, "review_host_profile_detector", fake_host_profile_raises
122
+ )
123
+
124
+ exit_code = run_review_cli(working_directory, session_model=FIXTURE_SESSION_OPUS)
125
+
126
+ assert exit_code == HOST_PROFILE_ERROR_RETURNCODE
127
+ captured = capsys.readouterr()
128
+ parsed_payload = json.loads(captured.out)
129
+ assert parsed_payload == {
130
+ RESULT_KEY_MODE: MODE_CHAIN,
131
+ RESULT_KEY_SERVED_COMMAND: None,
132
+ RESULT_KEY_RETURNCODE: HOST_PROFILE_ERROR_RETURNCODE,
133
+ RESULT_KEY_DIRTY_TREE: False,
134
+ }
135
+
136
+
137
+ def test_cli_rejects_ultra_effort_with_nonzero_exit(
138
+ monkeypatch: pytest.MonkeyPatch,
139
+ tmp_path: Path,
140
+ capsys: pytest.CaptureFixture[str],
141
+ ) -> None:
142
+ install_seams(
143
+ monkeypatch,
144
+ host_profile=HOST_PROFILE_CLAUDE,
145
+ claude_outcome=None,
146
+ working_directory=tmp_path,
147
+ )
148
+ exit_code = invoker.main(
149
+ [
150
+ CWD_FLAG,
151
+ str(tmp_path),
152
+ CLI_SESSION_MODEL_FLAG,
153
+ FIXTURE_SESSION_OPUS,
154
+ REJECTED_ULTRA_EFFORT,
155
+ ]
156
+ )
157
+ assert exit_code == INVALID_EFFORT_RETURNCODE
158
+ assert REJECTED_ULTRA_EFFORT in capsys.readouterr().err
159
+
160
+
161
+ def test_cli_record_stamp_returns_non_convergence_code_on_cap(
162
+ monkeypatch: pytest.MonkeyPatch,
163
+ tmp_path: Path,
164
+ capsys: pytest.CaptureFixture[str],
165
+ ) -> None:
166
+ working_directory = prepared_surface_repo(monkeypatch, tmp_path)
167
+ monkeypatch.setattr(invoker, "invoke_code_review", DriftingReview())
168
+ exit_code = run_record_stamp_cli(working_directory, effort=EFFORT_LOW)
169
+ assert exit_code == STAMP_DID_NOT_CONVERGE_RETURNCODE
170
+ parsed_payload = json.loads(capsys.readouterr().out)
171
+ assert parsed_payload[RESULT_KEY_STAMP_MINTED] is False
172
+ assert parsed_payload[RESULT_KEY_PASS_COUNT] == MAXIMUM_STAMP_MINT_PASSES
173
+ assert parsed_payload[RESULT_KEY_BOUND_HASH] is None
174
+
175
+
176
+ def test_cli_record_stamp_reports_missing_store_dependency(
177
+ monkeypatch: pytest.MonkeyPatch,
178
+ tmp_path: Path,
179
+ capsys: pytest.CaptureFixture[str],
180
+ ) -> None:
181
+ def raise_missing_store(*all_args: object, **all_keywords: object) -> object:
182
+ del all_args, all_keywords
183
+ raise ModuleNotFoundError("store missing", name="code_review_stamp_store")
184
+
185
+ working_directory = prepared_surface_repo(monkeypatch, tmp_path)
186
+ monkeypatch.setattr(invoker, "load_code_review_stamp_store", raise_missing_store)
187
+ exit_code = run_record_stamp_cli(working_directory, effort=EFFORT_LOW)
188
+ assert exit_code == INVALID_EFFORT_RETURNCODE
189
+ captured = capsys.readouterr()
190
+ assert "stamp store" in captured.err
191
+ parsed_payload = json.loads(captured.out)
192
+ assert parsed_payload[RESULT_KEY_STAMP_MINTED] is False
@@ -0,0 +1,77 @@
1
+ """Behavioral tests for text codec keyword forwarding in the ``/code-review`` invoker."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from pathlib import Path
6
+
7
+ import invoke_code_review as invoker
8
+ import pytest
9
+ from codec_forwarding_test_support import (
10
+ FIXTURE_CHAIN_ENCODING,
11
+ FIXTURE_CHAIN_ERRORS,
12
+ FIXTURE_ENCODING_KEYWORD_NAME,
13
+ FIXTURE_ERRORS_KEYWORD_NAME,
14
+ install_codec_seams,
15
+ )
16
+ from dev_env_scripts_constants.timing import DEFAULT_CODE_REVIEW_TIMEOUT_SECONDS
17
+
18
+ FIXTURE_CHAIN_STDOUT = '{"result":"review done"}'
19
+
20
+
21
+ def _install_invoker_codec_seams(
22
+ monkeypatch: pytest.MonkeyPatch,
23
+ *,
24
+ all_chain_codec_keywords: dict[str, str],
25
+ ) -> dict[str, object]:
26
+ return install_codec_seams(
27
+ monkeypatch,
28
+ all_chain_codec_keywords=all_chain_codec_keywords,
29
+ chain_stdout=FIXTURE_CHAIN_STDOUT,
30
+ runner_host=invoker,
31
+ runner_attribute_name="review_claude_runner",
32
+ )
33
+
34
+
35
+ def test_forwards_text_codec_keywords_to_subprocess_runner(
36
+ monkeypatch: pytest.MonkeyPatch, tmp_path: Path
37
+ ) -> None:
38
+ all_observed_runner_keywords = _install_invoker_codec_seams(
39
+ monkeypatch,
40
+ all_chain_codec_keywords={
41
+ FIXTURE_ENCODING_KEYWORD_NAME: FIXTURE_CHAIN_ENCODING,
42
+ FIXTURE_ERRORS_KEYWORD_NAME: FIXTURE_CHAIN_ERRORS,
43
+ },
44
+ )
45
+
46
+ invoker._run_claude_with_empty_stdin(
47
+ [],
48
+ timeout_seconds=DEFAULT_CODE_REVIEW_TIMEOUT_SECONDS,
49
+ working_directory=tmp_path,
50
+ )
51
+
52
+ assert (
53
+ all_observed_runner_keywords[FIXTURE_ENCODING_KEYWORD_NAME]
54
+ == FIXTURE_CHAIN_ENCODING
55
+ )
56
+ assert (
57
+ all_observed_runner_keywords[FIXTURE_ERRORS_KEYWORD_NAME]
58
+ == FIXTURE_CHAIN_ERRORS
59
+ )
60
+
61
+
62
+ def test_absent_text_codec_keywords_are_not_invented(
63
+ monkeypatch: pytest.MonkeyPatch, tmp_path: Path
64
+ ) -> None:
65
+ all_observed_runner_keywords = _install_invoker_codec_seams(
66
+ monkeypatch,
67
+ all_chain_codec_keywords={},
68
+ )
69
+
70
+ invoker._run_claude_with_empty_stdin(
71
+ [],
72
+ timeout_seconds=DEFAULT_CODE_REVIEW_TIMEOUT_SECONDS,
73
+ working_directory=tmp_path,
74
+ )
75
+
76
+ assert FIXTURE_ENCODING_KEYWORD_NAME not in all_observed_runner_keywords
77
+ assert FIXTURE_ERRORS_KEYWORD_NAME not in all_observed_runner_keywords