claude-dev-env 2.35.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/autoconverge/SKILL.md +19 -15
- package/.agents/skills/autoconverge/reference/gotchas.md +3 -3
- package/.agents/skills/autoconverge/reference/multi-pr.md +6 -2
- package/.agents/skills/autoconverge/workflow/converge.contract.test.mjs +11 -0
- package/.agents/skills/autoconverge/workflow/converge.mjs +26 -33
- package/.agents/skills/autoconverge/workflow/converge.run-input.test.mjs +26 -2
- package/.agents/skills/autoconverge/workflow/converge_multi.mjs +12 -28
- package/.agents/skills/autoconverge/workflow/converge_multi.run-input.test.mjs +16 -0
- 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/bin/cde.mjs +175 -0
- package/bin/cde.test.mjs +222 -0
- package/bin/install-constants.mjs +4 -0
- package/bin/install.cursor-rules.test.mjs +233 -2
- package/bin/install.mjs +49 -3
- package/bin/pstack-model-preferences.codex.json +23 -0
- package/bin/select-pstack-models.test.mjs +378 -0
- package/docs/references/team-advisor-skill.md +3 -3
- package/hooks/hooks_constants/tool_names.py +20 -0
- package/hooks/validators/README.md +35 -1
- package/hooks/validators/fast_save_validators.py +63 -89
- package/hooks/validators/run_all_validators.py +16 -8
- package/hooks/validators/test_fast_save_validators_policy_lint.py +25 -0
- package/hooks/validators/test_run_all_validators_policy_lint.py +52 -0
- package/package.json +3 -2
- package/rules/pstack-models.md +119 -0
- package/scripts/cde_lint.py +258 -0
- package/scripts/check.ps1 +18 -6
- package/scripts/policy_lint/__init__.py +41 -0
- package/scripts/policy_lint/adapter_configuration.py +91 -0
- package/scripts/policy_lint/adapter_detectors.py +368 -0
- package/scripts/policy_lint/adapter_pairing.py +145 -0
- package/scripts/policy_lint/adapter_support.py +97 -0
- package/scripts/policy_lint/adapters.py +376 -0
- package/scripts/policy_lint/config/__init__.py +1 -0
- package/scripts/policy_lint/config/constants.py +77 -0
- package/scripts/policy_lint/engine.py +224 -0
- package/scripts/policy_lint/model.py +388 -0
- package/scripts/policy_lint/registry.py +171 -0
- package/scripts/policy_lint/render.py +91 -0
- package/scripts/policy_lint/selection.py +372 -0
- package/scripts/policy_lint/selection_git.py +147 -0
- 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/select_pstack_models.mjs +304 -0
- package/scripts/tests/repository_policy_test_support.py +89 -0
- package/scripts/tests/test_cde_lint.py +398 -0
- 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_engine.py +381 -0
- package/scripts/tests/test_policy_lint_rules_registry.py +369 -0
- package/scripts/tests/test_policy_lint_selection.py +373 -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,96 @@
|
|
|
1
|
+
"""Behavior tests for committed package inventories."""
|
|
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
|
+
CHECK_ID_PACKAGE_INVENTORY,
|
|
16
|
+
FAILED_CHECK_EXIT_CODE,
|
|
17
|
+
FINDINGS_EXIT_CODE,
|
|
18
|
+
)
|
|
19
|
+
from repository_policy_test_support import (
|
|
20
|
+
commit_tracked_files,
|
|
21
|
+
initialize_repository,
|
|
22
|
+
patch_unreadable_named_file,
|
|
23
|
+
run_policy,
|
|
24
|
+
write_text,
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
_SKILL_INVENTORY_MARKDOWN = (
|
|
28
|
+
"# Skill\n\n"
|
|
29
|
+
"| File | Role |\n"
|
|
30
|
+
"|---|---|\n"
|
|
31
|
+
"| `listed_alpha.py` | Alpha helper |\n"
|
|
32
|
+
"| `listed_beta.py` | Beta helper |\n"
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def test_should_flag_a_stale_package_inventory(tmp_path: Path) -> None:
|
|
37
|
+
repository_root = tmp_path / "repo"
|
|
38
|
+
initialize_repository(repository_root)
|
|
39
|
+
package_directory = repository_root / "pipeline"
|
|
40
|
+
_write_package_inventory(package_directory)
|
|
41
|
+
commit_tracked_files(repository_root)
|
|
42
|
+
exit_code, stdout_text, _stderr_text = run_policy(repository_root)
|
|
43
|
+
assert exit_code == FINDINGS_EXIT_CODE
|
|
44
|
+
assert CHECK_ID_PACKAGE_INVENTORY in stdout_text
|
|
45
|
+
assert "pipeline/check_dialer_seam_cli.py" in stdout_text
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def test_should_check_active_skills_and_exclude_archived_skills(tmp_path: Path) -> None:
|
|
49
|
+
repository_root = tmp_path / "repo"
|
|
50
|
+
initialize_repository(repository_root)
|
|
51
|
+
_seed_skill_package(repository_root / ".agents" / "skills" / "live-skill")
|
|
52
|
+
_seed_skill_package(repository_root / ".agents" / "skills-archived" / "old-skill")
|
|
53
|
+
commit_tracked_files(repository_root)
|
|
54
|
+
exit_code, stdout_text, _stderr_text = run_policy(repository_root)
|
|
55
|
+
assert exit_code == FINDINGS_EXIT_CODE
|
|
56
|
+
assert CHECK_ID_PACKAGE_INVENTORY in stdout_text
|
|
57
|
+
assert ".agents/skills/live-skill/scripts/unlisted_gamma.py" in stdout_text
|
|
58
|
+
assert (
|
|
59
|
+
".agents/skills-archived/old-skill/scripts/unlisted_gamma.py" not in stdout_text
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def test_should_fail_closed_when_a_package_inventory_document_cannot_be_read(
|
|
64
|
+
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
|
65
|
+
) -> None:
|
|
66
|
+
repository_root = tmp_path / "repo"
|
|
67
|
+
initialize_repository(repository_root)
|
|
68
|
+
_write_package_inventory(repository_root / "pipeline")
|
|
69
|
+
commit_tracked_files(repository_root)
|
|
70
|
+
patch_unreadable_named_file(monkeypatch, "README.md", "inventory unreadable")
|
|
71
|
+
exit_code, stdout_text, _stderr_text = run_policy(repository_root)
|
|
72
|
+
assert exit_code == FAILED_CHECK_EXIT_CODE
|
|
73
|
+
assert CHECK_ID_PACKAGE_INVENTORY in stdout_text
|
|
74
|
+
assert "error: rule failed:" in stdout_text
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def _write_package_inventory(package_directory: Path) -> None:
|
|
78
|
+
write_text(
|
|
79
|
+
package_directory / "README.md",
|
|
80
|
+
"# Pipeline\n\n"
|
|
81
|
+
"| Path | Role |\n"
|
|
82
|
+
"|---|---|\n"
|
|
83
|
+
"| `dialer_compose.py` | Composes a dialer strip. |\n"
|
|
84
|
+
"| `compose_dialer_cli.py` | CLI for the dialer strip. |\n",
|
|
85
|
+
)
|
|
86
|
+
write_text(package_directory / "dialer_compose.py", "x = 1\n")
|
|
87
|
+
write_text(package_directory / "compose_dialer_cli.py", "x = 1\n")
|
|
88
|
+
write_text(package_directory / "check_dialer_seam_cli.py", "x = 1\n")
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def _seed_skill_package(skill_root: Path) -> None:
|
|
92
|
+
write_text(skill_root / "SKILL.md", _SKILL_INVENTORY_MARKDOWN)
|
|
93
|
+
scripts_directory = skill_root / "scripts"
|
|
94
|
+
write_text(scripts_directory / "listed_alpha.py", "x = 1\n")
|
|
95
|
+
write_text(scripts_directory / "listed_beta.py", "x = 1\n")
|
|
96
|
+
write_text(scripts_directory / "unlisted_gamma.py", "x = 1\n")
|
|
@@ -0,0 +1,381 @@
|
|
|
1
|
+
"""Engine and diagnostic adapter tests for policy lint."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import subprocess
|
|
6
|
+
import types
|
|
7
|
+
from collections.abc import Callable, Iterable
|
|
8
|
+
from pathlib import Path, PurePosixPath
|
|
9
|
+
|
|
10
|
+
import pytest
|
|
11
|
+
from policy_lint import adapters
|
|
12
|
+
from policy_lint.config.constants import INCOMPLETE_EXIT_CODE
|
|
13
|
+
from policy_lint.engine import lint
|
|
14
|
+
from policy_lint.model import (
|
|
15
|
+
ContentOrigin,
|
|
16
|
+
Diagnostic,
|
|
17
|
+
Document,
|
|
18
|
+
DocumentRule,
|
|
19
|
+
DocumentSet,
|
|
20
|
+
LintReport,
|
|
21
|
+
LintRequest,
|
|
22
|
+
Location,
|
|
23
|
+
SelectionKind,
|
|
24
|
+
Severity,
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
_CHANGED_RULE_SETS = frozenset({"changed"})
|
|
28
|
+
_SAMPLE_SOURCE = "x = 1\n"
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _accepts_every_document(document: Document) -> bool:
|
|
32
|
+
return document.path.as_posix() != ""
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _source_document(
|
|
36
|
+
relative_path: str,
|
|
37
|
+
changed_lines: frozenset[int] | None = None,
|
|
38
|
+
) -> Document:
|
|
39
|
+
return Document(
|
|
40
|
+
PurePosixPath(relative_path),
|
|
41
|
+
_SAMPLE_SOURCE,
|
|
42
|
+
None,
|
|
43
|
+
changed_lines,
|
|
44
|
+
ContentOrigin.EDITOR,
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _changed_document_rule(
|
|
49
|
+
rule_id: str,
|
|
50
|
+
collect_diagnostics: Callable[[Document, Path], Iterable[Diagnostic]],
|
|
51
|
+
) -> DocumentRule:
|
|
52
|
+
return DocumentRule(
|
|
53
|
+
rule_id, _CHANGED_RULE_SETS, _accepts_every_document, collect_diagnostics
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def _ensure_git_repository(repository_root: Path) -> None:
|
|
58
|
+
git_directory = repository_root / ".git"
|
|
59
|
+
if git_directory.exists():
|
|
60
|
+
return
|
|
61
|
+
subprocess.run(
|
|
62
|
+
("git", "init"),
|
|
63
|
+
cwd=repository_root,
|
|
64
|
+
check=True,
|
|
65
|
+
capture_output=True,
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def _lint_documents(
|
|
70
|
+
repository_root: Path,
|
|
71
|
+
all_documents: tuple[Document, ...],
|
|
72
|
+
all_rules: tuple[DocumentRule, ...],
|
|
73
|
+
) -> LintReport:
|
|
74
|
+
_ensure_git_repository(repository_root)
|
|
75
|
+
document_set = DocumentSet(all_documents, SelectionKind.TEXT, repository_root)
|
|
76
|
+
request = LintRequest(repository_root, document_set)
|
|
77
|
+
return lint(request, all_registry=all_rules)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def _flaky_diagnostics(document: Document) -> tuple[Diagnostic, ...]:
|
|
81
|
+
if document.path.name == "second.py":
|
|
82
|
+
raise ImportError("detector missing")
|
|
83
|
+
return (
|
|
84
|
+
Diagnostic(
|
|
85
|
+
"flaky",
|
|
86
|
+
Severity.ERROR,
|
|
87
|
+
"partial finding",
|
|
88
|
+
Location(document.path, 2, 1),
|
|
89
|
+
),
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def _stable_diagnostics(document: Document) -> tuple[Diagnostic, ...]:
|
|
94
|
+
return (
|
|
95
|
+
Diagnostic(
|
|
96
|
+
"stable",
|
|
97
|
+
Severity.ERROR,
|
|
98
|
+
"kept finding",
|
|
99
|
+
Location(document.path, 3, 1),
|
|
100
|
+
),
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def test_line_prefix_message_keeps_real_line(
|
|
105
|
+
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
|
106
|
+
) -> None:
|
|
107
|
+
def fake_hooks_module(module_name: str) -> object:
|
|
108
|
+
if not module_name:
|
|
109
|
+
raise AssertionError("missing module")
|
|
110
|
+
return types.SimpleNamespace(
|
|
111
|
+
validate_content_for_full_gate=lambda *args, **kwargs: [
|
|
112
|
+
"Line 47: comment found"
|
|
113
|
+
]
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
monkeypatch.setattr(adapters, "_hooks_module", fake_hooks_module)
|
|
117
|
+
all_diagnostics = adapters.code_rule_diagnostics(
|
|
118
|
+
Document.from_text("pkg/mod.py", _SAMPLE_SOURCE), tmp_path
|
|
119
|
+
)
|
|
120
|
+
assert len(all_diagnostics) == 1
|
|
121
|
+
assert all_diagnostics[0].location is not None
|
|
122
|
+
assert all_diagnostics[0].location.start_line == 47
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def test_path_line_message_keeps_real_line(
|
|
126
|
+
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
|
127
|
+
) -> None:
|
|
128
|
+
fake_outcome = types.SimpleNamespace(
|
|
129
|
+
name="ruff",
|
|
130
|
+
passed=False,
|
|
131
|
+
output="C:/Users/example/AppData/Local/Temp/tmp123/pkg/mod.py:47: magic number",
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
def fake_hooks_module(module_name: str) -> object:
|
|
135
|
+
if not module_name:
|
|
136
|
+
raise AssertionError("missing module")
|
|
137
|
+
return types.SimpleNamespace(
|
|
138
|
+
validate_proposed_file=lambda *args, **kwargs: [fake_outcome]
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
monkeypatch.setattr(adapters, "_hooks_module", fake_hooks_module)
|
|
142
|
+
all_diagnostics = adapters.validator_diagnostics(
|
|
143
|
+
Document.from_text("pkg/mod.py", _SAMPLE_SOURCE), tmp_path
|
|
144
|
+
)
|
|
145
|
+
assert len(all_diagnostics) == 1
|
|
146
|
+
assert all_diagnostics[0].location is not None
|
|
147
|
+
assert all_diagnostics[0].location.start_line == 47
|
|
148
|
+
assert all_diagnostics[0].message == "pkg/mod.py:47: magic number"
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def test_unknown_line_has_no_location() -> None:
|
|
152
|
+
document = Document.from_text("pkg/mod.py", _SAMPLE_SOURCE)
|
|
153
|
+
all_diagnostics = adapters._diagnostics_for_messages(
|
|
154
|
+
document, "plain-language", ("Banned prose term: landscape",)
|
|
155
|
+
)
|
|
156
|
+
assert len(all_diagnostics) == 1
|
|
157
|
+
assert all_diagnostics[0].location is None
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def test_changed_line_filter_keeps_matching_line(tmp_path: Path) -> None:
|
|
161
|
+
repository_root = tmp_path.resolve()
|
|
162
|
+
document = _source_document("pkg/mod.py", changed_lines=frozenset({47}))
|
|
163
|
+
|
|
164
|
+
def collect_diagnostics(
|
|
165
|
+
document: Document, repository_root: Path | None = None
|
|
166
|
+
) -> tuple[Diagnostic, ...]:
|
|
167
|
+
del repository_root
|
|
168
|
+
return (
|
|
169
|
+
Diagnostic(
|
|
170
|
+
"code-rules",
|
|
171
|
+
Severity.ERROR,
|
|
172
|
+
"Line 47: comment found",
|
|
173
|
+
Location(document.path, 47, 1),
|
|
174
|
+
),
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
lint_report = _lint_documents(
|
|
178
|
+
repository_root,
|
|
179
|
+
(document,),
|
|
180
|
+
(_changed_document_rule("code-rules", collect_diagnostics),),
|
|
181
|
+
)
|
|
182
|
+
assert len(lint_report.diagnostics) == 1
|
|
183
|
+
assert lint_report.diagnostics[0].location is not None
|
|
184
|
+
assert lint_report.diagnostics[0].location.start_line == 47
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def test_changed_line_filter_drops_other_located_line(tmp_path: Path) -> None:
|
|
188
|
+
repository_root = tmp_path.resolve()
|
|
189
|
+
document = _source_document("pkg/mod.py", changed_lines=frozenset({1}))
|
|
190
|
+
|
|
191
|
+
def collect_diagnostics(
|
|
192
|
+
document: Document, repository_root: Path | None = None
|
|
193
|
+
) -> tuple[Diagnostic, ...]:
|
|
194
|
+
del repository_root
|
|
195
|
+
return (
|
|
196
|
+
Diagnostic(
|
|
197
|
+
"code-rules",
|
|
198
|
+
Severity.ERROR,
|
|
199
|
+
"Line 47: comment found",
|
|
200
|
+
Location(document.path, 47, 1),
|
|
201
|
+
),
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
lint_report = _lint_documents(
|
|
205
|
+
repository_root,
|
|
206
|
+
(document,),
|
|
207
|
+
(_changed_document_rule("code-rules", collect_diagnostics),),
|
|
208
|
+
)
|
|
209
|
+
assert lint_report.diagnostics == ()
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
def test_lint_keeps_located_findings_when_changed_lines_are_empty(
|
|
213
|
+
tmp_path: Path,
|
|
214
|
+
) -> None:
|
|
215
|
+
repository_root = tmp_path.resolve()
|
|
216
|
+
document = _source_document("pkg/mod.py", changed_lines=frozenset())
|
|
217
|
+
|
|
218
|
+
def collect_diagnostics(
|
|
219
|
+
document: Document, repository_root: Path | None = None
|
|
220
|
+
) -> tuple[Diagnostic, ...]:
|
|
221
|
+
del repository_root
|
|
222
|
+
return (
|
|
223
|
+
Diagnostic(
|
|
224
|
+
"probe",
|
|
225
|
+
Severity.ERROR,
|
|
226
|
+
"finding",
|
|
227
|
+
Location(document.path, 1, 1),
|
|
228
|
+
),
|
|
229
|
+
)
|
|
230
|
+
|
|
231
|
+
lint_report = _lint_documents(
|
|
232
|
+
repository_root,
|
|
233
|
+
(document,),
|
|
234
|
+
(_changed_document_rule("probe", collect_diagnostics),),
|
|
235
|
+
)
|
|
236
|
+
assert len(lint_report.diagnostics) == 1
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
def test_code_rule_diagnostics_includes_comment_policy_without_prior_text(
|
|
240
|
+
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
|
241
|
+
) -> None:
|
|
242
|
+
include_comment_policy: dict[str, bool] = {}
|
|
243
|
+
|
|
244
|
+
def validate_content_for_full_gate(
|
|
245
|
+
*all_arguments: object, **all_keywords: object
|
|
246
|
+
) -> list[str]:
|
|
247
|
+
del all_arguments
|
|
248
|
+
include_comment_policy["enabled"] = bool(all_keywords["include_comment_policy"])
|
|
249
|
+
return []
|
|
250
|
+
|
|
251
|
+
monkeypatch.setattr(
|
|
252
|
+
adapters,
|
|
253
|
+
"_hooks_module",
|
|
254
|
+
lambda _module_name: types.SimpleNamespace(
|
|
255
|
+
validate_content_for_full_gate=validate_content_for_full_gate
|
|
256
|
+
),
|
|
257
|
+
)
|
|
258
|
+
adapters.code_rule_diagnostics(
|
|
259
|
+
Document.from_text("pkg/new.py", _SAMPLE_SOURCE), tmp_path
|
|
260
|
+
)
|
|
261
|
+
assert include_comment_policy == {"enabled": True}
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
def test_code_rule_diagnostics_keeps_legacy_stdout_private(
|
|
265
|
+
tmp_path: Path, monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]
|
|
266
|
+
) -> None:
|
|
267
|
+
def validate_content_for_full_gate(
|
|
268
|
+
*all_arguments: object, **all_keywords: object
|
|
269
|
+
) -> list[str]:
|
|
270
|
+
del all_arguments, all_keywords
|
|
271
|
+
print("large file advisory")
|
|
272
|
+
return ["Line 2: real diagnostic"]
|
|
273
|
+
|
|
274
|
+
monkeypatch.setattr(
|
|
275
|
+
adapters,
|
|
276
|
+
"_hooks_module",
|
|
277
|
+
lambda _module_name: types.SimpleNamespace(
|
|
278
|
+
validate_content_for_full_gate=validate_content_for_full_gate
|
|
279
|
+
),
|
|
280
|
+
)
|
|
281
|
+
all_diagnostics = adapters.code_rule_diagnostics(
|
|
282
|
+
Document.from_text("pkg/mod.py", _SAMPLE_SOURCE), tmp_path
|
|
283
|
+
)
|
|
284
|
+
captured_output = capsys.readouterr()
|
|
285
|
+
assert captured_output.out == ""
|
|
286
|
+
assert captured_output.err == ""
|
|
287
|
+
assert [each_diagnostic.message for each_diagnostic in all_diagnostics] == [
|
|
288
|
+
"Line 2: real diagnostic"
|
|
289
|
+
]
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
def test_state_description_diagnostics_maps_and_scopes_phrases(
|
|
293
|
+
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
|
294
|
+
) -> None:
|
|
295
|
+
monkeypatch.setattr(
|
|
296
|
+
adapters,
|
|
297
|
+
"_hooks_module",
|
|
298
|
+
lambda _module_name: types.SimpleNamespace(
|
|
299
|
+
find_violations=lambda _text, _path: ["used to", "replaced by"]
|
|
300
|
+
),
|
|
301
|
+
)
|
|
302
|
+
source_document = Document(
|
|
303
|
+
PurePosixPath("module.py"),
|
|
304
|
+
"# " + "replaced" + " by unchanged\n# " + "used" + " to changed\n",
|
|
305
|
+
None,
|
|
306
|
+
frozenset({2}),
|
|
307
|
+
ContentOrigin.EDITOR,
|
|
308
|
+
)
|
|
309
|
+
lint_report = _lint_documents(
|
|
310
|
+
tmp_path,
|
|
311
|
+
(source_document,),
|
|
312
|
+
(_changed_document_rule("state-description", adapters.state_description_diagnostics),),
|
|
313
|
+
)
|
|
314
|
+
assert len(lint_report.diagnostics) == 1
|
|
315
|
+
assert lint_report.diagnostics[0].location is not None
|
|
316
|
+
assert lint_report.diagnostics[0].location.start_line == 2
|
|
317
|
+
|
|
318
|
+
|
|
319
|
+
def test_unlocated_finding_survives_changed_line_filter(tmp_path: Path) -> None:
|
|
320
|
+
repository_root = tmp_path.resolve()
|
|
321
|
+
document = _source_document("pkg/mod.py", changed_lines=frozenset({8}))
|
|
322
|
+
|
|
323
|
+
def collect_diagnostics(
|
|
324
|
+
document: Document, repository_root: Path | None = None
|
|
325
|
+
) -> tuple[Diagnostic, ...]:
|
|
326
|
+
del repository_root
|
|
327
|
+
return (
|
|
328
|
+
Diagnostic(
|
|
329
|
+
"plain-language",
|
|
330
|
+
Severity.ERROR,
|
|
331
|
+
"Banned prose term: landscape",
|
|
332
|
+
None,
|
|
333
|
+
),
|
|
334
|
+
)
|
|
335
|
+
|
|
336
|
+
lint_report = _lint_documents(
|
|
337
|
+
repository_root,
|
|
338
|
+
(document,),
|
|
339
|
+
(_changed_document_rule("plain-language", collect_diagnostics),),
|
|
340
|
+
)
|
|
341
|
+
assert len(lint_report.diagnostics) == 1
|
|
342
|
+
assert lint_report.diagnostics[0].location is None
|
|
343
|
+
|
|
344
|
+
|
|
345
|
+
def test_failed_adapter_rolls_back_continues_and_exits_incomplete(
|
|
346
|
+
tmp_path: Path,
|
|
347
|
+
) -> None:
|
|
348
|
+
repository_root = tmp_path.resolve()
|
|
349
|
+
all_documents = (
|
|
350
|
+
_source_document("pkg/first.py"),
|
|
351
|
+
_source_document("pkg/second.py"),
|
|
352
|
+
)
|
|
353
|
+
all_rules = (
|
|
354
|
+
_changed_document_rule("flaky", lambda document, _root: _flaky_diagnostics(document)),
|
|
355
|
+
_changed_document_rule("stable", lambda document, _root: _stable_diagnostics(document)),
|
|
356
|
+
)
|
|
357
|
+
lint_report = _lint_documents(repository_root, all_documents, all_rules)
|
|
358
|
+
assert lint_report.failed_rules == ("flaky",)
|
|
359
|
+
assert lint_report.executed_rules == ("stable",)
|
|
360
|
+
assert lint_report.exit_code == INCOMPLETE_EXIT_CODE
|
|
361
|
+
assert [each_rule.rule_id for each_rule in lint_report.diagnostics] == [
|
|
362
|
+
"stable",
|
|
363
|
+
"stable",
|
|
364
|
+
]
|
|
365
|
+
|
|
366
|
+
|
|
367
|
+
def test_process_interrupt_is_not_swallowed(tmp_path: Path) -> None:
|
|
368
|
+
repository_root = tmp_path.resolve()
|
|
369
|
+
|
|
370
|
+
def interrupt_diagnostics(
|
|
371
|
+
document: Document, repository_root: Path | None = None
|
|
372
|
+
) -> tuple[Diagnostic, ...]:
|
|
373
|
+
del document, repository_root
|
|
374
|
+
raise KeyboardInterrupt
|
|
375
|
+
|
|
376
|
+
with pytest.raises(KeyboardInterrupt):
|
|
377
|
+
_lint_documents(
|
|
378
|
+
repository_root,
|
|
379
|
+
(_source_document("pkg/mod.py"),),
|
|
380
|
+
(_changed_document_rule("interrupt", interrupt_diagnostics),),
|
|
381
|
+
)
|