claude-dev-env 1.92.1 → 1.93.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 (215) hide show
  1. package/CLAUDE.md +6 -2
  2. package/_shared/CLAUDE.md +2 -1
  3. package/_shared/advisor/CLAUDE.md +21 -0
  4. package/_shared/advisor/advisor-protocol.md +142 -0
  5. package/_shared/advisor/scripts/config/advisor_scripts_constants/__init__.py +1 -0
  6. package/_shared/advisor/scripts/config/advisor_scripts_constants/model_tier_run_validator_constants.py +86 -0
  7. package/_shared/advisor/scripts/model_tier_run_validator.py +246 -0
  8. package/_shared/advisor/scripts/pyproject.toml +3 -0
  9. package/_shared/advisor/scripts/tests/test_model_tier_run_validator.py +302 -0
  10. package/_shared/advisor/scripts/tests/test_tier_model_ids.py +180 -0
  11. package/_shared/advisor/scripts/tier_model_ids.py +164 -0
  12. package/_shared/pr-loop/CLAUDE.md +1 -0
  13. package/_shared/pr-loop/precatch-rubric.md +65 -0
  14. package/_shared/pr-loop/scripts/CLAUDE.md +1 -1
  15. package/_shared/pr-loop/scripts/code_rules_gate.py +260 -14
  16. package/_shared/pr-loop/scripts/post_audit_thread.py +1 -1
  17. package/_shared/pr-loop/scripts/pr_loop_shared_constants/CLAUDE.md +1 -1
  18. package/_shared/pr-loop/scripts/pr_loop_shared_constants/code_rules_gate_constants.py +20 -0
  19. package/_shared/pr-loop/scripts/pr_loop_shared_constants/reviews_disabled_constants.py +2 -1
  20. package/_shared/pr-loop/scripts/reviewer_availability.py +42 -12
  21. package/_shared/pr-loop/scripts/reviews_disabled.py +65 -21
  22. package/_shared/pr-loop/scripts/terminology_sweep.py +69 -21
  23. package/_shared/pr-loop/scripts/tests/CLAUDE.md +1 -1
  24. package/_shared/pr-loop/scripts/tests/fixtures/{copilot_internal_user_jonecho.json → copilot_internal_user_example.json} +1 -1
  25. package/_shared/pr-loop/scripts/tests/test_agent_config_carveout.py +1 -1
  26. package/_shared/pr-loop/scripts/tests/test_code_rules_gate.py +292 -2
  27. package/_shared/pr-loop/scripts/tests/test_copilot_quota.py +8 -8
  28. package/_shared/pr-loop/scripts/tests/test_reviewer_availability.py +18 -5
  29. package/_shared/pr-loop/scripts/tests/test_reviews_disabled.py +56 -5
  30. package/_shared/pr-loop/scripts/tests/test_terminology_sweep.py +104 -17
  31. package/agents/CLAUDE.md +3 -2
  32. package/agents/clasp-deployment-orchestrator.md +2 -2
  33. package/agents/code-advisor.md +7 -5
  34. package/agents/code-verifier.md +6 -2
  35. package/agents/session-advisor.md +27 -0
  36. package/audit-rubrics/category_rubrics/category-j-code-rules-compliance.md +1 -1
  37. package/audit-rubrics/category_rubrics/category-k-codebase-conflicts.md +1 -1
  38. package/audit-rubrics/prompts/category-a-api-contracts.md +2 -2
  39. package/audit-rubrics/prompts/category-b-selector-engine-compat.md +2 -2
  40. package/audit-rubrics/prompts/category-c-resource-cleanup.md +2 -2
  41. package/audit-rubrics/prompts/category-d-scoping-and-ordering.md +2 -2
  42. package/audit-rubrics/prompts/category-e-dead-code.md +2 -2
  43. package/audit-rubrics/prompts/category-f-silent-failures.md +2 -2
  44. package/audit-rubrics/prompts/category-g-bounds-and-overflow.md +2 -2
  45. package/audit-rubrics/prompts/category-h-security-boundaries.md +2 -2
  46. package/audit-rubrics/prompts/category-i-concurrency.md +2 -2
  47. package/audit-rubrics/prompts/category-j-code-rules-compliance.md +2 -2
  48. package/audit-rubrics/prompts/category-k-codebase-conflicts.md +2 -2
  49. package/audit-rubrics/prompts/category-l-behavior-equivalence.md +2 -2
  50. package/audit-rubrics/prompts/category-m-producer-consumer-cardinality.md +2 -2
  51. package/audit-rubrics/prompts/category-n-test-name-scenario-verifier.md +2 -2
  52. package/audit-rubrics/prompts/category-o-docstring-vs-impl-drift.md +2 -2
  53. package/audit-rubrics/prompts/category-p-name-vs-behavior-contract.md +2 -2
  54. package/bin/CLAUDE.md +1 -0
  55. package/bin/expand_home_directory_tokens.mjs +75 -0
  56. package/bin/install.mjs +21 -7
  57. package/bin/install.test.mjs +220 -10
  58. package/hooks/CLAUDE.md +1 -1
  59. package/hooks/blocking/CLAUDE.md +4 -1
  60. package/hooks/blocking/_path_setup.py +13 -0
  61. package/hooks/blocking/bash_pre_tool_use_dispatcher.py +281 -0
  62. package/hooks/blocking/code_rules_dead_module_constant.py +88 -16
  63. package/hooks/blocking/code_rules_enforcer.py +6 -0
  64. package/hooks/blocking/code_rules_imports_logging.py +34 -1
  65. package/hooks/blocking/code_rules_paths_syspath.py +1 -1
  66. package/hooks/blocking/code_rules_test_layout.py +272 -0
  67. package/hooks/blocking/nas_ssh_binary_enforcer.py +22 -12
  68. package/hooks/blocking/pii_prevention_blocker.py +781 -0
  69. package/hooks/blocking/pii_scanner.py +365 -0
  70. package/hooks/blocking/pre_tool_use_dispatcher.py +3 -3
  71. package/hooks/blocking/stop_dispatcher.py +84 -0
  72. package/hooks/blocking/test__path_setup.py +39 -0
  73. package/hooks/blocking/test_bash_dispatcher_interpreter_starts.py +61 -0
  74. package/hooks/blocking/test_bash_pre_tool_use_dispatcher.py +294 -0
  75. package/hooks/blocking/test_code_rules_enforcer_cross_skill_duplicate.py +1 -1
  76. package/hooks/blocking/test_code_rules_enforcer_dead_config_field.py +2 -2
  77. package/hooks/blocking/test_code_rules_enforcer_dead_module_constant.py +51 -0
  78. package/hooks/blocking/test_code_rules_enforcer_dead_module_constant_read_cap.py +103 -0
  79. package/hooks/blocking/test_code_rules_enforcer_dot_test_pattern.py +1 -1
  80. package/hooks/blocking/test_code_rules_enforcer_file_global_constants.py +1 -1
  81. package/hooks/blocking/test_code_rules_enforcer_hardcoded_user_path.py +11 -11
  82. package/hooks/blocking/test_code_rules_enforcer_naive_datetime.py +1 -1
  83. package/hooks/blocking/test_code_rules_enforcer_same_file_inline_duplicate.py +1 -1
  84. package/hooks/blocking/test_code_rules_enforcer_test_layout.py +111 -0
  85. package/hooks/blocking/test_code_rules_enforcer_type_checking_scope.py +76 -0
  86. package/hooks/blocking/test_destructive_command_blocker.py +29 -9
  87. package/hooks/blocking/test_gh_body_arg_blocker.py +3 -3
  88. package/hooks/blocking/test_nas_ssh_binary_enforcer.py +96 -39
  89. package/hooks/blocking/test_pii_prevention_blocker.py +905 -0
  90. package/hooks/blocking/test_pii_prevention_windows_git_path.py +41 -0
  91. package/hooks/blocking/test_pii_scanner.py +190 -0
  92. package/hooks/blocking/test_pr_description_enforcer_pr_number.py +1 -1
  93. package/hooks/blocking/test_pre_tool_use_dispatcher.py +14 -13
  94. package/hooks/blocking/test_stop_dispatcher.py +187 -0
  95. package/hooks/blocking/test_verdict_directory_write_blocker.py +1 -1
  96. package/hooks/blocking/test_volatile_path_in_post_blocker.py +3 -3
  97. package/hooks/blocking/volatile_path_in_post_blocker.py +54 -5
  98. package/hooks/diagnostic/migrations/README.md +25 -26
  99. package/hooks/diagnostic/test_hook_log_extractor.py +10 -10
  100. package/hooks/hooks.json +10 -120
  101. package/hooks/hooks_constants/CLAUDE.md +12 -3
  102. package/hooks/hooks_constants/bash_pre_tool_use_dispatcher_constants.py +78 -0
  103. package/hooks/hooks_constants/dead_module_constant_constants.py +1 -0
  104. package/hooks/hooks_constants/hosted_hook_runner.py +73 -0
  105. package/hooks/hooks_constants/local_identity.py +182 -0
  106. package/hooks/hooks_constants/nas_ssh_binary_enforcer_constants.py +4 -37
  107. package/hooks/hooks_constants/pii_prevention_constants.py +295 -0
  108. package/hooks/hooks_constants/pre_tool_use_dispatcher_constants.py +4 -0
  109. package/hooks/hooks_constants/python_style_checks_constants.py +15 -0
  110. package/hooks/hooks_constants/stop_dispatcher_constants.py +26 -0
  111. package/hooks/hooks_constants/test_bash_pre_tool_use_dispatcher_constants.py +87 -0
  112. package/hooks/hooks_constants/test_hardcoded_user_path_constants.py +2 -2
  113. package/hooks/hooks_constants/test_hosted_hook_runner.py +101 -0
  114. package/hooks/hooks_constants/test_layout_constants.py +28 -0
  115. package/hooks/hooks_constants/test_local_identity.py +88 -0
  116. package/hooks/hooks_constants/test_session_env_cleanup_constants.py +1 -1
  117. package/hooks/hooks_constants/test_stop_dispatcher_constants.py +32 -0
  118. package/hooks/validators/README.md +5 -6
  119. package/hooks/validators/__init__.py +2 -2
  120. package/hooks/validators/python_style_checks.py +298 -243
  121. package/hooks/validators/run_all_validators.py +6 -0
  122. package/hooks/validators/test_python_style_checks.py +278 -163
  123. package/package.json +2 -2
  124. package/rules/CLAUDE.md +2 -1
  125. package/rules/bdd.md +1 -1
  126. package/rules/nas-ssh-invocation.md +6 -4
  127. package/rules/no-justification-noise.md +61 -0
  128. package/scripts/test_setup_project_paths.py +1 -1
  129. package/skills/CLAUDE.md +6 -3
  130. package/skills/_shared/pr-loop/scripts/_path_resolver.py +2 -2
  131. package/skills/_shared/pr-loop/scripts/build_audit_prompt.py +1 -1
  132. package/skills/_shared/pr-loop/scripts/build_fix_prompt.py +1 -1
  133. package/skills/_shared/pr-loop/scripts/test__path_resolver.py +42 -3
  134. package/skills/_shared/pr-loop/scripts/test_build_audit_prompt.py +11 -11
  135. package/skills/_shared/pr-loop/scripts/test_build_fix_prompt.py +26 -3
  136. package/skills/_shared/pr-loop/scripts/test_preflight_worktree.py +49 -25
  137. package/skills/autoconverge/CLAUDE.md +3 -3
  138. package/skills/autoconverge/SKILL.md +113 -35
  139. package/skills/autoconverge/reference/CLAUDE.md +2 -2
  140. package/skills/autoconverge/reference/convergence.md +44 -22
  141. package/skills/autoconverge/reference/gotchas.md +11 -0
  142. package/skills/autoconverge/reference/stop-conditions.md +23 -13
  143. package/skills/autoconverge/workflow/CLAUDE.md +2 -1
  144. package/skills/autoconverge/workflow/converge.clean-audit.test.mjs +15 -19
  145. package/skills/autoconverge/workflow/converge.contract.test.mjs +43 -42
  146. package/skills/autoconverge/workflow/converge.copilot-gate.test.mjs +141 -16
  147. package/skills/autoconverge/workflow/converge.fix-recovery.test.mjs +2 -2
  148. package/skills/autoconverge/workflow/converge.mjs +329 -68
  149. package/skills/autoconverge/workflow/converge.path-aware.test.mjs +1 -1
  150. package/skills/autoconverge/workflow/converge.precatch.test.mjs +152 -0
  151. package/skills/autoconverge/workflow/converge.run-input.test.mjs +3 -3
  152. package/skills/autoconverge/workflow/converge_multi.run-input.test.mjs +15 -15
  153. package/skills/autoconverge/workflow/test_convergence_summary.py +6 -6
  154. package/skills/bdd-protocol/SKILL.md +4 -5
  155. package/skills/bdd-protocol/references/anti-patterns.md +1 -1
  156. package/skills/bugteam/SKILL.md +8 -0
  157. package/skills/bugteam/reference/copilot-gap-analysis.md +20 -20
  158. package/skills/bugteam/reference/obstacles/audit-walk-categories.md +1 -1
  159. package/skills/bugteam/reference/team-setup.md +7 -5
  160. package/skills/bugteam/scripts/reflow_skill_md.py +1 -1
  161. package/skills/copilot-finding-triage/SKILL.md +124 -0
  162. package/skills/copilot-finding-triage/reference/tier-rubric.md +49 -0
  163. package/skills/copilot-finding-triage/scripts/conftest.py +8 -0
  164. package/skills/copilot-finding-triage/scripts/copilot_finding_triage_constants/__init__.py +0 -0
  165. package/skills/copilot-finding-triage/scripts/copilot_finding_triage_constants/config/__init__.py +0 -0
  166. package/skills/copilot-finding-triage/scripts/copilot_finding_triage_constants/config/notify_ntfy_constants.py +32 -0
  167. package/skills/copilot-finding-triage/scripts/notify_ntfy.py +192 -0
  168. package/skills/copilot-finding-triage/scripts/test_notify_ntfy.py +141 -0
  169. package/skills/copilot-finding-triage/templates/notification.md +34 -0
  170. package/skills/copilot-review/SKILL.md +8 -6
  171. package/skills/findbugs/SKILL.md +4 -0
  172. package/skills/fixbugs/SKILL.md +8 -7
  173. package/skills/gotcha/CLAUDE.md +2 -2
  174. package/skills/gotcha/SKILL.md +4 -4
  175. package/skills/log-audit/SKILL.md +4 -6
  176. package/skills/monitor-open-prs/CLAUDE.md +1 -1
  177. package/skills/monitor-open-prs/SKILL.md +6 -1
  178. package/skills/orchestrator/SKILL.md +74 -78
  179. package/skills/orchestrator-refresh/SKILL.md +24 -12
  180. package/skills/post-audit-findings/SKILL.md +5 -9
  181. package/skills/pr-consistency-audit/SKILL.md +5 -1
  182. package/skills/pr-converge/CLAUDE.md +1 -1
  183. package/skills/pr-converge/SKILL.md +86 -47
  184. package/skills/pr-converge/reference/CLAUDE.md +1 -1
  185. package/skills/pr-converge/reference/convergence-gates.md +20 -17
  186. package/skills/pr-converge/reference/examples.md +63 -47
  187. package/skills/pr-converge/reference/fix-protocol.md +15 -15
  188. package/skills/pr-converge/reference/ground-rules.md +11 -7
  189. package/skills/pr-converge/reference/multi-pr-orchestration.md +6 -5
  190. package/skills/pr-converge/reference/per-tick.md +129 -107
  191. package/skills/pr-converge/reference/state-schema.md +15 -10
  192. package/skills/pr-converge/scripts/check_convergence.py +5 -4
  193. package/skills/pr-converge/scripts/test_check_convergence.py +6 -4
  194. package/skills/pr-fix-protocol/SKILL.md +3 -8
  195. package/skills/pr-loop-cloud-transport/SKILL.md +102 -0
  196. package/skills/pr-loop-cloud-transport/reference/identity-and-hooks.md +40 -0
  197. package/skills/pr-loop-cloud-transport/reference/substitution-matrix.md +48 -0
  198. package/skills/pr-loop-lifecycle/SKILL.md +5 -9
  199. package/skills/pr-scope-resolve/SKILL.md +3 -6
  200. package/skills/privacy-hygiene/SKILL.md +114 -0
  201. package/skills/qbug/SKILL.md +8 -8
  202. package/skills/rebase/SKILL.md +5 -1
  203. package/skills/refine/SKILL.md +4 -5
  204. package/skills/reviewer-gates/SKILL.md +7 -11
  205. package/skills/session-log/SKILL.md +4 -1
  206. package/skills/skill-builder/SKILL.md +3 -6
  207. package/skills/structure-prompt/SKILL.md +4 -5
  208. package/skills/team-advisor/SKILL.md +56 -0
  209. package/skills/test_markdown_link_integrity.py +10 -6
  210. package/skills/update/SKILL.md +5 -1
  211. package/skills/usage-pause/SKILL.md +14 -5
  212. package/skills/usage-pause/scripts/resolve_usage_window.py +83 -5
  213. package/skills/usage-pause/scripts/test_resolve_usage_window.py +185 -17
  214. package/skills/usage-pause/scripts/usage_pause_constants/resolve_usage_window_constants.py +4 -3
  215. package/skills/verified-build/SKILL.md +4 -9
