claude-dev-env 1.95.0 → 2.0.1

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 (241) hide show
  1. package/_shared/advisor/CLAUDE.md +2 -2
  2. package/_shared/advisor/advisor-protocol.md +20 -20
  3. package/_shared/advisor/scripts/config/advisor_scripts_constants/model_tier_run_validator_constants.py +15 -12
  4. package/_shared/advisor/scripts/model_tier_run_validator.py +11 -10
  5. package/_shared/advisor/scripts/tests/test_model_tier_run_validator.py +25 -19
  6. package/_shared/advisor/scripts/tests/test_tier_model_ids.py +17 -17
  7. package/_shared/advisor/scripts/tier_model_ids.py +18 -18
  8. package/_shared/pr-loop/CLAUDE.md +1 -0
  9. package/_shared/pr-loop/scripts/CLAUDE.md +2 -1
  10. package/_shared/pr-loop/scripts/README.md +1 -0
  11. package/_shared/pr-loop/scripts/code_rules_gate.py +253 -1980
  12. package/_shared/pr-loop/scripts/code_rules_gate_parts/CLAUDE.md +32 -0
  13. package/_shared/pr-loop/scripts/code_rules_gate_parts/__init__.py +7 -0
  14. package/_shared/pr-loop/scripts/code_rules_gate_parts/added_line_maps.py +268 -0
  15. package/_shared/pr-loop/scripts/code_rules_gate_parts/enforcer_loading.py +172 -0
  16. package/_shared/pr-loop/scripts/code_rules_gate_parts/gate_arguments.py +70 -0
  17. package/_shared/pr-loop/scripts/code_rules_gate_parts/gate_running.py +326 -0
  18. package/_shared/pr-loop/scripts/code_rules_gate_parts/git_blob_readers.py +85 -0
  19. package/_shared/pr-loop/scripts/code_rules_gate_parts/git_file_sets.py +331 -0
  20. package/_shared/pr-loop/scripts/code_rules_gate_parts/staged_test_running.py +369 -0
  21. package/_shared/pr-loop/scripts/code_rules_gate_parts/tests/conftest.py +14 -0
  22. package/_shared/pr-loop/scripts/code_rules_gate_parts/tests/test_added_line_maps.py +118 -0
  23. package/_shared/pr-loop/scripts/code_rules_gate_parts/tests/test_enforcer_loading.py +17 -0
  24. package/_shared/pr-loop/scripts/code_rules_gate_parts/tests/test_gate_arguments.py +29 -0
  25. package/_shared/pr-loop/scripts/code_rules_gate_parts/tests/test_gate_running.py +99 -0
  26. package/_shared/pr-loop/scripts/code_rules_gate_parts/tests/test_git_blob_readers.py +69 -0
  27. package/_shared/pr-loop/scripts/code_rules_gate_parts/tests/test_git_file_sets.py +137 -0
  28. package/_shared/pr-loop/scripts/code_rules_gate_parts/tests/test_staged_test_running.py +116 -0
  29. package/_shared/pr-loop/scripts/code_rules_gate_parts/tests/test_violation_scoping.py +75 -0
  30. package/_shared/pr-loop/scripts/code_rules_gate_parts/tests/test_wrapper_plumb_check.py +49 -0
  31. package/_shared/pr-loop/scripts/code_rules_gate_parts/violation_scoping.py +328 -0
  32. package/_shared/pr-loop/scripts/code_rules_gate_parts/wrapper_plumb_check.py +206 -0
  33. package/_shared/pr-loop/scripts/pr_loop_shared_constants/code_rules_gate_constants.py +24 -17
  34. package/_shared/pr-loop/scripts/pr_loop_shared_constants/reviews_disabled_constants.py +1 -0
  35. package/_shared/pr-loop/scripts/reviews_disabled.py +19 -2
  36. package/_shared/pr-loop/scripts/test_code_rules_gate.py +278 -0
  37. package/_shared/pr-loop/scripts/tests/test_code_rules_gate_constants.py +6 -39
  38. package/_shared/pr-loop/scripts/tests/test_reviews_disabled.py +43 -0
  39. package/_shared/pr-loop/worker-spawn.md +186 -0
  40. package/agents/code-verifier.md +1 -1
  41. package/bin/ever-shipped-skills.mjs +3 -0
  42. package/bin/expand_home_directory_tokens.mjs +1 -1
  43. package/bin/install.mjs +5 -2
  44. package/hooks/advisory/refactor_guard.py +3 -4
  45. package/hooks/blocking/CLAUDE.md +7 -1
  46. package/hooks/blocking/block_main_commit.py +2 -2
  47. package/hooks/blocking/claude_md_orphan_file_blocker.py +75 -699
  48. package/hooks/blocking/claude_md_orphan_file_blocker_parts/CLAUDE.md +28 -0
  49. package/hooks/blocking/claude_md_orphan_file_blocker_parts/__init__.py +1 -0
  50. package/hooks/blocking/claude_md_orphan_file_blocker_parts/config/__init__.py +1 -0
  51. package/hooks/blocking/claude_md_orphan_file_blocker_parts/config/orphan_blocker_constants.py +18 -0
  52. package/hooks/blocking/claude_md_orphan_file_blocker_parts/decision.py +81 -0
  53. package/hooks/blocking/claude_md_orphan_file_blocker_parts/references.py +307 -0
  54. package/hooks/blocking/claude_md_orphan_file_blocker_parts/scan_plan.py +124 -0
  55. package/hooks/blocking/claude_md_orphan_file_blocker_parts/subtree_scan.py +179 -0
  56. package/hooks/blocking/claude_md_orphan_file_blocker_parts/tests/conftest.py +10 -0
  57. package/hooks/blocking/claude_md_orphan_file_blocker_parts/tests/test_decision.py +34 -0
  58. package/hooks/blocking/claude_md_orphan_file_blocker_parts/tests/test_references.py +42 -0
  59. package/hooks/blocking/claude_md_orphan_file_blocker_parts/tests/test_scan_plan.py +27 -0
  60. package/hooks/blocking/claude_md_orphan_file_blocker_parts/tests/test_subtree_scan.py +30 -0
  61. package/hooks/blocking/code_rules_boolean_mustcheck.py +1 -1
  62. package/hooks/blocking/code_rules_mock_completeness.py +1 -1
  63. package/hooks/blocking/code_rules_optional_params.py +2 -2
  64. package/hooks/blocking/code_rules_shared.py +1 -1
  65. package/hooks/blocking/code_rules_test_assertions.py +1 -1
  66. package/hooks/blocking/code_rules_typeddict_stub.py +1 -1
  67. package/hooks/blocking/gh_pr_author_enforcer.py +1 -1
  68. package/hooks/blocking/inventory_intent_records/CLAUDE.md +26 -0
  69. package/hooks/blocking/inventory_intent_records/__init__.py +1 -0
  70. package/hooks/blocking/inventory_intent_records/config/__init__.py +1 -0
  71. package/hooks/blocking/inventory_intent_records/config/intent_records_constants.py +20 -0
  72. package/hooks/blocking/inventory_intent_records/records.py +271 -0
  73. package/hooks/blocking/inventory_intent_records/tests/conftest.py +10 -0
  74. package/hooks/blocking/inventory_intent_records/tests/test_records.py +80 -0
  75. package/hooks/blocking/package_inventory_stale_blocker.py +54 -384
  76. package/hooks/blocking/package_inventory_stale_blocker_parts/CLAUDE.md +26 -0
  77. package/hooks/blocking/package_inventory_stale_blocker_parts/__init__.py +1 -0
  78. package/hooks/blocking/package_inventory_stale_blocker_parts/config/__init__.py +1 -0
  79. package/hooks/blocking/package_inventory_stale_blocker_parts/config/inventory_blocker_constants.py +16 -0
  80. package/hooks/blocking/package_inventory_stale_blocker_parts/decision.py +84 -0
  81. package/hooks/blocking/package_inventory_stale_blocker_parts/inventory_detection.py +307 -0
  82. package/hooks/blocking/package_inventory_stale_blocker_parts/tests/conftest.py +10 -0
  83. package/hooks/blocking/package_inventory_stale_blocker_parts/tests/test_decision.py +38 -0
  84. package/hooks/blocking/package_inventory_stale_blocker_parts/tests/test_inventory_detection.py +61 -0
  85. package/hooks/blocking/pii_payload_scan.py +138 -42
  86. package/hooks/blocking/pii_prevention_blocker.py +185 -291
  87. package/hooks/blocking/pii_prevention_blocker_parts/CLAUDE.md +24 -0
  88. package/hooks/blocking/pii_prevention_blocker_parts/__init__.py +1 -0
  89. package/hooks/blocking/pii_prevention_blocker_parts/config/__init__.py +1 -0
  90. package/hooks/blocking/pii_prevention_blocker_parts/config/repository_resolution_constants.py +28 -0
  91. package/hooks/blocking/pii_prevention_blocker_parts/repository_exemption.py +214 -0
  92. package/hooks/blocking/pii_prevention_blocker_parts/repository_resolution.py +208 -0
  93. package/hooks/blocking/pr_description_command_parser.py +8 -4
  94. package/hooks/blocking/precommit_code_rules_gate.py +3 -3
  95. package/hooks/blocking/tdd_enforcer.py +97 -608
  96. package/hooks/blocking/tdd_enforcer_parts/CLAUDE.md +30 -0
  97. package/hooks/blocking/tdd_enforcer_parts/__init__.py +1 -0
  98. package/hooks/blocking/tdd_enforcer_parts/candidate_paths.py +142 -0
  99. package/hooks/blocking/tdd_enforcer_parts/config/__init__.py +1 -0
  100. package/hooks/blocking/tdd_enforcer_parts/config/tdd_enforcer_constants.py +32 -0
  101. package/hooks/blocking/tdd_enforcer_parts/content_analysis.py +268 -0
  102. package/hooks/blocking/tdd_enforcer_parts/decisions.py +92 -0
  103. package/hooks/blocking/tdd_enforcer_parts/freshness.py +80 -0
  104. package/hooks/blocking/tdd_enforcer_parts/git_tracking.py +63 -0
  105. package/hooks/blocking/tdd_enforcer_parts/path_classification.py +119 -0
  106. package/hooks/blocking/tdd_enforcer_parts/tests/conftest.py +10 -0
  107. package/hooks/blocking/tdd_enforcer_parts/tests/test_candidate_paths.py +31 -0
  108. package/hooks/blocking/tdd_enforcer_parts/tests/test_content_analysis.py +30 -0
  109. package/hooks/blocking/tdd_enforcer_parts/tests/test_decisions.py +34 -0
  110. package/hooks/blocking/tdd_enforcer_parts/tests/test_freshness.py +28 -0
  111. package/hooks/blocking/tdd_enforcer_parts/tests/test_git_tracking.py +48 -0
  112. package/hooks/blocking/tdd_enforcer_parts/tests/test_path_classification.py +36 -0
  113. package/hooks/blocking/test_inventory_deadlock_resolution.py +154 -0
  114. package/hooks/blocking/test_pii_payload_scan.py +168 -0
  115. package/hooks/blocking/test_tdd_enforcer_restore.py +108 -0
  116. package/hooks/blocking/test_verifier_verdict_minter.py +55 -158
  117. package/hooks/blocking/tests/conftest.py +10 -0
  118. package/hooks/blocking/tests/test_pii_prevention_blocker.py +260 -0
  119. package/hooks/blocking/tests/test_repository_exemption.py +105 -0
  120. package/hooks/blocking/tests/test_repository_resolution.py +108 -0
  121. package/hooks/diagnostic/hook_log_extractor.py +12 -10
  122. package/hooks/git-hooks/post_commit.py +3 -4
  123. package/hooks/hooks_constants/CLAUDE.md +2 -2
  124. package/hooks/hooks_constants/banned_identifiers_constants.py +0 -1
  125. package/hooks/hooks_constants/code_rules_path_utils_constants.py +1 -1
  126. package/hooks/hooks_constants/local_identity.py +59 -8
  127. package/hooks/hooks_constants/pii_prevention_constants.py +0 -6
  128. package/hooks/hooks_constants/test_local_identity.py +105 -3
  129. package/hooks/pyproject.toml +13 -36
  130. package/hooks/session/plugin_data_dir_cleanup.py +0 -1
  131. package/hooks/validation/mypy_validator.py +2 -2
  132. package/hooks/validators/health_check.py +1 -0
  133. package/hooks/validators/mypy_integration.py +2 -0
  134. package/hooks/validators/ruff_integration.py +3 -0
  135. package/hooks/workflow/auto_formatter.py +5 -4
  136. package/package.json +1 -1
  137. package/scripts/CLAUDE.md +4 -0
  138. package/scripts/dev_env_scripts_constants/CLAUDE.md +6 -4
  139. package/scripts/dev_env_scripts_constants/code_review_constants.py +71 -0
  140. package/scripts/dev_env_scripts_constants/grok_worker_constants.py +435 -0
  141. package/scripts/dev_env_scripts_constants/timing.py +7 -1
  142. package/scripts/grok_headless_runner.py +294 -0
  143. package/scripts/grok_worker_preflight.py +410 -0
  144. package/scripts/invoke_code_review.py +463 -0
  145. package/scripts/resolve_worker_spawn.py +619 -0
  146. package/scripts/spawn_grok_batch.py +672 -0
  147. package/scripts/test_grok_headless_runner.py +626 -0
  148. package/scripts/test_grok_worker_preflight.py +1054 -0
  149. package/scripts/test_invoke_code_review.py +672 -0
  150. package/scripts/test_resolve_worker_spawn.py +1014 -0
  151. package/scripts/test_spawn_grok_batch.py +1017 -0
  152. package/skills/CLAUDE.md +5 -3
  153. package/skills/_shared/pr-loop/scripts/build_audit_prompt.py +72 -13
  154. package/skills/_shared/pr-loop/scripts/build_fix_prompt.py +121 -14
  155. package/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/path_resolver_constants.py +78 -0
  156. package/skills/_shared/pr-loop/scripts/test_build_audit_prompt.py +121 -0
  157. package/skills/_shared/pr-loop/scripts/test_build_fix_prompt.py +196 -6
  158. package/skills/autoconverge/CLAUDE.md +3 -3
  159. package/skills/autoconverge/SKILL.md +9 -3
  160. package/skills/autoconverge/reference/CLAUDE.md +2 -2
  161. package/skills/autoconverge/reference/convergence.md +33 -11
  162. package/skills/autoconverge/reference/stop-conditions.md +16 -5
  163. package/skills/autoconverge/workflow/CLAUDE.md +2 -1
  164. package/skills/autoconverge/workflow/converge.clean-audit.test.mjs +7 -2
  165. package/skills/autoconverge/workflow/converge.codex-gate.test.mjs +300 -0
  166. package/skills/autoconverge/workflow/converge.contract.test.mjs +5 -5
  167. package/skills/autoconverge/workflow/converge.copilot-gate.test.mjs +29 -29
  168. package/skills/autoconverge/workflow/converge.fix-progress.test.mjs +1 -1
  169. package/skills/autoconverge/workflow/converge.mjs +200 -16
  170. package/skills/bugteam/CLAUDE.md +2 -2
  171. package/skills/bugteam/CONSTRAINTS.md +3 -2
  172. package/skills/bugteam/PROMPTS.md +7 -6
  173. package/skills/bugteam/SKILL.md +18 -13
  174. package/skills/bugteam/reference/audit-and-teammates.md +215 -35
  175. package/skills/bugteam/reference/design-rationale.md +1 -1
  176. package/skills/bugteam/reference/obstacles/CLAUDE.md +1 -1
  177. package/skills/bugteam/reference/team-setup.md +8 -2
  178. package/skills/codex-review/CLAUDE.md +46 -0
  179. package/skills/codex-review/SKILL.md +181 -0
  180. package/skills/codex-review/reference/CLAUDE.md +15 -0
  181. package/skills/codex-review/reference/cli-contract.md +253 -0
  182. package/skills/codex-review/reference/loop-integration.md +118 -0
  183. package/skills/codex-review/scripts/codex_down_classifier.py +98 -0
  184. package/skills/codex-review/scripts/codex_review_scripts_constants/CLAUDE.md +18 -0
  185. package/skills/codex-review/scripts/codex_review_scripts_constants/__init__.py +1 -0
  186. package/skills/codex-review/scripts/codex_review_scripts_constants/classifier_constants.py +35 -0
  187. package/skills/codex-review/scripts/codex_review_scripts_constants/codex_usage_probe_constants.py +86 -0
  188. package/skills/codex-review/scripts/codex_review_scripts_constants/findings_constants.py +18 -0
  189. package/skills/codex-review/scripts/codex_review_scripts_constants/run_constants.py +45 -0
  190. package/skills/codex-review/scripts/codex_usage_probe.py +573 -0
  191. package/skills/codex-review/scripts/fixtures/auth_failure_synthetic.txt +1 -0
  192. package/skills/codex-review/scripts/fixtures/config_load_failure_v0.125.0.txt +1 -0
  193. package/skills/codex-review/scripts/fixtures/freeform_findings_v0.144.3.txt +6 -0
  194. package/skills/codex-review/scripts/fixtures/model_rejection_v0.125.0.jsonl +5 -0
  195. package/skills/codex-review/scripts/fixtures/structured_findings.txt +13 -0
  196. package/skills/codex-review/scripts/fixtures/success_stream_v0.144.3.jsonl +6 -0
  197. package/skills/codex-review/scripts/fixtures/unknown_failure_synthetic.txt +1 -0
  198. package/skills/codex-review/scripts/fixtures/usage_limit_synthetic.txt +1 -0
  199. package/skills/codex-review/scripts/parse_codex_findings.py +207 -0
  200. package/skills/codex-review/scripts/run_codex_review.py +415 -0
  201. package/skills/codex-review/scripts/test_codex_down_classifier.py +143 -0
  202. package/skills/codex-review/scripts/test_codex_usage_probe.py +678 -0
  203. package/skills/codex-review/scripts/test_parse_codex_findings.py +130 -0
  204. package/skills/codex-review/scripts/test_run_codex_review.py +812 -0
  205. package/skills/codex-review/test_skill_scaffold.py +192 -0
  206. package/skills/grok-spawn/CLAUDE.md +28 -0
  207. package/skills/grok-spawn/SKILL.md +226 -0
  208. package/skills/grok-spawn/reference/flag-profiles.md +132 -0
  209. package/skills/grok-spawn/reference/worker-briefs.md +152 -0
  210. package/skills/grokify/SKILL.md +9 -1
  211. package/skills/grokify/capability-claims.test.mjs +28 -0
  212. package/skills/grokify/evals/README.md +72 -0
  213. package/skills/grokify/evals/parse-payload.test.mjs +171 -0
  214. package/skills/grokify/evals/run-capability-evals.mjs +545 -0
  215. package/skills/orchestrator/SKILL.md +42 -29
  216. package/skills/orchestrator-refresh/SKILL.md +17 -9
  217. package/skills/pr-converge/SKILL.md +34 -13
  218. package/skills/pr-converge/reference/convergence-gates.md +42 -15
  219. package/skills/pr-converge/reference/fix-protocol.md +1 -1
  220. package/skills/pr-converge/reference/ground-rules.md +1 -1
  221. package/skills/pr-converge/reference/per-tick.md +130 -42
  222. package/skills/pr-converge/reference/state-schema.md +10 -0
  223. package/skills/pr-converge/scripts/CLAUDE.md +2 -0
  224. package/skills/pr-converge/scripts/_pr_converge_path_setup.py +5 -1
  225. package/skills/pr-converge/scripts/check_convergence.py +605 -29
  226. package/skills/pr-converge/scripts/check_convergence_availability.py +232 -0
  227. package/skills/pr-converge/scripts/check_convergence_gates.py +279 -235
  228. package/skills/pr-converge/scripts/check_convergence_thread_gates.py +1 -1
  229. package/skills/pr-converge/scripts/pr_converge_scripts_constants/convergence_gate_constants.py +36 -2
  230. package/skills/pr-converge/scripts/test__pr_converge_path_setup.py +4 -0
  231. package/skills/pr-converge/scripts/test_check_convergence.py +71 -3
  232. package/skills/pr-converge/scripts/test_check_convergence_availability.py +326 -0
  233. package/skills/pr-converge/scripts/test_check_convergence_codex.py +507 -0
  234. package/skills/pr-converge/scripts/test_check_convergence_contract.py +89 -17
  235. package/skills/pr-converge/scripts/test_check_convergence_fixture.py +179 -0
  236. package/skills/pr-converge/scripts/test_check_convergence_gates.py +84 -68
  237. package/skills/pr-converge/scripts/test_check_convergence_thread_gates.py +24 -0
  238. package/skills/pr-converge/test_step5_host_branch.py +106 -0
  239. package/skills/pr-loop-cloud-transport/SKILL.md +2 -0
  240. package/skills/reviewer-gates/SKILL.md +7 -5
  241. package/skills/team-advisor/SKILL.md +7 -7
