claude-dev-env 8.15.0 → 8.15.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (106) hide show
  1. package/.agents/agents/test_agent_frontmatter.py +0 -72
  2. package/.agents/agents-archived/code-quality-agent.md +16 -16
  3. package/.agents/skills/_shared/end-of-run-gotcha-recommendations.md +5 -21
  4. package/.agents/skills/e-code-review/SKILL.md +1 -3
  5. package/.agents/skills/e-simplify/SKILL.md +6 -6
  6. package/.agents/skills/eli5/SKILL.md +4 -14
  7. package/.agents/skills/eli5/reference/task-seeds.md +3 -4
  8. package/.agents/skills/everything-search/SKILL.md +2 -2
  9. package/.agents/skills/fresh-branch/SKILL.md +2 -3
  10. package/.agents/skills/grok-spawn/SKILL.md +3 -8
  11. package/.agents/skills/issue-tracker/SKILL.md +4 -6
  12. package/.agents/skills/orchestrator-refresh/SKILL.md +7 -7
  13. package/.agents/skills/plugin-eval-standalone-skill/SKILL.md +3 -3
  14. package/.agents/skills/privacy-hygiene/SKILL.md +6 -10
  15. package/.agents/skills/privacy-hygiene/reference/sweep-procedure.md +1 -1
  16. package/.agents/skills/pull-request/SKILL.md +11 -14
  17. package/.agents/skills/pull-request/reference/publication-tasks.md +2 -4
  18. package/.agents/skills/recovering-codex-startup/SKILL.md +3 -6
  19. package/.agents/skills/recovering-codex-startup/reference/task-seeds.md +1 -1
  20. package/.agents/skills/repairing-hook-boundaries/SKILL.md +4 -7
  21. package/.agents/skills/repairing-hook-boundaries/reference/task-seeds.md +1 -2
  22. package/.agents/skills/syncing-submodules/SKILL.md +7 -17
  23. package/.agents/skills/syncing-submodules/reference/run-tasks.md +1 -1
  24. package/.agents/skills/task-build/SKILL.md +1 -1
  25. package/.agents/skills/task-build/reference/tool-routing.md +1 -1
  26. package/.agents/skills/team-advisor/SKILL.md +2 -4
  27. package/.agents/skills/usage-pause/SKILL.md +1 -1
  28. package/.agents/skills/windows-scheduled-task/SKILL.md +1 -1
  29. package/AGENTS.md +1 -1
  30. package/_shared/advisor/advisor-protocol.md +4 -4
  31. package/_shared/advisor/reference/consult-format.md +1 -1
  32. package/_shared/advisor/reference/identity.md +2 -2
  33. package/_shared/advisor/reference/warm-up.md +1 -1
  34. package/audit-rubrics/category_rubrics/category-o-docstring-vs-impl-drift.md +1 -1
  35. package/audit-rubrics/prompts/category-o-docstring-vs-impl-drift.md +2 -16
  36. package/bin/install.mjs +1 -0
  37. package/bin/install.prompt-context.test.mjs +7 -3
  38. package/bin/install.test.mjs +3 -2
  39. package/commands/sr-loop.md +2 -2
  40. package/docs/CODE_RULES.md +2 -2
  41. package/docs/DJANGO_PATTERNS.md +3 -3
  42. package/docs/REACT_PATTERNS.md +6 -6
  43. package/docs/TEST_QUALITY.md +10 -16
  44. package/docs/agent-spawn-protocol.md +4 -4
  45. package/docs/codex-compatibility.md +4 -33
  46. package/docs/references/team-advisor-skill.md +0 -1
  47. package/docs/worker-completion-gate.md +0 -1
  48. package/hooks/advisory/migration_safety_advisor.py +3 -2
  49. package/hooks/advisory/refactor_guard.py +3 -3
  50. package/hooks/hooks.json +1 -1
  51. package/hooks/hooks_constants/issue_tracker_session_starter_constants.py +1 -2
  52. package/hooks/hooks_constants/orchestrator_auto_starter_constants.py +1 -2
  53. package/package.json +1 -1
  54. package/rules/explore-thoroughly.md +0 -1
  55. package/rules/filesystem-search.md +1 -1
  56. package/rules/git-workflow.md +5 -5
  57. package/rules/long-horizon-autonomy.md +1 -5
  58. package/rules/re-stage-before-commit.md +0 -2
  59. package/rules/shell-invocation.md +1 -1
  60. package/scripts/dev_env_scripts_constants/grok_worker_constants.py +8 -2
  61. package/scripts/policy_lint/config/constants.py +1 -0
  62. package/scripts/policy_lint/engine.py +5 -2
  63. package/scripts/repository_checks/config/constants.py +1 -1
  64. package/scripts/spawn_grok_batch.py +12 -1
  65. package/scripts/test_spawn_grok_batch.py +22 -0
  66. package/scripts/tests/test_banned_prose_words.py +1 -1
  67. package/scripts/tests/test_policy_lint_rules_archive.py +16 -0
  68. package/scripts/tests/test_policy_lint_rules_engine.py +2 -2
  69. package/scripts/tests/test_rules.py +9 -9
  70. package/system-prompts/software-engineer.xml +15 -30
  71. package/audit-rubrics/audit-categories.json +0 -700
  72. package/audit-rubrics/category_rubrics/category-a-api-contracts.md +0 -90
  73. package/audit-rubrics/category_rubrics/category-b-selector-engine-compat.md +0 -36
  74. package/audit-rubrics/category_rubrics/category-c-resource-cleanup.md +0 -35
  75. package/audit-rubrics/category_rubrics/category-d-scoping-and-ordering.md +0 -35
  76. package/audit-rubrics/category_rubrics/category-e-dead-code.md +0 -40
  77. package/audit-rubrics/category_rubrics/category-f-silent-failures.md +0 -41
  78. package/audit-rubrics/category_rubrics/category-g-bounds-and-overflow.md +0 -38
  79. package/audit-rubrics/category_rubrics/category-h-security-boundaries.md +0 -40
  80. package/audit-rubrics/category_rubrics/category-i-concurrency.md +0 -38
  81. package/audit-rubrics/category_rubrics/category-j-code-rules-compliance.md +0 -59
  82. package/audit-rubrics/category_rubrics/category-k-codebase-conflicts.md +0 -66
  83. package/audit-rubrics/category_rubrics/category-l-behavior-equivalence.md +0 -48
  84. package/audit-rubrics/category_rubrics/category-m-producer-consumer-cardinality.md +0 -44
  85. package/audit-rubrics/category_rubrics/category-n-test-name-scenario-verifier.md +0 -54
  86. package/audit-rubrics/category_rubrics/category-p-name-vs-behavior-contract.md +0 -59
  87. package/audit-rubrics/category_rubrics/category-q-cross-surface-claims.md +0 -46
  88. package/audit-rubrics/prompts/category-a-api-contracts.md +0 -407
  89. package/audit-rubrics/prompts/category-b-selector-engine-compat.md +0 -401
  90. package/audit-rubrics/prompts/category-c-resource-cleanup.md +0 -420
  91. package/audit-rubrics/prompts/category-d-scoping-and-ordering.md +0 -414
  92. package/audit-rubrics/prompts/category-e-dead-code.md +0 -434
  93. package/audit-rubrics/prompts/category-f-silent-failures.md +0 -432
  94. package/audit-rubrics/prompts/category-g-bounds-and-overflow.md +0 -383
  95. package/audit-rubrics/prompts/category-h-security-boundaries.md +0 -423
  96. package/audit-rubrics/prompts/category-i-concurrency.md +0 -429
  97. package/audit-rubrics/prompts/category-j-code-rules-compliance.md +0 -458
  98. package/audit-rubrics/prompts/category-k-codebase-conflicts.md +0 -338
  99. package/audit-rubrics/prompts/category-l-behavior-equivalence.md +0 -131
  100. package/audit-rubrics/prompts/category-m-producer-consumer-cardinality.md +0 -129
  101. package/audit-rubrics/prompts/category-n-test-name-scenario-verifier.md +0 -139
  102. package/audit-rubrics/prompts/category-p-name-vs-behavior-contract.md +0 -89
  103. package/audit-rubrics/prompts/category-q-cross-surface-claims.md +0 -51
  104. package/hooks/hooks_constants/style_reminder_prompt_constants.py +0 -9
  105. package/hooks/session/style_reminder_prompt.py +0 -55
  106. package/hooks/session/test_style_reminder_prompt.py +0 -86
