claude-dev-env 1.49.0 → 1.50.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.
Files changed (43) hide show
  1. package/audit-rubrics/category_rubrics/category-a-api-contracts.md +86 -0
  2. package/audit-rubrics/category_rubrics/category-b-selector-engine-compat.md +36 -0
  3. package/audit-rubrics/category_rubrics/category-c-resource-cleanup.md +35 -0
  4. package/audit-rubrics/category_rubrics/category-d-scoping-and-ordering.md +35 -0
  5. package/audit-rubrics/category_rubrics/category-e-dead-code.md +38 -0
  6. package/audit-rubrics/category_rubrics/category-f-silent-failures.md +38 -0
  7. package/audit-rubrics/category_rubrics/category-g-bounds-and-overflow.md +38 -0
  8. package/audit-rubrics/category_rubrics/category-h-security-boundaries.md +40 -0
  9. package/audit-rubrics/category_rubrics/category-i-concurrency.md +38 -0
  10. package/audit-rubrics/category_rubrics/category-j-code-rules-compliance.md +46 -0
  11. package/audit-rubrics/category_rubrics/category-k-codebase-conflicts.md +59 -0
  12. package/audit-rubrics/category_rubrics/category-l-behavior-equivalence.md +45 -0
  13. package/audit-rubrics/category_rubrics/category-m-producer-consumer-cardinality.md +44 -0
  14. package/audit-rubrics/category_rubrics/category-n-test-name-scenario-verifier.md +45 -0
  15. package/audit-rubrics/prompts/category-a-api-contracts.md +399 -0
  16. package/audit-rubrics/prompts/category-b-selector-engine-compat.md +401 -0
  17. package/audit-rubrics/prompts/category-c-resource-cleanup.md +420 -0
  18. package/audit-rubrics/prompts/category-d-scoping-and-ordering.md +414 -0
  19. package/audit-rubrics/prompts/category-e-dead-code.md +420 -0
  20. package/audit-rubrics/prompts/category-f-silent-failures.md +420 -0
  21. package/audit-rubrics/prompts/category-g-bounds-and-overflow.md +383 -0
  22. package/audit-rubrics/prompts/category-h-security-boundaries.md +423 -0
  23. package/audit-rubrics/prompts/category-i-concurrency.md +429 -0
  24. package/audit-rubrics/prompts/category-j-code-rules-compliance.md +463 -0
  25. package/audit-rubrics/prompts/category-k-codebase-conflicts.md +328 -0
  26. package/audit-rubrics/prompts/category-l-behavior-equivalence.md +128 -0
  27. package/audit-rubrics/prompts/category-m-producer-consumer-cardinality.md +129 -0
  28. package/audit-rubrics/prompts/category-n-test-name-scenario-verifier.md +132 -0
  29. package/audit-rubrics/source-material-section-types.md +51 -0
  30. package/docs/CODE_RULES.md +6 -1
  31. package/hooks/blocking/code_rules_enforcer.py +323 -11
  32. package/hooks/blocking/md_to_html_blocker.py +2 -2
  33. package/hooks/blocking/test_code_rules_enforcer.py +65 -0
  34. package/hooks/blocking/test_code_rules_enforcer_docstring_args_signature.py +256 -0
  35. package/hooks/blocking/test_code_rules_enforcer_ignored_must_check_return.py +256 -0
  36. package/hooks/blocking/test_code_rules_enforcer_naming_pattern.py +137 -1
  37. package/hooks/blocking/test_md_to_html_blocker.py +38 -0
  38. package/hooks/hooks_constants/blocking_check_limits.py +2 -0
  39. package/hooks/hooks_constants/code_rules_enforcer_constants.py +15 -1
  40. package/hooks/hooks_constants/md_to_html_blocker_constants.py +1 -1
  41. package/hooks/hooks_constants/test_md_to_html_blocker_constants.py +11 -4
  42. package/package.json +2 -1
  43. package/skills/bugteam/reference/teardown-publish-permissions.md +7 -2