@@ -27,9 +27,11 @@ import os
27
27
  from pathlib import Path
28
28
 
29
29
  NAS_HOST_ENV_VAR = "CLAUDE_NAS_HOST"
30
+ LOCAL_IDENTITY_PATH_ENV_VAR = "CLAUDE_LOCAL_IDENTITY_PATH"
30
31
  PII_EXEMPT_REPOS_ENV_VAR = "CLAUDE_PII_EXEMPT_REPOS"
31
32
  PII_EXEMPT_REPOS_JSON_KEY = "pii_exempt_repositories"
32
33
  PII_EXEMPT_REPOS_SEPARATOR = ","
34
+ PII_ALLOWLISTED_VALUES_JSON_KEY = "pii_allowlisted_values"
33
35
  NAS_SSH_USER_ENV_VAR = "CLAUDE_NAS_SSH_USER"
34
36
  NAS_SSH_PORT_ENV_VAR = "CLAUDE_NAS_SSH_PORT"
35
37
  NAS_JSON_KEY = "nas"
@@ -46,6 +48,9 @@ NAS_SSH_RULE_REFERENCE = "~/.claude/rules/nas-ssh-invocation.md"
46
48
 
47
49
 
48
50
  def _local_identity_file_path() -> Path:
51
+ path_override = os.environ.get(LOCAL_IDENTITY_PATH_ENV_VAR)
52
+ if path_override:
53
+ return Path(path_override)
49
54
  return Path.home() / CLAUDE_HOME_DIRECTORY_NAME / LOCAL_IDENTITY_FILE_NAME