@@ -1,458 +0,0 @@
1
- Audit [REPO/ARTIFACT] [TARGET_ID] for **Category J only** (CODE_RULES.md compliance). Skip A–I, K–P. 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; both use the same comment rule]
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 and which are production.
15
-
16
- Replace this paragraph with the chunked source material before issuing the prompt.
17
-
18
- ## Write-time exemptions do not scope this audit
19
-
20
- When a change touches code that an existing comment describes or is attached to, remove that comment in the same change and carry its meaning through clear names and structure. Leave comments tied to untouched code unchanged. Keep comment cleanup inside the requested task.
21
- Production and tests follow one rule. Changed directive, TODO, FIXME, HACK, XXX, and type-ignore comments are removed rather than added or justified.
22
-
23
- The write-time hook skips several rules on test files and on `.mjs` / `.js` files; comment changes use one rule for production and tests. This audit does not skip the other listed rules. Apply these rules to every changed line — production, test, and JavaScript files alike:
24
-
25
- - Naming (J5, J6) — banned identifiers and banned function prefixes; boolean names prefixed `is_` / `has_` / `should_` / `can_` / `was_` / `did_`.
26
- - Logging format (J9) — the format string and its arguments pass as separate parameters, never an f-string.
27
- - Type annotations (J7) — every changed parameter and return is typed; no `Any`, no `# type: ignore` directives.
28
- - Unused imports — a module-level import a changed file does not read.
29
- - Function length — a changed function past the length threshold splits into named helpers.
30
-
31
- A `test_*.py` name or a `.mjs` extension takes the line out of the write-time gate, not out of this audit. J1 (magic values) and J3 (constants location) keep their test-file exemption; the five rules above do not.
32
-
33
- ## Sub-buckets (each requires Shape A finding OR Shape B with ≥3 adversarial probes)
34
-
35
- **J1. Magic values in production function bodies**
36
- - Walk every numeric or non-trivial literal other than `0`, `1`, `-1` inside production function bodies.
37
- - Test files follow the same no-new-comment policy.
38
- - Changed directive, TODO, FIXME, HACK, XXX, and type-ignore comments are removed rather than added or justified. Module-level declarations are J3-scope, not J1-scope.
39
- - For each literal found, decide: structural value that belongs in `config/` (flag) vs. local arithmetic constant tied to the line's logic (defendable).
40
- - 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?
41
-
42
- **J2. String-template magic**
43
- - Walk every f-string / template string in production code. Strip the `{...}` interpolations and inspect the remaining literal residue.
44
- - 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.
45
- - 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?
46
-
47
- **J3. Constants location**
48
- - Walk every module-level `UPPER_SNAKE = ...` declaration in production files.
49
- - 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/`.
50
- - Distinguish *imports* (`from config.X import FOO`) from *declarations* — imports are not J3-scope.
51
- - 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?
52
-
53
- **J5. Abbreviations**
54
- - Walk every parameter, local, and attribute name across production, test, and JavaScript changed lines. 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.
55
- - This audit walks changed test-file and `.mjs` / `.js` lines for this rule, even though the write-time hook skips them.
56
- - Adversarial probes: (a) is there a borderline name (e.g., `removed`, `arguments`) that someone might mis-classify as an abbreviation but is 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?
57
-
58
- **J6. Vague names**
59
- - 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`.
60
- - This audit walks changed test-file and `.mjs` / `.js` lines for this rule, even though the write-time hook skips them.
61
- - Adversarial probes: (a) does any local variable use a domain-adjacent name that is 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?
62
-
63
- **J7. Type hints**
64
- - Walk every function across production, test, and JavaScript changed lines. Verify parameter and return types are present, no `Any`, no `# type: ignore`.
65
- - This audit walks changed test-file and `.mjs` / `.js` lines for this rule, even though the write-time hook skips them.
66
- - 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.
67
-
68
- **J8. New inline comments**
69
- - Every `#` or `//` comment line **added** by this diff in any code — flag.
70
- - Module/function/class docstrings are always allowed.
71
- - Comments tied to untouched code remain unchanged; a changed comment is removed with the code it describes.
72
- - Test files follow the same no-new-comment policy.
73
- - Changed directive, TODO, FIXME, HACK, XXX, and type-ignore comments are removed rather than added or justified.
74
- - Adversarial probes: (a) is there any `# type:` or marker comment that is inert prose rather than a 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?
75
-
76
- **J9. Logging format**
77
- - Walk every `log_*(...)` call. Must be `log_*("template with {}", arg)`, not `log_*(f"...")`.
78
- - 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.
79
- - This audit walks changed test-file and `.mjs` / `.js` lines for this rule, even though the write-time hook skips them.
80
- - 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?
81
-
82
- **J10. Imports inside functions**
83
- - Every `import` / `from ... import ...` statement — verify at module scope.
84
- - Test file — lines 152-158 are module-scope imports, and line 164 is a deferred import at module scope after the sys.path guard. The documented import placement remains allowed.
85
- - 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?
86
-
87
- **J11. sys.path.insert dedup**
88
- - Every `sys.path.insert(0, X)` must be guarded by `if X not in sys.path:` (or equivalent membership test).
89
- - Test files are explicitly in scope for J11 (the rule that always applies even to test files).
90
- - 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?
91
-
92
- **J12. Hardcoded user paths**
93
- - Any string literal containing a user-specific home path in production code? Use `pathlib.Path.home()` or `os.path.expanduser('~')`.
94
- - Exempt: test files, `config/` files, workflow registry paths (`/workflow/`, `_tab.py`, `/states.py`, `/modules.py`), Django migrations (`/migrations/`), and hook infrastructure.
95
- - Adversarial probes: (a) does any error message, help string, or docstring example embed a user-specific home path? 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?
96
-
97
- ## Cross-bucket questions to answer at the end
98
-
99
- 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 is also an abbreviation under J5)? Cite the literal/name and both sub-bucket IDs.
100
-
101
- 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.)
102
-
103
- 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.
104
-
105
- ## Output
106
-
107
- 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.
108
-
109
- Note: most Category J findings are P2 (style / cleanup) since they don't affect runtime behavior; the adversarial-pass quota uses P2 here.
110
-
111
- ---
112
-
113
- # Worked example: jl-cmd/claude-dev-env PR #394
114
-
115
- Audit jl-cmd/claude-dev-env PR #394 for **Category J only** (CODE_RULES.md compliance). Skip A–I, K–N. 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.
116
-
117
- PR: feat(scripts): add sweep-empty-dirs utility and scheduled-task installer
118
- Head SHA: 62c9c169ee7a44824e5da25c4cf8b74fdca08a53
119
- ID prefix: `find`.
120
-
121
- ## Sub-buckets (each requires Shape A finding OR Shape B with ≥3 adversarial probes)
122
-
123
- **J1. Magic values in production function bodies**
124
- - 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.
125
- - 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.
126
- - 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.
127
- - 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)?
128
-
129
- **J2. String-template magic**
130
- - 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).
131
- - Line 74 — `f"warning: cannot scan {os_error.filename} — {os_error.strerror}"` → residue `"warning: cannot scan — "` is descriptive output, not structural. Not flagged.
132
- - Line 93 — `f"deleted: {each_directory_path}"` → residue `"deleted: "` is a log prefix, not structural. Not flagged.
133
- - 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.)
134
- - Line 109 — `f"Poll interval in seconds when looping (default: {DEFAULT_POLL_INTERVAL})"` → residue is help text, not structural. Not flagged.
135
- - Line 118 — `f"error: not a directory: {arguments.root}"` → residue is descriptive, not structural. Not flagged.
136
- - Line 125 — `f"watching {arguments.root} every {arguments.interval}s (age threshold: {arguments.age}s)"` → residue is descriptive, not structural. Not flagged.
137
- - 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?
138
-
139
- **J3. Constants location**
140
- - Walk every module-level `UPPER_SNAKE = ...` declaration in production files.
141
- - `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.
142
- - `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.
143
- - 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.
144
- - `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.
145
- - 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?
146
-
147
- **J5. Abbreviations**
148
- - Walk every parameter, local, and attribute name in `sweep_empty_dirs.py`.
149
- - `_log_walk_error(os_error: OSError)` — `os_error` is full word, not `e` (exception loop var allowed but not required). Verified clean.
150
- - `sweep(root: str, min_age_seconds: int) -> list[str]` — `root` is a domain term, `min_age_seconds` is fully spelled. Not abbreviated. Verified clean.
151
- - Loop var `each_directory_path` (line 83) — follows the `each_` prefix convention. Not abbreviated.
152
- - `_, _` in tuple unpacking (line 83) — discard names, exempt.
153
- - `removed: list[str]` (line 81) — full word. Not abbreviated.
154
- - `_build_parser() -> argparse.ArgumentParser` — `parser` is full word, not `p`. Verified clean.
155
- - `main()` parameter `parser` (line 114), `arguments` (line 115) — full word `arguments`, not `args`. Verified clean.
156
- - Test file `test_sweep_empty_dirs.py` — exempt (test files).
157
- - 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.
158
-
159
- **J6. Vague names**
160
- - Flag any name from the vague list: `result`, `data`, `output`, `response`, `value`, `item`, `temp`, `info`, `stuff`, `thing`. Vague verb prefixes: `handle`, `process`, `manage`, `do`.
161
- - `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.
162
- - Function names: `sweep`, `_log_walk_error`, `_build_parser`, `main` — none use the vague verb prefixes (`handle`, `process`, `manage`, `do`). `sweep` is concrete domain action.
163
- - Test file — the same no-new-comment rule applies.
164
- - 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.
165
-
166
- **J7. Type hints**
167
- - Walk every function in production files. Verify parameter and return types are present, no `Any`, no `# type: ignore`.
168
- - `_log_walk_error(os_error: OSError) -> None` (line 73) — parameter typed, return typed. Clean.
169
- - `sweep(root: str, min_age_seconds: int) -> list[str]` (line 77) — both parameters typed, return typed. Clean.
170
- - `_build_parser() -> argparse.ArgumentParser` (line 101) — no parameters, return typed. Clean.
171
- - `main() -> None` (line 113) — no parameters, return typed. Clean.
172
- - `config/sweep_config.py:142-143` — `DEFAULT_AGE_SECONDS: int = 120`, `DEFAULT_POLL_INTERVAL: int = 30`. Both annotated. Clean.
173
- - 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).
174
- - 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.
175
-
176
- **J8. New inline comments**
177
- - Every `#` comment line added by this diff in production or test code — flag. Changed directive, TODO, FIXME, HACK, XXX, and type-ignore comments are also findings.
178
- - `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.
179
- - `config/sweep_config.py` — line 140 is a module-level docstring. No inline comments. Clean.
180
- - Test file — the same no-new-comment rule applies.
181
- - `Install-SweepEmptyDirs.ps1` — line 227 (`#!/usr/bin/env pwsh`) is a shebang, exempt. No inline `#` comments added in the PowerShell file. Clean.
182
- - Adversarial probes: (a) is there any `# type:` comment that is 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.
183
-
184
- **J9. Logging format**
185
- - Walk every `log_*(...)` call. Must be `log_*("template with {}", arg)`, not `log_*(f"...")`.
186
- - `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.
187
- - Test file — the same no-new-comment rule applies.
188
- - 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).
189
-
190
- **J10. Imports inside functions**
191
- - Every `import` / `from ... import ...` statement — verify at module scope.
192
- - `sweep_empty_dirs.py` lines 64-70 — all imports at module top. Function bodies (lines 73-135) contain no `import` statements. Clean.
193
- - `config/sweep_config.py` — no imports. Clean.
194
- - Test file — lines 152-158 are module-scope imports, and line 164 is a deferred import at module scope after the sys.path guard. The documented import placement remains allowed.
195
- - 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.
196
-
197
- **J11. sys.path.insert dedup**
198
- - 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.
199
- - `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.
200
- - No `sys.path.insert` calls in production files.
201
- - 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.
202
-
203
- **J12. Hardcoded user paths**
204
- - Any string literal containing a user-specific home path?
205
- - `sweep_empty_dirs.py` — no hardcoded user paths. The `arguments.root` value is supplied at runtime via argparse positional argument (line 103). Clean.
206
- - `config/sweep_config.py` — no paths at all. Clean.
207
- - Test file — uses `tempfile.TemporaryDirectory()` (lines 178, 188, 197, 210, 216), which is the canonical safe pattern. Clean.
208
- - `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.
209
- - Adversarial probes: (a) does any error message or help string embed a user-specific home path? Grep the diff — 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.
210
-
211
- ## Cross-bucket questions to answer at the end
212
-
213
- 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.
214
- 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.)
215
- 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.
216
-
217
- ## Output
218
-
219
- 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.
220
-
221
- Note: most Category J findings are P2 (style / cleanup) since they don't affect runtime behavior; the adversarial-pass quota uses P2 here.
222
-
223
- ## Diff (4 new files, all lines in scope)
224
-
225
- ### packages/claude-dev-env/scripts/sweep_empty_dirs.py
226
- ```python
227
- #!/usr/bin/env python3
228
- """Delete empty directories older than 2 minutes under a given root."""
229
-
230
- import argparse
231
- import os
232
- import sys
233
- import time
234
-
235
- from config.sweep_config import DEFAULT_AGE_SECONDS
236
- from config.sweep_config import DEFAULT_POLL_INTERVAL
237
-
238
- def _log_walk_error(os_error: OSError) -> None:
239
- print(f"warning: cannot scan {os_error.filename} — {os_error.strerror}", file=sys.stderr)
240
-
241
- def sweep(root: str, min_age_seconds: int) -> list[str]:
242
- """Remove empty directories under *root* older than *min_age_seconds*."""
243
-
244
- now = time.time()
245
- removed: list[str] = []
246
-
247
- for each_directory_path, _, _ in os.walk(
248
- root, onerror=_log_walk_error, topdown=False
249
- ):
250
- try:
251
- created = os.path.getctime(each_directory_path)
252
- except OSError:
253
- continue
254
- if now - created >= min_age_seconds:
255
- try:
256
- os.rmdir(each_directory_path)
257
- print(f"deleted: {each_directory_path}")
258
- removed.append(each_directory_path)
259
- except OSError:
260
- pass
261
-
262
- return removed
263
-
264
- def _build_parser() -> argparse.ArgumentParser:
265
- parser = argparse.ArgumentParser(description="Delete empty directories older than a given age.")
266
- parser.add_argument("root", help="Root directory to scan")
267
- parser.add_argument("--age", type=int, default=DEFAULT_AGE_SECONDS,
268
- help=f"Minimum age in seconds (default: {DEFAULT_AGE_SECONDS} = 2 minutes)")
269
- parser.add_argument("--once", action="store_true",
270
- help="Single pass and exit instead of watching in a loop")
271
- parser.add_argument("--interval", type=int, default=DEFAULT_POLL_INTERVAL,
272
- help=f"Poll interval in seconds when looping (default: {DEFAULT_POLL_INTERVAL})")
273
- return parser
274
-
275
- def main() -> None:
276
- parser = _build_parser()
277
- arguments = parser.parse_args()
278
-
279
- if not os.path.isdir(arguments.root):
280
- print(f"error: not a directory: {arguments.root}", file=sys.stderr)
281
- sys.exit(1)
282
-
283
- if arguments.once:
284
- sweep(arguments.root, arguments.age)
285
- return
286
-
287
- print(f"watching {arguments.root} every {arguments.interval}s (age threshold: {arguments.age}s)")
288
- try:
289
- while True:
290
- sweep(arguments.root, arguments.age)
291
- time.sleep(arguments.interval)
292
- except KeyboardInterrupt:
293
- print("
294
- stopped.")
295
-
296
- if __name__ == "__main__":
297
- main()
298
- ```
299
-
300
- ### packages/claude-dev-env/scripts/config/sweep_config.py
301
- ```python
302
- """Centralized timing configuration for sweep_empty_dirs."""
303
-
304
- DEFAULT_AGE_SECONDS: int = 120
305
- DEFAULT_POLL_INTERVAL: int = 30
306
- ```
307
-
308
- ### packages/claude-dev-env/scripts/tests/test_sweep_empty_dirs.py
309
- ```python
310
- """Tests for sweep_empty_dirs.py"""
311
-
312
- from __future__ import annotations
313
-
314
- import datetime
315
- import os
316
- import subprocess
317
- import sys
318
- import tempfile
319
- import time
320
- from pathlib import Path
321
-
322
- _SCRIPTS_DIR = Path(__file__).resolve().parent.parent
323
- if str(_SCRIPTS_DIR) not in sys.path:
324
- sys.path.insert(0, str(_SCRIPTS_DIR))
325
-
326
- from sweep_empty_dirs import sweep # noqa: E402
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
- def test_deletes_empty_dir_older_than_threshold() -> None:
338
- with tempfile.TemporaryDirectory() as tmp:
339
- empty_dir = os.path.join(tmp, "old_empty")
340
- os.mkdir(empty_dir)
341
- _set_creation_time_windows(empty_dir, time.time() - 300)
342
- removed = sweep(tmp, min_age_seconds=120)
343
- assert empty_dir in removed
344
- assert not os.path.isdir(empty_dir)
345
-
346
- def test_skips_empty_dir_newer_than_threshold() -> None:
347
- with tempfile.TemporaryDirectory() as tmp:
348
- fresh_dir = os.path.join(tmp, "fresh_empty")
349
- os.mkdir(fresh_dir)
350
- removed = sweep(tmp, min_age_seconds=120)
351
- assert fresh_dir not in removed
352
- assert os.path.isdir(fresh_dir)
353
-
354
- def test_deletes_nested_empty_dirs() -> None:
355
- with tempfile.TemporaryDirectory() as tmp:
356
- leaf = os.path.join(tmp, "parent", "child", "leaf")
357
- os.makedirs(leaf)
358
- _set_creation_time_windows(os.path.join(tmp, "parent"), time.time() - 300)
359
- _set_creation_time_windows(os.path.join(tmp, "parent", "child"), time.time() - 300)
360
- _set_creation_time_windows(leaf, time.time() - 300)
361
- removed = sweep(tmp, min_age_seconds=120)
362
- assert leaf in removed
363
- assert os.path.join(tmp, "parent", "child") in removed
364
- assert os.path.join(tmp, "parent") in removed
365
-
366
- def test_empty_root_does_not_crash() -> None:
367
- with tempfile.TemporaryDirectory() as tmp:
368
- _set_creation_time_windows(tmp, time.time() - 300)
369
- sweep(tmp, min_age_seconds=120)
370
-
371
- def test_skips_nonempty_dir() -> None:
372
- with tempfile.TemporaryDirectory() as tmp:
373
- nonempty_dir = os.path.join(tmp, "has_stuff")
374
- os.mkdir(nonempty_dir)
375
- Path(nonempty_dir, "keepme.txt").write_text("hello")
376
- removed = sweep(tmp, min_age_seconds=0)
377
- assert nonempty_dir not in removed
378
- assert os.path.isdir(nonempty_dir)
379
- ```
380
-
381
- ### packages/claude-dev-env/scripts/Install-SweepEmptyDirs.ps1
382
- ```powershell
383
- #!/usr/bin/env pwsh
384
- param(
385
- [Parameter(ParameterSetName = "install")]
386
- [string]$Target,
387
-
388
- [Parameter(ParameterSetName = "install")]
389
- [int]$IntervalMinutes = 5,
390
-
391
- [Parameter(ParameterSetName = "install")]
392
- [int]$AgeSeconds = 120,
393
-
394
- [Parameter(ParameterSetName = "remove")]
395
- [switch]$Remove,
396
-
397
- [Parameter(ParameterSetName = "status")]
398
- [switch]$Status
399
- )
400
-
401
- $TaskName = "SweepEmptyDirs"
402
-
403
- if ($Status) {
404
- $task = Get-ScheduledTask -TaskName $TaskName -ErrorAction SilentlyContinue
405
- if (-not $task) {
406
- Write-Host "STATUS: $TaskName is not registered."
407
- return
408
- }
409
- Write-Host "STATUS: $TaskName is registered."
410
- Write-Host " State: $($task.State)"
411
- Write-Host " Actions:"
412
- foreach ($action in $task.Actions) {
413
- Write-Host " $($action.Execute) $($action.Arguments)"
414
- }
415
- Write-Host " Triggers:"
416
- foreach ($trigger in $task.Triggers) {
417
- Write-Host " $($trigger.Repetition.Interval) (starting $($trigger.StartBoundary))"
418
- }
419
- return
420
- }
421
-
422
- if ($Remove) {
423
- Unregister-ScheduledTask -TaskName $TaskName -Confirm:$false -ErrorAction SilentlyContinue
424
- Write-Host "$TaskName removed."
425
- return
426
- }
427
-
428
- $ScriptDir = Split-Path -Parent $PSCommandPath
429
- $ScriptPath = Join-Path $ScriptDir "sweep_empty_dirs.py"
430
-
431
- if (-not (Test-Path $ScriptPath)) {
432
- Write-Error "sweep_empty_dirs.py not found at: $ScriptPath"
433
- exit 1
434
- }
435
-
436
- if (-not $Target) {
437
- Write-Error "Parameter -Target is required (the directory to watch)."
438
- exit 1
439
- }
440
-
441
- if (-not (Test-Path $Target)) {
442
- Write-Error "Target directory does not exist: $Target"
443
- exit 1
444
- }
445
-
446
- $_py = Get-Command py -ErrorAction SilentlyContinue
447
- $PythonPath = if ($_py) { $_py.Source } else { (Get-Command python).Source }
448
- if (-not $PythonPath) {
449
- Write-Error "Cannot find Python (py or python) on PATH."
450
- exit 1
451
- }
452
- $Action = New-ScheduledTaskAction -Execute $PythonPath -Argument "$ScriptPath --once --age $AgeSeconds ""$Target"""
453
- $Trigger = New-ScheduledTaskTrigger -Daily -At "00:00" -RepetitionInterval (New-TimeSpan -Minutes $IntervalMinutes)
454
- $Settings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries -StartWhenAvailable
455
-
456
- Register-ScheduledTask -TaskName $TaskName -Action $Action -Trigger $Trigger -Settings $Settings -Force | Out-Null
457
- Write-Host "$TaskName registered — runs every ${IntervalMinutes}min against '$Target' (age ≥ ${AgeSeconds}s)."
458
- ```