claude-dev-env 8.10.0 → 8.11.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/pr-loop/scripts/code_rules_gate_parts/staged_test_regression.py +85 -3
- package/_shared/pr-loop/scripts/code_rules_gate_parts/tests/test_staged_test_regression.py +34 -0
- package/_shared/pr-loop/scripts/fix_pr_test_proof.py +233 -0
- package/_shared/pr-loop/scripts/pr_loop_shared_constants/fix_pr_test_proof_constants.py +47 -0
- package/_shared/pr-loop/scripts/test_fix_pr_test_proof.py +186 -0
- package/bin/codex-skill-load-block.mjs +93 -0
- package/bin/codex-skill-load-block.test.mjs +134 -0
- package/bin/install.agents-home.test.mjs +76 -1
- package/bin/install.mjs +55 -31
- package/bin/install.test.mjs +57 -0
- package/bin/install.transaction.test.mjs +29 -7
- package/bin/seed-codex-pstack-models.mjs +27 -0
- package/bin/seed-codex-pstack-models.test.mjs +72 -0
- package/docs/CODE_RULES.md +5 -1
- package/hooks/blocking/code_rules_enforcer.py +1 -3
- package/hooks/blocking/code_rules_magic_values.py +1 -3
- package/hooks/blocking/code_rules_paired_test.py +5 -6
- package/hooks/blocking/pii_prevention_blocker_parts/repository_exemption.py +3 -3
- package/hooks/blocking/tdd_enforcer_parts/__init__.py +1 -1
- package/hooks/blocking/test_code_rules_enforcer_ephemeral.py +1 -46
- package/hooks/blocking/test_pre_tool_use_dispatcher.py +2 -40
- package/hooks/blocking/tests/test_repository_exemption.py +7 -0
- package/hooks/followup_ledger.py +8 -1
- package/hooks/git-hooks/test_installed_policy_lint.py +4 -2
- package/hooks/hooks.json +15 -0
- package/hooks/hooks_constants/followup_ledger_constants.py +4 -0
- package/hooks/hooks_constants/messages.py +0 -1
- package/hooks/hooks_constants/nested_project_hooks_constants.py +37 -0
- package/hooks/hooks_constants/test_messages.py +1 -7
- package/hooks/lifecycle/nested_project_hooks.py +395 -0
- package/hooks/lifecycle/test_nested_project_hooks.py +251 -0
- package/hooks/test_followup_ledger.py +18 -0
- package/package.json +2 -1
- package/pstack-codex-models.md +37 -0
- package/rules/bdd.md +1 -1
- package/rules/build-needs-a-user.md +53 -0
- package/rules/code-standards.md +1 -1
- package/rules/flag-non-breaking-findings.md +7 -0
- package/rules/paired-test-coverage.md +4 -2
- package/rules/testing.md +5 -1
- package/scripts/cde_lint.py +47 -1
- package/scripts/policy_lint/adapter_callers.py +124 -0
- package/scripts/policy_lint/adapter_pairing.py +4 -2
- package/scripts/policy_lint/adapter_support.py +4 -3
- package/scripts/policy_lint/adapters.py +15 -0
- package/scripts/policy_lint/check_catalog.py +24 -0
- package/scripts/policy_lint/config/approved_test_pairs.py +3 -2
- package/scripts/policy_lint/config/check_catalog_constants.py +13 -0
- package/scripts/policy_lint/config/constants.py +18 -0
- package/scripts/policy_lint/engine.py +9 -3
- package/scripts/policy_lint/model.py +6 -2
- package/scripts/policy_lint/registry.py +6 -0
- package/scripts/policy_lint/selection.py +2 -0
- package/scripts/repository_checks/tracked_private_terms.py +15 -1
- package/scripts/tests/test_cde_lint.py +50 -0
- package/scripts/tests/test_policy_lint_rules_archive.py +16 -0
- package/scripts/tests/test_policy_lint_rules_callers.py +140 -0
- package/scripts/tests/test_policy_lint_rules_check_catalog.py +25 -1
- package/scripts/tests/test_policy_lint_rules_pairing.py +31 -2
- package/scripts/tests/test_policy_lint_selection.py +35 -0
- package/scripts/tests/test_tracked_private_terms.py +35 -0
- package/hooks/blocking/tdd_enforcer.py +0 -195
- package/hooks/blocking/tdd_enforcer_parts/candidate_paths.py +0 -142
- package/hooks/blocking/tdd_enforcer_parts/config/__init__.py +0 -1
- package/hooks/blocking/tdd_enforcer_parts/config/tdd_enforcer_constants.py +0 -49
- package/hooks/blocking/tdd_enforcer_parts/content_hash_store.py +0 -298
- package/hooks/blocking/tdd_enforcer_parts/decisions.py +0 -96
- package/hooks/blocking/tdd_enforcer_parts/freshness.py +0 -95
- package/hooks/blocking/tdd_enforcer_parts/git_tracking.py +0 -132
- package/hooks/blocking/tdd_enforcer_parts/path_classification.py +0 -119
- package/hooks/blocking/tdd_enforcer_parts/tests/test_candidate_paths.py +0 -31
- package/hooks/blocking/tdd_enforcer_parts/tests/test_content_hash_store.py +0 -256
- package/hooks/blocking/tdd_enforcer_parts/tests/test_decisions.py +0 -46
- package/hooks/blocking/tdd_enforcer_parts/tests/test_freshness.py +0 -28
- package/hooks/blocking/tdd_enforcer_parts/tests/test_git_tracking.py +0 -111
- package/hooks/blocking/tdd_enforcer_parts/tests/test_path_classification.py +0 -36
- package/hooks/blocking/test_tdd_enforcer.py +0 -1506
- package/hooks/blocking/test_tdd_enforcer_restore.py +0 -108
- package/hooks/blocking/test_tdd_enforcer_scratchpad.py +0 -105
- package/scripts/codex_compat_watcher.py +0 -502
- package/scripts/tests/test_codex_compat_watcher.py +0 -599
|
@@ -1,108 +0,0 @@
|
|
|
1
|
-
"""End-to-end tests for the absent-but-tracked restore exemption.
|
|
2
|
-
|
|
3
|
-
Drives the real ``tdd_enforcer.py`` entry hook over a Write payload in a temp
|
|
4
|
-
git repository, proving a remove-then-Write rewrite of committed code is allowed
|
|
5
|
-
even when the paired test's freshness window has lapsed, while new untracked
|
|
6
|
-
code and real edits to a present file stay gated.
|
|
7
|
-
"""
|
|
8
|
-
|
|
9
|
-
import json
|
|
10
|
-
import os
|
|
11
|
-
import subprocess
|
|
12
|
-
import sys
|
|
13
|
-
from pathlib import Path
|
|
14
|
-
|
|
15
|
-
SCRIPT_PATH = Path(__file__).parent / "tdd_enforcer.py"
|
|
16
|
-
STALE_MTIME_OFFSET_SECONDS = 700
|
|
17
|
-
DISABLE_EPHEMERAL_ENVIRONMENT = {"CLAUDE_CODE_RULES_DISABLE_EPHEMERAL_EXEMPT": "1"}
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
def _run_hook(payload: dict) -> subprocess.CompletedProcess[str]:
|
|
21
|
-
return subprocess.run(
|
|
22
|
-
[sys.executable, str(SCRIPT_PATH)],
|
|
23
|
-
input=json.dumps(payload),
|
|
24
|
-
text=True,
|
|
25
|
-
capture_output=True,
|
|
26
|
-
check=False,
|
|
27
|
-
env={**os.environ, **DISABLE_EPHEMERAL_ENVIRONMENT},
|
|
28
|
-
)
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
def _write_payload(file_path: Path, content: str) -> dict:
|
|
32
|
-
return {"tool_name": "Write", "tool_input": {"file_path": str(file_path), "content": content}}
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
def _decision_from(completed: subprocess.CompletedProcess[str]) -> str | None:
|
|
36
|
-
if not completed.stdout:
|
|
37
|
-
return None
|
|
38
|
-
hook_output = json.loads(completed.stdout).get("hookSpecificOutput", {})
|
|
39
|
-
return hook_output.get("permissionDecision")
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
def _run_git(repository_root: Path, *arguments: str) -> None:
|
|
43
|
-
subprocess.run(
|
|
44
|
-
["git", *arguments],
|
|
45
|
-
cwd=str(repository_root),
|
|
46
|
-
check=True,
|
|
47
|
-
capture_output=True,
|
|
48
|
-
env={**os.environ, "GIT_CONFIG_GLOBAL": os.devnull, "GIT_CONFIG_SYSTEM": os.devnull},
|
|
49
|
-
)
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
def _init_git_repository(repository_root: Path) -> None:
|
|
53
|
-
_run_git(repository_root, "init", "-q")
|
|
54
|
-
_run_git(repository_root, "config", "user.email", "test@example.com")
|
|
55
|
-
_run_git(repository_root, "config", "user.name", "Test")
|
|
56
|
-
_run_git(repository_root, "config", "commit.gpgsign", "false")
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
def _commit_tracked_service(repository_root: Path) -> tuple[Path, Path]:
|
|
60
|
-
production_file = repository_root / "service.py"
|
|
61
|
-
production_file.write_text("def serve(): return 1\n")
|
|
62
|
-
sibling_test = repository_root / "test_service.py"
|
|
63
|
-
sibling_test.write_text("def test_serve(): pass\n")
|
|
64
|
-
_run_git(repository_root, "add", "-A")
|
|
65
|
-
_run_git(repository_root, "commit", "-q", "-m", "init")
|
|
66
|
-
return production_file, sibling_test
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
def test_should_allow_restore_write_of_absent_but_tracked_file_despite_stale_window(
|
|
70
|
-
tmp_path: Path,
|
|
71
|
-
) -> None:
|
|
72
|
-
repository_root = tmp_path / "repo"
|
|
73
|
-
repository_root.mkdir()
|
|
74
|
-
_init_git_repository(repository_root)
|
|
75
|
-
production_file, sibling_test = _commit_tracked_service(repository_root)
|
|
76
|
-
stale_timestamp = os.stat(sibling_test).st_mtime - STALE_MTIME_OFFSET_SECONDS
|
|
77
|
-
os.utime(sibling_test, (stale_timestamp, stale_timestamp))
|
|
78
|
-
production_file.unlink()
|
|
79
|
-
|
|
80
|
-
completed = _run_hook(_write_payload(production_file, "def serve(): return 2\n"))
|
|
81
|
-
|
|
82
|
-
assert _decision_from(completed) == "allow"
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
def test_should_still_deny_write_of_absent_untracked_file_without_fresh_test(
|
|
86
|
-
tmp_path: Path,
|
|
87
|
-
) -> None:
|
|
88
|
-
repository_root = tmp_path / "repo"
|
|
89
|
-
repository_root.mkdir()
|
|
90
|
-
_init_git_repository(repository_root)
|
|
91
|
-
untracked_file = repository_root / "brand_new.py"
|
|
92
|
-
|
|
93
|
-
completed = _run_hook(_write_payload(untracked_file, "def fresh(): return 1\n"))
|
|
94
|
-
|
|
95
|
-
assert _decision_from(completed) == "deny"
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
def test_should_still_deny_write_of_present_file_with_stale_test(tmp_path: Path) -> None:
|
|
99
|
-
repository_root = tmp_path / "repo"
|
|
100
|
-
repository_root.mkdir()
|
|
101
|
-
_init_git_repository(repository_root)
|
|
102
|
-
production_file, sibling_test = _commit_tracked_service(repository_root)
|
|
103
|
-
stale_timestamp = os.stat(sibling_test).st_mtime - STALE_MTIME_OFFSET_SECONDS
|
|
104
|
-
os.utime(sibling_test, (stale_timestamp, stale_timestamp))
|
|
105
|
-
|
|
106
|
-
completed = _run_hook(_write_payload(production_file, "def serve(): return 2\n"))
|
|
107
|
-
|
|
108
|
-
assert _decision_from(completed) == "deny"
|
|
@@ -1,105 +0,0 @@
|
|
|
1
|
-
"""Tests for the session-scratchpad exemption wired into tdd_enforcer.main."""
|
|
2
|
-
|
|
3
|
-
import importlib.util
|
|
4
|
-
import io
|
|
5
|
-
import json
|
|
6
|
-
import os
|
|
7
|
-
import sys
|
|
8
|
-
import tempfile
|
|
9
|
-
from pathlib import Path
|
|
10
|
-
from types import ModuleType
|
|
11
|
-
|
|
12
|
-
import pytest
|
|
13
|
-
|
|
14
|
-
TDD_ENFORCER_PATH = Path(__file__).parent / "tdd_enforcer.py"
|
|
15
|
-
FIXED_USER_ID = 5150
|
|
16
|
-
WORKING_DIRECTORY = "/home/user/project"
|
|
17
|
-
SESSION_ID = "tdd-session-987"
|
|
18
|
-
PRODUCTION_CONTENT = "def fulfill_order():\n return True\n"
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
def _load_tdd_enforcer() -> ModuleType:
|
|
22
|
-
module_spec = importlib.util.spec_from_file_location(
|
|
23
|
-
"tdd_enforcer_scratchpad_under_test", TDD_ENFORCER_PATH
|
|
24
|
-
)
|
|
25
|
-
assert module_spec is not None and module_spec.loader is not None
|
|
26
|
-
loaded_module = importlib.util.module_from_spec(module_spec)
|
|
27
|
-
module_spec.loader.exec_module(loaded_module)
|
|
28
|
-
return loaded_module
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
_TDD_ENFORCER = _load_tdd_enforcer()
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
def _install_scratchpad_signals(monkeypatch: pytest.MonkeyPatch, temporary_root: Path) -> Path:
|
|
35
|
-
monkeypatch.setattr(os, "getuid", lambda: FIXED_USER_ID, raising=False)
|
|
36
|
-
monkeypatch.setattr(tempfile, "gettempdir", lambda: str(temporary_root))
|
|
37
|
-
mangled_working_directory = WORKING_DIRECTORY.replace("/", "-")
|
|
38
|
-
scratchpad_directory = (
|
|
39
|
-
temporary_root
|
|
40
|
-
/ f"claude-{FIXED_USER_ID}"
|
|
41
|
-
/ mangled_working_directory
|
|
42
|
-
/ SESSION_ID
|
|
43
|
-
/ "scratchpad"
|
|
44
|
-
)
|
|
45
|
-
scratchpad_directory.mkdir(parents=True)
|
|
46
|
-
return scratchpad_directory
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
def _write_payload(target: Path) -> dict[str, object]:
|
|
50
|
-
return {
|
|
51
|
-
"tool_name": "Write",
|
|
52
|
-
"cwd": WORKING_DIRECTORY,
|
|
53
|
-
"session_id": SESSION_ID,
|
|
54
|
-
"tool_input": {"file_path": str(target), "content": PRODUCTION_CONTENT},
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
def _run_main(
|
|
59
|
-
monkeypatch: pytest.MonkeyPatch, payload: dict[str, object]
|
|
60
|
-
) -> tuple[int | None, str]:
|
|
61
|
-
monkeypatch.setattr(sys, "stdin", io.StringIO(json.dumps(payload)))
|
|
62
|
-
captured_stdout = io.StringIO()
|
|
63
|
-
monkeypatch.setattr(sys, "stdout", captured_stdout)
|
|
64
|
-
exit_code: int | None = None
|
|
65
|
-
try:
|
|
66
|
-
_TDD_ENFORCER.main()
|
|
67
|
-
except SystemExit as raised_exit:
|
|
68
|
-
raw_code = raised_exit.code
|
|
69
|
-
exit_code = raw_code if isinstance(raw_code, int) else None
|
|
70
|
-
return exit_code, captured_stdout.getvalue()
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
def _decision_from(stdout_text: str) -> str | None:
|
|
74
|
-
if not stdout_text.strip():
|
|
75
|
-
return None
|
|
76
|
-
parsed = json.loads(stdout_text)
|
|
77
|
-
return parsed.get("hookSpecificOutput", {}).get("permissionDecision")
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
def test_scratchpad_production_write_is_exempt_from_tdd_gate(
|
|
81
|
-
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
|
82
|
-
) -> None:
|
|
83
|
-
scratchpad_directory = _install_scratchpad_signals(monkeypatch, tmp_path)
|
|
84
|
-
monkeypatch.setenv("CLAUDE_CODE_RULES_DISABLE_EPHEMERAL_EXEMPT", "1")
|
|
85
|
-
throwaway_script = scratchpad_directory / "one_off_probe.py"
|
|
86
|
-
|
|
87
|
-
exit_code, stdout_text = _run_main(monkeypatch, _write_payload(throwaway_script))
|
|
88
|
-
|
|
89
|
-
assert exit_code == 0
|
|
90
|
-
assert _decision_from(stdout_text) != "deny"
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
def test_identical_non_scratchpad_write_is_still_blocked(
|
|
94
|
-
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
|
95
|
-
) -> None:
|
|
96
|
-
_install_scratchpad_signals(monkeypatch, tmp_path)
|
|
97
|
-
monkeypatch.setenv("CLAUDE_CODE_RULES_DISABLE_EPHEMERAL_EXEMPT", "1")
|
|
98
|
-
outside_directory = tmp_path / "project" / "orders"
|
|
99
|
-
outside_directory.mkdir(parents=True)
|
|
100
|
-
production_module = outside_directory / "one_off_probe.py"
|
|
101
|
-
|
|
102
|
-
exit_code, stdout_text = _run_main(monkeypatch, _write_payload(production_module))
|
|
103
|
-
|
|
104
|
-
assert exit_code == 0
|
|
105
|
-
assert _decision_from(stdout_text) == "deny"
|