50
55
 
51
56
 
@@ -73,13 +78,22 @@ def _normalized_slug_set(all_raw_slugs: list[object]) -> frozenset[str]:
73
78
  )
74
79
 
75
80
 
81
+ def _environment_exempt_slugs() -> frozenset[str]:
82
+ """Return the exempt slugs named by ``CLAUDE_PII_EXEMPT_REPOS``, or empty."""
83
+ slugs_from_environment = os.environ.get(PII_EXEMPT_REPOS_ENV_VAR)
84
+ if slugs_from_environment is None:
85
+ return frozenset()
86
+ return _normalized_slug_set(
87
+ list(slugs_from_environment.split(PII_EXEMPT_REPOS_SEPARATOR))
88
+ )
89
+
90
+
76
91
  def pii_exempt_repository_slugs() -> frozenset[str]:
77
92
  """Return owner/repo slugs whose commits skip the staged PII scan.
78
93
 
79
94
  ::
80
95
 
81
96
  CLAUDE_PII_EXEMPT_REPOS="Owner/repo-a, Other/b" -> {"owner/repo-a", "other/b"}
82
- CLAUDE_PII_EXEMPT_REPOS=" , , " + file lists A -> {"a"} (whitespace falls through)
83
97
  (env unset, file lists ["Owner/repo-a"]) -> {"owner/repo-a"}
84
98
  (env unset, no file) -> frozenset()
85
99
 
@@ -91,13 +105,9 @@ def pii_exempt_repository_slugs() -> frozenset[str]:
91
105
  Returns:
92
106
  Lowercased ``owner/repo`` slugs exempt from staged-commit PII scanning.
93
107
  """
