claude-dev-env 1.35.0 → 1.36.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.
- package/agents/clean-coder.md +109 -1
- package/bin/install.mjs +28 -8
- package/bin/install.test.mjs +9 -1
- package/docs/CODE_RULES.md +3 -0
- package/docs/agents-md-alignment-plan.md +123 -0
- package/hooks/blocking/code_rules_enforcer.py +451 -39
- package/hooks/blocking/es_exe_path_rewriter.py +10 -4
- package/hooks/blocking/test_code_rules_enforcer.py +182 -0
- package/hooks/blocking/test_code_rules_enforcer_banned_identifier.py +106 -0
- package/hooks/blocking/test_code_rules_enforcer_cap_meta.py +173 -0
- package/hooks/blocking/test_code_rules_enforcer_collection_prefix.py +191 -0
- package/hooks/blocking/test_code_rules_enforcer_constant_equality.py +40 -0
- package/hooks/blocking/test_code_rules_enforcer_hardcoded_user_path.py +291 -0
- package/hooks/blocking/test_code_rules_enforcer_loop_variable_naming.py +87 -3
- package/hooks/blocking/test_code_rules_enforcer_naming_pattern.py +49 -0
- package/hooks/blocking/test_code_rules_enforcer_sys_path_insert.py +157 -0
- package/hooks/blocking/test_code_rules_enforcer_unused_imports.py +244 -0
- package/hooks/blocking/test_es_exe_path_rewriter.py +81 -3
- package/hooks/blocking/test_windows_rmtree_blocker.py +120 -8
- package/hooks/blocking/windows_rmtree_blocker.py +23 -6
- package/hooks/config/banned_identifiers_constants.py +24 -0
- package/hooks/config/hardcoded_user_path_constants.py +12 -0
- package/hooks/config/hook_log_extractor_constants.py +1 -1
- package/hooks/config/pre_tool_use_stdin.py +48 -0
- package/hooks/config/setup_project_paths_constants.py +4 -0
- package/hooks/config/stuttering_check_config.py +14 -0
- package/hooks/config/stuttering_import_binding_constants.py +11 -0
- package/hooks/config/sys_path_insert_constants.py +4 -0
- package/hooks/config/test_banned_identifiers_constants.py +48 -0
- package/hooks/config/test_hardcoded_user_path_constants.py +78 -0
- package/hooks/config/test_hook_log_extractor_constants.py +3 -3
- package/hooks/config/test_pre_tool_use_stdin.py +80 -0
- package/hooks/config/unused_module_import_constants.py +7 -0
- package/hooks/config/windows_rmtree_blocker_constants.py +3 -0
- package/hooks/diagnostic/hook_log_stop_wrapper.py +7 -4
- package/hooks/git-hooks/config.py +3 -3
- package/hooks/git-hooks/test_gate_utils.py +10 -10
- package/hooks/mypy.ini +2 -0
- package/package.json +1 -1
- package/rules/gh-paginate.md +125 -0
- package/skills/bugteam/CONSTRAINTS.md +12 -6
- package/skills/bugteam/SKILL.md +364 -154
- package/skills/bugteam/SKILL_EVALS.md +25 -23
- package/skills/bugteam/reference/README.md +2 -0
- package/skills/bugteam/reference/audit-and-teammates.md +2 -2
- package/skills/bugteam/reference/teardown-publish-permissions.md +1 -1
- package/skills/bugteam/reference/workflow-path-a-orchestrated-teams.md +113 -0
- package/skills/bugteam/reference/workflow-path-b-task-harness.md +48 -0
- package/skills/bugteam/scripts/reflow_skill_md.py +298 -0
- package/skills/bugteam/test_skill_additions.py +13 -4
- package/skills/bugteam/test_team_lifecycle.py +103 -0
- package/skills/findbugs/SKILL.md +3 -3
- package/skills/fixbugs/SKILL.md +4 -4
- package/skills/monitor-open-prs/SKILL.md +32 -2
- package/skills/monitor-open-prs/test_team_lifecycle.py +46 -0
- package/skills/pr-converge/SKILL.md +1206 -131
- package/skills/pr-converge/scripts/README.md +145 -0
- package/skills/pr-converge/scripts/caller-window-pid.ps1 +86 -0
- package/skills/pr-converge/scripts/check_pr_mergeability.py +79 -0
- package/skills/pr-converge/scripts/config/pr_converge_constants.py +65 -0
- package/skills/pr-converge/scripts/config/test_pr_converge_constants.py +176 -0
- package/skills/pr-converge/scripts/cursor-agents-continue-caller.cmd +9 -0
- package/skills/pr-converge/scripts/cursor-agents-continue-stop-others.ps1 +16 -0
- package/skills/pr-converge/scripts/cursor-agents-continue.ahk +172 -0
- package/skills/pr-converge/scripts/cursor-agents-continue.cmd +2 -0
- package/skills/pr-converge/scripts/evict_cached_config_modules.py +20 -0
- package/skills/pr-converge/scripts/fetch_bugbot_inline_comments.py +110 -0
- package/skills/pr-converge/scripts/fetch_bugbot_reviews.py +103 -0
- package/skills/pr-converge/scripts/fetch_copilot_inline_comments.py +112 -0
- package/skills/pr-converge/scripts/fetch_copilot_reviews.py +121 -0
- package/skills/pr-converge/scripts/mark_pr_ready.py +54 -0
- package/skills/pr-converge/scripts/open_followup_copilot_pr.py +136 -0
- package/skills/pr-converge/scripts/post-bugbot-run.helpers.ps1 +49 -0
- package/skills/pr-converge/scripts/post-bugbot-run.ps1 +33 -0
- package/skills/pr-converge/scripts/reflow_skill_md.py +288 -0
- package/skills/pr-converge/scripts/reply_to_inline_comment.py +84 -0
- package/skills/pr-converge/scripts/request_copilot_review.py +71 -0
- package/skills/pr-converge/scripts/resolve_pr_head.py +58 -0
- package/skills/pr-converge/scripts/review_field_helpers.py +43 -0
- package/skills/pr-converge/scripts/test_check_pr_mergeability.py +126 -0
- package/skills/pr-converge/scripts/test_evict_cached_config_modules.py +22 -0
- package/skills/pr-converge/scripts/test_fetch_bugbot_inline_comments.py +342 -0
- package/skills/pr-converge/scripts/test_fetch_bugbot_reviews.py +220 -0
- package/skills/pr-converge/scripts/test_fetch_copilot_inline_comments.py +372 -0
- package/skills/pr-converge/scripts/test_fetch_copilot_reviews.py +280 -0
- package/skills/pr-converge/scripts/test_mark_pr_ready.py +69 -0
- package/skills/pr-converge/scripts/test_open_followup_copilot_pr.py +236 -0
- package/skills/pr-converge/scripts/test_post_bugbot_run.py +195 -0
- package/skills/pr-converge/scripts/test_reply_to_inline_comment.py +159 -0
- package/skills/pr-converge/scripts/test_request_copilot_review.py +101 -0
- package/skills/pr-converge/scripts/test_resolve_pr_head.py +79 -0
- package/skills/pr-converge/scripts/test_review_field_helpers.py +80 -0
- package/skills/pr-converge/scripts/test_trigger_bugbot.py +139 -0
- package/skills/pr-converge/scripts/test_view_pr_context.py +111 -0
- package/skills/pr-converge/scripts/trigger_bugbot.py +77 -0
- package/skills/pr-converge/scripts/view_pr_context.py +47 -0
- package/skills/pr-converge/test_team_lifecycle.py +56 -0
- package/skills/pr-converge/workflows/ahk-auto-continue-loop.md +108 -0
- package/skills/pr-converge/workflows/schedule-wakeup-loop.md +37 -0
- package/skills/qbug/SKILL.md +4 -4
- package/skills/qbug/test_qbug_skill_post_fix_audit.py +2 -2
- package/skills/resume-review/SKILL.md +261 -0
- package/skills/bugteam/scripts/README.md +0 -58
- package/skills/bugteam/scripts/_claude_permissions_common.py +0 -219
- package/skills/bugteam/scripts/bugteam_code_rules_gate.py +0 -633
- package/skills/bugteam/scripts/bugteam_fix_hookspath.py +0 -260
- package/skills/bugteam/scripts/bugteam_preflight.py +0 -201
- package/skills/bugteam/scripts/config/bugteam_fix_hookspath_constants.py +0 -17
- package/skills/bugteam/scripts/grant_project_claude_permissions.py +0 -109
- package/skills/bugteam/scripts/revoke_project_claude_permissions.py +0 -135
- package/skills/bugteam/scripts/test_bugteam_code_rules_gate.py +0 -271
- package/skills/bugteam/scripts/test_bugteam_fix_hookspath.py +0 -267
- package/skills/bugteam/scripts/test_bugteam_preflight.py +0 -189
- package/skills/bugteam/scripts/test_claude_permissions_common.py +0 -44
- /package/skills/{bugteam → pr-converge}/scripts/config/__init__.py +0 -0
package/agents/clean-coder.md
CHANGED
|
@@ -128,7 +128,7 @@ for each_user in all_users:
|
|
|
128
128
|
|
|
129
129
|
### Complete type hints
|
|
130
130
|
|
|
131
|
-
Every parameter and return type is declared explicitly. `Any` is replaced with the concrete type. `# type: ignore` is
|
|
131
|
+
Every parameter and return type is declared explicitly. `Any` is replaced with the concrete type. When `# type: ignore` is genuinely necessary (a third-party type stub gap, an unavoidable runtime cast), append a trailing `# <reason>` of at least 5 characters explaining the constraint — the hook fires on bare `# type: ignore` without a justification. Output is mypy-clean: every file you write or edit passes `mypy_validator.py` at write time.
|
|
132
132
|
|
|
133
133
|
```python
|
|
134
134
|
def fetch_orders_for_customer(customer_id: int) -> list[Order]:
|
|
@@ -149,6 +149,14 @@ def fetch_with_retries(url: str) -> str:
|
|
|
149
149
|
|
|
150
150
|
String templates also count: when the structural literal text inside an f-string (paths, URLs, patterns) survives stripping the interpolations, that text is a magic value and belongs in config.
|
|
151
151
|
|
|
152
|
+
Bare string literals also count when their shape is structural: a multi-segment path (`/api/v1/users`), a URL with scheme, a Windows drive prefix, a leading absolute path, a regex anchor (`^foo`, `bar$`), or a regex escape sequence (`\d`, `\w`, `\s`). Extract these to `config/constants.py`.
|
|
153
|
+
|
|
154
|
+
Inline list and set literals of two or more constants in production function bodies move to `config/`. The hook fires on `[1, 2, 3]` or `{1, 2, 3}` inside a function body when every element is a constant — extract to a named module-level (or `config/`) constant.
|
|
155
|
+
|
|
156
|
+
### Library print() and CLI markers
|
|
157
|
+
|
|
158
|
+
Use logging for application and runtime output. `print()` is allowed when stdout is the integration contract: CLI entrypoints (paths under `/scripts/`, filenames ending `_cli.py`, `/cli.py`), or one-off automation helpers where the script's contract is its stdout (for example `print(json.dumps(...))`). The `check_library_print` hook fires on `print()` outside those path markers.
|
|
159
|
+
|
|
152
160
|
### Comment preservation
|
|
153
161
|
|
|
154
162
|
Existing comments on lines that remain otherwise unchanged stay exactly as you found them. The hook enforces both directions: the gate fires on a new inline `#` or `//` in production code, and the gate also fires when an existing comment disappears from a line you touched. New code self-documents via names; new docstrings on functions, methods, classes, and modules remain allowed.
|
|
@@ -189,6 +197,8 @@ def fetch_with_retries(url: str) -> str:
|
|
|
189
197
|
|
|
190
198
|
Production-code `UPPER_SNAKE = ...` at module scope outside `config/` is flagged. Exempt path families: `config/*`, `/migrations/`, `/workflow/`, `_tab.py`, `/states.py`, `/modules.py`, and all test files (`test_*.py`, `*_test.py`, `*.spec.*`, `conftest.py`, paths under `/tests/`).
|
|
191
199
|
|
|
200
|
+
Function-local `UPPER_SNAKE_CASE = ...` (assigned inside a function body in production code) is a non-blocking advisory: it usually belongs in `config/`. Move the value when there is no caller-specific reason for the local form.
|
|
201
|
+
|
|
192
202
|
### Logging format
|
|
193
203
|
|
|
194
204
|
Logging calls take the format string and arguments as separate parameters. The hook fires on any f-string passed to `log_*`.
|
|
@@ -247,10 +257,79 @@ def render_dashboard(profile: Profile) -> Dashboard:
|
|
|
247
257
|
|
|
248
258
|
When `profile` is already loaded, build the dashboard from it; fetch only when the data is genuinely absent.
|
|
249
259
|
|
|
260
|
+
### Test quality rules
|
|
261
|
+
|
|
262
|
+
Tests document behavior. The hook layer enforces several constraints on test files; clean-coder produces code that satisfies these on the first write.
|
|
263
|
+
|
|
264
|
+
- **Mock completeness.** When a mock object stands in for a record, populate every attribute the production code path under test reads. The `check_incomplete_mocks` hook flags mocks missing fields the assertions touch.
|
|
265
|
+
- **No decorators named `skip*` on test functions.** Tests fail with a clear error rather than skip when a system dependency is missing. The hook fires on any decorator (whether `@skip_if_missing_dependency`, `@unittest.skipIf`, `@pytest.mark.skip`, or any custom variant) whose identifier contains the substring `skip`.
|
|
266
|
+
- **No existence-only tests.** A test whose entire body is `assert callable(x)`, `assert hasattr(module, "name")`, or `assert obj is not None` covers no behavior. Replace with an assertion that exercises the behavior — call the function and assert on its return value or side effect.
|
|
267
|
+
- **No constant-equality tests.** A test whose sole assertion is `assert CACHE_DIR == "cache"` (or any `UPPER_SNAKE == LITERAL` pattern) just verifies the constant has not changed. Delete it or replace with a behavior assertion.
|
|
268
|
+
- **No tautological assertions.** `assert CONSTANT == CONSTANT` and `assert hasattr(module, "name")` pass regardless of the implementation. Replace with assertions that would fail if the implementation regressed.
|
|
269
|
+
- **Test through the public API.** Do not assert on private state, hook return values, internal class fields, `_protected_field`, `__private_field`, or `component.state.X`. If the test needs visibility the public API does not provide, the public API needs a method, not the test.
|
|
270
|
+
- **For React components**, query in this priority order: `getByRole > getByLabelText > getByText > getByTestId`. Use `userEvent` over `fireEvent` (more realistic). Mock at API boundaries (network calls, external services), not internal hooks or utilities.
|
|
271
|
+
- **Test infrastructure stays pragmatic.** A test helper file passes when all of these hold: ONE file (not a package); only `def` functions (no class definitions); no module-level state besides one or two simple constants; no caching, no lazy initialization, no abstractions added "for future use"; imports cover the test target plus stdlib only — no helper imports another helper.
|
|
272
|
+
- **E2E spec test naming.** In `*.spec.*` files, do not include `online` or `offline` substrings inside `test()`, `it()`, or `describe()` titles — file scope defines online/offline behavior. The `check_e2e_test_naming` hook fires on these substrings inside spec test titles.
|
|
273
|
+
|
|
274
|
+
### Required vs optional parameters
|
|
275
|
+
|
|
276
|
+
Add an optional parameter the moment a caller actually varies the value (YAGNI). When every existing call site passes the same value, make the parameter required (or inline the value as a local constant). Remove parameters that no caller passes and no body reads. The `check_unused_optional_parameters` hook fires when an optional parameter has no call site that passes a value different from the default.
|
|
277
|
+
|
|
278
|
+
### Platform safety and external tools
|
|
279
|
+
|
|
280
|
+
Several patterns silently fail or corrupt output on Windows or in shell/CLI integrations. Avoid each pattern when generating new code.
|
|
281
|
+
|
|
282
|
+
**Windows filesystem cleanup.** When generating Python code that removes a directory tree, do not call `shutil.rmtree` with the `ignore_errors=True` keyword argument. Files carrying the `ReadOnly` attribute (any file under `.git/objects/pack/`, anything Claude Code writes under `~/.claude/teams/`) raise `PermissionError`, which the keyword silently swallows; the tree stays on disk and cleanup looks successful but removes nothing. Linux is unaffected because `unlink` only needs write on the parent directory. Use a handler that strips `S_IWRITE` and retries the failing syscall:
|
|
283
|
+
|
|
284
|
+
```python
|
|
285
|
+
import os
|
|
286
|
+
import shutil
|
|
287
|
+
import stat
|
|
288
|
+
import sys
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
def _strip_read_only_and_retry(removal_function, target_path, *_exc_info):
|
|
292
|
+
try:
|
|
293
|
+
os.chmod(target_path, stat.S_IWRITE)
|
|
294
|
+
removal_function(target_path)
|
|
295
|
+
except OSError:
|
|
296
|
+
pass
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
def force_rmtree(target_path: str) -> None:
|
|
300
|
+
handler_kw = (
|
|
301
|
+
{"onexc": _strip_read_only_and_retry}
|
|
302
|
+
if sys.version_info >= (3, 12)
|
|
303
|
+
else {"onerror": _strip_read_only_and_retry}
|
|
304
|
+
)
|
|
305
|
+
try:
|
|
306
|
+
shutil.rmtree(target_path, **handler_kw)
|
|
307
|
+
except OSError:
|
|
308
|
+
pass
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
`onexc` is Python 3.12+; `onerror` is the pre-3.12 form. `*_exc_info` collapses the signature difference between them. `removal_function` is whichever syscall the rmtree was attempting (`os.unlink` for files, `os.rmdir` for directories) — re-call it after `chmod` to finish the work that originally failed.
|
|
312
|
+
|
|
313
|
+
**Windows directory creation in Node.** When generating Node code, prefer `mkdirSync(path, { recursive: true })` against any path that may already exist. Existing directories carrying the `ReadOnly` attribute raise without `recursive: true`. If a non-recursive call is intentional (you want the existence check to fail loudly), strip the attribute first via `os.chmod(path, stat.S_IWRITE)` (Python) or `(Get-Item $path -Force).Attributes = "Directory"` (PowerShell).
|
|
314
|
+
|
|
315
|
+
**Windows API integer parameters.** Use `0` rather than `None` for unused integer parameters in `win32gui` calls. The `check_windows_api_none` hook fires on `win32gui.X(.., None)` patterns — the Win32 API rejects `None` for these positions.
|
|
316
|
+
|
|
317
|
+
**`gh` CLI body content.** Every `gh` command that includes markdown body content uses `--body-file <path>` with a temporary file. Never pass body text via the `--body` argument or its `-b` shorthand: backticks in markdown body content are stored on GitHub as the literal string `\`` instead of rendering as code formatting. Affects: `gh issue create|edit|comment`, `gh pr create|edit|comment|review`. The `gh_body_arg_blocker.py` hook fires on the bash form.
|
|
318
|
+
|
|
250
319
|
### Test-file exemptions
|
|
251
320
|
|
|
252
321
|
Tests are exempt from several gates: magic values, constants location, file-global use-count, and the new-inline-comment gate. Test-file detection covers `test_*.py`, `*_test.py`, `*.test.*`, `*.spec.*`, `conftest.py`, and any path under `/tests/`.
|
|
253
322
|
|
|
323
|
+
## Files Clean-Coder Does Not Create or Edit
|
|
324
|
+
|
|
325
|
+
Several file classes are blocked from edit by the `sensitive_file_protector.py` hook or are otherwise out of scope for code generation:
|
|
326
|
+
|
|
327
|
+
- **Lock files.** `package-lock.json`, `yarn.lock`, `Pipfile.lock`, `poetry.lock`, `pnpm-lock.yaml`, `composer.lock`. Lock files are regenerated by their package manager, not edited by hand.
|
|
328
|
+
- **Secret and credential files.** `.env`, `.env.*`, `*.env`, `*.pem`, `*.key`, `*.p12`, `*.pfx`, `credentials.json`, `secrets.json`, `id_rsa`, `id_ed25519`. The hook denies edits — values belong in environment configuration outside the repository.
|
|
329
|
+
- **Scratch helper files.** Do not generate files matching `scratch_*.py`, `debug_*.py`, `try_*.py`, `repro_*.py`, or temporary log/output files (`*.log` outside `logs/`, `output_*.txt`, `dump_*.json`). Investigate in memory or via tool output instead. If the task genuinely requires a one-off script, name it after the feature it supports and remove it before the task closes.
|
|
330
|
+
- **Planning and audit artifacts.** `docs/plans/*.md`, `*.plan.md`, `SESSION_STATE.md`, `*.audit.json`, `*.audit.md`, `gate_audit_report.json` and similar. Plan documents live in `~/.claude/plans/` (untracked).
|
|
331
|
+
- **Image assets.** `*.png`, `*.jpg`, `*.jpeg`, `*.gif`, `*.webp`, `*.avif`, `*.svg`, `*.ico` belong in external storage, not the repository.
|
|
332
|
+
|
|
254
333
|
## Hook-Enforced Rules (pass these gates to commit your write)
|
|
255
334
|
|
|
256
335
|
These gates are checked by `code_rules_enforcer.py`. Satisfying each gate lets your file write succeed.
|
|
@@ -264,6 +343,30 @@ These gates are checked by `code_rules_enforcer.py`. Satisfying each gate lets y
|
|
|
264
343
|
| File length | Advisory at 400 lines (soft), strong nudge at 1000 — emitted to stderr; the write proceeds |
|
|
265
344
|
| Magic values | Literals inside production function bodies (0, 1, -1 exempt; structural f-string fragments included) |
|
|
266
345
|
| Constants location | Module-level `UPPER_SNAKE = ...` outside `config/` in production code (exempt path families listed in Inline Rule Reference) |
|
|
346
|
+
| Inline literal collections | `[1, 2, 3]` / `{1, 2, 3}` of two or more constants in production function bodies |
|
|
347
|
+
| Bare string structural magic | String literals matching path / URL / regex shapes outside f-strings |
|
|
348
|
+
| Banned identifiers | Variable named `result`, `data`, `output`, `response`, `value`, `item`, or `temp` in production code |
|
|
349
|
+
| Boolean naming | Boolean assignments lacking the `is_` / `has_` / `should_` / `can_` prefix |
|
|
350
|
+
| Loop variable naming | Loop variables not in `i` / `j` / `k` / `e` and not prefixed with `each_` |
|
|
351
|
+
| Collection naming | Collection assignments lacking the `all_` prefix |
|
|
352
|
+
| Parameter type annotations | Function parameter without an annotation |
|
|
353
|
+
| Return type annotations | Function without a declared return type |
|
|
354
|
+
| `Any` / `# type: ignore` | `Any` annotations and `# type: ignore` without a trailing reason of ≥5 characters |
|
|
355
|
+
| Function-local UPPER_SNAKE | Advisory — `UPPER_SNAKE_CASE = ...` inside a function body suggests a constant that should live in `config/` |
|
|
356
|
+
| File-global constant use count | Module-level UPPER_SNAKE constant used by exactly one function/method |
|
|
357
|
+
| Library `print()` | `print()` outside CLI markers (`/scripts/`, `_cli.py`, `/cli.py`) |
|
|
358
|
+
| Unused optional parameters | Optional parameter where every call site passes the same value as the default |
|
|
359
|
+
| Skip decorators on tests | Any decorator named `skip*` applied to a `test_*` function |
|
|
360
|
+
| Existence-only tests | Test body whose only assertions are `callable(x)`, `hasattr(...)`, or `x is not None` |
|
|
361
|
+
| Constant-equality tests | Test whose sole assertion is `UPPER_SNAKE == LITERAL` |
|
|
362
|
+
| Incomplete mocks | Advisory — mock objects missing fields the production code under test reads |
|
|
363
|
+
| Duplicated format patterns | Advisory — identical format-string templates at multiple call sites |
|
|
364
|
+
| `win32gui` calls with `None` | `win32gui.X(.., None)` for unused integer parameters — use `0` |
|
|
365
|
+
| E2E spec test names | `online` / `offline` substring in `test()` / `it()` / `describe()` titles in `*.spec.*` files |
|
|
366
|
+
| mypy validation | `mypy_validator.py` runs at write time — type errors block the write |
|
|
367
|
+
| Sensitive files | Edits to `.env*`, `*.pem`, `*.key`, lock files, etc. (`sensitive_file_protector.py`) |
|
|
368
|
+
| Windows-unsafe rmtree | The `shutil.rmtree` call with `ignore_errors=True` (`windows_rmtree_blocker.py`) |
|
|
369
|
+
| `gh` body argument | `gh ... --body "..."` in shell calls — must use `--body-file` (`gh_body_arg_blocker.py`) |
|
|
267
370
|
|
|
268
371
|
## Code Generation Checklist (the first-attempt-quality evaluator)
|
|
269
372
|
|
|
@@ -340,12 +443,17 @@ Every line you write or modify will:
|
|
|
340
443
|
- Satisfy every hook-enforced gate so each write succeeds on the first attempt
|
|
341
444
|
- Return CLEAN from `/check`, `/review-code`, and `/readability-review`
|
|
342
445
|
- Use complete type hints on every parameter and return
|
|
446
|
+
- Pass `mypy_validator.py` cleanly — every file is mypy-clean at write time
|
|
447
|
+
- Land in the format the project's `auto_formatter.py` produces — the formatter runs at write time, but generation should already match the canonical Black/Prettier output
|
|
448
|
+
- Carry no `Any` annotations and no `# type: ignore` without a ≥5-character trailing reason
|
|
343
449
|
- Pull every literal into a named constant (with the documented 0, 1, -1 exemptions)
|
|
344
450
|
- Use full words throughout (every abbreviation expanded)
|
|
345
451
|
- Self-document through naming alone (zero new inline comments)
|
|
346
452
|
- Use guard clauses and early returns in place of every `else` block
|
|
347
453
|
- Stay under 15 lines per function
|
|
348
454
|
- Import constants from centralized config (or module-level for hooks)
|
|
455
|
+
- Avoid platform pitfalls — no `shutil.rmtree` with `ignore_errors=True`, no `mkdirSync` against a possibly-existing path without `{ recursive: true }`, no `gh ... --body "..."` in shell calls
|
|
456
|
+
- Avoid generating sensitive files, lock files, or scratch helpers (see "Files Clean-Coder Does Not Create or Edit")
|
|
349
457
|
|
|
350
458
|
These standards apply to YOUR code — lines you add or change. Untouched code in the same file stays out of scope unless the task explicitly extends it.
|
|
351
459
|
|
package/bin/install.mjs
CHANGED
|
@@ -16,7 +16,7 @@ const PACKAGE_NAME = 'claude-dev-env';
|
|
|
16
16
|
const PACKAGE_VERSION = JSON.parse(readFileSync(join(PACKAGE_ROOT, 'package.json'), 'utf8')).version;
|
|
17
17
|
const packageRequire = createRequire(import.meta.url);
|
|
18
18
|
|
|
19
|
-
const CONTENT_DIRECTORIES = ['rules', 'docs', 'commands', 'agents', 'system-prompts', 'scripts'];
|
|
19
|
+
export const CONTENT_DIRECTORIES = ['rules', 'docs', 'commands', 'agents', 'system-prompts', 'scripts', '_shared'];
|
|
20
20
|
|
|
21
21
|
export function collectPackageSourceConflicts(packageDirectory) {
|
|
22
22
|
const gitConflictStatusCodes = new Set(['DD', 'AU', 'UD', 'UA', 'DU', 'AA', 'UU']);
|
|
@@ -289,7 +289,17 @@ function writeManifest(installedFiles) {
|
|
|
289
289
|
writeFileSync(MANIFEST_FILE, JSON.stringify(manifest, null, 2) + '\n');
|
|
290
290
|
}
|
|
291
291
|
|
|
292
|
-
function install(selectedGroups) {
|
|
292
|
+
function install(selectedGroups, options = {}) {
|
|
293
|
+
const isUpdateRefresh = Boolean(options.isUpdateRefresh);
|
|
294
|
+
if (isUpdateRefresh && !selectedGroups && existsSync(MANIFEST_FILE)) {
|
|
295
|
+
console.log(
|
|
296
|
+
`${PACKAGE_NAME}: --update — removing prior managed files under ${CLAUDE_HOME}, then reinstalling from the package.\n`,
|
|
297
|
+
);
|
|
298
|
+
purgeManagedInstallation({ requireManifest: false });
|
|
299
|
+
} else if (isUpdateRefresh) {
|
|
300
|
+
const installScope = selectedGroups ? `groups: ${selectedGroups.join(', ')}` : 'full';
|
|
301
|
+
console.log(`${PACKAGE_NAME}: --update — re-running ${installScope} install into ${CLAUDE_HOME}\n`);
|
|
302
|
+
}
|
|
293
303
|
const groupLabel = selectedGroups ? `groups: ${selectedGroups.join(', ')}` : 'all';
|
|
294
304
|
console.log(`\nInstalling ${PACKAGE_NAME} (${groupLabel})...\n`);
|
|
295
305
|
abortWhenPackageSourceHasConflicts(PACKAGE_ROOT);
|
|
@@ -534,11 +544,13 @@ function unsetGlobalGitHooksPathIfOurs() {
|
|
|
534
544
|
}
|
|
535
545
|
|
|
536
546
|
|
|
537
|
-
function
|
|
538
|
-
console.log(`\nUninstalling ${PACKAGE_NAME}...\n`);
|
|
547
|
+
function purgeManagedInstallation({ requireManifest }) {
|
|
539
548
|
if (!existsSync(MANIFEST_FILE)) {
|
|
540
|
-
|
|
541
|
-
|
|
549
|
+
if (requireManifest) {
|
|
550
|
+
console.error('No installation manifest found. Nothing to uninstall.');
|
|
551
|
+
process.exit(1);
|
|
552
|
+
}
|
|
553
|
+
return 0;
|
|
542
554
|
}
|
|
543
555
|
const manifest = JSON.parse(readFileSync(MANIFEST_FILE, 'utf8'));
|
|
544
556
|
let removed = 0;
|
|
@@ -581,12 +593,18 @@ function uninstall() {
|
|
|
581
593
|
console.log(`\nRemoved ${removed} files.\n`);
|
|
582
594
|
}
|
|
583
595
|
|
|
596
|
+
function uninstall() {
|
|
597
|
+
console.log(`\nUninstalling ${PACKAGE_NAME}...\n`);
|
|
598
|
+
purgeManagedInstallation({ requireManifest: true });
|
|
599
|
+
}
|
|
600
|
+
|
|
584
601
|
function printHelp() {
|
|
585
602
|
console.log(`
|
|
586
603
|
${PACKAGE_NAME} - Claude Code development standards installer
|
|
587
604
|
|
|
588
605
|
Usage:
|
|
589
606
|
npx ${PACKAGE_NAME} Install everything
|
|
607
|
+
npx ${PACKAGE_NAME} --update Full install: remove prior manifest-tracked files first, then reinstall
|
|
590
608
|
npx ${PACKAGE_NAME} --only X Install specific groups
|
|
591
609
|
npx ${PACKAGE_NAME} --uninstall Remove installed files
|
|
592
610
|
npx ${PACKAGE_NAME} --help Show this help
|
|
@@ -608,7 +626,9 @@ writes the previous contents to ~/.claude/backups/CLAUDE.md.<timestamp>.bak firs
|
|
|
608
626
|
`);
|
|
609
627
|
}
|
|
610
628
|
|
|
611
|
-
const
|
|
629
|
+
const rawArgs = process.argv.slice(2);
|
|
630
|
+
const args = rawArgs.filter((flag) => flag !== '--update');
|
|
631
|
+
const isUpdateRefresh = rawArgs.includes('--update');
|
|
612
632
|
if (args.includes('--help') || args.includes('-h')) {
|
|
613
633
|
printHelp();
|
|
614
634
|
} else if (args.includes('--uninstall')) {
|
|
@@ -629,5 +649,5 @@ if (args.includes('--help') || args.includes('-h')) {
|
|
|
629
649
|
process.exit(1);
|
|
630
650
|
}
|
|
631
651
|
}
|
|
632
|
-
install(selectedGroups);
|
|
652
|
+
install(selectedGroups, { isUpdateRefresh });
|
|
633
653
|
}
|
package/bin/install.test.mjs
CHANGED
|
@@ -5,7 +5,7 @@ import { mkdtempSync, rmSync, mkdirSync, writeFileSync } from 'node:fs';
|
|
|
5
5
|
import { tmpdir } from 'node:os';
|
|
6
6
|
import { join } from 'node:path';
|
|
7
7
|
|
|
8
|
-
import { collectPackageSourceConflicts } from './install.mjs';
|
|
8
|
+
import { collectPackageSourceConflicts, CONTENT_DIRECTORIES } from './install.mjs';
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
function createTemporaryGitRepository() {
|
|
@@ -127,6 +127,14 @@ test('collectPackageSourceConflicts returns empty when directory is not inside a
|
|
|
127
127
|
});
|
|
128
128
|
|
|
129
129
|
|
|
130
|
+
test('CONTENT_DIRECTORIES includes _shared so installer copies _shared/pr-loop/ to ~/.claude/_shared/', () => {
|
|
131
|
+
assert.ok(
|
|
132
|
+
CONTENT_DIRECTORIES.includes('_shared'),
|
|
133
|
+
'_shared must be in CONTENT_DIRECTORIES so the installer copies _shared/pr-loop/ alongside skills/',
|
|
134
|
+
);
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
|
|
130
138
|
test('collectPackageSourceConflicts surfaces both-added and deleted-by-them entries', () => {
|
|
131
139
|
const repositoryRoot = createTemporaryGitRepository();
|
|
132
140
|
try {
|
package/docs/CODE_RULES.md
CHANGED
|
@@ -49,6 +49,9 @@ These rules are automatically enforced by `code_rules_enforcer.py`. Violations b
|
|
|
49
49
|
| File line count | Advisory only — see [File length guidance](#65-file-length-guidance) |
|
|
50
50
|
| Magic values | No literals in production function bodies (0, 1, -1 exempt). **Test files exempt.** Includes string templates — if you strip the interpolations from an f-string and the remaining literal text is structural (paths, URLs, patterns), those fragments are magic values that belong in config |
|
|
51
51
|
| Constants location | No `UPPER_SNAKE =` outside `config/` in **production code**. **Test files may define local constants.** |
|
|
52
|
+
| Hardcoded user paths | No string literals naming a specific user's home directory in production code (`C:/Users/jon/...`, `/Users/alice/...`, `/home/bob/...`). Use `pathlib.Path.home()` or `os.path.expanduser('~')`. **Exempt:** test files, `config/` files, workflow registry paths (`/workflow/`, `_tab.py`, `/states.py`, `/modules.py`), Django migrations (`/migrations/`), and hook infrastructure (the enforcer embeds path patterns and must not self-block). |
|
|
53
|
+
| sys.path.insert dedup | `sys.path.insert(0, X)` must be guarded by `if X not in sys.path:` (or equivalent membership test) so reloads do not push the same entry repeatedly. **Test files exempt.** |
|
|
54
|
+
| Unused module-level imports | Module-level imports never referenced in the file body are flagged. Skipped for files declaring `__all__` (re-exports), files using `TYPE_CHECKING` (annotation-only imports), and lines marked `# noqa`. **Test files exempt.** |
|
|
52
55
|
|
|
53
56
|
### Where UPPER_SNAKE is allowed
|
|
54
57
|
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
# AGENTS.md Alignment Plan
|
|
2
|
+
|
|
3
|
+
This document captures the alignment between `AGENTS.md` (the canonical code-rules instruction set consumed by Cursor BugBot, Copilot, Claude, and other code-review/authoring agents) and the local enforcement layer (`code_rules_enforcer.py` and companion blocking hooks).
|
|
4
|
+
|
|
5
|
+
## Goal
|
|
6
|
+
|
|
7
|
+
Two-way alignment:
|
|
8
|
+
|
|
9
|
+
1. Every rule the hooks enforce at write time is documented in `AGENTS.md` so review tools without hook access (Cursor BugBot, Copilot, external reviewers) flag the same violations from the diff alone.
|
|
10
|
+
2. Every rule `AGENTS.md` requires is either (a) enforced by a hook at write time, (b) explicitly listed as bugbot-only judgment because it requires diff-semantic reasoning, or (c) a candidate for a future hook.
|
|
11
|
+
|
|
12
|
+
This file is the single source of truth for the alignment status.
|
|
13
|
+
|
|
14
|
+
## Methodology
|
|
15
|
+
|
|
16
|
+
- Read every `check_*` function in `packages/claude-dev-env/hooks/blocking/code_rules_enforcer.py` (28 functions, lines 108–2236) plus the `validate_content` dispatch (line 2239).
|
|
17
|
+
- Read every Write/Edit hook registered in `packages/claude-dev-env/hooks/hooks.json`.
|
|
18
|
+
- Read `packages/claude-dev-env/hooks/blocking/sensitive_file_protector.py` for file-level blocks.
|
|
19
|
+
- Read every rule file in `~/.claude/rules/*.md` and the canonical CODE_RULES.md files in this repo.
|
|
20
|
+
- Walked `AGENTS.md` bullet by bullet against the above.
|
|
21
|
+
|
|
22
|
+
## What this PR changes
|
|
23
|
+
|
|
24
|
+
### Added to `AGENTS.md`
|
|
25
|
+
|
|
26
|
+
- Intro pointer to the new **Hook enforcement** appendix.
|
|
27
|
+
- **Magic values & configuration**: explicit config-domain table (`config/timing.py` for timing values, `config/constants.py` for ports/URLs/thresholds, `config/selectors.py` for DOM locators) and a flag rule for new constants whose value or semantic name already exists in `config/`.
|
|
28
|
+
- **Design**: tightened the YAGNI bullet to specify "every existing call site passes the same value → required (or inline)".
|
|
29
|
+
- **Tests**: five new bullets — delete-no-value tests, fail-not-skip rule, pragmatic-infra five-point checklist, public-API-only assertions, React query priority and `userEvent`/API-boundary mocking.
|
|
30
|
+
- **Platform and tooling**: the unsafe-`rmtree`-on-Windows pattern is blocked (with the canonical `force_rmtree` helper inlined), Node `mkdirSync` requires `{ recursive: true }` on possibly-existing paths, and all `gh` commands with markdown body content must use `--body-file`.
|
|
31
|
+
- **Repo hygiene**: never-commit list (`docs/plans/*.md`, `*.plan.md`, `SESSION_STATE.md`, `*.audit.json`, `*.audit.md`, image extensions) and a scratch-file pattern enumeration.
|
|
32
|
+
- **Hook enforcement** appendix mapping each hook-enforced rule to its enforcing hook file.
|
|
33
|
+
|
|
34
|
+
### Deliberately excluded
|
|
35
|
+
|
|
36
|
+
PR draft lifecycle bullets and one-commit-per-review-stage rules were proposed and then removed. Cursor BugBot reviews diffs and PR descriptions; it cannot toggle PR ready/draft state or enforce commit-stage discipline. Those rules belong in human-facing workflow documentation, not in `AGENTS.md`.
|
|
37
|
+
|
|
38
|
+
## Open items
|
|
39
|
+
|
|
40
|
+
The following are documented gaps. None are addressed in this PR.
|
|
41
|
+
|
|
42
|
+
### Category A — enforced locally, not yet in AGENTS.md
|
|
43
|
+
|
|
44
|
+
Each closure is concrete and additive — recommended for a follow-up PR titled `docs(agents): close remaining hook-vs-rule gaps`.
|
|
45
|
+
|
|
46
|
+
| ID | Hook check (file:line) | Gap |
|
|
47
|
+
|---|---|---|
|
|
48
|
+
| A1 | `code_rules_enforcer.py:404` `check_windows_api_none` | `win32gui.X(.., None)` — use `0` for unused int params |
|
|
49
|
+
| A2 | `code_rules_enforcer.py:603` `check_e2e_test_naming` | `online`/`offline` in `test()`/`it()`/`describe()` titles in `*.spec.*` files |
|
|
50
|
+
| A3 | `code_rules_enforcer.py:713` `check_type_escape_hatches` | `# type: ignore` requires trailing `# <reason>` of ≥5 chars |
|
|
51
|
+
| A4 | `code_rules_enforcer.py:2126` `check_inline_literal_collections` | inline `[...]` / `{...}` of constants in production function bodies |
|
|
52
|
+
| A5 | `code_rules_enforcer.py:2085` `check_string_literal_magic` | bare string literals (not just f-string fragments) matching path / URL / regex shape |
|
|
53
|
+
| A6 | `code_rules_enforcer.py:843` `check_constants_outside_config_advisory` | function-local `UPPER_SNAKE = ...` advisory |
|
|
54
|
+
| A7 | `code_rules_enforcer.py:2003` `check_library_print` | `print()` outside CLI markers (`/scripts/`, `_cli.py`, `/cli.py`) |
|
|
55
|
+
| A8 | `sensitive_file_protector.py` | edits to `package-lock.json`, `yarn.lock`, `Pipfile.lock`, `poetry.lock`, `pnpm-lock.yaml`, `composer.lock` |
|
|
56
|
+
| A9 | `sensitive_file_protector.py` | edits to `.env*`, `*.pem`, `*.key`, `*.p12`, `*.pfx`, `credentials.json`, `secrets.json`, `id_rsa`, `id_ed25519` |
|
|
57
|
+
| A10 | `code_rules_enforcer.py:1175` `check_existence_check_tests` | `x is not None` as sole-assertion form (currently `AGENTS.md` lists only `callable` and `hasattr`) |
|
|
58
|
+
| A11 | `code_rules_enforcer.py:1839` `check_unused_optional_parameters` | the exact trigger: optional param where every call site passes the same value |
|
|
59
|
+
| A12 | `mypy_validator.py` | mypy-clean is required at write time |
|
|
60
|
+
| A13 | `auto_formatter.py` | auto-formatter runs on Write/Edit |
|
|
61
|
+
| A14 | `code_rules_enforcer.py:1072` `check_skip_decorators_in_tests` | the broader rule is "any decorator named `skip*`", not just `@skip_if_missing_dependency` |
|
|
62
|
+
| A15 | `code_rules_enforcer.py:1786` `check_duplicated_format_patterns` | identical format-string patterns at multiple call sites |
|
|
63
|
+
|
|
64
|
+
### Category B — required by AGENTS.md, no local hook (bugbot-only judgment)
|
|
65
|
+
|
|
66
|
+
These are reviewable from a diff but not amenable to AST/regex enforcement; they remain bugbot's judgment.
|
|
67
|
+
|
|
68
|
+
- **Naming**: `ctx`/`cfg`/`msg`/`btn`/`idx`/`cnt`/`elem`/`val`/`tmp` abbreviation expansion (B1); `X_by_Y` map naming (B2); preposition parameter prefixes (B3); banned function-name prefixes `handle_`/`process_`/`manage_`/`do_` (B4); component naming for what they ARE (B5).
|
|
69
|
+
- **Structure**: function length ≤ 30 lines (B6); two blank lines between Python top-level functions (B7).
|
|
70
|
+
- **Types**: concrete type captures actual shape, even when bare `object` would compile (B8).
|
|
71
|
+
- **Architecture**: functions vs classes vs ABCs (B9); SOLID S/O/L/I/D (B10); construction logic in models/services (B11); self-contained components (B12); `TODO:` on scaffolding (B14).
|
|
72
|
+
- **Data flow**: reuse data already in scope (B13); reuse-before-create semantic duplication (B23).
|
|
73
|
+
- **Tests**: pragmatic-infra five-point checklist (B15); test through public API (B16); React query priority and mocking strategy (B17).
|
|
74
|
+
- **Platform**: Node `mkdirSync({recursive: true})` (B18) — Python `shutil.rmtree` is hooked, Node equivalent is not.
|
|
75
|
+
- **Hygiene**: planning/image file globs (B19); scratch-file patterns (B20); PR-description references for kept files (B21).
|
|
76
|
+
- **Config**: domain placement within `config/` (B22); 0-reference dead-code constants (B24).
|
|
77
|
+
|
|
78
|
+
### Category C — JS/TS asymmetry
|
|
79
|
+
|
|
80
|
+
`code_rules_enforcer.py` `validate_content` (line 2239) dispatches on file extension. For `.js`/`.ts`/`.tsx`/`.jsx`, only three checks run:
|
|
81
|
+
|
|
82
|
+
- `check_comment_changes` — added inline / removed existing comments.
|
|
83
|
+
- `check_e2e_test_naming` — online/offline in spec test titles.
|
|
84
|
+
- `advise_file_line_count` — advisory.
|
|
85
|
+
|
|
86
|
+
Every other rule (magic values, constants location, banned identifiers, type annotations, boolean naming, loop variable naming, parameter annotations, return annotations, library print, optional-param-unused, `Any` detection) runs on Python files only. For JS/TS code, `AGENTS.md` is the only enforcement layer — bugbot review is the gate.
|
|
87
|
+
|
|
88
|
+
Whether each Python check has a JS/TS equivalent that ports cleanly: not investigated. Out of scope for this PR.
|
|
89
|
+
|
|
90
|
+
## Recommended hook additions
|
|
91
|
+
|
|
92
|
+
Not part of this PR — proposed as separate small PRs (one new check + tests per PR, following the existing `test_code_rules_enforcer_*.py` pattern). Each is AST/regex-tractable.
|
|
93
|
+
|
|
94
|
+
| Item | Suggested check | Note |
|
|
95
|
+
|---|---|---|
|
|
96
|
+
| B1 | extend `BANNED_IDENTIFIERS` | add `ctx`, `cfg`, `msg`, `btn`, `idx`, `cnt`, `elem`, `val`, `tmp` |
|
|
97
|
+
| B2 | dict-target naming rule | flag dict assignments whose target name lacks `_by_` |
|
|
98
|
+
| B3 | parameter-name prefix rule | flag direction parameters lacking `from_`/`to_`/`into_` |
|
|
99
|
+
| B4 | banned function-name prefixes | flag `def handle_*`, `def process_*`, `def manage_*`, `def do_*` |
|
|
100
|
+
| B6 | function-length advisory | per-function line count, advisory above 30 |
|
|
101
|
+
| B19 | extend `sensitive_file_protector.py` | block edits to `*.plan.md`, `SESSION_STATE.md`, `docs/plans/*.md`, `*.audit.{json,md}`, image extensions |
|
|
102
|
+
| B20 | scratch-file name patterns | block edits to `scratch_*.py`, `debug_*.py`, `try_*.py`, `repro_*.py` |
|
|
103
|
+
| B22 | config-domain placement | flag a constant added to `config/constants.py` whose name suggests `timing.py` or `selectors.py` |
|
|
104
|
+
| B24 | 0-reference dead-code constants | extend `check_file_global_constants_use_count` to also flag 0 callers |
|
|
105
|
+
|
|
106
|
+
## Files in this PR
|
|
107
|
+
|
|
108
|
+
- `AGENTS.md` (modified) — adds the rules and sections listed above.
|
|
109
|
+
- `packages/claude-dev-env/docs/agents-md-alignment-plan.md` (new) — this document.
|
|
110
|
+
|
|
111
|
+
## Verification
|
|
112
|
+
|
|
113
|
+
- Read updated `AGENTS.md` end-to-end and confirm each new bullet lives under the correct section heading.
|
|
114
|
+
- Confirm no rule mentioned in the **Hook enforcement** appendix is missing its source check in `code_rules_enforcer.py` or sibling hook file.
|
|
115
|
+
- Confirm the canonical `force_rmtree` helper code block in **Platform and tooling** describes the rule without containing the exact match-string the `windows_rmtree_blocker.py` hook scans for. Otherwise the hook will block any future edit to the file.
|
|
116
|
+
- No code changes — the `AGENTS.md` and plan doc are documentation only.
|
|
117
|
+
|
|
118
|
+
## Out of scope
|
|
119
|
+
|
|
120
|
+
- Closing the Category A gaps in `AGENTS.md` (tracked above; recommended follow-up PR).
|
|
121
|
+
- Implementing any of the Category B hook additions (each is its own small PR).
|
|
122
|
+
- Investigating JS/TS hook coverage parity (Category C).
|
|
123
|
+
- Changes to `code_rules_enforcer.py` or any other hook source file.
|