@@ -0,0 +1,463 @@
1
+ Audit [REPO/ARTIFACT] [TARGET_ID] for **Category J only** (CODE_RULES.md compliance). Skip A–I, K. Sub-bucket forced-exhaustion mode: Category J is decomposed into 12 sub-buckets below. Each sub-bucket REQUIRES at least one Shape A finding OR exactly one Shape B proof-of-absence with **at least 3 adversarial probes** specific to that sub-bucket. A sub-bucket returning neither is a protocol gap.
2
+
3
+ [ARTIFACT METADATA]
4
+ - Artifact: [PR title / commit subject / file set / patch series]
5
+ - Head SHA / Revision: [SHA or revision identifier]
6
+ - Scope: [files / line ranges / packages in scope]
7
+ - Languages in scope: [e.g., Python, PowerShell, TypeScript]
8
+ - Production vs test split: [explicit list of which files are production and which are test files; test files are exempt from most J sub-buckets]
9
+
10
+ ID prefix: `find`.
11
+
12
+ ## Source material
13
+
14
+ Inline the artifact (full diff or full file contents) under a clearly delimited block below this section. Use the chunking guide in [`../source-material-section-types.md`](../source-material-section-types.md) to choose the right Source-material section type (full-diff, file-set, patch-series, or excerpt-with-context). Mark every line range that is in scope; mark explicitly which files are test files (exempt) and which are production.
15
+
16
+ Replace this paragraph with the chunked source material before issuing the prompt.
17
+
18
+ ## Sub-buckets (each requires Shape A finding OR Shape B with ≥3 adversarial probes)
19
+
20
+ **J1. Magic values in production function bodies**
21
+ - Walk every numeric or non-trivial literal other than `0`, `1`, `-1` inside production function bodies.
22
+ - Test files are exempt. Module-level declarations are J3-scope, not J1-scope.
23
+ - For each literal found, decide: structural value that belongs in `config/` (flag) vs. truly local arithmetic constant tied to the line's logic (defendable).
24
+ - Adversarial probes must each verify a distinct angle: (a) does the literal duplicate an existing value already centralized in `config/`? (b) does the literal silently couple two languages (e.g., a Python config value and a hand-typed PowerShell / shell mirror)? (c) does the literal appear in user-facing help/doc text in a way that would silently lie if the canonical value changed?
25
+
26
+ **J2. String-template magic**
27
+ - Walk every f-string / template string in production code. Strip the `{...}` interpolations and inspect the remaining literal residue.
28
+ - Flag only when the residue is **structural** (paths, URLs, regex, command patterns, query DSL fragments). Descriptive output / log prefixes / human-readable help text are not J2-scope by themselves.
29
+ - Adversarial probes: (a) does any f-string concatenate a path, URL, or pattern fragment that should be sourced from `config/`? (b) does any literal repeat across two languages or two files in a way that belongs in shared config? (c) does the literal include an embedded number that mirrors a `config/` constant and would drift if the constant changed?
30
+
31
+ **J3. Constants location**
32
+ - Walk every module-level `UPPER_SNAKE = ...` declaration in production files.
33
+ - Exempt path families: `config/*`, `/migrations/`, `/workflow/`, `_tab.py`, `/states.py`, `/modules.py`, and all test files. Anywhere else, an UPPER_SNAKE module-level constant must move to `config/`.
34
+ - Distinguish *imports* (`from config.X import FOO`) from *declarations* — imports are not J3-scope.
35
+ - Adversarial probes: (a) does any module-level constant masquerade as an "import" via a re-export pattern? (b) is there a `_PRIVATE_UPPER` declaration that escapes the visual UPPER_SNAKE filter but is still module-level? (c) does any test file declare a constant that *would* be flagged if it were in production, indicating the constant probably belongs in `config/` even if test-exempt?
36
+
37
+ **J4. File-global use-count**
38
+ - For every file-global constant outside `config/`, count references in the same file. Single ref → move to `config/`. Zero refs → delete.
39
+ - The rule applies to *constants*, not functions, classes, or imports.
40
+ - Adversarial probes: (a) is any imported constant referenced only once in the importing file (suggesting the import itself is gratuitous)? (b) is any helper function defined in a production file but never called from inside the same file (separate dead-code concern, surfaced here for completeness)? (c) does any constant in `config/` get imported from zero call sites across the repo?
41
+
42
+ **J5. Abbreviations**
43
+ - Walk every parameter, local, and attribute name in production code. Flag: `ctx`, `cfg`, `msg`, `btn`, `idx`, `cnt`, `elem`, `val`, `tmp`, `str`, `num`, `arr`, `obj`, `fn`, `cb`, `req`, `res`. Loop counters `i`/`j`/`k` and `e` for exceptions are exempt.
44
+ - Test files are exempt.
45
+ - Adversarial probes: (a) is there a borderline name (e.g., `removed`, `arguments`) that someone might mis-classify as an abbreviation but is actually a full English word? Confirm. (b) does any callback / parameter / attribute use a short variant of a domain term that is technically a full word but conventionally abbreviates a longer one? (c) does any variable in a comprehension or lambda use a single letter outside the `i`/`j`/`k`/`e` exemption?
46
+
47
+ **J6. Vague names**
48
+ - Flag any name from the vague list: `result`, `data`, `output`, `response`, `value`, `item`, `temp`, `info`, `stuff`, `thing`. Vague verb prefixes for function names: `handle`, `process`, `manage`, `do`.
49
+ - Test files are exempt.
50
+ - Adversarial probes: (a) does any local variable use a domain-adjacent name that is actually on the vague list (e.g., `result` from a parser, `data` from a fetch)? (b) does any newly-introduced function name start with a vague prefix? (c) does any public attribute / dict key use a vague label that the call site has to disambiguate by surrounding context?
51
+
52
+ **J7. Type hints**
53
+ - Walk every function in production files. Verify parameter and return types are present, no `Any`, no `# type: ignore`.
54
+ - Test files are exempt.
55
+ - Adversarial probes: (a) does any production function rely on inferred return type from a single `return` path? (b) does any parameter use a string-quoted forward reference that masks `Any`? (c) is there a `# type: ignore` anywhere? Grep the diff explicitly.
56
+
57
+ **J8. New inline comments**
58
+ - Every `#` or `//` comment line **added** by this diff in production code — flag, except for exempt markers (shebangs, `# type:`, `# noqa`, `# pylint:`, `# pragma:`, `// @ts-`, `// eslint-`, `// prettier-`, `/// `).
59
+ - Module/function/class docstrings are always allowed.
60
+ - Existing comments are NEVER removed (Comment Preservation rule); if the diff removes an existing comment, that is a separate violation outside J8 (also blocked by the hook).
61
+ - Test files are exempt.
62
+ - Adversarial probes: (a) is there any `# type:` or marker comment that is actually inert prose rather than a real type-checker / linter directive? (b) is any docstring carrying inline-comment content (line-level explanations rather than module/function description)? (c) does any newly-added blank line between code stanzas function as a comment substitute, suggesting the author wanted to add a comment but couldn't?
63
+
64
+ **J9. Logging format**
65
+ - Walk every `log_*(...)` call. Must be `log_*("template with {}", arg)`, not `log_*(f"...")`.
66
+ - The rule applies to the project's structured `log_*` family, not stdlib `print`. `print` f-strings are J2-scope (string-template magic), not J9-scope.
67
+ - Test files are exempt.
68
+ - Adversarial probes: (a) is there any imported `log_*` function in production code that uses an f-string? (b) is there a logger-equivalent call (e.g., `logger.info(f"...")` from `logging` stdlib) that should be subject to the same rule? (c) does any non-Python logger family (e.g., `console.log`, `Write-Host`, structured-log helpers) appear with a template-string pattern that mirrors the J9 anti-pattern?
69
+
70
+ **J10. Imports inside functions**
71
+ - Every `import` / `from ... import ...` statement — verify at module scope.
72
+ - Test files: deferred imports after a `sys.path.insert(0, ...)` guard at module scope are allowed (documented circular-import-style workaround).
73
+ - Adversarial probes: (a) is there any lazy `import` inside a production function body? (b) does any conditional `import` (e.g., inside `if TYPE_CHECKING:`) escape into runtime accidentally? (c) does any non-Python language analog (e.g., `require(...)` inside a JS function, `Import-Module` inside a PowerShell `if` branch) appear in production code?
74
+
75
+ **J11. sys.path.insert dedup**
76
+ - Every `sys.path.insert(0, X)` must be guarded by `if X not in sys.path:` (or equivalent membership test).
77
+ - Test files are explicitly in scope for J11 (the rule that always applies even to test files).
78
+ - Adversarial probes: (a) is the guard expression semantically equivalent to `if X not in sys.path:` where `X` is exactly the value being inserted (no string/Path mismatch)? (b) is there a second `sys.path` mutation elsewhere in the file that is not guarded? (c) would importing the module twice (e.g., via test collection re-runs) re-trigger the insert?
79
+
80
+ **J12. Hardcoded user paths**
81
+ - Any string literal containing `C:/Users/<name>/...`, `/Users/<name>/...`, `/home/<name>/...` in production code? Use `pathlib.Path.home()` or `os.path.expanduser('~')`.
82
+ - Exempt: test files, `config/` files, workflow registry paths (`/workflow/`, `_tab.py`, `/states.py`, `/modules.py`), Django migrations (`/migrations/`), and hook infrastructure.
83
+ - Adversarial probes: (a) does any error message, help string, or docstring example embed a `C:/Users/...`, `/home/...`, or `/Users/...` example? Grep the diff. (b) does any scheduled-task / launcher / installer artifact hardcode a path that should be derived from `$PSCommandPath`, `__file__`, or `os.getcwd()`? (c) does any docstring example show a user-specific home, even if the runtime code itself is path-clean?
84
+
85
+ ## Cross-bucket questions to answer at the end
86
+
87
+ Q1: Are there literals or names that span two sub-buckets (e.g., a magic value in J1 that also appears inside an f-string scrutinized by J2; an UPPER_SNAKE in J3 that also fails the use-count test in J4)? Cite the literal/name and both sub-bucket IDs.
88
+
89
+ Q2: What is the worst CODE_RULES drift introduced by this artifact? Cite `<file>:<line>`. (Common candidates: cross-language duplicates, stale help text mirroring a config constant, abbreviations in a public API surface, bare `Any` annotations, hardcoded user paths in installer scripts.)
90
+
91
+ Q3: Which findings would `code_rules_enforcer.py` block at write time, vs. which would only be caught by audit (slipping past the hook's pattern)? Cite `<file>:<line>` for any audit-only finding so the hook can be tightened later.
92
+
93
+ ## Output
94
+
95
+ Lead: `Total: N (P0=N, P1=N, P2=N)`. For each sub-bucket J1–J12, produce Shape A or Shape B (with ≥3 probes). Cross-bucket Q1–Q3 answers after the per-sub-bucket walk. Adversarial second pass: "assume your first pass missed at least 3 P2 CODE_RULES violations across these 12 sub-buckets — find them." Open Questions section for ambiguities. Read-only. No edits, no commits.
96
+
97
+ Note: most Category J findings are P2 (style / cleanup) since they don't affect runtime behavior; the adversarial-pass quota uses P2 here.
98
+
99
+ ---
100
+
101
+ # Worked example: jl-cmd/claude-code-config PR #394
102
+
103
+ Audit jl-cmd/claude-code-config PR #394 for **Category J only** (CODE_RULES.md compliance). Skip A–I, K. Sub-bucket forced-exhaustion mode: Category J is decomposed into 12 sub-buckets below. Each sub-bucket REQUIRES at least one Shape A finding OR exactly one Shape B proof-of-absence with **at least 3 adversarial probes** specific to that sub-bucket. A sub-bucket returning neither is a protocol gap.
104
+
105
+ PR: feat(scripts): add sweep-empty-dirs utility and scheduled-task installer
106
+ Head SHA: 62c9c169ee7a44824e5da25c4cf8b74fdca08a53
107
+ ID prefix: `find`.
108
+
109
+ ## Sub-buckets (each requires Shape A finding OR Shape B with ≥3 adversarial probes)
110
+
111
+ **J1. Magic values in production function bodies**
112
+ - Walk every numeric literal other than `0`, `1`, `-1` in production function bodies in `sweep_empty_dirs.py` (lines 73-135) and `Install-SweepEmptyDirs.ps1` (lines 227-302). Test file lines 147-223 are exempt.
113
+ - Python side: `sweep(...)` body (lines 80-98) and `main()` body (lines 113-131) — the visible numbers `0` (in tuple unpacking `for each_directory_path, _, _`) and `1` (in `sys.exit(1)`) are exempt by the `0`, `1`, `-1` rule. Assert no other numeric literal appears.
114
+ - PowerShell side: `Install-SweepEmptyDirs.ps1` line 233 (`[int]$IntervalMinutes = 5`), line 236 (`[int]$AgeSeconds = 120`), line 297 (`-Daily -At "00:00"` — string but the `5` and `120` defaults are numeric). Both `5` and `120` are bare literals in the param block. Note: `120` is already centralized as `DEFAULT_AGE_SECONDS` in `config/sweep_config.py` (line 142) — flag the PowerShell duplicate as J1 magic-value, with the cross-language drift framed in Category K.
115
+ - Adversarial probes: (a) is `5` (line 233) the same value as any constant in `config/sweep_config.py`? (b) is `00:00` a structural literal that belongs in config? (c) is `120` (line 236) duplicated against `DEFAULT_AGE_SECONDS` (sweep_config.py:142)?
116
+
117
+ **J2. String-template magic**
118
+ - Walk every f-string in `sweep_empty_dirs.py`. Strip the `{...}` interpolations and inspect the remaining literal text. Flag only when the residue is structural (paths, URLs, regex, command patterns).
119
+ - Line 74 — `f"warning: cannot scan {os_error.filename} — {os_error.strerror}"` → residue `"warning: cannot scan — "` is descriptive output, not structural. Not flagged.
120
+ - Line 93 — `f"deleted: {each_directory_path}"` → residue `"deleted: "` is a log prefix, not structural. Not flagged.
121
+ - Line 105 — `f"Minimum age in seconds (default: {DEFAULT_AGE_SECONDS} = 2 minutes)"` → residue `"Minimum age in seconds (default: = 2 minutes)"` is help text, not structural. Not flagged. (However: the embedded number `2` in `"= 2 minutes"` is a magic-numeric-literal-inside-help-text — adversarial probe candidate.)
122
+ - Line 109 — `f"Poll interval in seconds when looping (default: {DEFAULT_POLL_INTERVAL})"` → residue is help text, not structural. Not flagged.
123
+ - Line 118 — `f"error: not a directory: {arguments.root}"` → residue is descriptive, not structural. Not flagged.
124
+ - Line 125 — `f"watching {arguments.root} every {arguments.interval}s (age threshold: {arguments.age}s)"` → residue is descriptive, not structural. Not flagged.
125
+ - Adversarial probes: (a) is the `"= 2 minutes"` substring in line 105 a hidden duplicate of `DEFAULT_AGE_SECONDS = 120`? If a future change makes the default 180s, the help text will silently lie. (b) does any f-string concatenate a path that should come from config? (c) does any literal repeat across both Python and PowerShell that would belong in shared config?
126
+
127
+ **J3. Constants location**
128
+ - Walk every module-level `UPPER_SNAKE = ...` declaration in production files.
129
+ - `sweep_empty_dirs.py` (lines 61-136) — no module-level UPPER_SNAKE declarations. Both `DEFAULT_AGE_SECONDS` (line 69) and `DEFAULT_POLL_INTERVAL` (line 70) are *imports*, not declarations; the canonical home is `config/sweep_config.py:142-143`. Verified clean.
130
+ - `config/sweep_config.py` (lines 138-144) — `DEFAULT_AGE_SECONDS: int = 120` (line 142) and `DEFAULT_POLL_INTERVAL: int = 30` (line 143) live in `config/`. Exempt by the `config/*` path family.
131
+ - Test file `test_sweep_empty_dirs.py` line 160 — `_SCRIPTS_DIR = Path(__file__).resolve().parent.parent` is *not* UPPER_SNAKE (leading underscore + mixed case); even if it were UPPER_SNAKE, test files are exempt by the rule.
132
+ - `Install-SweepEmptyDirs.ps1` line 245 — `$TaskName = "SweepEmptyDirs"` is a PowerShell variable; J3 enforces Python module-level UPPER_SNAKE outside `config/`. PowerShell out of scope for J3.
133
+ - Adversarial probes: (a) does any module-level constant in `sweep_empty_dirs.py` masquerade as an "import"? (b) is there any `_PRIVATE_UPPER` declaration that escapes the visual UPPER_SNAKE filter? (c) does the test file accidentally declare a constant that *would* be flagged if it were in production?
134
+
135
+ **J4. File-global use-count**
136
+ - For every file-global constant outside `config/`, count references in the same file. Single ref → move to `config/`. Zero refs → delete.
137
+ - `sweep_empty_dirs.py` — no file-global constants declared (`DEFAULT_AGE_SECONDS` and `DEFAULT_POLL_INTERVAL` are imports). Imports follow standard import-usage rules, not the file-global use-count rule.
138
+ - `config/sweep_config.py:142-143` — `DEFAULT_AGE_SECONDS` and `DEFAULT_POLL_INTERVAL` live in `config/`, exempt by location.
139
+ - Test file `_SCRIPTS_DIR` — test files exempt.
140
+ - Adversarial probes: (a) is any imported constant in `sweep_empty_dirs.py` referenced only once (line 104 for `DEFAULT_AGE_SECONDS`, line 108 for `DEFAULT_POLL_INTERVAL`) — wait, `DEFAULT_AGE_SECONDS` is referenced TWICE on line 104 (`default=DEFAULT_AGE_SECONDS`) and line 105 (`f"...default: {DEFAULT_AGE_SECONDS} = 2 minutes"`); `DEFAULT_POLL_INTERVAL` is referenced TWICE on line 108 and line 109. Both meet the ≥2-references threshold (note: file-global use-count technically applies to declarations, not imports — listed here for completeness). (b) is any helper function in `sweep_empty_dirs.py` defined but never called from inside the file? `_log_walk_error` (line 73) is referenced once at line 84; `_build_parser` (line 101) is referenced once at line 114. The use-count rule applies to *constants*, not functions, so neither is flagged. (c) does any constant in `config/sweep_config.py` get imported from zero call sites?
141
+
142
+ **J5. Abbreviations**
143
+ - Walk every parameter, local, and attribute name in `sweep_empty_dirs.py`.
144
+ - `_log_walk_error(os_error: OSError)` — `os_error` is full word, not `e` (exception loop var allowed but not required). Verified clean.
145
+ - `sweep(root: str, min_age_seconds: int) -> list[str]` — `root` is a domain term, `min_age_seconds` is fully spelled. Not abbreviated. Verified clean.
146
+ - Loop var `each_directory_path` (line 83) — follows the `each_` prefix convention. Not abbreviated.
147
+ - `_, _` in tuple unpacking (line 83) — discard names, exempt.
148
+ - `removed: list[str]` (line 81) — full word. Not abbreviated.
149
+ - `_build_parser() -> argparse.ArgumentParser` — `parser` is full word, not `p`. Verified clean.
150
+ - `main()` parameter `parser` (line 114), `arguments` (line 115) — full word `arguments`, not `args`. Verified clean.
151
+ - Test file `test_sweep_empty_dirs.py` — exempt (test files).
152
+ - Adversarial probes: (a) does `removed` count as the abbreviation list? `removed` is a past-participle verb, not on the abbreviation list. (b) does `tmp` (test file line 178) count? Test files exempt. (c) does `dt` (test file line 168) count as `datetime` abbreviation? Test files exempt.
153
+
154
+ **J6. Vague names**
155
+ - Flag any name from the vague list: `result`, `data`, `output`, `response`, `value`, `item`, `temp`, `info`, `stuff`, `thing`. Vague verb prefixes: `handle`, `process`, `manage`, `do`.
156
+ - `sweep_empty_dirs.py` — variable `removed: list[str]` (line 81) is domain-specific (what was removed from disk), not vague. `now` (line 80) is a time concept, not on the vague list. `created` (line 87) is a domain timestamp, not vague.
157
+ - Function names: `sweep`, `_log_walk_error`, `_build_parser`, `main` — none use the vague verb prefixes (`handle`, `process`, `manage`, `do`). `sweep` is concrete domain action.
158
+ - Test file — exempt.
159
+ - Adversarial probes: (a) does `removed` (line 81) overlap with the vague-name list? It does not — the list is `result`, `data`, `output`, etc., and `removed` is none of those. (b) is `arguments` (line 115) on any vague list? No — `arguments` is the argparse-namespace canonical name. (c) does any callback parameter use a vague name? `_log_walk_error(os_error)` — `os_error` is concrete. Clean.
160
+
161
+ **J7. Type hints**
162
+ - Walk every function in production files. Verify parameter and return types are present, no `Any`, no `# type: ignore`.
163
+ - `_log_walk_error(os_error: OSError) -> None` (line 73) — parameter typed, return typed. Clean.
164
+ - `sweep(root: str, min_age_seconds: int) -> list[str]` (line 77) — both parameters typed, return typed. Clean.
165
+ - `_build_parser() -> argparse.ArgumentParser` (line 101) — no parameters, return typed. Clean.
166
+ - `main() -> None` (line 113) — no parameters, return typed. Clean.
167
+ - `config/sweep_config.py:142-143` — `DEFAULT_AGE_SECONDS: int = 120`, `DEFAULT_POLL_INTERVAL: int = 30`. Both annotated. Clean.
168
+ - Test file `test_sweep_empty_dirs.py` — exempt by test-file rule (the helper `_set_creation_time_windows(path: str, timestamp: float) -> None` at line 167 is annotated anyway, and `test_*() -> None` functions are all annotated).
169
+ - Adversarial probes: (a) does any production function rely on inferred return type from a single `return` path? All four production functions have explicit return annotations. (b) does any parameter use a string-quoted forward reference that masks `Any`? No — all annotations are direct. (c) is there a `# type: ignore` anywhere? Grep the diff: no occurrences.
170
+
171
+ **J8. New inline comments**
172
+ - Every `#` comment line added by this diff in production code — flag, except for exempt markers (shebangs, `# type:`, `# noqa`, `# pylint:`, `# pragma:`).
173
+ - `sweep_empty_dirs.py` — line 61 (`#!/usr/bin/env python3`) is a shebang, exempt. Line 62 is a module-level docstring, allowed. Lines 78 and 102 are function docstrings, allowed. No inline `#` comments. Clean.
174
+ - `config/sweep_config.py` — line 140 is a module-level docstring. No inline comments. Clean.
175
+ - Test file — exempt.
176
+ - `Install-SweepEmptyDirs.ps1` — line 227 (`#!/usr/bin/env pwsh`) is a shebang, exempt. No inline `#` comments added in the PowerShell file. Clean.
177
+ - Adversarial probes: (a) is there any `# type:` comment that is actually inert noise rather than a type-checker directive? No occurrences in the diff. (b) is any docstring carrying inline-comment content as line-level explanations rather than module/function description? Module docstring on line 62 is one line ("Delete empty directories older than 2 minutes under a given root."); function docstrings on 78 and 102 are one-liners. Clean. (c) does any newly-added blank line between code stanzas function as a comment substitute? Visual whitespace is allowed.
178
+
179
+ **J9. Logging format**
180
+ - Walk every `log_*(...)` call. Must be `log_*("template with {}", arg)`, not `log_*(f"...")`.
181
+ - `sweep_empty_dirs.py` uses `print()`, not a logger. The rule applies to `log_*` (the project's structured logger), not stdlib `print`. The print f-strings on lines 74, 93, 118, 125, 131 are J2-scope (string-template magic), not J9-scope.
182
+ - Test file — exempt.
183
+ - Adversarial probes: (a) is there any imported `log_*` function in `sweep_empty_dirs.py` that uses an f-string? No — no logger import in the diff. (b) is `print(..., file=sys.stderr)` (lines 74, 118) a logger-equivalent call that should be subject to J9? No — `print` is stdlib stdout/stderr, not the structured-logger family. (c) does the PowerShell `Write-Host` / `Write-Error` family count? J9 is Python-specific (`log_*` callable convention).
184
+
185
+ **J10. Imports inside functions**
186
+ - Every `import` / `from ... import ...` statement — verify at module scope.
187
+ - `sweep_empty_dirs.py` lines 64-70 — all imports at module top. Function bodies (lines 73-135) contain no `import` statements. Clean.
188
+ - `config/sweep_config.py` — no imports. Clean.
189
+ - Test file — exempt; nevertheless lines 152-158 are all module-scope imports, line 164 is a deferred import (`from sweep_empty_dirs import sweep # noqa: E402`) at module scope after the `sys.path.insert` guard. Documented circular-import-style workaround pattern; allowed.
190
+ - Adversarial probes: (a) is there any lazy `import` inside `sweep`, `main`, or `_build_parser` body? No occurrences. (b) is `argparse.ArgumentParser` accessed via `argparse.ArgumentParser` (line 102) using the module-scope import on line 64? Yes. (c) does the PowerShell file have any `Import-Module` calls inside `if` branches? No — the script imports nothing.
191
+
192
+ **J11. sys.path.insert dedup**
193
+ - Every `sys.path.insert(0, X)` must be guarded by `if X not in sys.path:`. Test files exempt by general-rule but J11 is the rule that explicitly applies to test files.
194
+ - `test_sweep_empty_dirs.py` lines 160-162 — `_SCRIPTS_DIR = Path(__file__).resolve().parent.parent / if str(_SCRIPTS_DIR) not in sys.path: / sys.path.insert(0, str(_SCRIPTS_DIR))`. Guarded. Clean.
195
+ - No `sys.path.insert` calls in production files.
196
+ - Adversarial probes: (a) is the guard expression `if str(_SCRIPTS_DIR) not in sys.path:` semantically equivalent to `if X not in sys.path:` where `X` is exactly the value being inserted? Yes — both use `str(_SCRIPTS_DIR)`. (b) is there a second `sys.path` mutation elsewhere in the test file that's not guarded? No — only one occurrence at line 162. (c) would importing the test module twice (e.g., via pytest's collection re-runs) re-trigger the insert? The guard prevents it. Clean.
197
+
198
+ **J12. Hardcoded user paths**
199
+ - Any string literal containing `C:/Users/<name>/...`, `/Users/<name>/...`, `/home/<name>/...`?
200
+ - `sweep_empty_dirs.py` — no hardcoded user paths. The `arguments.root` value is supplied at runtime via argparse positional argument (line 103). Clean.
201
+ - `config/sweep_config.py` — no paths at all. Clean.
202
+ - Test file — exempt; uses `tempfile.TemporaryDirectory()` (lines 178, 188, 197, 210, 216) which is the canonical safe pattern. Clean.
203
+ - `Install-SweepEmptyDirs.ps1` — `$ScriptDir = Split-Path -Parent $PSCommandPath` (line 272), `$ScriptPath = Join-Path $ScriptDir "sweep_empty_dirs.py"` (line 273). Both derive from `$PSCommandPath` (the script's own path), not a hardcoded user home. Clean. The `$Target` parameter is supplied at install time (line 230); not hardcoded.
204
+ - Adversarial probes: (a) does any error message or help string embed a `C:/Users/...` example? Grep the diff for `C:/Users`, `/home/`, `/Users/` — no occurrences. (b) does the scheduled task `-Argument` string at line 296 hardcode a path? It interpolates `$ScriptPath`, `$AgeSeconds`, and `$Target` — all dynamic. Clean. (c) does any docstring example show a user-specific home? Module docstrings on lines 62 and 140 are short and contain no example paths. Clean.
205
+
206
+ ## Cross-bucket questions to answer at the end
207
+
208
+ Q1: Are there constants that span two sub-buckets (e.g., a magic value J1 inside an f-string J2 — the same literal flagged twice)? Cite the literal and both sub-bucket IDs.
209
+ Q2: What's the worst CODE_RULES drift introduced by this PR? Cite `<file>:<line>`. (Hint: cross-language `120` duplication between PowerShell line 236 and Python `config/sweep_config.py:142` is the load-bearing one — it's a J1 magic value on the PowerShell side and a Category K conflict-with-existing-code on the cross-language side.)
210
+ Q3: Which finding would `code_rules_enforcer.py` block at write time, vs. which would only be caught by audit (slipping past the hook's pattern)? Cite the file:line for any audit-only finding.
211
+
212
+ ## Output
213
+
214
+ Lead: `Total: N (P0=N, P1=N, P2=N)`. For each sub-bucket J1-J12, produce Shape A or Shape B (with ≥3 probes). Cross-bucket Q1-Q3 answers after the per-sub-bucket walk. Adversarial second pass: "assume your first pass missed at least 3 P2 CODE_RULES violations across these 12 sub-buckets — find them." Open Questions section for ambiguities. Read-only. No edits, no commits.
215
+
216
+ Note: most Category J findings are P2 (style / cleanup) since they don't affect runtime behavior; the adversarial-pass quota uses P2 here.
217
+
218
+ ## Diff (4 new files, all lines in scope)
219
+
220
+ ### packages/claude-dev-env/scripts/sweep_empty_dirs.py
221
+ ```python
222
+ #!/usr/bin/env python3
223
+ """Delete empty directories older than 2 minutes under a given root."""
224
+
225
+ import argparse
226
+ import os
227
+ import sys
228
+ import time
229
+
230
+ from config.sweep_config import DEFAULT_AGE_SECONDS
231
+ from config.sweep_config import DEFAULT_POLL_INTERVAL
232
+
233
+
234
+ def _log_walk_error(os_error: OSError) -> None:
235
+ print(f"warning: cannot scan {os_error.filename} — {os_error.strerror}", file=sys.stderr)
236
+
237
+
238
+ def sweep(root: str, min_age_seconds: int) -> list[str]:
239
+ """Remove empty directories under *root* older than *min_age_seconds*."""
240
+
241
+ now = time.time()
242
+ removed: list[str] = []
243
+
244
+ for each_directory_path, _, _ in os.walk(
245
+ root, onerror=_log_walk_error, topdown=False
246
+ ):
247
+ try:
248
+ created = os.path.getctime(each_directory_path)
249
+ except OSError:
250
+ continue
251
+ if now - created >= min_age_seconds:
252
+ try:
253
+ os.rmdir(each_directory_path)
254
+ print(f"deleted: {each_directory_path}")
255
+ removed.append(each_directory_path)
256
+ except OSError:
257
+ pass
258
+
259
+ return removed
260
+
261
+
262
+ def _build_parser() -> argparse.ArgumentParser:
263
+ parser = argparse.ArgumentParser(description="Delete empty directories older than a given age.")
264
+ parser.add_argument("root", help="Root directory to scan")
265
+ parser.add_argument("--age", type=int, default=DEFAULT_AGE_SECONDS,
266
+ help=f"Minimum age in seconds (default: {DEFAULT_AGE_SECONDS} = 2 minutes)")
267
+ parser.add_argument("--once", action="store_true",
268
+ help="Single pass and exit instead of watching in a loop")
269
+ parser.add_argument("--interval", type=int, default=DEFAULT_POLL_INTERVAL,
270
+ help=f"Poll interval in seconds when looping (default: {DEFAULT_POLL_INTERVAL})")
271
+ return parser
272
+
273
+
274
+ def main() -> None:
275
+ parser = _build_parser()
276
+ arguments = parser.parse_args()
277
+
278
+ if not os.path.isdir(arguments.root):
279
+ print(f"error: not a directory: {arguments.root}", file=sys.stderr)
280
+ sys.exit(1)
281
+
282
+ if arguments.once:
283
+ sweep(arguments.root, arguments.age)
284
+ return
285
+
286
+ print(f"watching {arguments.root} every {arguments.interval}s (age threshold: {arguments.age}s)")
287
+ try:
288
+ while True:
289
+ sweep(arguments.root, arguments.age)
290
+ time.sleep(arguments.interval)
291
+ except KeyboardInterrupt:
292
+ print("\nstopped.")
293
+
294
+
295
+ if __name__ == "__main__":
296
+ main()
297
+ ```
298
+
299
+ ### packages/claude-dev-env/scripts/config/sweep_config.py
300
+ ```python
301
+ """Centralized timing configuration for sweep_empty_dirs."""
302
+
303
+ DEFAULT_AGE_SECONDS: int = 120
304
+ DEFAULT_POLL_INTERVAL: int = 30
305
+ ```
306
+
307
+ ### packages/claude-dev-env/scripts/tests/test_sweep_empty_dirs.py
308
+ ```python
309
+ """Tests for sweep_empty_dirs.py"""
310
+
311
+ from __future__ import annotations
312
+
313
+ import datetime
314
+ import os
315
+ import subprocess
316
+ import sys
317
+ import tempfile
318
+ import time
319
+ from pathlib import Path
320
+
321
+ _SCRIPTS_DIR = Path(__file__).resolve().parent.parent
322
+ if str(_SCRIPTS_DIR) not in sys.path:
323
+ sys.path.insert(0, str(_SCRIPTS_DIR))
324
+
325
+ from sweep_empty_dirs import sweep # noqa: E402
326
+
327
+
328
+ def _set_creation_time_windows(path: str, timestamp: float) -> None:
329
+ dt = datetime.datetime.fromtimestamp(timestamp, tz=datetime.timezone.utc)
330
+ date_str = dt.strftime("%Y-%m-%d %H:%M:%S")
331
+ subprocess.run(
332
+ ["powershell", "-Command",
333
+ f"(Get-Item '{path}').CreationTimeUtc = [DateTime]'{date_str}'"],
334
+ check=True, capture_output=True,
335
+ )
336
+
337
+
338
+ def test_deletes_empty_dir_older_than_threshold() -> None:
339
+ with tempfile.TemporaryDirectory() as tmp:
340
+ empty_dir = os.path.join(tmp, "old_empty")
341
+ os.mkdir(empty_dir)
342
+ _set_creation_time_windows(empty_dir, time.time() - 300)
343
+ removed = sweep(tmp, min_age_seconds=120)
344
+ assert empty_dir in removed
345
+ assert not os.path.isdir(empty_dir)
346
+
347
+
348
+ def test_skips_empty_dir_newer_than_threshold() -> None:
349
+ with tempfile.TemporaryDirectory() as tmp:
350
+ fresh_dir = os.path.join(tmp, "fresh_empty")
351
+ os.mkdir(fresh_dir)
352
+ removed = sweep(tmp, min_age_seconds=120)
353
+ assert fresh_dir not in removed
354
+ assert os.path.isdir(fresh_dir)
355
+
356
+
357
+ def test_deletes_nested_empty_dirs() -> None:
358
+ with tempfile.TemporaryDirectory() as tmp:
359
+ leaf = os.path.join(tmp, "parent", "child", "leaf")
360
+ os.makedirs(leaf)
361
+ _set_creation_time_windows(os.path.join(tmp, "parent"), time.time() - 300)
362
+ _set_creation_time_windows(os.path.join(tmp, "parent", "child"), time.time() - 300)
363
+ _set_creation_time_windows(leaf, time.time() - 300)
364
+ removed = sweep(tmp, min_age_seconds=120)
365
+ assert leaf in removed
366
+ assert os.path.join(tmp, "parent", "child") in removed
367
+ assert os.path.join(tmp, "parent") in removed
368
+
369
+
370
+ def test_empty_root_does_not_crash() -> None:
371
+ with tempfile.TemporaryDirectory() as tmp:
372
+ _set_creation_time_windows(tmp, time.time() - 300)
373
+ sweep(tmp, min_age_seconds=120)
374
+
375
+
376
+ def test_skips_nonempty_dir() -> None:
377
+ with tempfile.TemporaryDirectory() as tmp:
378
+ nonempty_dir = os.path.join(tmp, "has_stuff")
379
+ os.mkdir(nonempty_dir)
380
+ Path(nonempty_dir, "keepme.txt").write_text("hello")
381
+ removed = sweep(tmp, min_age_seconds=0)
382
+ assert nonempty_dir not in removed
383
+ assert os.path.isdir(nonempty_dir)
384
+ ```
385
+
386
+ ### packages/claude-dev-env/scripts/Install-SweepEmptyDirs.ps1
387
+ ```powershell
388
+ #!/usr/bin/env pwsh
389
+ param(
390
+ [Parameter(ParameterSetName = "install")]
391
+ [string]$Target,
392
+
393
+ [Parameter(ParameterSetName = "install")]
394
+ [int]$IntervalMinutes = 5,
395
+
396
+ [Parameter(ParameterSetName = "install")]
397
+ [int]$AgeSeconds = 120,
398
+
399
+ [Parameter(ParameterSetName = "remove")]
400
+ [switch]$Remove,
401
+
402
+ [Parameter(ParameterSetName = "status")]
403
+ [switch]$Status
404
+ )
405
+
406
+ $TaskName = "SweepEmptyDirs"
407
+
408
+ if ($Status) {
409
+ $task = Get-ScheduledTask -TaskName $TaskName -ErrorAction SilentlyContinue
410
+ if (-not $task) {
411
+ Write-Host "STATUS: $TaskName is not registered."
412
+ return
413
+ }
414
+ Write-Host "STATUS: $TaskName is registered."
415
+ Write-Host " State: $($task.State)"
416
+ Write-Host " Actions:"
417
+ foreach ($action in $task.Actions) {
418
+ Write-Host " $($action.Execute) $($action.Arguments)"
419
+ }
420
+ Write-Host " Triggers:"
421
+ foreach ($trigger in $task.Triggers) {
422
+ Write-Host " $($trigger.Repetition.Interval) (starting $($trigger.StartBoundary))"
423
+ }
424
+ return
425
+ }
426
+
427
+ if ($Remove) {
428
+ Unregister-ScheduledTask -TaskName $TaskName -Confirm:$false -ErrorAction SilentlyContinue
429
+ Write-Host "$TaskName removed."
430
+ return
431
+ }
432
+
433
+ $ScriptDir = Split-Path -Parent $PSCommandPath
434
+ $ScriptPath = Join-Path $ScriptDir "sweep_empty_dirs.py"
435
+
436
+ if (-not (Test-Path $ScriptPath)) {
437
+ Write-Error "sweep_empty_dirs.py not found at: $ScriptPath"
438
+ exit 1
439
+ }
440
+
441
+ if (-not $Target) {
442
+ Write-Error "Parameter -Target is required (the directory to watch)."
443
+ exit 1
444
+ }
445
+
446
+ if (-not (Test-Path $Target)) {
447
+ Write-Error "Target directory does not exist: $Target"
448
+ exit 1
449
+ }
450
+
451
+ $_py = Get-Command py -ErrorAction SilentlyContinue
452
+ $PythonPath = if ($_py) { $_py.Source } else { (Get-Command python).Source }
453
+ if (-not $PythonPath) {
454
+ Write-Error "Cannot find Python (py or python) on PATH."
455
+ exit 1
456
+ }
457
+ $Action = New-ScheduledTaskAction -Execute $PythonPath -Argument "$ScriptPath --once --age $AgeSeconds ""$Target"""
458
+ $Trigger = New-ScheduledTaskTrigger -Daily -At "00:00" -RepetitionInterval (New-TimeSpan -Minutes $IntervalMinutes)
459
+ $Settings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries -StartWhenAvailable
460
+
461
+ Register-ScheduledTask -TaskName $TaskName -Action $Action -Trigger $Trigger -Settings $Settings -Force | Out-Null
462
+ Write-Host "$TaskName registered — runs every ${IntervalMinutes}min against '$Target' (age ≥ ${AgeSeconds}s)."
463
+ ```