94
- slugs_from_environment = os.environ.get(PII_EXEMPT_REPOS_ENV_VAR)
95
- if slugs_from_environment is not None:
96
- normalized_environment_slugs = _normalized_slug_set(
97
- list(slugs_from_environment.split(PII_EXEMPT_REPOS_SEPARATOR))
98
- )
99
- if normalized_environment_slugs:
100
- return normalized_environment_slugs
108
+ environment_slugs = _environment_exempt_slugs()
109
+ if environment_slugs:
110
+ return environment_slugs
101
111
  stored_slugs = _identity_dictionary_from_local_file().get(
102
112
  PII_EXEMPT_REPOS_JSON_KEY
103
113
  )
@@ -106,6 +116,47 @@ def pii_exempt_repository_slugs() -> frozenset[str]:
106
116
  return _normalized_slug_set(stored_slugs)
107
117
 
108
118
 
119
+ def _normalized_allowlisted_values(all_raw_values: object) -> frozenset[str]:
120
+ """Return the non-empty string members of a raw values list, or empty."""
121
+ if not isinstance(all_raw_values, list):
122
+ return frozenset()
123
+ return frozenset(
124
+ each_value
125
+ for each_value in all_raw_values
126
+ if isinstance(each_value, str) and each_value
127
+ )
128
+
129
+
130
+ def pii_allowlisted_values_by_repository() -> dict[str, frozenset[str]]:
131
+ """Return the exact literal values each repository allows past the PII scan.
132
+
133
+ ::
134
+
135
+ file {"Owner/Repo": ["user@example.com"]} -> {"owner/repo": {"user@example.com"}}
136
+ (env unset, no file, or bad json) -> {}
137
+
138
+ Keyed by lowercased ``owner/repo`` slug so matching is case-insensitive; the
139
+ values stay exact. The mapping lives under ``pii_allowlisted_values`` in the
140
+ git-ignored ``~/.claude/local-identity.json`` so a private repository's real
141
+ fixture addresses stay out of this published tree.
142
+
143
+ Returns:
144
+ Lowercased ``owner/repo`` slug to the frozenset of exact allowed values.
145
+ """
146
+ stored_mapping = _identity_dictionary_from_local_file().get(
147
+ PII_ALLOWLISTED_VALUES_JSON_KEY
148
+ )
149
+ if not isinstance(stored_mapping, dict):
150
+ return {}
151
+ all_values_by_slug: dict[str, frozenset[str]] = {}
152
+ for each_raw_slug, each_raw_values in stored_mapping.items():
153
+ normalized_slug = each_raw_slug.strip().lower() if isinstance(each_raw_slug, str) else ""
154
+ allowlisted_values = _normalized_allowlisted_values(each_raw_values)
155
+ if normalized_slug and allowlisted_values:
156
+ all_values_by_slug[normalized_slug] = allowlisted_values
157
+ return all_values_by_slug
158
+
159
+
109
160
  def nas_host() -> str:
