claude-dev-env 2.36.0 → 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agents/skills/_shared/advisor/scripts/README.md +1 -1
- package/.agents/skills/team-advisor/SKILL.md +11 -11
- package/_shared/advisor/advisor-protocol.md +51 -51
- package/_shared/advisor/reference/advisor-block.md +9 -9
- package/_shared/advisor/reference/astra-rung.md +39 -0
- package/_shared/advisor/reference/identity.md +4 -4
- package/_shared/advisor/reference/lifecycle.md +6 -6
- package/_shared/advisor/reference/spawn-walk-log.md +9 -9
- package/_shared/advisor/reference/third-party-bind.md +11 -11
- package/_shared/advisor/reference/warm-up.md +13 -13
- package/_shared/advisor/scripts/codex_astra_advisor.py +308 -0
- package/_shared/advisor/scripts/codex_astra_preflight.py +115 -0
- package/_shared/advisor/scripts/codex_astra_reply.py +159 -0
- package/_shared/advisor/scripts/config/advisor_scripts_constants/advisor_route_constants.py +3 -3
- package/_shared/advisor/scripts/config/advisor_scripts_constants/astra_advisor_constants.py +38 -0
- package/_shared/advisor/scripts/model_tier_run_validator.py +17 -18
- package/_shared/advisor/scripts/tests/test_codex_astra_advisor.py +271 -0
- package/_shared/advisor/scripts/tests/test_codex_astra_preflight.py +24 -0
- package/_shared/advisor/scripts/tests/test_codex_astra_reply.py +42 -0
- package/_shared/advisor/scripts/tests/test_model_tier_run_validator.py +55 -30
- package/_shared/advisor/scripts/tests/test_tier_model_ids.py +14 -4
- package/_shared/pr-loop/scripts/pr_loop_shared_constants/terminology_sweep_constants.py +14 -0
- package/_shared/pr-loop/scripts/terminology_sweep.py +138 -50
- package/_shared/pr-loop/scripts/tests/test_terminology_sweep.py +13 -1
- package/docs/references/team-advisor-skill.md +3 -3
- package/hooks/validators/README.md +35 -1
- package/package.json +1 -1
- package/scripts/check.ps1 +18 -6
- package/scripts/policy_lint/adapter_detectors.py +42 -2
- package/scripts/policy_lint/adapters.py +24 -0
- package/scripts/policy_lint/config/constants.py +11 -0
- package/scripts/policy_lint/engine.py +1 -1
- package/scripts/policy_lint/model.py +1 -0
- package/scripts/policy_lint/registry.py +10 -1
- package/scripts/repository_checks/__init__.py +1 -0
- package/scripts/repository_checks/claude_md.py +108 -0
- package/scripts/repository_checks/config/__init__.py +1 -0
- package/scripts/repository_checks/config/constants.py +150 -0
- package/scripts/repository_checks/env_var_documentation.py +243 -0
- package/scripts/repository_checks/hook_modules.py +34 -0
- package/scripts/repository_checks/models.py +37 -0
- package/scripts/repository_checks/package_inventory.py +118 -0
- package/scripts/repository_checks/pytest_testpaths.py +84 -0
- package/scripts/repository_checks/reporting.py +39 -0
- package/scripts/repository_checks/runner.py +93 -0
- package/scripts/repository_checks/tracked_secrets.py +147 -0
- package/scripts/repository_checks/tracked_tree.py +28 -0
- package/scripts/repository_policy.py +120 -0
- package/scripts/tests/repository_policy_test_support.py +89 -0
- package/scripts/tests/test_cde_lint.py +25 -1
- package/scripts/tests/test_claude_md.py +68 -0
- package/scripts/tests/test_env_var_documentation.py +133 -0
- package/scripts/tests/test_hook_modules.py +17 -0
- package/scripts/tests/test_models.py +26 -0
- package/scripts/tests/test_package_inventory.py +96 -0
- package/scripts/tests/test_policy_lint_rules_registry.py +117 -0
- package/scripts/tests/test_pytest_testpaths.py +66 -0
- package/scripts/tests/test_reporting.py +54 -0
- package/scripts/tests/test_repository_policy.py +71 -0
- package/scripts/tests/test_runner.py +59 -0
- package/scripts/tests/test_tracked_secrets.py +175 -0
- package/scripts/tests/test_tracked_tree.py +28 -0
- package/_shared/advisor/reference/sol-rung.md +0 -39
- package/_shared/advisor/scripts/codex_sol_advisor.py +0 -581
- package/_shared/advisor/scripts/config/advisor_scripts_constants/sol_advisor_constants.py +0 -38
- package/_shared/advisor/scripts/tests/test_codex_sol_advisor.py +0 -740
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"""Behavior tests for repository-check report rendering."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import sys
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
_SCRIPTS_DIRECTORY = Path(__file__).resolve().parents[1]
|
|
9
|
+
_TESTS_DIRECTORY = Path(__file__).resolve().parent
|
|
10
|
+
if str(_SCRIPTS_DIRECTORY) not in sys.path:
|
|
11
|
+
sys.path.insert(0, str(_SCRIPTS_DIRECTORY))
|
|
12
|
+
if str(_TESTS_DIRECTORY) not in sys.path:
|
|
13
|
+
sys.path.insert(0, str(_TESTS_DIRECTORY))
|
|
14
|
+
|
|
15
|
+
from repository_checks.models import RepositoryCheckReport, RepositoryFinding
|
|
16
|
+
from repository_checks.reporting import render_report
|
|
17
|
+
from repository_policy_test_support import (
|
|
18
|
+
commit_tracked_files,
|
|
19
|
+
initialize_repository,
|
|
20
|
+
run_policy,
|
|
21
|
+
write_text,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def test_should_render_findings_and_failed_checks_in_report_order() -> None:
|
|
26
|
+
report = RepositoryCheckReport(
|
|
27
|
+
(RepositoryFinding("check", "relative/file.py", "message"),),
|
|
28
|
+
("failed-check",),
|
|
29
|
+
)
|
|
30
|
+
assert render_report(report) == (
|
|
31
|
+
"check: relative/file.py: message\nerror: rule failed: failed-check\n"
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def test_should_render_an_empty_clean_report() -> None:
|
|
36
|
+
assert render_report(RepositoryCheckReport((), ())) == ""
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def test_should_print_repository_relative_paths_only(tmp_path: Path) -> None:
|
|
40
|
+
repository_root = tmp_path / "repo"
|
|
41
|
+
initialize_repository(repository_root)
|
|
42
|
+
write_text(
|
|
43
|
+
repository_root / "notes" / "CLAUDE.md",
|
|
44
|
+
"# notes\n\n"
|
|
45
|
+
"| File | Role |\n"
|
|
46
|
+
"|---|---|\n"
|
|
47
|
+
"| `absent_helper.py` | Missing helper |\n",
|
|
48
|
+
)
|
|
49
|
+
commit_tracked_files(repository_root)
|
|
50
|
+
_exit_code, stdout_text, stderr_text = run_policy(repository_root)
|
|
51
|
+
absolute_root_text = str(repository_root.resolve())
|
|
52
|
+
assert absolute_root_text not in stdout_text
|
|
53
|
+
assert absolute_root_text not in stderr_text
|
|
54
|
+
assert "notes/CLAUDE.md" in stdout_text
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"""Command behavior for the committed-tree repository checker."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import subprocess
|
|
6
|
+
import sys
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
_SCRIPTS_DIRECTORY = Path(__file__).resolve().parents[1]
|
|
10
|
+
_TESTS_DIRECTORY = Path(__file__).resolve().parent
|
|
11
|
+
if str(_SCRIPTS_DIRECTORY) not in sys.path:
|
|
12
|
+
sys.path.insert(0, str(_SCRIPTS_DIRECTORY))
|
|
13
|
+
if str(_TESTS_DIRECTORY) not in sys.path:
|
|
14
|
+
sys.path.insert(0, str(_TESTS_DIRECTORY))
|
|
15
|
+
|
|
16
|
+
import repository_policy
|
|
17
|
+
from repository_checks.config.constants import (
|
|
18
|
+
CHECK_ID_CLAUDE_MD_ORPHANS,
|
|
19
|
+
CHECK_ID_ENV_VAR_DOCUMENTATION,
|
|
20
|
+
CHECK_ID_PACKAGE_INVENTORY,
|
|
21
|
+
CHECK_ID_PYTEST_TESTPATHS,
|
|
22
|
+
CHECK_ID_TRACKED_PERSONAL_DATA,
|
|
23
|
+
)
|
|
24
|
+
from repository_policy_test_support import run_policy, seed_clean_repository
|
|
25
|
+
|
|
26
|
+
_POLICY_SCRIPT_PATH = _SCRIPTS_DIRECTORY / "repository_policy.py"
|
|
27
|
+
_CHECK_SCRIPT_PATH = _SCRIPTS_DIRECTORY / "check.ps1"
|
|
28
|
+
_UTF8_ENCODING = "utf-8"
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def test_should_exit_zero_on_a_clean_tracked_tree(tmp_path: Path) -> None:
|
|
32
|
+
repository_root = seed_clean_repository(tmp_path / "repo")
|
|
33
|
+
exit_code, stdout_text, stderr_text = run_policy(repository_root)
|
|
34
|
+
assert exit_code == 0
|
|
35
|
+
assert stdout_text == ""
|
|
36
|
+
assert stderr_text == ""
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def test_should_keep_stable_check_identifiers() -> None:
|
|
40
|
+
assert repository_policy.ALL_CHECK_IDS == (
|
|
41
|
+
CHECK_ID_CLAUDE_MD_ORPHANS,
|
|
42
|
+
CHECK_ID_ENV_VAR_DOCUMENTATION,
|
|
43
|
+
CHECK_ID_PACKAGE_INVENTORY,
|
|
44
|
+
CHECK_ID_PYTEST_TESTPATHS,
|
|
45
|
+
CHECK_ID_TRACKED_PERSONAL_DATA,
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def test_should_invoke_the_checker_from_check_script() -> None:
|
|
50
|
+
script_text = _CHECK_SCRIPT_PATH.read_text(encoding=_UTF8_ENCODING)
|
|
51
|
+
assert "Invoke-Tool -Label 'repository-policy'" in script_text
|
|
52
|
+
assert "repository_policy.py" in script_text
|
|
53
|
+
assert "--repository-root" in script_text
|
|
54
|
+
assert "SkipRepositoryPolicy" in script_text
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def test_should_run_the_checker_as_a_subprocess(tmp_path: Path) -> None:
|
|
58
|
+
repository_root = seed_clean_repository(tmp_path / "repo")
|
|
59
|
+
completed = subprocess.run(
|
|
60
|
+
[
|
|
61
|
+
sys.executable,
|
|
62
|
+
str(_POLICY_SCRIPT_PATH),
|
|
63
|
+
"--repository-root",
|
|
64
|
+
str(repository_root),
|
|
65
|
+
],
|
|
66
|
+
check=False,
|
|
67
|
+
capture_output=True,
|
|
68
|
+
text=True,
|
|
69
|
+
)
|
|
70
|
+
assert completed.returncode == 0
|
|
71
|
+
assert completed.stdout == ""
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"""Behavior tests for repository-check orchestration."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import sys
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
import pytest
|
|
9
|
+
|
|
10
|
+
_TESTS_DIRECTORY = Path(__file__).resolve().parent
|
|
11
|
+
if str(_TESTS_DIRECTORY) not in sys.path:
|
|
12
|
+
sys.path.insert(0, str(_TESTS_DIRECTORY))
|
|
13
|
+
|
|
14
|
+
from repository_checks.config.constants import (
|
|
15
|
+
FAILED_CHECK_EXIT_CODE,
|
|
16
|
+
FINDINGS_EXIT_CODE,
|
|
17
|
+
)
|
|
18
|
+
from repository_policy_test_support import (
|
|
19
|
+
commit_tracked_files,
|
|
20
|
+
initialize_repository,
|
|
21
|
+
run_policy,
|
|
22
|
+
seed_clean_repository,
|
|
23
|
+
write_text,
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def test_should_fail_closed_when_tracked_paths_cannot_be_read(
|
|
28
|
+
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
|
29
|
+
) -> None:
|
|
30
|
+
repository_root = seed_clean_repository(tmp_path / "repo")
|
|
31
|
+
|
|
32
|
+
def fail_tracked_paths(_repository_root: Path) -> tuple[str, ...]:
|
|
33
|
+
raise OSError("git ls-files failed")
|
|
34
|
+
|
|
35
|
+
monkeypatch.setattr(
|
|
36
|
+
"repository_checks.tracked_tree.tracked_relative_paths", fail_tracked_paths
|
|
37
|
+
)
|
|
38
|
+
exit_code, stdout_text, _stderr_text = run_policy(repository_root)
|
|
39
|
+
assert exit_code == FAILED_CHECK_EXIT_CODE
|
|
40
|
+
assert "error: rule failed:" in stdout_text
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def test_should_emit_stable_sorted_findings(tmp_path: Path) -> None:
|
|
44
|
+
repository_root = tmp_path / "repo"
|
|
45
|
+
initialize_repository(repository_root)
|
|
46
|
+
write_text(
|
|
47
|
+
repository_root / "zeta" / "CLAUDE.md",
|
|
48
|
+
"# zeta\n\n| File | Role |\n|---|---|\n| `missing_zeta.py` | Missing |\n",
|
|
49
|
+
)
|
|
50
|
+
write_text(
|
|
51
|
+
repository_root / "alpha" / "CLAUDE.md",
|
|
52
|
+
"# alpha\n\n| File | Role |\n|---|---|\n| `missing_alpha.py` | Missing |\n",
|
|
53
|
+
)
|
|
54
|
+
commit_tracked_files(repository_root)
|
|
55
|
+
first_exit_code, first_stdout, _first_stderr = run_policy(repository_root)
|
|
56
|
+
_second_exit_code, second_stdout, _second_stderr = run_policy(repository_root)
|
|
57
|
+
assert first_exit_code == FINDINGS_EXIT_CODE
|
|
58
|
+
assert first_stdout == second_stdout
|
|
59
|
+
assert first_stdout.index("alpha/CLAUDE.md") < first_stdout.index("zeta/CLAUDE.md")
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
"""Behavior tests for tracked-secret checks."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import hashlib
|
|
6
|
+
import sys
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
import pytest
|
|
10
|
+
from repository_checks.config.constants import (
|
|
11
|
+
ALL_TRACKED_SECRET_EXACT_EXEMPTIONS,
|
|
12
|
+
CHECK_ID_TRACKED_PERSONAL_DATA,
|
|
13
|
+
FINDINGS_EXIT_CODE,
|
|
14
|
+
)
|
|
15
|
+
from repository_checks.hook_modules import load_hooks_module
|
|
16
|
+
from repository_checks.tracked_secrets import collect_tracked_secret_findings
|
|
17
|
+
|
|
18
|
+
_TESTS_DIRECTORY = Path(__file__).resolve().parent
|
|
19
|
+
if str(_TESTS_DIRECTORY) not in sys.path:
|
|
20
|
+
sys.path.insert(0, str(_TESTS_DIRECTORY))
|
|
21
|
+
|
|
22
|
+
from repository_policy_test_support import (
|
|
23
|
+
commit_tracked_files,
|
|
24
|
+
initialize_repository,
|
|
25
|
+
run_policy,
|
|
26
|
+
write_text,
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
_SYNTHETIC_GITHUB_TOKEN = "ghp_" + ("A" * 36)
|
|
30
|
+
_UTF8_ENCODING = "utf-8"
|
|
31
|
+
_REPOSITORY_ROOT = Path(__file__).resolve().parents[4]
|
|
32
|
+
_EXEMPT_FIXTURE_EMAIL = "fixture-owner@company.io"
|
|
33
|
+
_OTHER_FIXTURE_EMAIL = "other-owner@company.io"
|
|
34
|
+
_OTHER_FIXTURE_HOME_PATH = "C:/Users/realname/notes.txt"
|
|
35
|
+
_FIXTURE_NOTES_RELATIVE_PATH = "src/notes.py"
|
|
36
|
+
_OTHER_NOTES_RELATIVE_PATH = "src/other_notes.py"
|
|
37
|
+
_PII_PREVENTION_CONSTANTS = load_hooks_module(
|
|
38
|
+
"hooks_constants.pii_prevention_constants"
|
|
39
|
+
)
|
|
40
|
+
_PII_CATEGORY_EMAIL = _PII_PREVENTION_CONSTANTS.CATEGORY_EMAIL
|
|
41
|
+
_PII_CATEGORY_HOME_PATH = _PII_PREVENTION_CONSTANTS.CATEGORY_HOME_PATH
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def test_should_flag_a_tracked_secret(tmp_path: Path) -> None:
|
|
45
|
+
repository_root = tmp_path / "repo"
|
|
46
|
+
initialize_repository(repository_root)
|
|
47
|
+
write_text(
|
|
48
|
+
repository_root / "src" / "app.py", f"token = '{_SYNTHETIC_GITHUB_TOKEN}'\n"
|
|
49
|
+
)
|
|
50
|
+
commit_tracked_files(repository_root)
|
|
51
|
+
exit_code, stdout_text, _stderr_text = run_policy(repository_root)
|
|
52
|
+
assert exit_code == FINDINGS_EXIT_CODE
|
|
53
|
+
assert CHECK_ID_TRACKED_PERSONAL_DATA in stdout_text
|
|
54
|
+
assert "src/app.py" in stdout_text
|
|
55
|
+
assert _SYNTHETIC_GITHUB_TOKEN not in stdout_text
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def test_should_fail_closed_before_reading_a_tracked_symlink_outside_repository(
|
|
59
|
+
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
|
60
|
+
) -> None:
|
|
61
|
+
repository_root = tmp_path / "repo"
|
|
62
|
+
repository_root.mkdir()
|
|
63
|
+
outside_path = tmp_path / "outside.py"
|
|
64
|
+
write_text(outside_path, f"token = '{_SYNTHETIC_GITHUB_TOKEN}'\n")
|
|
65
|
+
tracked_symlink = repository_root / "linked.py"
|
|
66
|
+
tracked_symlink.symlink_to(outside_path)
|
|
67
|
+
original_read_text = Path.read_text
|
|
68
|
+
all_read_paths: list[Path] = []
|
|
69
|
+
|
|
70
|
+
def record_read_path(
|
|
71
|
+
file_path: Path,
|
|
72
|
+
encoding: str | None = None,
|
|
73
|
+
errors: str | None = None,
|
|
74
|
+
) -> str:
|
|
75
|
+
all_read_paths.append(file_path)
|
|
76
|
+
return original_read_text(file_path, encoding=encoding, errors=errors)
|
|
77
|
+
|
|
78
|
+
monkeypatch.setattr(Path, "read_text", record_read_path)
|
|
79
|
+
with pytest.raises(OSError):
|
|
80
|
+
collect_tracked_secret_findings(repository_root, ["linked.py"])
|
|
81
|
+
assert tracked_symlink not in all_read_paths
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def test_should_flag_a_different_email_in_the_same_exact_exempted_file(
|
|
85
|
+
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
|
86
|
+
) -> None:
|
|
87
|
+
repository_root = tmp_path / "repo"
|
|
88
|
+
initialize_repository(repository_root)
|
|
89
|
+
_write_exact_exemption_fixture(repository_root)
|
|
90
|
+
commit_tracked_files(repository_root)
|
|
91
|
+
_install_exact_email_exemption(monkeypatch)
|
|
92
|
+
exit_code, stdout_text, _stderr_text = run_policy(repository_root)
|
|
93
|
+
assert exit_code == FINDINGS_EXIT_CODE
|
|
94
|
+
assert _pii_categories_for_path(stdout_text, _FIXTURE_NOTES_RELATIVE_PATH) == [
|
|
95
|
+
_PII_CATEGORY_EMAIL,
|
|
96
|
+
_PII_CATEGORY_HOME_PATH,
|
|
97
|
+
_PII_PREVENTION_CONSTANTS.CATEGORY_SECRET,
|
|
98
|
+
]
|
|
99
|
+
assert _EXEMPT_FIXTURE_EMAIL not in stdout_text
|
|
100
|
+
assert _SYNTHETIC_GITHUB_TOKEN not in stdout_text
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def test_should_flag_an_exact_exempt_value_at_a_different_path(
|
|
104
|
+
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
|
105
|
+
) -> None:
|
|
106
|
+
repository_root = tmp_path / "repo"
|
|
107
|
+
initialize_repository(repository_root)
|
|
108
|
+
_write_exact_exemption_fixture(repository_root)
|
|
109
|
+
commit_tracked_files(repository_root)
|
|
110
|
+
_install_exact_email_exemption(monkeypatch)
|
|
111
|
+
_exit_code, stdout_text, _stderr_text = run_policy(repository_root)
|
|
112
|
+
assert _PII_CATEGORY_EMAIL in _pii_categories_for_path(
|
|
113
|
+
stdout_text, _OTHER_NOTES_RELATIVE_PATH
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def test_should_store_tracked_secret_exemptions_as_digests() -> None:
|
|
118
|
+
sha256_hex_digest_length = hashlib.sha256(b"").digest_size * 2
|
|
119
|
+
assert ALL_TRACKED_SECRET_EXACT_EXEMPTIONS
|
|
120
|
+
for _relative_path, _category, each_digest in ALL_TRACKED_SECRET_EXACT_EXEMPTIONS:
|
|
121
|
+
assert len(each_digest) == sha256_hex_digest_length
|
|
122
|
+
bytes.fromhex(each_digest)
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def test_should_keep_owned_checker_modules_free_of_tracked_secret_findings() -> None:
|
|
126
|
+
checker_directory = (
|
|
127
|
+
_REPOSITORY_ROOT
|
|
128
|
+
/ "packages"
|
|
129
|
+
/ "claude-dev-env"
|
|
130
|
+
/ "scripts"
|
|
131
|
+
/ "repository_checks"
|
|
132
|
+
)
|
|
133
|
+
all_relative_paths = [
|
|
134
|
+
each_path.relative_to(_REPOSITORY_ROOT).as_posix()
|
|
135
|
+
for each_path in checker_directory.rglob("*.py")
|
|
136
|
+
if each_path.is_file()
|
|
137
|
+
]
|
|
138
|
+
assert collect_tracked_secret_findings(_REPOSITORY_ROOT, all_relative_paths) == []
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def _write_exact_exemption_fixture(repository_root: Path) -> None:
|
|
142
|
+
write_text(
|
|
143
|
+
repository_root / _FIXTURE_NOTES_RELATIVE_PATH,
|
|
144
|
+
f"contact = '{_EXEMPT_FIXTURE_EMAIL}'\nalt = '{_OTHER_FIXTURE_EMAIL}'\nhome = '{_OTHER_FIXTURE_HOME_PATH}'\ntoken = '{_SYNTHETIC_GITHUB_TOKEN}'\n",
|
|
145
|
+
)
|
|
146
|
+
write_text(
|
|
147
|
+
repository_root / _OTHER_NOTES_RELATIVE_PATH,
|
|
148
|
+
f"contact = '{_EXEMPT_FIXTURE_EMAIL}'\n",
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def _pii_categories_for_path(stdout_text: str, relative_path: str) -> list[str]:
|
|
153
|
+
finding_prefix = f"{CHECK_ID_TRACKED_PERSONAL_DATA}: {relative_path}:"
|
|
154
|
+
return sorted(
|
|
155
|
+
each_line.split("[", 1)[1].split("]", 1)[0]
|
|
156
|
+
for each_line in stdout_text.splitlines()
|
|
157
|
+
if each_line.startswith(finding_prefix)
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def _install_exact_email_exemption(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
162
|
+
monkeypatch.setattr(
|
|
163
|
+
"repository_checks.tracked_secrets.repository_constants.ALL_TRACKED_SECRET_EXACT_EXEMPTIONS",
|
|
164
|
+
frozenset(
|
|
165
|
+
{
|
|
166
|
+
(
|
|
167
|
+
_FIXTURE_NOTES_RELATIVE_PATH,
|
|
168
|
+
_PII_CATEGORY_EMAIL,
|
|
169
|
+
hashlib.sha256(
|
|
170
|
+
_EXEMPT_FIXTURE_EMAIL.encode(_UTF8_ENCODING)
|
|
171
|
+
).hexdigest(),
|
|
172
|
+
)
|
|
173
|
+
}
|
|
174
|
+
),
|
|
175
|
+
)
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"""Behavior tests for tracked repository path discovery."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import sys
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
_TESTS_DIRECTORY = Path(__file__).resolve().parent
|
|
9
|
+
if str(_TESTS_DIRECTORY) not in sys.path:
|
|
10
|
+
sys.path.insert(0, str(_TESTS_DIRECTORY))
|
|
11
|
+
|
|
12
|
+
from repository_checks.tracked_tree import tracked_relative_paths
|
|
13
|
+
from repository_policy_test_support import run_policy, seed_clean_repository, write_text
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def test_should_list_only_committed_relative_paths(tmp_path: Path) -> None:
|
|
17
|
+
repository_root = seed_clean_repository(tmp_path / "repo")
|
|
18
|
+
write_text(repository_root / "untracked.txt", "not committed\n")
|
|
19
|
+
assert tracked_relative_paths(repository_root) == ("README.md",)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def test_should_skip_a_tracked_path_absent_from_the_worktree(tmp_path: Path) -> None:
|
|
23
|
+
repository_root = seed_clean_repository(tmp_path / "repo")
|
|
24
|
+
(repository_root / "README.md").unlink()
|
|
25
|
+
exit_code, stdout_text, stderr_text = run_policy(repository_root)
|
|
26
|
+
assert exit_code == 0
|
|
27
|
+
assert stdout_text == ""
|
|
28
|
+
assert stderr_text == ""
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
# Sol rung
|
|
2
|
-
|
|
3
|
-
Detail behind the **Host profiles → Sol rung** section of [`advisor-protocol.md`](../advisor-protocol.md).
|
|
4
|
-
Open this when Fable is out of usage on a Claude or ThirdParty host and a Sol fallback bind is starting.
|
|
5
|
-
On a Codex host, Sol is the in-session default; see [`identity.md`](identity.md).
|
|
6
|
-
|
|
7
|
-
## Flag
|
|
8
|
-
|
|
9
|
-
`ADVISOR_SOL=1` (or `true` / `yes` / `on`) opens the Sol fallback after Fable is out of usage. Two channels exist: set the variable in the helper's process environment, or pass `--enable-sol` on the helper invocation — the CLI flag opens the rung for that run without touching the environment.
|
|
10
|
-
Flag off both ways: fail closed when Fable did not bind.
|
|
11
|
-
A Windows `setx` write only updates the persisted user environment; only a process started after that write inherits the new value, so an already-running session either sets the flag in its own invoking process environment or passes `--enable-sol`.
|
|
12
|
-
|
|
13
|
-
## Effort
|
|
14
|
-
|
|
15
|
-
`ADVISOR_EFFORT` selects Codex `model_reasoning_effort` for Sol and `--effort` for Fable: `low`, `medium`, `high`, `xhigh`, or `max`. The default is `low`, which sends `model_reasoning_effort="low"`. Pass `--effort <level>` on the helper to set effort for that Sol run without changing the environment. An unset or unrecognized value uses `low`.
|
|
16
|
-
|
|
17
|
-
Every fallback reply carries a `fallback_kind` field: `declined` when policy closed the rung (flag off, usage meter at or below the gate) and `broken` when the Sol path itself failed (missing executable, spawn error, timeout, malformed reply). A `broken` fallback is a defect to report, not a routing outcome.
|
|
18
|
-
|
|
19
|
-
## Preflight
|
|
20
|
-
|
|
21
|
-
Flag on: run the Codex preflight first —
|
|
22
|
-
|
|
23
|
-
```
|
|
24
|
-
python ~/.claude/_shared/pr-loop/scripts/codex_usage_probe.py
|
|
25
|
-
```
|
|
26
|
-
|
|
27
|
-
**GOTCHA — probe path:** that installed path is the only one. `codex_sol_advisor.resolve_usage_probe_path` builds `~/.claude/_shared/pr-loop/scripts/codex_usage_probe.py` and stops. Do not hunt `skills/codex-review/`, Codex worktrees under `C:\dev\.codex\worktrees\`, or other copies. Repo home: `packages/claude-dev-env/_shared/pr-loop/scripts/`.
|
|
28
|
-
|
|
29
|
-
The shared entry point is `~/.claude/_shared/advisor/scripts/codex_sol_advisor.py`; it calls the installed probe and owns Sol bind or resume parsing. Bind with `python ~/.claude/_shared/advisor/scripts/codex_sol_advisor.py --bind --enable-sol --cwd <repo-root>` and pipe the charter on stdin. Resume with `--resume <session_id>` and pipe the delta consult on stdin.
|
|
30
|
-
|
|
31
|
-
The gate passes only when the probe exits 0, `percent_left` is finite numeric data, and `percent_left` is strictly greater than `WEEKLY_USAGE_GATE_THRESHOLD_PERCENT` from the existing probe. The exact-threshold case fails closed when Fable did not bind.
|
|
32
|
-
|
|
33
|
-
## Branches
|
|
34
|
-
|
|
35
|
-
**Preflight pass** — bind one Codex CLI session at `gpt-5.6-sol` with `model_reasoning_effort` set from `ADVISOR_EFFORT` (default `low`), `--sandbox read-only`, and JSON output. The helper receives the standing-reviewer charter on stdin and returns only parsed ENDORSE / CORRECTION / PLAN / STOP guidance with a session ID.
|
|
36
|
-
|
|
37
|
-
**Preflight fail** — probe failure, non-zero exit, timeout, missing or malformed usage, `null`, non-finite usage, or usage at or below the threshold fails closed when Fable did not bind.
|
|
38
|
-
|
|
39
|
-
The helper owns the Sol attempt and returns an explicit fallback result. The consuming advisor path owns the Fable bind. Apply the same gate to every Sol attempt, including resume.
|