@@ -2,7 +2,7 @@
2
2
 
3
3
  Every case drives the production ``main`` / ``evaluate_copilot_quota`` path with
4
4
  only the ``gh`` subprocess boundary (``_run_gh``) stubbed, fed a captured
5
- ``copilot_internal/user`` JSON fixture in the jonecho shape.
5
+ ``copilot_internal/user`` JSON fixture in the example-account shape.
6
6
  """
7
7
 
8
8
  import importlib.util
@@ -30,7 +30,7 @@ def _load_copilot_quota_module() -> ModuleType:
30
30
 
31
31
  copilot_quota = _load_copilot_quota_module()
32
32
 
33
- FIXTURE_PATH = Path(__file__).parent / "fixtures" / "copilot_internal_user_jonecho.json"
33
+ FIXTURE_PATH = Path(__file__).parent / "fixtures" / "copilot_internal_user_example.json"
34
34
  AVAILABLE_USER_JSON = FIXTURE_PATH.read_text(encoding="utf-8")
35
35
  FAKE_TOKEN_RESULT = (0, "ghp_faketoken_value\n")
36
36
 
@@ -75,7 +75,7 @@ def _isolate_account_sources(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) ->
75
75
  def test_main_runs_copilot_when_premium_quota_available(
76
76
  monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]
77
77
  ) -> None:
78
- monkeypatch.setenv("COPILOT_QUOTA_ACCOUNT", "jonecho")
78
+ monkeypatch.setenv("COPILOT_QUOTA_ACCOUNT", "example-account")
79
79
  monkeypatch.setattr(
80
80
  copilot_quota,
81
81
  "_run_gh",
@@ -91,7 +91,7 @@ def test_main_runs_copilot_when_premium_quota_available(
91
91
  def test_main_exits_out_of_quota_when_premium_exhausted(
92
92
  monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]
93
93
  ) -> None:
94
- monkeypatch.setenv("COPILOT_QUOTA_ACCOUNT", "jonecho")
94
+ monkeypatch.setenv("COPILOT_QUOTA_ACCOUNT", "example-account")
95
95
  monkeypatch.setattr(
96
96
  copilot_quota,
97
97
  "_run_gh",
@@ -106,7 +106,7 @@ def test_main_exits_out_of_quota_when_premium_exhausted(
106
106
  def test_main_exits_api_down_on_non_json_response(
107
107
  monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]
108
108
  ) -> None:
109
- monkeypatch.setenv("COPILOT_QUOTA_ACCOUNT", "jonecho")
109
+ monkeypatch.setenv("COPILOT_QUOTA_ACCOUNT", "example-account")
110
110
  monkeypatch.setattr(
111
111
  copilot_quota,
112
112
  "_run_gh",
@@ -121,7 +121,7 @@ def test_main_exits_api_down_on_non_json_response(
121
121
  def test_main_exits_api_down_on_missing_premium_snapshot(
122
122
  monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]
123
123
  ) -> None:
124
- monkeypatch.setenv("COPILOT_QUOTA_ACCOUNT", "jonecho")
124
+ monkeypatch.setenv("COPILOT_QUOTA_ACCOUNT", "example-account")
125
125
  monkeypatch.setattr(
126
126
  copilot_quota,
127
127
  "_run_gh",
@@ -136,7 +136,7 @@ def test_main_exits_api_down_on_missing_premium_snapshot(
136
136
  def test_main_exits_api_down_when_gh_token_unresolved(
137
137
  monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]
138
138
  ) -> None:
139
- monkeypatch.setenv("COPILOT_QUOTA_ACCOUNT", "jonecho")
139
+ monkeypatch.setenv("COPILOT_QUOTA_ACCOUNT", "example-account")
140
140
  monkeypatch.setattr(
141
141
  copilot_quota,
142
142
  "_run_gh",
@@ -180,7 +180,7 @@ def test_every_gh_backed_skip_writes_a_log_line_naming_the_scenario(
180
180
  expected_exit_code: int,
181
181
  expected_scenario: str,
182
182
  ) -> None:
183
- monkeypatch.setenv("COPILOT_QUOTA_ACCOUNT", "jonecho")
183
+ monkeypatch.setenv("COPILOT_QUOTA_ACCOUNT", "example-account")
184
184
  monkeypatch.setattr(
185
185
  copilot_quota, "_run_gh", _gh_stub(FAKE_TOKEN_RESULT, api_result)
186
186
  )
@@ -33,7 +33,7 @@ def _load_module(module_name: str) -> ModuleType:
33
33
  copilot_quota = _load_module("copilot_quota")
34
34
  reviewer_availability = _load_module("reviewer_availability")
35
35
 
36
- FIXTURE_PATH = Path(__file__).parent / "fixtures" / "copilot_internal_user_jonecho.json"
36
+ FIXTURE_PATH = Path(__file__).parent / "fixtures" / "copilot_internal_user_example.json"
37
37
  AVAILABLE_USER_JSON = FIXTURE_PATH.read_text(encoding="utf-8")
38
38
  FAKE_TOKEN_RESULT = (0, "ghp_faketoken_value\n")
39
39
 
@@ -67,6 +67,7 @@ def _isolate_reviewer_environment(
67
67
  ) -> None:
68
68
  monkeypatch.delenv("COPILOT_QUOTA_ACCOUNT", raising=False)
69
69
  monkeypatch.delenv("CLAUDE_REVIEWS_DISABLED", raising=False)
70
+ monkeypatch.delenv("CLAUDE_REVIEWS_ENABLED", raising=False)
70
71
  monkeypatch.setattr(
71
72
  reviewer_availability, "COPILOT_QUOTA_DEFAULT_ENV_FILE_PATH", tmp_path / ".env"
72
73
  )
@@ -75,7 +76,7 @@ def _isolate_reviewer_environment(
75
76
  def test_main_reports_copilot_available_when_quota_confirmed_available(
76
77
  monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]
77
78
  ) -> None:
78
- monkeypatch.setenv("COPILOT_QUOTA_ACCOUNT", "jonecho")
79
+ monkeypatch.setenv("COPILOT_QUOTA_ACCOUNT", "example-account")
79
80
  monkeypatch.setattr(
80
81
  copilot_quota, "_run_gh", _gh_stub(FAKE_TOKEN_RESULT, (0, AVAILABLE_USER_JSON))
81
82
  )
@@ -106,7 +107,7 @@ def test_main_reports_copilot_down_when_opted_out_via_env(
106
107
  def test_main_reports_copilot_down_when_out_of_quota(
107
108
  monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]
108
109
  ) -> None:
109
- monkeypatch.setenv("COPILOT_QUOTA_ACCOUNT", "jonecho")
110
+ monkeypatch.setenv("COPILOT_QUOTA_ACCOUNT", "example-account")
110
111
  monkeypatch.setattr(
111
112
  copilot_quota,
112
113
  "_run_gh",
@@ -121,7 +122,7 @@ def test_main_reports_copilot_down_when_out_of_quota(
121
122
  def test_main_reports_copilot_down_when_quota_api_is_down(
122
123
  monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]
123
124
  ) -> None:
124
- monkeypatch.setenv("COPILOT_QUOTA_ACCOUNT", "jonecho")
125
+ monkeypatch.setenv("COPILOT_QUOTA_ACCOUNT", "example-account")
125
126
  monkeypatch.setattr(
126
127
  copilot_quota, "_run_gh", _gh_stub(FAKE_TOKEN_RESULT, (0, "not json at all"))
127
128
  )
@@ -140,9 +141,20 @@ def test_main_reports_copilot_down_when_no_account_configured(
140
141
  assert "scenario C" in captured.err
141
142
 
142
143
 
143
- def test_main_reports_bugbot_available_when_not_opted_out(
144
+ def test_main_reports_bugbot_down_when_no_env_vars_set(
144
145
  capsys: pytest.CaptureFixture[str],
145
146
  ) -> None:
147
+ exit_code = reviewer_availability.main(["--reviewer", "bugbot"])
148
+ captured = capsys.readouterr()
149
+ assert exit_code == reviewer_availability.EXIT_CODE_REVIEWER_DOWN
150
+ assert "CLAUDE_REVIEWS_ENABLED" in captured.err
151
+ assert "CLAUDE_REVIEWS_DISABLED" not in captured.err
152
+
153
+
154
+ def test_main_reports_bugbot_available_when_enabled_via_env(
155
+ monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]
156
+ ) -> None:
157
+ monkeypatch.setenv("CLAUDE_REVIEWS_ENABLED", "bugbot")
146
158
  exit_code = reviewer_availability.main(["--reviewer", "bugbot"])
147
159
  captured = capsys.readouterr()
148
160
  assert exit_code == reviewer_availability.EXIT_CODE_REVIEWER_AVAILABLE
@@ -153,6 +165,7 @@ def test_main_reports_bugbot_down_when_opted_out_via_env(
153
165
  monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]
154
166
  ) -> None:
155
167
  monkeypatch.setenv("CLAUDE_REVIEWS_DISABLED", "bugbot")
168
+ monkeypatch.setenv("CLAUDE_REVIEWS_ENABLED", "bugbot")
156
169
  exit_code = reviewer_availability.main(["--reviewer", "bugbot"])
157
170
  captured = capsys.readouterr()
158
171
  assert exit_code == reviewer_availability.EXIT_CODE_REVIEWER_DOWN
@@ -43,20 +43,46 @@ def test_is_bugbot_disabled_via_env_returns_true_when_env_lists_bugbot(
43
43
  assert reviews_disabled.is_bugbot_disabled_via_env() is True
44
44
 
45
45
 
46
- def test_is_bugbot_disabled_via_env_returns_false_when_env_is_empty(
46
+ def test_is_bugbot_disabled_via_env_returns_true_when_no_env_vars_set(
47
47
  monkeypatch: pytest.MonkeyPatch,
48
48
  ) -> None:
49
49
  monkeypatch.delenv("CLAUDE_REVIEWS_DISABLED", raising=False)
50
+ monkeypatch.delenv("CLAUDE_REVIEWS_ENABLED", raising=False)
51
+ assert reviews_disabled.is_bugbot_disabled_via_env() is True
52
+
53
+
54
+ def test_is_bugbot_disabled_via_env_returns_false_when_enabled_lists_bugbot(
55
+ monkeypatch: pytest.MonkeyPatch,
56
+ ) -> None:
57
+ monkeypatch.delenv("CLAUDE_REVIEWS_DISABLED", raising=False)
58
+ monkeypatch.setenv("CLAUDE_REVIEWS_ENABLED", "bugbot")
50
59
  assert reviews_disabled.is_bugbot_disabled_via_env() is False
51
60
 
52
61
 
53
- def test_is_bugbot_disabled_via_env_returns_false_when_only_bugteam_listed(
62
+ def test_is_bugbot_disabled_via_env_enabled_parse_is_case_and_space_tolerant(
54
63
  monkeypatch: pytest.MonkeyPatch,
55
64
  ) -> None:
56
- monkeypatch.setenv("CLAUDE_REVIEWS_DISABLED", "bugteam")
65
+ monkeypatch.delenv("CLAUDE_REVIEWS_DISABLED", raising=False)
66
+ monkeypatch.setenv("CLAUDE_REVIEWS_ENABLED", " BugBot ")
57
67
  assert reviews_disabled.is_bugbot_disabled_via_env() is False
58
68
 
59
69
 
70
+ def test_is_bugbot_disabled_via_env_true_when_disabled_overrides_enabled(
71
+ monkeypatch: pytest.MonkeyPatch,
72
+ ) -> None:
73
+ monkeypatch.setenv("CLAUDE_REVIEWS_DISABLED", "bugbot")
74
+ monkeypatch.setenv("CLAUDE_REVIEWS_ENABLED", "bugbot")
75
+ assert reviews_disabled.is_bugbot_disabled_via_env() is True
76
+
77
+
78
+ def test_is_bugbot_disabled_via_env_true_when_bugteam_listed_and_not_enabled(
79
+ monkeypatch: pytest.MonkeyPatch,
80
+ ) -> None:
81
+ monkeypatch.setenv("CLAUDE_REVIEWS_DISABLED", "bugteam")
82
+ monkeypatch.delenv("CLAUDE_REVIEWS_ENABLED", raising=False)
83
+ assert reviews_disabled.is_bugbot_disabled_via_env() is True
84
+
85
+
60
86
  def test_is_bugbot_disabled_via_env_true_when_both_tokens_listed_mixed_case(
61
87
  monkeypatch: pytest.MonkeyPatch,
62
88
  ) -> None:
@@ -65,6 +91,29 @@ def test_is_bugbot_disabled_via_env_true_when_both_tokens_listed_mixed_case(
65
91
  assert reviews_disabled.is_bugteam_disabled_via_env() is True
66
92
 
67
93
 
94
+ def test_is_bugbot_opted_out_via_env_returns_true_when_disabled_lists_bugbot(
95
+ monkeypatch: pytest.MonkeyPatch,
96
+ ) -> None:
97
+ monkeypatch.setenv("CLAUDE_REVIEWS_DISABLED", "bugbot")
98
+ assert reviews_disabled.is_bugbot_opted_out_via_env() is True
99
+
100
+
101
+ def test_is_bugbot_opted_out_via_env_returns_false_when_only_enabled_lists_bugbot(
102
+ monkeypatch: pytest.MonkeyPatch,
103
+ ) -> None:
104
+ monkeypatch.delenv("CLAUDE_REVIEWS_DISABLED", raising=False)
105
+ monkeypatch.setenv("CLAUDE_REVIEWS_ENABLED", "bugbot")
106
+ assert reviews_disabled.is_bugbot_opted_out_via_env() is False
107
+
108
+
109
+ def test_is_bugbot_opted_out_via_env_returns_false_when_no_env_vars_set(
110
+ monkeypatch: pytest.MonkeyPatch,
111
+ ) -> None:
112
+ monkeypatch.delenv("CLAUDE_REVIEWS_DISABLED", raising=False)
113
+ monkeypatch.delenv("CLAUDE_REVIEWS_ENABLED", raising=False)
114
+ assert reviews_disabled.is_bugbot_opted_out_via_env() is False
115
+
116
+
68
117
  def test_is_copilot_disabled_via_env_returns_true_when_env_lists_copilot(
69
118
  monkeypatch: pytest.MonkeyPatch,
70
119
  ) -> None:
@@ -101,17 +150,19 @@ def test_cli_main_returns_zero_when_named_reviewer_disabled(
101
150
  assert reviews_disabled.main(["--reviewer", "bugbot"]) == 0
102
151
 
103
152
 
104
- def test_cli_main_returns_one_when_named_reviewer_not_disabled(
153
+ def test_cli_main_returns_one_when_bugbot_enabled_via_env(
105
154
  monkeypatch: pytest.MonkeyPatch,
106
155
  ) -> None:
107
156
  monkeypatch.delenv("CLAUDE_REVIEWS_DISABLED", raising=False)
157
+ monkeypatch.setenv("CLAUDE_REVIEWS_ENABLED", "bugbot")
108
158
  assert reviews_disabled.main(["--reviewer", "bugbot"]) == 1
109
159
 
110
160
 
111
- def test_cli_main_returns_one_when_other_reviewer_disabled(
161
+ def test_cli_main_returns_one_for_bugbot_when_enabled_despite_bugteam_optout(
112
162
  monkeypatch: pytest.MonkeyPatch,
113
163
  ) -> None:
114
164
  monkeypatch.setenv("CLAUDE_REVIEWS_DISABLED", "bugteam")
165
+ monkeypatch.setenv("CLAUDE_REVIEWS_ENABLED", "bugbot")
115
166
  assert reviews_disabled.main(["--reviewer", "bugbot"]) == 1
116
167
 
117
168
 
@@ -49,12 +49,12 @@ CODE_AND_PROSE_DIFF = (
49
49
  "+++ b/api/quota.py\n"
50
50
  "@@ -0,0 +1,2 @@\n"
51
51
  "+def read_quota(account):\n"
52
- '+ return account["premium_interactions"]\n'
52
+ '+ return account["premium_request_interactions"]\n'
53
53
  "diff --git a/docs/README.md b/docs/README.md\n"
54
54
  "--- a/docs/README.md\n"
55
55
  "+++ b/docs/README.md\n"
56
56
  "@@ -0,0 +1,1 @@\n"
57
- "+The premium-request budget gates the run.\n"
57
+ "+The premium-request-budget field gates the run.\n"
58
58
  )
59
59
 
60
60
 
@@ -63,7 +63,7 @@ def test_flags_prose_near_miss_of_code_identifier() -> None:
63
63
  assert len(findings) == 1
64
64
  assert "docs/README.md:1" in findings[0]
65
65
  assert "premium-request" in findings[0]
66
- assert "premium_interactions" in findings[0]
66
+ assert "premium_request_interactions" in findings[0]
67
67
 
68
68
 
69
69
  def test_does_not_flag_exact_hyphen_variant() -> None:
@@ -208,7 +208,7 @@ def test_still_flags_non_plural_divergent_tail() -> None:
208
208
  "--- a/api/quota.py\n"
209
209
  "+++ b/api/quota.py\n"
210
210
  "@@ -0,0 +1,1 @@\n"
211
- "+premium_interactions = 5\n"
211
+ "+premium_request_interactions = 5\n"
212
212
  "diff --git a/docs/README.md b/docs/README.md\n"
213
213
  "--- a/docs/README.md\n"
214
214
  "+++ b/docs/README.md\n"
@@ -226,8 +226,8 @@ def test_flags_near_miss_inside_code_comment() -> None:
226
226
  "--- a/api/quota.py\n"
227
227
  "+++ b/api/quota.py\n"
228
228
  "@@ -0,0 +1,2 @@\n"
229
- "+premium_interactions = 5\n"
230
- "+# the premium-request path resets the counter\n"
229
+ "+premium_request_interactions = 5\n"
230
+ "+# the premium-request-budget path resets the counter\n"
231
231
  )
232
232
  findings = sweep_diff(diff)
233
233
  assert len(findings) == 1
@@ -268,9 +268,11 @@ def test_main_exits_zero_when_clean(tmp_path: Path) -> None:
268
268
 
269
269
  def test_staged_terminology_findings_flags_staged_prose(tmp_path: Path) -> None:
270
270
  _init_git_repository(tmp_path)
271
- (tmp_path / "quota.py").write_text("premium_interactions = 5\n", encoding="utf-8")
271
+ (tmp_path / "quota.py").write_text(
272
+ "premium_request_interactions = 5\n", encoding="utf-8"
273
+ )
272
274
  (tmp_path / "README.md").write_text(
273
- "The premium-request budget gates the run.\n", encoding="utf-8"
275
+ "The premium-request-budget field gates the run.\n", encoding="utf-8"
274
276
  )
275
277
  subprocess.run(
276
278
  ["git", "add", "-A"],
@@ -341,12 +343,12 @@ def test_does_not_flag_tail_word_that_is_identifier_vocabulary() -> None:
341
343
  "--- a/api/layout.py\n"
342
344
  "+++ b/api/layout.py\n"
343
345
  "@@ -0,0 +1,1 @@\n"
344
- "+target_width = box_height\n"
346
+ "+target_width_ratio = box_height\n"
345
347
  "diff --git a/docs/README.md b/docs/README.md\n"
346
348
  "--- a/docs/README.md\n"
347
349
  "+++ b/docs/README.md\n"
348
350
  "@@ -0,0 +1,1 @@\n"
349
- "+Scale the art until it covers the target box.\n"
351
+ "+Scale the art until it covers the target width box.\n"
350
352
  )
351
353
  assert sweep_diff(diff) == []
352
354
 
@@ -401,13 +403,13 @@ def test_skips_string_literal_fragments_in_test_files() -> None:
401
403
  "--- a/api/quota.py\n"
402
404
  "+++ b/api/quota.py\n"
403
405
  "@@ -0,0 +1,1 @@\n"
404
- "+premium_interactions = 5\n"
406
+ "+premium_request_interactions = 5\n"
405
407
  "diff --git a/tests/test_quota.py b/tests/test_quota.py\n"
406
408
  "--- a/tests/test_quota.py\n"
407
409
  "+++ b/tests/test_quota.py\n"
408
410
  "@@ -0,0 +1,2 @@\n"
409
- '+fixture_text = "the premium-request budget gates the run"\n'
410
- "+# the premium-request path resets the counter\n"
411
+ '+fixture_text = "the premium-request-budget gates the run"\n'
412
+ "+# the premium-request-budget path resets the counter\n"
411
413
  )
412
414
  findings = sweep_diff(diff)
413
415
  assert len(findings) == 1
@@ -460,12 +462,12 @@ def test_skips_string_literal_fragments_in_dot_test_named_files() -> None:
460
462
  "--- a/api/quota.py\n"
461
463
  "+++ b/api/quota.py\n"
462
464
  "@@ -0,0 +1,1 @@\n"
463
- "+premium_interactions = 5\n"
465
+ "+premium_request_interactions = 5\n"
464
466
  "diff --git a/api/quota.test.mjs b/api/quota.test.mjs\n"
465
467
  "--- a/api/quota.test.mjs\n"
466
468
  "+++ b/api/quota.test.mjs\n"
467
469
  "@@ -0,0 +1,1 @@\n"
468
- '+const fixture_line = "the premium-request budget gates the run"\n'
470
+ '+const fixture_line = "the premium-request-budget gates the run"\n'
469
471
  )
470
472
  assert sweep_diff(diff) == []
471
473
 
@@ -476,11 +478,96 @@ def test_skips_string_literal_fragments_in_tests_directory_files() -> None:
476
478
  "--- a/api/quota.py\n"
477
479
  "+++ b/api/quota.py\n"
478
480
  "@@ -0,0 +1,1 @@\n"
479
- "+premium_interactions = 5\n"
481
+ "+premium_request_interactions = 5\n"
480
482
  "diff --git a/tests/fixtures.py b/tests/fixtures.py\n"
481
483
  "--- a/tests/fixtures.py\n"
482
484
  "+++ b/tests/fixtures.py\n"
483
485
  "@@ -0,0 +1,1 @@\n"
484
- '+sample_line = "the premium-request budget gates the run"\n'
486
+ '+sample_line = "the premium-request-budget gates the run"\n'
487
+ )
488
+ assert sweep_diff(diff) == []
489
+
490
+
491
+ def test_does_not_flag_first_word_only_three_token_coincidence() -> None:
492
+ diff = (
493
+ "diff --git a/api/store.py b/api/store.py\n"
494
+ "--- a/api/store.py\n"
495
+ "+++ b/api/store.py\n"
496
+ "@@ -0,0 +1,1 @@\n"
497
+ "+report_run_stage = collect()\n"
498
+ "diff --git a/docs/README.md b/docs/README.md\n"
499
+ "--- a/docs/README.md\n"
500
+ "+++ b/docs/README.md\n"
501
+ "@@ -0,0 +1,1 @@\n"
502
+ "+The report claude view ships the config.\n"
503
+ )
504
+ assert sweep_diff(diff) == []
505
+
506
+
507
+ def test_flags_three_token_near_miss_sharing_two_leading_tokens() -> None:
508
+ diff = (
509
+ "diff --git a/api/store.py b/api/store.py\n"
510
+ "--- a/api/store.py\n"
511
+ "+++ b/api/store.py\n"
512
+ "@@ -0,0 +1,1 @@\n"
513
+ "+premium_request_interactions = 5\n"
514
+ "diff --git a/docs/README.md b/docs/README.md\n"
515
+ "--- a/docs/README.md\n"
516
+ "+++ b/docs/README.md\n"
517
+ "@@ -0,0 +1,1 @@\n"
518
+ "+The premium-request-budget field gates the run.\n"
519
+ )
520
+ findings = sweep_diff(diff)
521
+ assert len(findings) == 1
522
+ assert "premium-request-budget" in findings[0]
523
+ assert "premium_request_interactions" in findings[0]
524
+
525
+
526
+ def test_does_not_flag_shared_prefix_with_multiple_divergent_tail_tokens() -> None:
527
+ diff = (
528
+ "diff --git a/api/store.py b/api/store.py\n"
529
+ "--- a/api/store.py\n"
530
+ "+++ b/api/store.py\n"
531
+ "@@ -0,0 +1,1 @@\n"
532
+ "+plugin_config_cache_key = build()\n"
533
+ "diff --git a/docs/README.md b/docs/README.md\n"
534
+ "--- a/docs/README.md\n"
535
+ "+++ b/docs/README.md\n"
536
+ "@@ -0,0 +1,1 @@\n"
537
+ "+The plugin config table row loads first.\n"
485
538
  )
486
539
  assert sweep_diff(diff) == []
540
+
541
+
542
+ def test_does_not_flag_two_token_identifier_first_word_coincidence() -> None:
543
+ diff = (
544
+ "diff --git a/api/store.py b/api/store.py\n"
545
+ "--- a/api/store.py\n"
546
+ "+++ b/api/store.py\n"
547
+ "@@ -0,0 +1,1 @@\n"
548
+ "+read_count = 0\n"
549
+ "diff --git a/docs/README.md b/docs/README.md\n"
550
+ "--- a/docs/README.md\n"
551
+ "+++ b/docs/README.md\n"
552
+ "@@ -0,0 +1,1 @@\n"
553
+ "+We read back the value from disk.\n"
554
+ )
555
+ assert sweep_diff(diff) == []
556
+
557
+
558
+ def test_flags_near_miss_when_second_leading_token_is_plural_variant() -> None:
559
+ diff = (
560
+ "diff --git a/api/store.py b/api/store.py\n"
561
+ "--- a/api/store.py\n"
562
+ "+++ b/api/store.py\n"
563
+ "@@ -0,0 +1,1 @@\n"
564
+ "+retry_policy_window = 5\n"
565
+ "diff --git a/docs/README.md b/docs/README.md\n"
566
+ "--- a/docs/README.md\n"
567
+ "+++ b/docs/README.md\n"
568
+ "@@ -0,0 +1,1 @@\n"
569
+ "+The retry policies limit gates the run.\n"
570
+ )
571
+ findings = sweep_diff(diff)
572
+ assert len(findings) == 1
573
+ assert "retry policies limit" in findings[0]
package/agents/CLAUDE.md CHANGED
@@ -9,14 +9,15 @@ Agent definition files installed into `~/.claude/agents/` by `bin/install.mjs`.
9
9
  | `caveman.md` | Caveman Agent | Terse voice and smallest-possible artifacts; questions premise before building |
10
10
  | `clasp-deployment-orchestrator.md` | Clasp Deployment Orchestrator | Creates and deploys Google Apps Script projects with multiple files |
11
11
  | `clean-coder.md` | Clean Coder | Primary code-writing agent; internalizes CODE_RULES.md and targets zero `/check` findings |
12
- | `code-advisor.md` | Code Advisor | Mid-run advisor for executor agents; returns plans or stop signals — no tools, no edits |
12
+ | `code-advisor.md` | Code Advisor | Single-executor mid-run advisor (PLAN/CORRECTION/STOP as final text); distinct from session-advisor |
13
13
  | `code-quality-agent.md` | Code Quality Agent | Multi-file code quality review across an entire diff or set of files |
14
- | `code-verifier.md` | Code Verifier | Post-hoc verification after coder agents finish; read-only, fresh context, ends with a fenced verdict |
14
+ | `code-verifier.md` | Code Verifier | Post-hoc verification after coder agents finish; read-only, fresh context, puts the draft verdict through one strongest-tier validation subagent, ends with a fenced verdict |
15
15
  | `deep-research.md` | Deep Research | Citation-grounded research with web search |
16
16
  | `docs-agent.md` | Docs Agent | Documentation authoring and maintenance |
17
17
  | `git-commit-crafter.md` | Git Commit Crafter | Stages changes, writes conventional commit messages, creates commits |
18
18
  | `plan-packet-validator.md` | Plan Packet Validator | Fresh-context validator for workflow-generated plan packets under `docs/plans/` |
19
19
  | `pr-description-writer.md` | PR Description Writer | Authors PR descriptions in Anthropic-style shapes that pass the `pr_description_enforcer` hook's body audit |
20
+ | `session-advisor.md` | Session Advisor | Standing multi-consumer reviewer; SendMessage only; returns endorse/correction/plan/stop |
20
21
 
21
22
  ## Format
22
23
 
@@ -266,7 +266,7 @@ Exit after delegation
266
266
  Deployment Health Report:
267
267
 
268
268
  ✓ scheduled-report
269
- - scriptId: 1QAH00eF0z_...
269
+ - scriptId: SCRIPT_ID_EXAMPLE
270
270
  - Files: Code.gs, Utils.gs
271
271
  - Last push: Success
272
272
  - Triggers: 1 time-driven (verified in web UI)
@@ -277,7 +277,7 @@ Exit after delegation
277
277
  - Action: Recreate .clasp.json or new project
278
278
 
279
279
  ✓ api-connector
280
- - scriptId: 1g5mDpQ4gtI...
280
+ - scriptId: SCRIPT_ID_EXAMPLE
281
281
  - Files: Code.gs
282
282
  - Last push: Success
283
283
  - Triggers: NOT CONFIGURED (needs setup)
@@ -1,22 +1,24 @@
1
1
  ---
2
2
  name: code-advisor
3
- description: Mid-run advisor for executor agents. A coder that hits a decision it can't reasonably solve consults this agent with its task, what it tried, and the exact blocker. Returns a plan, a correction, or a stop signal guidance only. Has zero tools by design; it never runs commands, never edits files, never produces user-facing output.
3
+ description: Single-executor mid-run advisor. A coder that hits a decision it cannot reasonably solve consults this agent with its task, what it tried, and the exact blocker. Returns PLAN, CORRECTION, or STOP as final reply text (not SendMessage). Distinct from session-advisor, which is the standing multi-consumer four-signal reviewer. Has zero tools; it never runs commands, never edits files, never produces user-facing output.
4
4
  tools: []
5
5
  model: inherit
6
6
  color: purple
7
7
  ---
8
8
 
9
- You are the advisor in an executor/advisor pair (Anthropic's advisor strategy: https://claude.com/blog/the-advisor-strategy). An executor agent — a coder partway through a task — consults you when it hits a decision it can't reasonably solve. You have no tools; everything you know arrives in the consultation message: the task, what the executor tried, the exact blocker, and any code excerpts it chose to include.
9
+ You are the advisor in an executor/advisor pair (Anthropic's advisor strategy: https://claude.com/blog/the-advisor-strategy). An executor agent — a coder partway through a task — consults you when it hits a decision it cannot reasonably solve. You have no tools; everything you know arrives in the consultation message: the task, what the executor tried, the exact blocker, and any code excerpts it chose to include. Your final assistant text *is* the reply — do not use SendMessage.
10
+
11
+ Use this agent for a one-off mid-run consult from a single executor. For a standing multi-consumer reviewer that answers ENDORSE / CORRECTION / PLAN / STOP over a shared transcript (team-advisor, orchestrator), use `session-advisor` instead.
10
12
 
11
13
  Reply with exactly one of three signals, named on the first line:
12
14
 
13
- - **PLAN** — the blocker needs a different approach. Give concrete ordered steps the executor can run with its own tools. Name files, commands, and decision points; never hand back vague direction.
14
- - **CORRECTION** — the executor's approach is right but one thing is wrong. Name the wrong assumption or step and the precise fix.
15
+ - **PLAN** — the blocker needs a different approach. Give concrete ordered steps the executor can run with its own tools. Name files, commands, and decision points; never hand back vague direction. The executor acts on a PLAN.
16
+ - **CORRECTION** — the executor's approach is right but one thing is wrong. Name the wrong assumption or step and the precise fix. The executor acts on a CORRECTION before treating the work as done.
15
17
  - **STOP** — no path satisfies the task as assigned (contradictory constraints, missing access, a rule that forbids every way through). Say why in one or two sentences so the executor can report it upward.
16
18
 
17
19
  Rules:
18
20
 
19
- - Guidance only. You never call tools, never write code blocks longer than a focused excerpt, and your reply goes to the executor, not the user.
21
+ - Answer only. You never call tools, never write code blocks longer than a focused excerpt, and your reply goes to the executor, not the user.
20
22
  - Reason from what the executor sent. When the consultation lacks the facts a sound answer needs, your PLAN's first step is the exact lookup the executor should run, then what to do with each likely answer.
21
23
  - Keep replies short. The executor pays for every token of your answer twice — reading it and acting on it.
22
24
  - Never invent repository facts. Tie every claim to something in the consultation or label it for the executor to check.
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: code-verifier
3
- description: Post-hoc verification agent for the two-phase code workflow. Spawned by the main session after coder agents finish. Runs every check itself in a fresh context — named gates, tests against recorded baselines, two-way diff-vs-task reading — and ends with a fenced verdict block the verifier_verdict_minter hook turns into the commit-gate verdict. Read and execute only; it never edits files.
4
- tools: Read, Grep, Glob, Bash
3
+ description: Post-hoc verification agent for the two-phase code workflow. Spawned by the main session after coder agents finish. Runs every check itself in a fresh context — named gates, tests against recorded baselines, two-way diff-vs-task reading — puts the draft verdict through one strongest-tier validation subagent that tries to refute it, then ends with a fenced verdict block the verifier_verdict_minter hook turns into the commit-gate verdict. Read and execute only; it never edits files.
4
+ tools: Read, Grep, Glob, Bash, Task
5
5
  model: sonnet
6
6
  color: orange
7
7
  ---
@@ -33,6 +33,10 @@ On Windows the same file sits at %USERPROFILE%\.claude\hooks\blocking\verificati
33
33
 
34
34
  The printed hash commits to every changed and untracked file's content in the verified work tree, so it names that surface no matter which directory you or the committer run from. If the CLI prints an empty-surface or wrong-work-tree error and no hash, you are pointed at a work tree with no changes versus origin/main — re-run with the branch mode to locate the correct work tree.
35
35
 
36
+ As the last step before the verdict, put your draft verdict through one best-effort strongest-tier validation pass. Spawn a single validation subagent through the Task tool as the `Explore` agent type at the strongest reachable tier: set the Task `subagent_type` to `Explore`, detect the host profile first per `~/.claude/_shared/advisor/advisor-protocol.md` — the source of truth for host detection, the ladder, and its aliases — then on a Claude host pick the strongest reachable tier on the Fable → Opus → Sonnet → Haiku ladder and on a Grok host use the single Grok tier, and set the Task `model:` field to that tier's alias. The `Explore` type carries no Edit or Write tools and cannot spawn further agents, so the harness itself holds the validator to the no-edit, no-spawn contract the next paragraph names. Hand it the draft verdict together with your evidence — every command you ran with its output, and your two-way diff-to-task mapping — and state that its task is adversarial verification of that supplied draft verdict: refute it against the supplied evidence rather than discover code, naming any gate you misread, any task item you mapped wrong, or any finding that does not hold. When the spawn is unavailable — a Task tool error, an unreachable tier at every rung, or this subagent being barred from spawning further agents — skip the validation pass and emit the draft verdict as it stands, noting the skip in your final message; a spawn failure never blocks the verdict fence from being emitted.
37
+
38
+ This validation pass is terminal: the `Explore` type gives the validation subagent no way to spawn a further agent or edit a file, so it answers with prose only. When it refutes any part, re-check that part yourself against the commands and the diff, and correct the verdict before you emit it. When it refutes nothing, the draft verdict stands. Then emit the fenced verdict block below — it stays the last thing in your message so the verifier_verdict_minter hook reads it.
39
+
36
40
  End your final message with exactly one fenced verdict block — the verifier_verdict_minter hook parses it, binds it to that hash, and the verified_commit_gate hook unlocks `git commit`/`git push` for any work tree whose live surface matches it:
37
41
 
38
42
  ```verdict
@@ -0,0 +1,27 @@
1
+ ---
2
+ name: session-advisor
3
+ description: Standing reviewer for a whole session, or for a shared session and the subagents it spawns. Consulted before committing to a multistep plan, once the session believes the work is complete, before any commit is executed, when the same failure repeats, or when reconsidering the chosen approach. Returns one of four signals — a clean endorse, a correction (covering both a wrong step and a risk worth naming), plan, or stop. Messaging only (SendMessage); it never runs commands, edits files, or produces user-facing output.
4
+ tools: SendMessage
5
+ color: cyan
6
+ ---
7
+
8
+ You are a standing reviewer a Claude Code session consults across the life of a task — not only when it is stuck, but before it commits to a plan, once it believes the work is finished, before any commit is executed, and when it is weighing whether to change course. Your only tool is SendMessage, used to reply to the sender of each consult by name. Everything else you know arrives in the consultation message: what changed since the last consult, the live decision or question, and any file paths or excerpts the session chose to include.
9
+
10
+ Many different consumers may reach you over one shared transcript — a coordinating session and the executor subagents it spawns, not just the single session that spawned you. Three things follow from that:
11
+
12
+ - **Reply to the sender, by name.** Send each reply with SendMessage straight to whoever sent that consult, addressed by their name — never routed back through the session that spawned you or through "main." Each consult's reply goes to its own sender.
13
+ - **Keep each consult on its own terms.** Consults from different consumers interleave in your one transcript. Answer each one keyed to the sender's stated assignment; blend context across consumers only when a consult explicitly asks you to.
14
+ - **Restate an answered question.** If a consult re-raises a question you already answered with nothing new attached, reply by restating your prior answer and naming it as a restatement.
15
+
16
+ Reply with exactly one of four signals, named on the first line:
17
+
18
+ - **ENDORSE** — the plan, or the finished work, holds up, with nothing worth flagging. A clean yes.
19
+ - **CORRECTION** — something needs attention before the plan or the finished work is genuinely done: a wrong assumption or step, or a risk worth naming and closing. Name the specific problem(s) and the precise fix(es) or mitigation(s). A risk you would otherwise have mentioned in passing belongs here, not folded into an ENDORSE.
20
+ - **PLAN** — the current approach needs to change. Give concrete ordered steps the session can run with its own tools, naming files, commands, and decision points.
21
+ - **STOP** — no path satisfies the task as assigned (contradictory constraints, missing access, a rule that forbids every way through). Say why, and report your reasoning with cited proof and examples.
22
+
23
+ Rules:
24
+
25
+ - Serve as a focused advisor. Answer the consulting session directly, keep code examples to focused excerpts, and leave tool use and implementation work to that session. Use SendMessage only to deliver each reply to its sender.
26
+ - Reason from what the session sent. When the consultation lacks the facts a sound answer needs, your reply's first step is the exact lookup the session should run, then what to do with each likely answer.
27
+ - Ground every repository claim in cited proof. Name the specific file, command output, or consultation detail that supports it, and include a concrete example when useful. When proof is not yet available, mark the claim for session verification.
@@ -28,7 +28,7 @@
28
28
  | J9 | Logging format | `log_*(f"...")` rather than `log_*("...", arg)`. |
29
29
  | J10 | Imports inside functions | `import` statements placed inside function bodies. |
30
30
  | J11 | sys.path.insert dedup | `sys.path.insert(0, X)` must be guarded by `if X not in sys.path:` (test files exempt). |
31
- | J12 | Hardcoded user paths | String literals naming a specific user's home directory (`C:/Users/jon/...`, `/Users/alice/...`, `/home/bob/...`). Use `pathlib.Path.home()`. |
31
+ | J12 | Hardcoded user paths | String literals naming a specific user's home directory (`C:/Users/example/...`, `/Users/alice/...`, `/home/bob/...`). Use `pathlib.Path.home()`. |
32
32
 
33
33
  The write-time hook (`code_rules_enforcer.py`) exempts test files (`test_*.py`, `*_test.py`, `*.test.*`, `*.spec.*`, `conftest.py`, paths under `/tests/`) from most Category J sub-buckets, and skips the naming, logging, annotation, and unused-import rules on `.mjs` / `.js` files. J11 (`sys.path.insert`) always applies. Read the next section for the sub-buckets this audit applies more widely than the hook.
34
34
 
@@ -4,7 +4,7 @@
4
4
 
5
5
  **Why this category is narrow but recurrent:** the change *itself* is internally consistent — the diff looks correct in isolation. The bug only surfaces when you compare the diff against the *unchanged* parts of the codebase that share a contract with what was changed. Linters and unit tests rarely catch these; reviewers only catch them by mentally cross-referencing the change against every parallel site.
6
6
 
7
- **Canonical example:** [jl-cmd/claude-code-config PR #397, comment r3210166636](https://github.com/jl-cmd/claude-code-config/pull/397#discussion_r3210166636). The PR updated an instruction at line 137 to direct the model to use `AskUserQuestion` instead of bailing out with "I don't know." But the fallback `skill_reference` string at lines 123–127 in the same file *still* told the model to "reply 'I don't know'." Both strings interpolate into the same `reason` field, giving the model contradictory guidance — the exact escape hatch the PR was meant to close remained available through the unchanged path.
7
+ **Canonical example:** [jl-cmd/claude-dev-env PR #397, comment r3210166636](https://github.com/jl-cmd/claude-dev-env/pull/397#discussion_r3210166636). The PR updated an instruction at line 137 to direct the model to use `AskUserQuestion` instead of bailing out with "I don't know." But the fallback `skill_reference` string at lines 123–127 in the same file *still* told the model to "reply 'I don't know'." Both strings interpolate into the same `reason` field, giving the model contradictory guidance — the exact escape hatch the PR was meant to close remained available through the unchanged path.
8
8
 
9
9
  ## Other typical patterns
10
10
 
@@ -101,9 +101,9 @@ Lead: `Total: N (P0=N, P1=N, P2=N)`. For each sub-bucket A1–A9, produce Shape
101
101
 
102
102
  ---
103
103
 
104
- # Worked example: jl-cmd/claude-code-config PR #394 (May 2026 audit experiment)
104
+ # Worked example: jl-cmd/claude-dev-env PR #394 (May 2026 audit experiment)
105
105
 
106
- Audit jl-cmd/claude-code-config PR #394 for **Category A only** (API contract verification). Skip B–N. Sub-bucket forced-exhaustion mode: Category A is decomposed into 8 sub-buckets below. Each sub-bucket REQUIRES at least one Shape A finding OR exactly one Shape B proof-of-absence with **at least 3 adversarial probes** specific to that sub-bucket. A sub-bucket returning neither is a protocol gap.
106
+ Audit jl-cmd/claude-dev-env PR #394 for **Category A only** (API contract verification). Skip B–N. Sub-bucket forced-exhaustion mode: Category A is decomposed into 8 sub-buckets below. Each sub-bucket REQUIRES at least one Shape A finding OR exactly one Shape B proof-of-absence with **at least 3 adversarial probes** specific to that sub-bucket. A sub-bucket returning neither is a protocol gap.
107
107
 
108
108
  PR: feat(scripts): add sweep-empty-dirs utility and scheduled-task installer
109
109
  Head SHA: 62c9c169ee7a44824e5da25c4cf8b74fdca08a53
@@ -78,9 +78,9 @@ Lead: `Total: N (P0=N, P1=N, P2=N)`. For each sub-bucket B1–B7, produce Shape
78
78
 
79
79
  ---
80
80
 
81
- # Worked example: jl-cmd/claude-code-config PR #394
81
+ # Worked example: jl-cmd/claude-dev-env PR #394
82
82
 
83
- Audit jl-cmd/claude-code-config PR #394 for **Category B only** (selector / query / engine compatibility). Skip A, C–N. Sub-bucket forced-exhaustion mode: Category B is decomposed into 7 sub-buckets below. Each sub-bucket REQUIRES at least one Shape A finding OR exactly one Shape B proof-of-absence with **at least 3 adversarial probes** specific to that sub-bucket. A sub-bucket returning neither is a protocol gap.
83
+ Audit jl-cmd/claude-dev-env PR #394 for **Category B only** (selector / query / engine compatibility). Skip A, C–N. Sub-bucket forced-exhaustion mode: Category B is decomposed into 7 sub-buckets below. Each sub-bucket REQUIRES at least one Shape A finding OR exactly one Shape B proof-of-absence with **at least 3 adversarial probes** specific to that sub-bucket. A sub-bucket returning neither is a protocol gap.
84
84
 
85
85
  PR: feat(scripts): add sweep-empty-dirs utility and scheduled-task installer
86
86
  Head SHA: 62c9c169ee7a44824e5da25c4cf8b74fdca08a53
@@ -96,9 +96,9 @@ Read-only. No edits, no commits.
96
96
 
97
97
  ---
98
98
 
99
- # Worked example: jl-cmd/claude-code-config PR #394
99
+ # Worked example: jl-cmd/claude-dev-env PR #394
100
100
 
101
- Audit jl-cmd/claude-code-config PR #394 for **Category C only** (resource cleanup and lifecycle). Skip A, B, D–N. Sub-bucket forced-exhaustion mode: Category C is decomposed into 8 sub-buckets below. Each sub-bucket REQUIRES at least one Shape A finding OR exactly one Shape B proof-of-absence with **at least 3 adversarial probes** specific to that sub-bucket. A sub-bucket returning neither is a protocol gap.
101
+ Audit jl-cmd/claude-dev-env PR #394 for **Category C only** (resource cleanup and lifecycle). Skip A, B, D–N. Sub-bucket forced-exhaustion mode: Category C is decomposed into 8 sub-buckets below. Each sub-bucket REQUIRES at least one Shape A finding OR exactly one Shape B proof-of-absence with **at least 3 adversarial probes** specific to that sub-bucket. A sub-bucket returning neither is a protocol gap.
102
102
 
103
103
  PR: feat(scripts): add sweep-empty-dirs utility and scheduled-task installer
104
104
  Head SHA: 62c9c169ee7a44824e5da25c4cf8b74fdca08a53