110
161
  """Return the NAS host the ssh enforcer guards.
111
162
 
@@ -293,12 +293,6 @@ STAGED_LIST_FAILURE_REASON: str = (
293
293
  "(git diff --cached failed). Refuse commit until the index is readable."
294
294
  )
295
295
 
296
- REPOSITORY_ROOT_UNRESOLVED_REASON: str = (
297
- "BLOCKED [pii_prevention_blocker]: could not resolve the git repository root "
298
- "for a commit command (not a git work tree, git missing, or bad -C path). "
299
- "Refuse commit until the repository root is resolvable."
300
- )
301
-
302
296
  STAGED_BLOB_UNSCANNABLE_REASON_TEMPLATE: str = (
303
297
  "BLOCKED [pii_prevention_blocker]: staged file '{relative_path}' could not be "
304
298
  "scanned for PII ({reason}). Refuse commit, shrink the blob, or keep binary "
@@ -8,9 +8,11 @@ import sys
8
8
  from pathlib import Path
9
9
  from unittest.mock import patch
10
10
 
11
- sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
12
-
13
- from hooks_constants import local_identity # noqa: E402
11
+ try:
12
+ from hooks_constants import local_identity
13
+ except ImportError:
14
+ sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
15
+ from hooks_constants import local_identity
14
16
 
15
17
  ALL_NAS_ENV_VARS = ("CLAUDE_NAS_HOST", "CLAUDE_NAS_SSH_USER", "CLAUDE_NAS_SSH_PORT")
16
18
 
@@ -134,6 +136,106 @@ class TestPiiExemptRepositorySlugs:
134
136
  assert slugs == frozenset({"fileowner/file-repo"})
135
137
 
136
138
 
139
+ class TestPiiAllowlistedValuesByRepository:
140
+ def test_reads_the_mapping_lowercasing_slugs_and_keeping_values_exact(
141
+ self, tmp_path: Path
142
+ ) -> None:
143
+ allowed_value = "owner.fixture" + "@" + "acme-corp" + ".example" + ".io"
144
+ identity_path = tmp_path / "local-identity.json"
145
+ identity_path.write_text(
146
+ json.dumps(
147
+ {"pii_allowlisted_values": {"Owner/Private-Repo": [allowed_value]}}
148
+ ),
149
+ encoding="utf-8",
150
+ )
151
+ with patch.dict(
152
+ os.environ,
153
+ {"CLAUDE_LOCAL_IDENTITY_PATH": str(identity_path)},
154
+ clear=False,
155
+ ):
156
+ values_by_slug = local_identity.pii_allowlisted_values_by_repository()
157
+ assert values_by_slug == {"owner/private-repo": frozenset({allowed_value})}
158
+
159
+ def test_returns_empty_mapping_when_the_file_is_missing(
160
+ self, tmp_path: Path
161
+ ) -> None:
162
+ with patch.dict(
163
+ os.environ,
164
+ {"CLAUDE_LOCAL_IDENTITY_PATH": str(tmp_path / "absent.json")},
165
+ clear=False,
166
+ ):
167
+ assert local_identity.pii_allowlisted_values_by_repository() == {}
168
+
169
+ def test_returns_empty_mapping_when_the_json_is_corrupt(
170
+ self, tmp_path: Path
171
+ ) -> None:
172
+ identity_path = tmp_path / "local-identity.json"
173
+ identity_path.write_text("{ not valid json", encoding="utf-8")
174
+ with patch.dict(
175
+ os.environ,
176
+ {"CLAUDE_LOCAL_IDENTITY_PATH": str(identity_path)},
177
+ clear=False,
178
+ ):
179
+ assert local_identity.pii_allowlisted_values_by_repository() == {}
180
+
181
+ def test_returns_empty_mapping_when_the_key_is_absent(
182
+ self, tmp_path: Path
183
+ ) -> None:
184
+ identity_path = tmp_path / "local-identity.json"
185
+ identity_path.write_text(
186
+ json.dumps({"nas": {"host": "10.0.0.5"}}), encoding="utf-8"
187
+ )
188
+ with patch.dict(
189
+ os.environ,
190
+ {"CLAUDE_LOCAL_IDENTITY_PATH": str(identity_path)},
191
+ clear=False,
192
+ ):
193
+ assert local_identity.pii_allowlisted_values_by_repository() == {}
194
+
195
+ def test_drops_blank_slugs_and_non_list_value_entries(
196
+ self, tmp_path: Path
197
+ ) -> None:
198
+ allowed_value = "keep.fixture" + "@" + "acme-corp" + ".example" + ".io"
199
+ identity_path = tmp_path / "local-identity.json"
200
+ identity_path.write_text(
201
+ json.dumps(
202
+ {
203
+ "pii_allowlisted_values": {
204
+ " ": [allowed_value],
205
+ "Owner/Repo": allowed_value,
206
+ "Owner/Kept": [allowed_value, 7],
207
+ }
208
+ }
209
+ ),
210
+ encoding="utf-8",
211
+ )
212
+ with patch.dict(
213
+ os.environ,
214
+ {"CLAUDE_LOCAL_IDENTITY_PATH": str(identity_path)},
215
+ clear=False,
216
+ ):
217
+ values_by_slug = local_identity.pii_allowlisted_values_by_repository()
218
+ assert values_by_slug == {"owner/kept": frozenset({allowed_value})}
219
+
220
+
221
+ class TestLocalIdentityPathOverride:
222
+ def test_prefers_the_env_path_over_the_home_directory_file(
223
+ self, tmp_path: Path
224
+ ) -> None:
225
+ override_path = tmp_path / "override" / "local-identity.json"
226
+ override_path.parent.mkdir()
227
+ override_path.write_text(
228
+ json.dumps({"nas": {"host": "10.9.9.9"}}), encoding="utf-8"
229
+ )
230
+ with patch.dict(
231
+ os.environ,
232
+ {"CLAUDE_LOCAL_IDENTITY_PATH": str(override_path)},
233
+ clear=False,
234
+ ), patch.object(Path, "home", return_value=tmp_path):
235
+ _clear_nas_env()
236
+ assert local_identity.nas_host() == "10.9.9.9"
237
+
238
+
137
239
  class TestDenyMessagesQuoteTheResolvedHost:
138
240
  def should_include_the_resolved_host_and_port_in_the_bare_binary_message(
139
241
  self,
@@ -15,19 +15,18 @@ no_implicit_optional = true
15
15
  strict_equality = true
16
16
  exclude = [
17
17
  "validators/test_files/",
18
- "test_.*\\.py$",
19
- ".*/test_.*\\.py$",
20
- # One-line mypy fixes re-surface whole-file CODE_RULES debt at pre-commit (#51).
21
- "abbreviation_checks\\.py$",
22
- "git_checks\\.py$",
23
- "exempt_paths\\.py$",
24
- "code_rules_typeddict_stub\\.py$",
25
- "validators/exempt_paths\\.py$",
26
- "validators/abbreviation_checks\\.py$",
27
- "validators/git_checks\\.py$",
28
- "blocking/code_rules_typeddict_stub\\.py$",
18
+ "test_",
19
+ "abbreviation_checks.py",
20
+ "git_checks.py",
21
+ "exempt_paths.py",
22
+ "code_rules_typeddict_stub.py",
29
23
  ]
30
24
 
25
+
26
+ [[tool.mypy.overrides]]
27
+ module = ["exempt_paths", "code_rules_path_utils", "gh_pr_author_enforcer"]
28
+ warn_unused_ignores = false
29
+
31
30
  [[tool.mypy.overrides]]
32
31
  module = "tests.*"
33
32
  disallow_untyped_defs = false
@@ -54,9 +53,7 @@ select = [
54
53
  "SIM",
55
54
  "PL",
56
55
  ]
57
- # Gate-restore (#51): principled + pure-style ignores. Non-negotiable defect
58
- # classes stay selected; residual hits live under per-file-ignores where a
59
- # one-line fix is blocked by whole-file CODE_RULES pre-commit debt.
56
+ # Principled + pure-style ignores (#51/#57). Non-negotiable defect classes stay selected.
60
57
  ignore = [
61
58
  "E501",
62
59
  "E402",
@@ -94,29 +91,9 @@ ignore = [
94
91
  ]
95
92
 
96
93
  [tool.ruff.lint.per-file-ignores]
97
- "advisory/refactor_guard.py" = ["F401", "PLW1510"]
98
- "blocking/block_main_commit.py" = ["PLW1510"]
99
- "blocking/code_rules_boolean_mustcheck.py" = ["B905"]
100
- "blocking/code_rules_mock_completeness.py" = ["B007"]
101
- "blocking/code_rules_optional_params.py" = ["B905"]
102
- "blocking/code_rules_shared.py" = ["B905"]
103
- "blocking/code_rules_test_assertions.py" = ["B905"]
104
- "blocking/code_rules_typeddict_stub.py" = ["B905"]
105
- "blocking/pii_prevention_blocker.py" = ["PLW1510", "B018"]
106
- "blocking/pr_description_command_parser.py" = ["B904"]
107
- "blocking/precommit_code_rules_gate.py" = ["PLW1510"]
108
- "diagnostic/hook_log_extractor.py" = ["SIM115"]
109
- "git-hooks/post_commit.py" = ["F401", "PLW1510"]
110
- "hooks_constants/banned_identifiers_constants.py" = ["B033"]
111
- "session/plugin_data_dir_cleanup.py" = ["F401"]
112
- "validation/mypy_validator.py" = ["PLW1510"]
113
- "validators/exempt_paths.py" = ["F401"]
114
- "validators/health_check.py" = ["PLW1510"]
115
- "validators/mypy_integration.py" = ["PLW1510"]
116
- "validators/python_style_checks.py" = ["B905"]
117
- "validators/ruff_integration.py" = ["PLW1510"]
94
+ # Residual production carve-outs re-homed on #57 (gate friction on multi-line touch).
118
95
  "validators/run_all_validators.py" = ["PLW1510"]
119
- "workflow/auto_formatter.py" = ["PLW1510"]
96
+ "validators/exempt_paths.py" = ["F401"]
120
97
  "test_*.py" = ["B011", "PLR2004", "F401", "F841", "PLW1510", "B905", "B033", "SIM115", "B007", "B018", "B904"]
121
98
  "*_test.py" = ["B011", "PLR2004", "F401", "F841", "PLW1510", "B905", "B033", "SIM115", "B007", "B018", "B904"]
122
99
  "conftest.py" = ["B011", "F401", "F841", "PLW1510"]
@@ -13,7 +13,6 @@ separate from the EEXIST fixes in v2.1.70-v2.1.72.
13
13
  """
