claude-dev-env 8.2.0 → 8.4.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/agents/poteto-agent.md +6 -0
- package/.agents/agents/test_agent_frontmatter.py +9 -9
- package/.agents/agents/test_pr_description_writer_style.py +1 -1
- package/.agents/agents-archived/ARCHIVE-MANIFEST.md +15 -0
- package/.agents/{agents → agents-archived}/code-quality-agent.md +1 -1
- package/.agents/{agents → agents-archived}/session-advisor.md +1 -1
- package/.agents/skills/_shared/end-of-run-gotcha-recommendations.md +2 -2
- package/.agents/skills/grok-spawn/SKILL.md +9 -6
- package/.agents/skills/grok-spawn/reference/flag-profiles.md +12 -8
- package/.agents/skills/issue-tracker/SKILL.md +2 -2
- package/.agents/skills/orchestrator/SKILL.md +16 -15
- package/.agents/skills/orchestrator-refresh/SKILL.md +2 -0
- package/.agents/skills/pull-request/SKILL.md +14 -5
- package/.agents/skills/pull-request/reference/publication-tasks.md +1 -1
- package/.agents/skills/team-advisor/SKILL.md +13 -8
- package/_shared/advisor/reference/spawn-walk-log.md +2 -1
- package/_shared/advisor/scripts/config/advisor_scripts_constants/model_tier_run_validator_constants.py +3 -0
- package/_shared/advisor/scripts/model_tier_run_validator.py +169 -14
- package/_shared/advisor/scripts/tests/test_codex_astra_advisor.py +20 -0
- package/_shared/advisor/scripts/tests/test_model_tier_run_validator.py +99 -0
- package/bin/install.agents-home.test.mjs +54 -94
- package/bin/install.mjs +31 -24
- package/bin/install.test.mjs +35 -0
- package/bin/resolve-package-managed-directory.test.mjs +1 -1
- package/bin/subagent-model-policy.test.mjs +15 -8
- package/docs/references/advisor-tool.md +32 -0
- package/docs/references/team-advisor-skill.md +1 -1
- package/docs/subagent-model-routing.md +6 -5
- package/hooks/blocking/tdd_enforcer.py +2 -3
- package/hooks/blocking/test_bash_post_call_dispatcher.py +7 -24
- package/hooks/hooks_constants/bash_post_call_dispatcher_constants.py +0 -3
- package/hooks/hooks_constants/hosted_hook_runner.py +1 -1
- package/hooks/hooks_constants/msys_path_conversion_advisor_constants.py +3 -35
- package/hooks/hooks_constants/test_bash_post_call_dispatcher_constants.py +2 -10
- package/hooks/routing/subagent_model_routing.mjs +15 -0
- package/hooks/routing/subagent_model_routing.test.mjs +58 -25
- package/hooks/validators/README.md +0 -1
- package/hooks/validators/test_files/EXECUTIVE_SUMMARY.md +1 -2
- package/package.json +1 -1
- package/rules/git-workflow.md +3 -3
- package/rules/shell-invocation.md +0 -2
- package/rules/subagent-model-policy.json +5 -13
- package/rules/testing.md +0 -2
- package/scripts/dev_env_scripts_constants/grok_worker_constants.py +3 -3
- package/scripts/grok_headless_runner.py +19 -1
- package/scripts/grok_worker_preflight.py +67 -1
- package/scripts/policy_lint/config/approved_test_pairs.py +9 -0
- package/scripts/resolve_advisor_model_route.mjs +2 -2
- package/scripts/subagent_model_policy.mjs +4 -1
- package/scripts/test_grok_headless_runner.py +35 -1
- package/scripts/test_grok_worker_preflight.py +73 -0
- package/scripts/tests/resolve_advisor_model_route.test.mjs +3 -3
- package/scripts/tests/test_policy_lint_rules_pairing.py +16 -0
- package/scripts/tests/test_rules.py +5 -5
- package/scripts/windows/pstack/pstack-command-shim.ps1 +41 -0
- package/scripts/windows/pstack/ship-pr.cmd +3 -0
- package/scripts/windows/pstack/watch-pr.cmd +3 -0
- package/hooks/advisory/msys_path_conversion_advisor.py +0 -200
- package/hooks/advisory/test_msys_path_conversion_advisor.py +0 -223
- package/hooks/hooks_constants/test_failure_recorder_constants.py +0 -34
- package/hooks/observability/test_failure_recorder.py +0 -221
- package/hooks/observability/test_test_failure_recorder.py +0 -173
- package/hooks/validators/VALIDATION_REPORT.md +0 -287
- package/hooks/validators/validator_defaults.py +0 -5
- /package/.agents/{agents → agents-archived}/clean-coder.md +0 -0
- /package/.agents/{agents → agents-archived}/git-commit-crafter.md +0 -0
- /package/.agents/{agents → agents-archived}/issue-tracker.md +0 -0
- /package/.agents/{agents → agents-archived}/plan-packet-validator.md +0 -0
- /package/.agents/{agents → agents-archived}/pr-description-writer.md +0 -0
- /package/.agents/{agents → agents-archived}/skill-writer-agent.md +0 -0
|
@@ -1,173 +0,0 @@
|
|
|
1
|
-
"""Behavior tests for the test_failure_recorder PostToolUse hook.
|
|
2
|
-
|
|
3
|
-
Drives the hook's main through real stdin payloads shaped like a genuine
|
|
4
|
-
Bash PostToolUse call, then reads the shared content-hash store back to
|
|
5
|
-
confirm what -- if anything -- it recorded. The store's real state file
|
|
6
|
-
under the OS temp directory is read directly, the same way
|
|
7
|
-
test_content_hash_store.py and test_tdd_enforcer.py already do, rather than
|
|
8
|
-
mocking tempfile.gettempdir. ``hooks/conftest.py`` already puts the hooks
|
|
9
|
-
and blocking directories on sys.path for test collection, so this file needs
|
|
10
|
-
no bootstrap of its own before importing content_hash_store.
|
|
11
|
-
"""
|
|
12
|
-
|
|
13
|
-
import contextlib
|
|
14
|
-
import importlib.util
|
|
15
|
-
import io
|
|
16
|
-
import json
|
|
17
|
-
from pathlib import Path
|
|
18
|
-
from unittest import mock
|
|
19
|
-
|
|
20
|
-
from tdd_enforcer_parts import content_hash_store
|
|
21
|
-
|
|
22
|
-
_HOOK_DIRECTORY = Path(__file__).parent
|
|
23
|
-
_hook_spec = importlib.util.spec_from_file_location(
|
|
24
|
-
"test_failure_recorder_under_test", _HOOK_DIRECTORY / "test_failure_recorder.py"
|
|
25
|
-
)
|
|
26
|
-
assert _hook_spec is not None
|
|
27
|
-
assert _hook_spec.loader is not None
|
|
28
|
-
_hook_module = importlib.util.module_from_spec(_hook_spec)
|
|
29
|
-
_hook_spec.loader.exec_module(_hook_module)
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
def _run_hook_with_payload(payload: dict) -> None:
|
|
33
|
-
with (
|
|
34
|
-
mock.patch("sys.stdin", io.StringIO(json.dumps(payload))),
|
|
35
|
-
contextlib.suppress(SystemExit),
|
|
36
|
-
):
|
|
37
|
-
_hook_module.main()
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
def _recorded_failure_entry(test_file: Path, session_id: str, repository_root: str) -> dict | None:
|
|
41
|
-
state_file = content_hash_store._state_file_path(session_id, repository_root)
|
|
42
|
-
if not state_file.exists():
|
|
43
|
-
return None
|
|
44
|
-
state = json.loads(state_file.read_text())
|
|
45
|
-
return state.get(content_hash_store._state_key_for(test_file))
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
def _bash_payload(command: str, tool_response: object, cwd: Path, session_id: str) -> dict:
|
|
49
|
-
return {
|
|
50
|
-
"session_id": session_id,
|
|
51
|
-
"cwd": str(cwd),
|
|
52
|
-
"tool_name": "Bash",
|
|
53
|
-
"tool_input": {"command": command},
|
|
54
|
-
"tool_response": tool_response,
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
_FAILING_OUTPUT = (
|
|
59
|
-
"Error: Exit code 1\n"
|
|
60
|
-
"F [100%]\n"
|
|
61
|
-
"=================================== FAILURES ===================================\n"
|
|
62
|
-
"FAILED test_orders.py::test_fulfill - AssertionError\n"
|
|
63
|
-
"1 failed in 0.02s"
|
|
64
|
-
)
|
|
65
|
-
_PASSING_OUTPUT = {"stdout": "1 passed in 0.02s", "stderr": "", "interrupted": False}
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
def test_records_a_failure_for_a_bare_pytest_command_naming_a_real_file(
|
|
69
|
-
tmp_path: Path,
|
|
70
|
-
) -> None:
|
|
71
|
-
test_file = tmp_path / "test_orders.py"
|
|
72
|
-
test_file.write_text("def test_fulfill(): assert False\n")
|
|
73
|
-
session_id = "recorder-session-1"
|
|
74
|
-
|
|
75
|
-
_run_hook_with_payload(
|
|
76
|
-
_bash_payload("pytest test_orders.py", _FAILING_OUTPUT, tmp_path, session_id)
|
|
77
|
-
)
|
|
78
|
-
|
|
79
|
-
entry = _recorded_failure_entry(test_file, session_id, str(tmp_path))
|
|
80
|
-
assert entry is not None
|
|
81
|
-
assert entry[content_hash_store.STORED_FAILURE_EXIT_STATUS_KEY] == 1
|
|
82
|
-
assert entry[content_hash_store.STORED_FAILURE_COMMAND_KEY] == "pytest test_orders.py"
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
def test_does_not_record_for_a_passing_command(tmp_path: Path) -> None:
|
|
86
|
-
test_file = tmp_path / "test_orders.py"
|
|
87
|
-
test_file.write_text("def test_fulfill(): pass\n")
|
|
88
|
-
session_id = "recorder-session-2"
|
|
89
|
-
|
|
90
|
-
_run_hook_with_payload(
|
|
91
|
-
_bash_payload("pytest test_orders.py", _PASSING_OUTPUT, tmp_path, session_id)
|
|
92
|
-
)
|
|
93
|
-
|
|
94
|
-
assert _recorded_failure_entry(test_file, session_id, str(tmp_path)) is None
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
def test_does_not_record_for_a_non_pytest_command(tmp_path: Path) -> None:
|
|
98
|
-
test_file = tmp_path / "test_orders.py"
|
|
99
|
-
test_file.write_text("def test_fulfill(): assert False\n")
|
|
100
|
-
session_id = "recorder-session-3"
|
|
101
|
-
|
|
102
|
-
_run_hook_with_payload(
|
|
103
|
-
_bash_payload("npm test", "Error: Exit code 1\n1 failing", tmp_path, session_id)
|
|
104
|
-
)
|
|
105
|
-
|
|
106
|
-
assert _recorded_failure_entry(test_file, session_id, str(tmp_path)) is None
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
def test_does_not_record_for_a_chained_command(tmp_path: Path) -> None:
|
|
110
|
-
test_file = tmp_path / "test_orders.py"
|
|
111
|
-
test_file.write_text("def test_fulfill(): assert False\n")
|
|
112
|
-
session_id = "recorder-session-4"
|
|
113
|
-
|
|
114
|
-
_run_hook_with_payload(
|
|
115
|
-
_bash_payload("cd /repo && pytest test_orders.py", _FAILING_OUTPUT, tmp_path, session_id)
|
|
116
|
-
)
|
|
117
|
-
|
|
118
|
-
assert _recorded_failure_entry(test_file, session_id, str(tmp_path)) is None
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
def test_does_not_record_for_a_bare_pytest_run_with_no_path_argument(
|
|
122
|
-
tmp_path: Path,
|
|
123
|
-
) -> None:
|
|
124
|
-
test_file = tmp_path / "test_orders.py"
|
|
125
|
-
test_file.write_text("def test_fulfill(): assert False\n")
|
|
126
|
-
session_id = "recorder-session-5"
|
|
127
|
-
|
|
128
|
-
_run_hook_with_payload(_bash_payload("pytest -q", _FAILING_OUTPUT, tmp_path, session_id))
|
|
129
|
-
|
|
130
|
-
assert _recorded_failure_entry(test_file, session_id, str(tmp_path)) is None
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
def test_records_only_the_named_file_not_an_option_or_expression_token(
|
|
134
|
-
tmp_path: Path,
|
|
135
|
-
) -> None:
|
|
136
|
-
test_file = tmp_path / "test_orders.py"
|
|
137
|
-
test_file.write_text("def test_fulfill(): assert False\n")
|
|
138
|
-
session_id = "recorder-session-6"
|
|
139
|
-
|
|
140
|
-
_run_hook_with_payload(
|
|
141
|
-
_bash_payload("pytest test_orders.py -k fulfill", _FAILING_OUTPUT, tmp_path, session_id)
|
|
142
|
-
)
|
|
143
|
-
|
|
144
|
-
entry = _recorded_failure_entry(test_file, session_id, str(tmp_path))
|
|
145
|
-
assert entry is not None
|
|
146
|
-
state_file = content_hash_store._state_file_path(session_id, str(tmp_path))
|
|
147
|
-
state = json.loads(state_file.read_text())
|
|
148
|
-
assert len(state) == 1
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
def test_ignores_a_tool_other_than_bash(tmp_path: Path) -> None:
|
|
152
|
-
test_file = tmp_path / "test_orders.py"
|
|
153
|
-
test_file.write_text("def test_fulfill(): assert False\n")
|
|
154
|
-
session_id = "recorder-session-7"
|
|
155
|
-
payload = _bash_payload("pytest test_orders.py", _FAILING_OUTPUT, tmp_path, session_id)
|
|
156
|
-
payload["tool_name"] = "Write"
|
|
157
|
-
|
|
158
|
-
_run_hook_with_payload(payload)
|
|
159
|
-
|
|
160
|
-
assert _recorded_failure_entry(test_file, session_id, str(tmp_path)) is None
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
def test_does_not_record_when_the_named_file_does_not_exist_on_disk(
|
|
164
|
-
tmp_path: Path,
|
|
165
|
-
) -> None:
|
|
166
|
-
session_id = "recorder-session-8"
|
|
167
|
-
missing_file = tmp_path / "test_missing.py"
|
|
168
|
-
|
|
169
|
-
_run_hook_with_payload(
|
|
170
|
-
_bash_payload("pytest test_missing.py", _FAILING_OUTPUT, tmp_path, session_id)
|
|
171
|
-
)
|
|
172
|
-
|
|
173
|
-
assert _recorded_failure_entry(missing_file, session_id, str(tmp_path)) is None
|
|
@@ -1,287 +0,0 @@
|
|
|
1
|
-
# Test Safety Checks Validator - Comprehensive Validation Report
|
|
2
|
-
|
|
3
|
-
## Executive Summary
|
|
4
|
-
|
|
5
|
-
**Critical Gaps Found: 2**
|
|
6
|
-
**Minor Issues: 1**
|
|
7
|
-
**Tests Passed: 14/17**
|
|
8
|
-
|
|
9
|
-
---
|
|
10
|
-
|
|
11
|
-
## Check 1: No Skip Decorators
|
|
12
|
-
|
|
13
|
-
### Test Results
|
|
14
|
-
|
|
15
|
-
| Test Case | Expected | Result | Status |
|
|
16
|
-
|-----------|----------|--------|--------|
|
|
17
|
-
| `@skip` | CAUGHT | CAUGHT | PASS |
|
|
18
|
-
| `@pytest.mark.skipif(...)` | CAUGHT | CAUGHT | PASS |
|
|
19
|
-
| `@unittest.skipIf(...)` | CAUGHT | CAUGHT | PASS |
|
|
20
|
-
| `@skip()` with parentheses | CAUGHT | CAUGHT | PASS |
|
|
21
|
-
| `@pytest.mark.xfail` | ALLOWED | ALLOWED | PASS |
|
|
22
|
-
| `@skip_on_windows` custom | ALLOWED | ALLOWED | PASS |
|
|
23
|
-
| `@Skip` (capital S) | CAUGHT | **ALLOWED** | **FAIL** |
|
|
24
|
-
| `@unittest.skipUnless(...)` | CAUGHT | CAUGHT | PASS |
|
|
25
|
-
| `@pytest.mark.skip` (no args) | CAUGHT | CAUGHT | PASS |
|
|
26
|
-
|
|
27
|
-
### GAP #1: Case-Sensitive Decorator Matching
|
|
28
|
-
|
|
29
|
-
**Severity: MEDIUM**
|
|
30
|
-
|
|
31
|
-
**Issue:** The validator uses case-sensitive matching, so `@Skip` (capital S) is NOT caught.
|
|
32
|
-
|
|
33
|
-
**Current Code (lines 16-22):**
|
|
34
|
-
```python
|
|
35
|
-
SKIP_DECORATOR_NAMES = frozenset([
|
|
36
|
-
"skip",
|
|
37
|
-
"skipif",
|
|
38
|
-
"skipunless",
|
|
39
|
-
"skipIf",
|
|
40
|
-
"skipUnless",
|
|
41
|
-
])
|
|
42
|
-
```
|
|
43
|
-
|
|
44
|
-
**Problem:** This misses decorators with different capitalization like `@Skip`, `@SkipIf`, `@SKIP`, etc.
|
|
45
|
-
|
|
46
|
-
**Recommended Fix:**
|
|
47
|
-
```python
|
|
48
|
-
def _get_decorator_name(decorator: ast.expr) -> str:
|
|
49
|
-
"""Extract the name from a decorator node (lowercase for comparison)."""
|
|
50
|
-
# ... existing logic ...
|
|
51
|
-
return name.lower() # Convert to lowercase for case-insensitive matching
|
|
52
|
-
```
|
|
53
|
-
|
|
54
|
-
Then update the frozenset to all lowercase:
|
|
55
|
-
```python
|
|
56
|
-
SKIP_DECORATOR_NAMES = frozenset([
|
|
57
|
-
"skip",
|
|
58
|
-
"skipif",
|
|
59
|
-
"skipunless",
|
|
60
|
-
])
|
|
61
|
-
```
|
|
62
|
-
|
|
63
|
-
**Impact:** Currently allows `@Skip`, `@SkipIf`, `@SkipUnless` to slip through.
|
|
64
|
-
|
|
65
|
-
---
|
|
66
|
-
|
|
67
|
-
## Check 2: DEBUG Guard in Management Commands
|
|
68
|
-
|
|
69
|
-
### Test Results
|
|
70
|
-
|
|
71
|
-
| Test Case | Expected | Result | Status |
|
|
72
|
-
|-----------|----------|--------|--------|
|
|
73
|
-
| No DEBUG check | CAUGHT | CAUGHT | PASS |
|
|
74
|
-
| `if not settings.DEBUG: raise` | ALLOWED | ALLOWED | PASS |
|
|
75
|
-
| `if not settings.DEBUG: return` | ALLOWED | ALLOWED | PASS |
|
|
76
|
-
| Using imported `DEBUG` variable | CAUGHT | CAUGHT | PASS |
|
|
77
|
-
| DEBUG check in helper function | CAUGHT | CAUGHT | PASS |
|
|
78
|
-
| DEBUG check after 5 statements | CAUGHT | CAUGHT | PASS |
|
|
79
|
-
| `if settings.DEBUG: ... else: raise` | ALLOWED | **CAUGHT** | **FAIL** |
|
|
80
|
-
| `if not settings.DEBUG and X:` | DEBATABLE | CAUGHT | WARN |
|
|
81
|
-
| File not in `management/commands/` | IGNORED | IGNORED | PASS |
|
|
82
|
-
|
|
83
|
-
### GAP #2: Positive DEBUG Check Pattern Rejected
|
|
84
|
-
|
|
85
|
-
**Severity: HIGH**
|
|
86
|
-
|
|
87
|
-
**Issue:** The validator incorrectly rejects the pattern `if settings.DEBUG: ... else: raise`.
|
|
88
|
-
|
|
89
|
-
**Test File:** `cmd_07_positive_debug_check.py`
|
|
90
|
-
```python
|
|
91
|
-
def handle(self, *args, **options):
|
|
92
|
-
if settings.DEBUG:
|
|
93
|
-
print("Running in DEBUG mode")
|
|
94
|
-
else:
|
|
95
|
-
raise CommandError("This command can only be run in DEBUG mode")
|
|
96
|
-
```
|
|
97
|
-
|
|
98
|
-
**Expected:** ALLOWED (this is a valid DEBUG guard)
|
|
99
|
-
**Actual:** CAUGHT (flagged as violation)
|
|
100
|
-
|
|
101
|
-
**Root Cause (lines 186-187):**
|
|
102
|
-
```python
|
|
103
|
-
if _is_debug_check(test) and stmt.orelse:
|
|
104
|
-
return False # This returns False when it should return True!
|
|
105
|
-
```
|
|
106
|
-
|
|
107
|
-
**The Logic Flaw:**
|
|
108
|
-
- Line 186: Detects `if settings.DEBUG:` and checks if there's an `else` clause
|
|
109
|
-
- Line 187: Returns `False` (no DEBUG guard found)
|
|
110
|
-
- **BUG:** This should return `True` because the pattern IS a valid guard!
|
|
111
|
-
|
|
112
|
-
**Recommended Fix:**
|
|
113
|
-
```python
|
|
114
|
-
def _has_debug_guard(func: ast.FunctionDef) -> bool:
|
|
115
|
-
"""Check if a function has a settings.DEBUG guard at the start."""
|
|
116
|
-
if not func.body:
|
|
117
|
-
return False
|
|
118
|
-
|
|
119
|
-
for stmt in func.body[:5]:
|
|
120
|
-
if isinstance(stmt, ast.If):
|
|
121
|
-
test = stmt.test
|
|
122
|
-
|
|
123
|
-
# Pattern 1: if not settings.DEBUG: raise/return
|
|
124
|
-
if isinstance(test, ast.UnaryOp) and isinstance(test.op, ast.Not):
|
|
125
|
-
if _is_debug_check(test.operand):
|
|
126
|
-
return True
|
|
127
|
-
|
|
128
|
-
# Pattern 2: if settings.DEBUG: ... else: raise/return
|
|
129
|
-
if _is_debug_check(test):
|
|
130
|
-
if stmt.orelse:
|
|
131
|
-
# Check if else clause has raise/return
|
|
132
|
-
if _has_early_exit(stmt.orelse):
|
|
133
|
-
return True
|
|
134
|
-
# if settings.DEBUG: with no else is also valid
|
|
135
|
-
return True
|
|
136
|
-
|
|
137
|
-
return False
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
def _has_early_exit(statements: List[ast.stmt]) -> bool:
|
|
141
|
-
"""Check if statements contain raise or return."""
|
|
142
|
-
for stmt in statements:
|
|
143
|
-
if isinstance(stmt, (ast.Raise, ast.Return)):
|
|
144
|
-
return True
|
|
145
|
-
# Check nested if/else
|
|
146
|
-
if isinstance(stmt, ast.If):
|
|
147
|
-
if _has_early_exit(stmt.body) or (stmt.orelse and _has_early_exit(stmt.orelse)):
|
|
148
|
-
return True
|
|
149
|
-
return False
|
|
150
|
-
```
|
|
151
|
-
|
|
152
|
-
**Impact:** Currently REJECTS valid DEBUG guard patterns, forcing developers to use only negative checks.
|
|
153
|
-
|
|
154
|
-
### MINOR ISSUE: Complex Boolean Expressions
|
|
155
|
-
|
|
156
|
-
**Severity: LOW**
|
|
157
|
-
|
|
158
|
-
**Issue:** The validator rejects `if not settings.DEBUG and other_condition:`.
|
|
159
|
-
|
|
160
|
-
**Test File:** `cmd_08_debug_with_and.py`
|
|
161
|
-
```python
|
|
162
|
-
if not settings.DEBUG and True:
|
|
163
|
-
raise CommandError("This command can only be run in DEBUG mode")
|
|
164
|
-
```
|
|
165
|
-
|
|
166
|
-
**Expected:** DEBATABLE (could be allowed if DEBUG is the primary condition)
|
|
167
|
-
**Actual:** CAUGHT (flagged as violation)
|
|
168
|
-
|
|
169
|
-
**Analysis:**
|
|
170
|
-
- Current code only checks for simple `UnaryOp(Not)` with `settings.DEBUG`
|
|
171
|
-
- Doesn't handle `BoolOp` (and/or) expressions
|
|
172
|
-
- This is arguably correct behavior - keeps the check simple and strict
|
|
173
|
-
|
|
174
|
-
**Recommendation:** Document this limitation. Developers should use:
|
|
175
|
-
```python
|
|
176
|
-
if not settings.DEBUG:
|
|
177
|
-
raise CommandError("...")
|
|
178
|
-
# Then check other conditions separately
|
|
179
|
-
```
|
|
180
|
-
|
|
181
|
-
---
|
|
182
|
-
|
|
183
|
-
## Additional Edge Cases Tested
|
|
184
|
-
|
|
185
|
-
### Nested Directory Structure
|
|
186
|
-
- File in `management/commands/subdir/command.py` - NOT tested
|
|
187
|
-
- **Recommendation:** Add test case to verify path matching works with subdirectories
|
|
188
|
-
|
|
189
|
-
### File Encoding Issues
|
|
190
|
-
- Files with syntax errors - Handled gracefully (returns empty violations)
|
|
191
|
-
- Files that don't exist - Handled with error message
|
|
192
|
-
|
|
193
|
-
### Multiple Commands in One File
|
|
194
|
-
- NOT tested
|
|
195
|
-
- **Recommendation:** Add test case with multiple Command classes in one file
|
|
196
|
-
|
|
197
|
-
---
|
|
198
|
-
|
|
199
|
-
## Recommended Priority Fixes
|
|
200
|
-
|
|
201
|
-
1. **HIGH PRIORITY:** Fix GAP #2 (positive DEBUG check pattern)
|
|
202
|
-
- Current behavior rejects valid code
|
|
203
|
-
- Breaking change for developers using this pattern
|
|
204
|
-
|
|
205
|
-
2. **MEDIUM PRIORITY:** Fix GAP #1 (case-sensitive skip decorators)
|
|
206
|
-
- Low probability but easy to fix
|
|
207
|
-
- Prevents trivial evasion
|
|
208
|
-
|
|
209
|
-
3. **LOW PRIORITY:** Add test for nested management commands
|
|
210
|
-
- Verify path matching works with subdirectories
|
|
211
|
-
|
|
212
|
-
---
|
|
213
|
-
|
|
214
|
-
## Test Coverage Summary
|
|
215
|
-
|
|
216
|
-
**Skip Decorators:**
|
|
217
|
-
- Simple decorators: TESTED
|
|
218
|
-
- Attribute decorators: TESTED
|
|
219
|
-
- Called decorators: TESTED
|
|
220
|
-
- Case variations: **GAP FOUND**
|
|
221
|
-
- Custom decorators: TESTED (correctly allowed)
|
|
222
|
-
|
|
223
|
-
**DEBUG Guards:**
|
|
224
|
-
- No check: TESTED
|
|
225
|
-
- Negative check (`if not`): TESTED
|
|
226
|
-
- Positive check (`if ... else`): **GAP FOUND**
|
|
227
|
-
- Imported DEBUG: TESTED (correctly rejected)
|
|
228
|
-
- Helper function: TESTED (correctly rejected)
|
|
229
|
-
- Late check (>5 statements): TESTED (correctly rejected)
|
|
230
|
-
- Path matching: TESTED
|
|
231
|
-
- Complex boolean: TESTED (debatable behavior)
|
|
232
|
-
|
|
233
|
-
---
|
|
234
|
-
|
|
235
|
-
## Files Created for Testing
|
|
236
|
-
|
|
237
|
-
### Skip Decorators (9 files)
|
|
238
|
-
```
|
|
239
|
-
test_files/skip_decorators/
|
|
240
|
-
├── test_01_simple_skip.py
|
|
241
|
-
├── test_02_pytest_skipif.py
|
|
242
|
-
├── test_03_unittest_skipIf.py
|
|
243
|
-
├── test_04_skip_with_parens.py
|
|
244
|
-
├── test_05_xfail.py
|
|
245
|
-
├── test_06_custom_skip.py
|
|
246
|
-
├── test_07_capital_Skip.py (GAP)
|
|
247
|
-
├── test_08_skipUnless.py
|
|
248
|
-
└── test_09_pytest_mark_skip_simple.py
|
|
249
|
-
```
|
|
250
|
-
|
|
251
|
-
### Management Commands (9 files)
|
|
252
|
-
```
|
|
253
|
-
test_files/management/commands/
|
|
254
|
-
├── cmd_01_no_debug_check.py
|
|
255
|
-
├── cmd_02_proper_debug_check.py
|
|
256
|
-
├── cmd_03_debug_check_with_return.py
|
|
257
|
-
├── cmd_04_imported_DEBUG.py
|
|
258
|
-
├── cmd_05_debug_check_in_helper.py
|
|
259
|
-
├── cmd_06_debug_check_late.py
|
|
260
|
-
├── cmd_07_positive_debug_check.py (GAP)
|
|
261
|
-
├── cmd_08_debug_with_and.py
|
|
262
|
-
test_files/
|
|
263
|
-
└── not_management_command.py
|
|
264
|
-
```
|
|
265
|
-
|
|
266
|
-
---
|
|
267
|
-
|
|
268
|
-
## Conclusion
|
|
269
|
-
|
|
270
|
-
The validator is **mostly effective** but has **2 critical gaps**:
|
|
271
|
-
|
|
272
|
-
1. **Case-sensitive decorator matching** - Easy fix, low impact
|
|
273
|
-
2. **Positive DEBUG check rejection** - HIGH PRIORITY, rejects valid code
|
|
274
|
-
|
|
275
|
-
The implementation correctly handles:
|
|
276
|
-
- Most skip decorator patterns
|
|
277
|
-
- Simple DEBUG guard patterns
|
|
278
|
-
- Path filtering for management commands
|
|
279
|
-
- Graceful error handling
|
|
280
|
-
|
|
281
|
-
**Overall Assessment: 82% effective (14/17 test cases passed)**
|
|
282
|
-
|
|
283
|
-
**Action Required:**
|
|
284
|
-
1. Fix GAP #2 immediately (breaking valid code)
|
|
285
|
-
2. Fix GAP #1 for completeness
|
|
286
|
-
3. Add test for nested management command directories
|
|
287
|
-
4. Document the complex boolean limitation
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|