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.
- package/_shared/advisor/CLAUDE.md +2 -2
- package/_shared/advisor/advisor-protocol.md +20 -20
- package/_shared/advisor/scripts/config/advisor_scripts_constants/model_tier_run_validator_constants.py +15 -12
- package/_shared/advisor/scripts/model_tier_run_validator.py +11 -10
- package/_shared/advisor/scripts/tests/test_model_tier_run_validator.py +25 -19
- package/_shared/advisor/scripts/tests/test_tier_model_ids.py +17 -17
- package/_shared/advisor/scripts/tier_model_ids.py +18 -18
- package/_shared/pr-loop/CLAUDE.md +1 -0
- package/_shared/pr-loop/scripts/CLAUDE.md +2 -1
- package/_shared/pr-loop/scripts/README.md +1 -0
- package/_shared/pr-loop/scripts/code_rules_gate.py +253 -1980
- package/_shared/pr-loop/scripts/code_rules_gate_parts/CLAUDE.md +32 -0
- package/_shared/pr-loop/scripts/code_rules_gate_parts/__init__.py +7 -0
- package/_shared/pr-loop/scripts/code_rules_gate_parts/added_line_maps.py +268 -0
- package/_shared/pr-loop/scripts/code_rules_gate_parts/enforcer_loading.py +172 -0
- package/_shared/pr-loop/scripts/code_rules_gate_parts/gate_arguments.py +70 -0
- package/_shared/pr-loop/scripts/code_rules_gate_parts/gate_running.py +326 -0
- package/_shared/pr-loop/scripts/code_rules_gate_parts/git_blob_readers.py +85 -0
- package/_shared/pr-loop/scripts/code_rules_gate_parts/git_file_sets.py +331 -0
- package/_shared/pr-loop/scripts/code_rules_gate_parts/staged_test_running.py +369 -0
- package/_shared/pr-loop/scripts/code_rules_gate_parts/tests/conftest.py +14 -0
- package/_shared/pr-loop/scripts/code_rules_gate_parts/tests/test_added_line_maps.py +118 -0
- package/_shared/pr-loop/scripts/code_rules_gate_parts/tests/test_enforcer_loading.py +17 -0
- package/_shared/pr-loop/scripts/code_rules_gate_parts/tests/test_gate_arguments.py +29 -0
- package/_shared/pr-loop/scripts/code_rules_gate_parts/tests/test_gate_running.py +99 -0
- package/_shared/pr-loop/scripts/code_rules_gate_parts/tests/test_git_blob_readers.py +69 -0
- package/_shared/pr-loop/scripts/code_rules_gate_parts/tests/test_git_file_sets.py +137 -0
- package/_shared/pr-loop/scripts/code_rules_gate_parts/tests/test_staged_test_running.py +116 -0
- package/_shared/pr-loop/scripts/code_rules_gate_parts/tests/test_violation_scoping.py +75 -0
- package/_shared/pr-loop/scripts/code_rules_gate_parts/tests/test_wrapper_plumb_check.py +49 -0
- package/_shared/pr-loop/scripts/code_rules_gate_parts/violation_scoping.py +328 -0
- package/_shared/pr-loop/scripts/code_rules_gate_parts/wrapper_plumb_check.py +206 -0
- package/_shared/pr-loop/scripts/pr_loop_shared_constants/code_rules_gate_constants.py +24 -17
- package/_shared/pr-loop/scripts/pr_loop_shared_constants/reviews_disabled_constants.py +1 -0
- package/_shared/pr-loop/scripts/reviews_disabled.py +19 -2
- package/_shared/pr-loop/scripts/test_code_rules_gate.py +278 -0
- package/_shared/pr-loop/scripts/tests/test_code_rules_gate_constants.py +6 -39
- package/_shared/pr-loop/scripts/tests/test_reviews_disabled.py +43 -0
- package/_shared/pr-loop/worker-spawn.md +186 -0
- package/agents/code-verifier.md +1 -1
- package/bin/ever-shipped-skills.mjs +3 -0
- package/bin/expand_home_directory_tokens.mjs +1 -1
- package/bin/install.mjs +5 -2
- package/hooks/advisory/refactor_guard.py +3 -4
- package/hooks/blocking/CLAUDE.md +7 -1
- package/hooks/blocking/block_main_commit.py +2 -2
- package/hooks/blocking/claude_md_orphan_file_blocker.py +75 -699
- package/hooks/blocking/claude_md_orphan_file_blocker_parts/CLAUDE.md +28 -0
- package/hooks/blocking/claude_md_orphan_file_blocker_parts/__init__.py +1 -0
- package/hooks/blocking/claude_md_orphan_file_blocker_parts/config/__init__.py +1 -0
- package/hooks/blocking/claude_md_orphan_file_blocker_parts/config/orphan_blocker_constants.py +18 -0
- package/hooks/blocking/claude_md_orphan_file_blocker_parts/decision.py +81 -0
- package/hooks/blocking/claude_md_orphan_file_blocker_parts/references.py +307 -0
- package/hooks/blocking/claude_md_orphan_file_blocker_parts/scan_plan.py +124 -0
- package/hooks/blocking/claude_md_orphan_file_blocker_parts/subtree_scan.py +179 -0
- package/hooks/blocking/claude_md_orphan_file_blocker_parts/tests/conftest.py +10 -0
- package/hooks/blocking/claude_md_orphan_file_blocker_parts/tests/test_decision.py +34 -0
- package/hooks/blocking/claude_md_orphan_file_blocker_parts/tests/test_references.py +42 -0
- package/hooks/blocking/claude_md_orphan_file_blocker_parts/tests/test_scan_plan.py +27 -0
- package/hooks/blocking/claude_md_orphan_file_blocker_parts/tests/test_subtree_scan.py +30 -0
- package/hooks/blocking/code_rules_boolean_mustcheck.py +1 -1
- package/hooks/blocking/code_rules_mock_completeness.py +1 -1
- package/hooks/blocking/code_rules_optional_params.py +2 -2
- package/hooks/blocking/code_rules_shared.py +1 -1
- package/hooks/blocking/code_rules_test_assertions.py +1 -1
- package/hooks/blocking/code_rules_typeddict_stub.py +1 -1
- package/hooks/blocking/gh_pr_author_enforcer.py +1 -1
- package/hooks/blocking/inventory_intent_records/CLAUDE.md +26 -0
- package/hooks/blocking/inventory_intent_records/__init__.py +1 -0
- package/hooks/blocking/inventory_intent_records/config/__init__.py +1 -0
- package/hooks/blocking/inventory_intent_records/config/intent_records_constants.py +20 -0
- package/hooks/blocking/inventory_intent_records/records.py +271 -0
- package/hooks/blocking/inventory_intent_records/tests/conftest.py +10 -0
- package/hooks/blocking/inventory_intent_records/tests/test_records.py +80 -0
- package/hooks/blocking/package_inventory_stale_blocker.py +54 -384
- package/hooks/blocking/package_inventory_stale_blocker_parts/CLAUDE.md +26 -0
- package/hooks/blocking/package_inventory_stale_blocker_parts/__init__.py +1 -0
- package/hooks/blocking/package_inventory_stale_blocker_parts/config/__init__.py +1 -0
- package/hooks/blocking/package_inventory_stale_blocker_parts/config/inventory_blocker_constants.py +16 -0
- package/hooks/blocking/package_inventory_stale_blocker_parts/decision.py +84 -0
- package/hooks/blocking/package_inventory_stale_blocker_parts/inventory_detection.py +307 -0
- package/hooks/blocking/package_inventory_stale_blocker_parts/tests/conftest.py +10 -0
- package/hooks/blocking/package_inventory_stale_blocker_parts/tests/test_decision.py +38 -0
- package/hooks/blocking/package_inventory_stale_blocker_parts/tests/test_inventory_detection.py +61 -0
- package/hooks/blocking/pii_payload_scan.py +138 -42
- package/hooks/blocking/pii_prevention_blocker.py +185 -291
- package/hooks/blocking/pii_prevention_blocker_parts/CLAUDE.md +24 -0
- package/hooks/blocking/pii_prevention_blocker_parts/__init__.py +1 -0
- package/hooks/blocking/pii_prevention_blocker_parts/config/__init__.py +1 -0
- package/hooks/blocking/pii_prevention_blocker_parts/config/repository_resolution_constants.py +28 -0
- package/hooks/blocking/pii_prevention_blocker_parts/repository_exemption.py +214 -0
- package/hooks/blocking/pii_prevention_blocker_parts/repository_resolution.py +208 -0
- package/hooks/blocking/pr_description_command_parser.py +8 -4
- package/hooks/blocking/precommit_code_rules_gate.py +3 -3
- package/hooks/blocking/tdd_enforcer.py +97 -608
- package/hooks/blocking/tdd_enforcer_parts/CLAUDE.md +30 -0
- package/hooks/blocking/tdd_enforcer_parts/__init__.py +1 -0
- package/hooks/blocking/tdd_enforcer_parts/candidate_paths.py +142 -0
- package/hooks/blocking/tdd_enforcer_parts/config/__init__.py +1 -0
- package/hooks/blocking/tdd_enforcer_parts/config/tdd_enforcer_constants.py +32 -0
- package/hooks/blocking/tdd_enforcer_parts/content_analysis.py +268 -0
- package/hooks/blocking/tdd_enforcer_parts/decisions.py +92 -0
- package/hooks/blocking/tdd_enforcer_parts/freshness.py +80 -0
- package/hooks/blocking/tdd_enforcer_parts/git_tracking.py +63 -0
- package/hooks/blocking/tdd_enforcer_parts/path_classification.py +119 -0
- package/hooks/blocking/tdd_enforcer_parts/tests/conftest.py +10 -0
- package/hooks/blocking/tdd_enforcer_parts/tests/test_candidate_paths.py +31 -0
- package/hooks/blocking/tdd_enforcer_parts/tests/test_content_analysis.py +30 -0
- package/hooks/blocking/tdd_enforcer_parts/tests/test_decisions.py +34 -0
- package/hooks/blocking/tdd_enforcer_parts/tests/test_freshness.py +28 -0
- package/hooks/blocking/tdd_enforcer_parts/tests/test_git_tracking.py +48 -0
- package/hooks/blocking/tdd_enforcer_parts/tests/test_path_classification.py +36 -0
- package/hooks/blocking/test_inventory_deadlock_resolution.py +154 -0
- package/hooks/blocking/test_pii_payload_scan.py +168 -0
- package/hooks/blocking/test_tdd_enforcer_restore.py +108 -0
- package/hooks/blocking/test_verifier_verdict_minter.py +55 -158
- package/hooks/blocking/tests/conftest.py +10 -0
- package/hooks/blocking/tests/test_pii_prevention_blocker.py +260 -0
- package/hooks/blocking/tests/test_repository_exemption.py +105 -0
- package/hooks/blocking/tests/test_repository_resolution.py +108 -0
- package/hooks/diagnostic/hook_log_extractor.py +12 -10
- package/hooks/git-hooks/post_commit.py +3 -4
- package/hooks/hooks_constants/CLAUDE.md +2 -2
- package/hooks/hooks_constants/banned_identifiers_constants.py +0 -1
- package/hooks/hooks_constants/code_rules_path_utils_constants.py +1 -1
- package/hooks/hooks_constants/local_identity.py +59 -8
- package/hooks/hooks_constants/pii_prevention_constants.py +0 -6
- package/hooks/hooks_constants/test_local_identity.py +105 -3
- package/hooks/pyproject.toml +13 -36
- package/hooks/session/plugin_data_dir_cleanup.py +0 -1
- package/hooks/validation/mypy_validator.py +2 -2
- package/hooks/validators/health_check.py +1 -0
- package/hooks/validators/mypy_integration.py +2 -0
- package/hooks/validators/ruff_integration.py +3 -0
- package/hooks/workflow/auto_formatter.py +5 -4
- package/package.json +1 -1
- package/scripts/CLAUDE.md +4 -0
- package/scripts/dev_env_scripts_constants/CLAUDE.md +6 -4
- package/scripts/dev_env_scripts_constants/code_review_constants.py +71 -0
- package/scripts/dev_env_scripts_constants/grok_worker_constants.py +435 -0
- package/scripts/dev_env_scripts_constants/timing.py +7 -1
- package/scripts/grok_headless_runner.py +294 -0
- package/scripts/grok_worker_preflight.py +410 -0
- package/scripts/invoke_code_review.py +463 -0
- package/scripts/resolve_worker_spawn.py +619 -0
- package/scripts/spawn_grok_batch.py +672 -0
- package/scripts/test_grok_headless_runner.py +626 -0
- package/scripts/test_grok_worker_preflight.py +1054 -0
- package/scripts/test_invoke_code_review.py +672 -0
- package/scripts/test_resolve_worker_spawn.py +1014 -0
- package/scripts/test_spawn_grok_batch.py +1017 -0
- package/skills/CLAUDE.md +5 -3
- package/skills/_shared/pr-loop/scripts/build_audit_prompt.py +72 -13
- package/skills/_shared/pr-loop/scripts/build_fix_prompt.py +121 -14
- package/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/path_resolver_constants.py +78 -0
- package/skills/_shared/pr-loop/scripts/test_build_audit_prompt.py +121 -0
- package/skills/_shared/pr-loop/scripts/test_build_fix_prompt.py +196 -6
- package/skills/autoconverge/CLAUDE.md +3 -3
- package/skills/autoconverge/SKILL.md +9 -3
- package/skills/autoconverge/reference/CLAUDE.md +2 -2
- package/skills/autoconverge/reference/convergence.md +33 -11
- package/skills/autoconverge/reference/stop-conditions.md +16 -5
- package/skills/autoconverge/workflow/CLAUDE.md +2 -1
- package/skills/autoconverge/workflow/converge.clean-audit.test.mjs +7 -2
- package/skills/autoconverge/workflow/converge.codex-gate.test.mjs +300 -0
- package/skills/autoconverge/workflow/converge.contract.test.mjs +5 -5
- package/skills/autoconverge/workflow/converge.copilot-gate.test.mjs +29 -29
- package/skills/autoconverge/workflow/converge.fix-progress.test.mjs +1 -1
- package/skills/autoconverge/workflow/converge.mjs +200 -16
- package/skills/bugteam/CLAUDE.md +2 -2
- package/skills/bugteam/CONSTRAINTS.md +3 -2
- package/skills/bugteam/PROMPTS.md +7 -6
- package/skills/bugteam/SKILL.md +18 -13
- package/skills/bugteam/reference/audit-and-teammates.md +215 -35
- package/skills/bugteam/reference/design-rationale.md +1 -1
- package/skills/bugteam/reference/obstacles/CLAUDE.md +1 -1
- package/skills/bugteam/reference/team-setup.md +8 -2
- package/skills/codex-review/CLAUDE.md +46 -0
- package/skills/codex-review/SKILL.md +181 -0
- package/skills/codex-review/reference/CLAUDE.md +15 -0
- package/skills/codex-review/reference/cli-contract.md +253 -0
- package/skills/codex-review/reference/loop-integration.md +118 -0
- package/skills/codex-review/scripts/codex_down_classifier.py +98 -0
- package/skills/codex-review/scripts/codex_review_scripts_constants/CLAUDE.md +18 -0
- package/skills/codex-review/scripts/codex_review_scripts_constants/__init__.py +1 -0
- package/skills/codex-review/scripts/codex_review_scripts_constants/classifier_constants.py +35 -0
- package/skills/codex-review/scripts/codex_review_scripts_constants/codex_usage_probe_constants.py +86 -0
- package/skills/codex-review/scripts/codex_review_scripts_constants/findings_constants.py +18 -0
- package/skills/codex-review/scripts/codex_review_scripts_constants/run_constants.py +45 -0
- package/skills/codex-review/scripts/codex_usage_probe.py +573 -0
- package/skills/codex-review/scripts/fixtures/auth_failure_synthetic.txt +1 -0
- package/skills/codex-review/scripts/fixtures/config_load_failure_v0.125.0.txt +1 -0
- package/skills/codex-review/scripts/fixtures/freeform_findings_v0.144.3.txt +6 -0
- package/skills/codex-review/scripts/fixtures/model_rejection_v0.125.0.jsonl +5 -0
- package/skills/codex-review/scripts/fixtures/structured_findings.txt +13 -0
- package/skills/codex-review/scripts/fixtures/success_stream_v0.144.3.jsonl +6 -0
- package/skills/codex-review/scripts/fixtures/unknown_failure_synthetic.txt +1 -0
- package/skills/codex-review/scripts/fixtures/usage_limit_synthetic.txt +1 -0
- package/skills/codex-review/scripts/parse_codex_findings.py +207 -0
- package/skills/codex-review/scripts/run_codex_review.py +415 -0
- package/skills/codex-review/scripts/test_codex_down_classifier.py +143 -0
- package/skills/codex-review/scripts/test_codex_usage_probe.py +678 -0
- package/skills/codex-review/scripts/test_parse_codex_findings.py +130 -0
- package/skills/codex-review/scripts/test_run_codex_review.py +812 -0
- package/skills/codex-review/test_skill_scaffold.py +192 -0
- package/skills/grok-spawn/CLAUDE.md +28 -0
- package/skills/grok-spawn/SKILL.md +226 -0
- package/skills/grok-spawn/reference/flag-profiles.md +132 -0
- package/skills/grok-spawn/reference/worker-briefs.md +152 -0
- package/skills/grokify/SKILL.md +9 -1
- package/skills/grokify/capability-claims.test.mjs +28 -0
- package/skills/grokify/evals/README.md +72 -0
- package/skills/grokify/evals/parse-payload.test.mjs +171 -0
- package/skills/grokify/evals/run-capability-evals.mjs +545 -0
- package/skills/orchestrator/SKILL.md +42 -29
- package/skills/orchestrator-refresh/SKILL.md +17 -9
- package/skills/pr-converge/SKILL.md +34 -13
- package/skills/pr-converge/reference/convergence-gates.md +42 -15
- package/skills/pr-converge/reference/fix-protocol.md +1 -1
- package/skills/pr-converge/reference/ground-rules.md +1 -1
- package/skills/pr-converge/reference/per-tick.md +130 -42
- package/skills/pr-converge/reference/state-schema.md +10 -0
- package/skills/pr-converge/scripts/CLAUDE.md +2 -0
- package/skills/pr-converge/scripts/_pr_converge_path_setup.py +5 -1
- package/skills/pr-converge/scripts/check_convergence.py +605 -29
- package/skills/pr-converge/scripts/check_convergence_availability.py +232 -0
- package/skills/pr-converge/scripts/check_convergence_gates.py +279 -235
- package/skills/pr-converge/scripts/check_convergence_thread_gates.py +1 -1
- package/skills/pr-converge/scripts/pr_converge_scripts_constants/convergence_gate_constants.py +36 -2
- package/skills/pr-converge/scripts/test__pr_converge_path_setup.py +4 -0
- package/skills/pr-converge/scripts/test_check_convergence.py +71 -3
- package/skills/pr-converge/scripts/test_check_convergence_availability.py +326 -0
- package/skills/pr-converge/scripts/test_check_convergence_codex.py +507 -0
- package/skills/pr-converge/scripts/test_check_convergence_contract.py +89 -17
- package/skills/pr-converge/scripts/test_check_convergence_fixture.py +179 -0
- package/skills/pr-converge/scripts/test_check_convergence_gates.py +84 -68
- package/skills/pr-converge/scripts/test_check_convergence_thread_gates.py +24 -0
- package/skills/pr-converge/test_step5_host_branch.py +106 -0
- package/skills/pr-loop-cloud-transport/SKILL.md +2 -0
- package/skills/reviewer-gates/SKILL.md +7 -5
- 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
|
-
|
|
95
|
-
if
|
|
96
|
-
|
|
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
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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,
|
package/hooks/pyproject.toml
CHANGED
|
@@ -15,19 +15,18 @@ no_implicit_optional = true
|
|
|
15
15
|
strict_equality = true
|
|
16
16
|
exclude = [
|
|
17
17
|
"validators/test_files/",
|
|
18
|
-
"test_
|
|
19
|
-
"
|
|
20
|
-
|
|
21
|
-
"
|
|
22
|
-
"
|
|
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
|
-
#
|
|
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
|
-
|
|
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
|
-
"
|
|
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"]
|
|
@@ -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,
|
|
@@ -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=
|
|
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
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`
|
|
10
|
-
| `gh_artifact_upload_constants.py` | `gh_artifact_upload.py`
|
|
11
|
-
| `claude_chain_constants.py` | `claude_chain_runner.py`
|
|
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."""
|