14
14
 
15
15
  import os
16
- import sys
17
16
 
18
17
  PLUGINS_DATA_DIRECTORY = os.path.join(
19
18
  os.path.expanduser("~"), ".claude", "plugins", "data"
@@ -70,7 +70,7 @@ def discover_project_root(target_file: str) -> Path | None:
70
70
  try:
71
71
  completed_process = subprocess.run(
72
72
  ["git", "rev-parse", "--show-toplevel"],
73
- capture_output=True,
73
+ check=False, capture_output=True,
74
74
  text=True,
75
75
  timeout=GIT_COMMAND_TIMEOUT_SECONDS,
76
76
  cwd=str(Path(target_file).parent),
@@ -359,7 +359,7 @@ def run_mypy(target_file: str, project_root: str) -> tuple[int, str]:
359
359
 
360
360
  completed_process = subprocess.run(
361
361
  mypy_command,
362
- capture_output=True,
362
+ check=False, capture_output=True,
363
363
  text=True,
364
364
  env=os.environ.copy(),
365
365
  timeout=MYPY_TIMEOUT_SECONDS,
@@ -110,6 +110,7 @@ def check_optional_tool(tool_name: str) -> bool:
110
110
  try:
111
111
  result = subprocess.run(
112
112
  [tool_name, "--version"],
113
+ check=False,
113
114
  capture_output=True,
114
115
  text=True,
115
116
  )
@@ -18,6 +18,7 @@ def check_mypy_available() -> bool:
18
18
  try:
19
19
  result = subprocess.run(
20
20
  ["mypy", "--version"],
21
+ check=False,
21
22
  capture_output=True,
22
23
  text=True,
23
24
  )
@@ -85,6 +86,7 @@ def run_mypy_check(files: list[Path]) -> MypyResult:
85
86
  result = subprocess.run(
86
87
  ["mypy", *config_argument, "--ignore-missing-imports", "--no-error-summary"]
87
88
  + py_files,
89
+ check=False,
88
90
  capture_output=True,
89
91
  text=True,
90
92
  )
@@ -17,6 +17,7 @@ def check_ruff_available() -> bool:
17
17
  try:
18
18
  result = subprocess.run(
19
19
  ["ruff", "--version"],
20
+ check=False,
20
21
  capture_output=True,
21
22
  text=True,
22
23
  )
@@ -39,6 +40,7 @@ def run_ruff_check(files: list[Path]) -> RuffResult:
39
40
 
40
41
  result = subprocess.run(
41
42
  ["ruff", "check"] + py_files,
43
+ check=False,
42
44
  capture_output=True,
43
45
  text=True,
44
46
  )
@@ -61,6 +63,7 @@ def run_ruff_fix(files: list[Path]) -> RuffResult:
61
63
 
62
64
  result = subprocess.run(
63
65
  ["ruff", "check", "--fix"] + py_files,
66
+ check=False,
64
67
  capture_output=True,
65
68
  text=True,
66
69
  )
@@ -48,6 +48,7 @@ PLUGIN_ROOT = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__
48
48
  HOOKS_DIR = os.path.join(PLUGIN_ROOT, "hooks") + os.sep
49
49
  PYTHON_FORMAT_TIMEOUT_SECONDS = 12
50
50
  JS_FORMAT_TIMEOUT_SECONDS = 30
51
+ GIT_LS_FILES_TIMEOUT_SECONDS = 5
51
52
  PRETTIER_CONFIG_NAMES = {
52
53
  ".prettierrc",
53
54
  ".prettierrc.json",
@@ -99,10 +100,10 @@ def is_untracked_in_git(file_path: str) -> bool:
99
100
  try:
100
101
  git_check = subprocess.run(
101
102
  ["git", "ls-files", "--error-unmatch", file_path],
102
- capture_output=True,
103
+ check=False, capture_output=True,
103
104
  text=True,
104
105
  cwd=containing_directory,
105
- timeout=5,
106
+ timeout=GIT_LS_FILES_TIMEOUT_SECONDS,
106
107
  )
107
108
  return git_check.returncode != 0
108
109
  except (FileNotFoundError, subprocess.TimeoutExpired):
@@ -150,7 +151,7 @@ def main() -> None:
150
151
  [sys.executable, "-m", "black", file_path],
151
152
  ]:
152
153
  try:
153
- format_run = subprocess.run(each_formatter_command, capture_output=True, text=True, timeout=PYTHON_FORMAT_TIMEOUT_SECONDS)
154
+ format_run = subprocess.run(each_formatter_command, check=False, capture_output=True, text=True, timeout=PYTHON_FORMAT_TIMEOUT_SECONDS)
154
155
  if format_run.returncode == 0:
155
156
  formatter_name = each_formatter_command[0] if each_formatter_command[0] != sys.executable else each_formatter_command[2]
156
157
  send_format_notification(file_path, formatter_name)
@@ -165,7 +166,7 @@ def main() -> None:
165
166
  try:
166
167
  prettier_run = subprocess.run(
167
168
  ["npx", "--yes", "prettier", "--write", file_path],
168
- capture_output=True,
169
+ check=False, capture_output=True,
169
170
  text=True,
170
171
  timeout=JS_FORMAT_TIMEOUT_SECONDS,
171
172
  )
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-dev-env",
3
- "version": "1.95.0",
3
+ "version": "2.0.1",
4
4
  "description": "Claude Code development standards — rules, hooks, agents, commands, and skills",
5
5
  "type": "module",
6
6
  "bin": {
package/scripts/CLAUDE.md CHANGED
@@ -8,9 +8,13 @@ Utility scripts installed into `~/.claude/scripts/` by `bin/install.mjs`. Each s
8
8
  |---|---|
9
9
  | `claude_chain_runner.py` | Runs a `claude` invocation through a config-driven fallback chain (`~/.claude/claude-chain.json`): the leading binary serves the call, and only a usage-limit failure falls over to the next logged-in binary; usable as an imported module (`run_claude`) or a CLI. Copy `claude-chain.example.json` to `~/.claude/claude-chain.json` and list your binaries in fallback order |
10
10
  | `gh_artifact_upload.py` | Uploads a file to a repo's durable `artifacts` prerelease under a timestamped asset name and prints the permanent download URL a GitHub post can link |
11
+ | `grok_headless_runner.py` | Runs one worker as headless `grok`: builds argv, mints a unique leader socket, captures streams, kills on timeout with grace, classifies ok/usage_limit/auth_failure/timeout/error; imported by `spawn_grok_batch.py` |
12
+ | `grok_worker_preflight.py` | Soft gate for the headless grok tier: binary on PATH, `grok models` auth, install manifest + role agents, opt-in cached live ping; non-zero exit is fallthrough, not failure |
11
13
  | `setup_project_paths.py` | One-time bootstrap: discovers git repos via `es.exe` (Everything) and writes `~/.claude/project-paths.json`; never hardcodes scan roots |
14
+ | `spawn_grok_batch.py` | Launches a fleet of headless grok workers from a JSON batch spec: gates once through the preflight, assembles each prompt from part files, staggers starts, runs each through `grok_headless_runner.py`, and emits one batch summary JSON |
12
15
  | `sweep_empty_dirs.py` | Deletes empty directories older than a configurable age under a given root; runs once (`--once`) or in continuous-watch mode |
13
16
  | `sync_to_cursor.py` | Entry point for syncing Claude rules to Cursor `.mdc` files; delegates to the `sync_to_cursor/` package |
17
+ | `resolve_worker_spawn.py` | Dispatches a worker role through grok then claude fallback tiers (preflight, headless grok, `claude_agent_required` handoff, optional claude headless); protocol: [`../_shared/pr-loop/worker-spawn.md`](../_shared/pr-loop/worker-spawn.md) |
14
18
 
15
19
  ## PowerShell scripts
16
20
 
@@ -6,11 +6,13 @@ Named constants for scripts in `scripts/`. Follows the project convention that t
6
6
 
7
7
  | File | Constants for |
8
8
  |---|---|
9
- | `timing.py` | `sweep_empty_dirs.py` `DEFAULT_AGE_SECONDS` (smallest age before an empty directory is eligible for removal) and `DEFAULT_POLL_INTERVAL` (seconds between sweep passes in continuous-watch mode) |
10
- | `gh_artifact_upload_constants.py` | `gh_artifact_upload.py` the `artifacts` release tag, title, and notes body, the GitHub CLI binary name, the asset-name timestamp format and template, the asset download URL template, the notes-file suffix, and the text encoding |
11
- | `claude_chain_constants.py` | `claude_chain_runner.py` the chain config filename and home subdirectory, the usage-limit signature text, the per-binary status labels, the default timeout, CLI flag and separator tokens, config JSON keys, invalid-shape reason text, config-error and exhausted-chain message templates, and CLI exit codes |
9
+ | `timing.py` | `sweep_empty_dirs.py` - `DEFAULT_AGE_SECONDS` (smallest age before an empty directory is eligible for removal) and `DEFAULT_POLL_INTERVAL` (seconds between sweep passes in continuous-watch mode); `spawn_grok_batch.py` - `WORKER_STAGGER_SECONDS` (seconds between staggered headless grok worker starts); `invoke_code_review.py` - `DEFAULT_CODE_REVIEW_TIMEOUT_SECONDS` (timeout for one headless `/code-review` chain run) |
10
+ | `gh_artifact_upload_constants.py` | `gh_artifact_upload.py` - the `artifacts` release tag, title, and notes body, the GitHub CLI binary name, the asset-name timestamp format and template, the asset download URL template, the notes-file suffix, and the text encoding |
11
+ | `claude_chain_constants.py` | `claude_chain_runner.py` - the chain config filename and home subdirectory, the usage-limit signature text, the per-binary status labels, the default timeout, CLI flag and separator tokens, config JSON keys, invalid-shape reason text, config-error and exhausted-chain message templates, and CLI exit codes |
12
+ | `grok_worker_constants.py` | `grok_worker_preflight.py`, `grok_headless_runner.py`, and `spawn_grok_batch.py` - the `grok` binary name and CLI flags, model and subcommand tokens, leader-socket and scratch-file name parts, auth and usage-limit signature lists, outcome classifications, fallthrough reasons, tool-profile names and prompt headers, timeouts and turn caps (including launch-failure return code and post-kill grace), ping-cache keys and TTL, batch-spec and summary JSON keys, the prompt-part and report-stream join separators, and the CLI launch-error stderr prefix |
13
+ | `code_review_constants.py` | `invoke_code_review.py` - the `/code-review high --fix` prompt, opus model alias, permission-mode flag and value, result mode and JSON keys, session-model CLI flag, git dirty-check tokens, and in-session return markers |
12
14
  | `__init__.py` | Empty package marker |
13
15
 
14
16
  ## Convention
15
17
 
16
- Every constant is `UPPER_SNAKE_CASE` with an explicit type annotation and a docstring. Scripts import from here rather than embedding literal values in their bodies.
18
+ Every constant is `UPPER_SNAKE_CASE` with an explicit type annotation and a docstring. Scripts import from here rather than embedding literal values in their bodies.
@@ -0,0 +1,71 @@
1
+ """Named constants for the host-aware `/code-review` invoker.
2
+
3
+ `invoke_code_review.py` imports every scalar and structural constant it
4
+ needs from this module (and shared flag tokens from
5
+ `grok_worker_constants`) so the prompt, model alias, and JSON keys are
6
+ never hardcoded twice.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ CODE_REVIEW_SLASH_COMMAND: str = "/code-review"
12
+ """Built-in Claude Code slash command that runs the repository review."""
13
+
14
+ CODE_REVIEW_EFFORT: str = "high"
15
+ """Effort level passed to the built-in review slash command."""
16
+
17
+ CODE_REVIEW_FIX_FLAG: str = "--fix"
18
+ """Slash-command flag that applies automatic fixes for review findings."""
19
+
20
+ CODE_REVIEW_PROMPT: str = (
21
+ f"{CODE_REVIEW_SLASH_COMMAND} {CODE_REVIEW_EFFORT} {CODE_REVIEW_FIX_FLAG}"
22
+ )
23
+ """Single-turn prompt that runs the built-in review slash command with fixes."""
24
+
25
+ CODE_REVIEW_MODEL_ALIAS: str = "opus"
26
+ """CLI `--model` short alias the review always pins to."""
27
+
28
+ PERMISSION_MODE_FLAG: str = "--permission-mode"
29
+ """CLI flag that selects how the headless claude process handles tool permission prompts."""
30
+
31
+ PERMISSION_MODE_BYPASS: str = "bypassPermissions"
32
+ """Permission-mode value that auto-approves tools for unattended chain runs."""
33
+
34
+ MODE_IN_SESSION: str = "in_session"
35
+ """Result mode when the host is Claude and the session already runs opus."""
36
+
37
+ MODE_CHAIN: str = "chain"
38
+ """Result mode when the helper spawns a headless claude chain for the review."""
39
+
40
+ RESULT_KEY_MODE: str = "mode"
41
+ """JSON result key naming the review mode (`in_session` or `chain`)."""
42
+
43
+ RESULT_KEY_SERVED_COMMAND: str = "served_command"
44
+ """JSON result key naming the chain binary that served the call, or null."""
45
+
46
+ RESULT_KEY_RETURNCODE: str = "returncode"
47
+ """JSON result key holding the process return code from the chain run."""
48
+
49
+ RESULT_KEY_DIRTY_TREE: str = "dirty_tree"
50
+ """JSON result key holding whether the working tree is dirty after the review."""
51
+
52
+ CLI_SESSION_MODEL_FLAG: str = "--session-model"
53
+ """CLI flag naming the caller's current session model short alias."""
54
+
55
+ GIT_BINARY: str = "git"
56
+ """Executable name resolved on PATH for working-tree dirty checks."""
57
+
58
+ GIT_STATUS_SUBCOMMAND: str = "status"
59
+ """Git subcommand used to detect an uncommitted dirty working tree."""
60
+
61
+ GIT_PORCELAIN_FLAG: str = "--porcelain"
62
+ """Git status flag that prints machine-readable dirty-path lines."""
63
+
64
+ IN_SESSION_RETURNCODE: int = 0
65
+ """Return code reported when the helper hands the review back to the in-session skill."""
66
+
67
+ HOST_PROFILE_ERROR_RETURNCODE: int = 1
68
+ """Return code when host-profile detection raises ValueError at the CLI boundary."""
69
+
70
+ SUCCESSFUL_REVIEW_RETURNCODE: int = 0
71
+ """Return code required before a clean stamp may advance past CODE_REVIEW."""