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.
Files changed (115) hide show
  1. package/agents/clean-coder.md +109 -1
  2. package/bin/install.mjs +28 -8
  3. package/bin/install.test.mjs +9 -1
  4. package/docs/CODE_RULES.md +3 -0
  5. package/docs/agents-md-alignment-plan.md +123 -0
  6. package/hooks/blocking/code_rules_enforcer.py +451 -39
  7. package/hooks/blocking/es_exe_path_rewriter.py +10 -4
  8. package/hooks/blocking/test_code_rules_enforcer.py +182 -0
  9. package/hooks/blocking/test_code_rules_enforcer_banned_identifier.py +106 -0
  10. package/hooks/blocking/test_code_rules_enforcer_cap_meta.py +173 -0
  11. package/hooks/blocking/test_code_rules_enforcer_collection_prefix.py +191 -0
  12. package/hooks/blocking/test_code_rules_enforcer_constant_equality.py +40 -0
  13. package/hooks/blocking/test_code_rules_enforcer_hardcoded_user_path.py +291 -0
  14. package/hooks/blocking/test_code_rules_enforcer_loop_variable_naming.py +87 -3
  15. package/hooks/blocking/test_code_rules_enforcer_naming_pattern.py +49 -0
  16. package/hooks/blocking/test_code_rules_enforcer_sys_path_insert.py +157 -0
  17. package/hooks/blocking/test_code_rules_enforcer_unused_imports.py +244 -0
  18. package/hooks/blocking/test_es_exe_path_rewriter.py +81 -3
  19. package/hooks/blocking/test_windows_rmtree_blocker.py +120 -8
  20. package/hooks/blocking/windows_rmtree_blocker.py +23 -6
  21. package/hooks/config/banned_identifiers_constants.py +24 -0
  22. package/hooks/config/hardcoded_user_path_constants.py +12 -0
  23. package/hooks/config/hook_log_extractor_constants.py +1 -1
  24. package/hooks/config/pre_tool_use_stdin.py +48 -0
  25. package/hooks/config/setup_project_paths_constants.py +4 -0
  26. package/hooks/config/stuttering_check_config.py +14 -0
  27. package/hooks/config/stuttering_import_binding_constants.py +11 -0
  28. package/hooks/config/sys_path_insert_constants.py +4 -0
  29. package/hooks/config/test_banned_identifiers_constants.py +48 -0
  30. package/hooks/config/test_hardcoded_user_path_constants.py +78 -0
  31. package/hooks/config/test_hook_log_extractor_constants.py +3 -3
  32. package/hooks/config/test_pre_tool_use_stdin.py +80 -0
  33. package/hooks/config/unused_module_import_constants.py +7 -0
  34. package/hooks/config/windows_rmtree_blocker_constants.py +3 -0
  35. package/hooks/diagnostic/hook_log_stop_wrapper.py +7 -4
  36. package/hooks/git-hooks/config.py +3 -3
  37. package/hooks/git-hooks/test_gate_utils.py +10 -10
  38. package/hooks/mypy.ini +2 -0
  39. package/package.json +1 -1
  40. package/rules/gh-paginate.md +125 -0
  41. package/skills/bugteam/CONSTRAINTS.md +12 -6
  42. package/skills/bugteam/SKILL.md +364 -154
  43. package/skills/bugteam/SKILL_EVALS.md +25 -23
  44. package/skills/bugteam/reference/README.md +2 -0
  45. package/skills/bugteam/reference/audit-and-teammates.md +2 -2
  46. package/skills/bugteam/reference/teardown-publish-permissions.md +1 -1
  47. package/skills/bugteam/reference/workflow-path-a-orchestrated-teams.md +113 -0
  48. package/skills/bugteam/reference/workflow-path-b-task-harness.md +48 -0
  49. package/skills/bugteam/scripts/reflow_skill_md.py +298 -0
  50. package/skills/bugteam/test_skill_additions.py +13 -4
  51. package/skills/bugteam/test_team_lifecycle.py +103 -0
  52. package/skills/findbugs/SKILL.md +3 -3
  53. package/skills/fixbugs/SKILL.md +4 -4
  54. package/skills/monitor-open-prs/SKILL.md +32 -2
  55. package/skills/monitor-open-prs/test_team_lifecycle.py +46 -0
  56. package/skills/pr-converge/SKILL.md +1206 -131
  57. package/skills/pr-converge/scripts/README.md +145 -0
  58. package/skills/pr-converge/scripts/caller-window-pid.ps1 +86 -0
  59. package/skills/pr-converge/scripts/check_pr_mergeability.py +79 -0
  60. package/skills/pr-converge/scripts/config/pr_converge_constants.py +65 -0
  61. package/skills/pr-converge/scripts/config/test_pr_converge_constants.py +176 -0
  62. package/skills/pr-converge/scripts/cursor-agents-continue-caller.cmd +9 -0
  63. package/skills/pr-converge/scripts/cursor-agents-continue-stop-others.ps1 +16 -0
  64. package/skills/pr-converge/scripts/cursor-agents-continue.ahk +172 -0
  65. package/skills/pr-converge/scripts/cursor-agents-continue.cmd +2 -0
  66. package/skills/pr-converge/scripts/evict_cached_config_modules.py +20 -0
  67. package/skills/pr-converge/scripts/fetch_bugbot_inline_comments.py +110 -0
  68. package/skills/pr-converge/scripts/fetch_bugbot_reviews.py +103 -0
  69. package/skills/pr-converge/scripts/fetch_copilot_inline_comments.py +112 -0
  70. package/skills/pr-converge/scripts/fetch_copilot_reviews.py +121 -0
  71. package/skills/pr-converge/scripts/mark_pr_ready.py +54 -0
  72. package/skills/pr-converge/scripts/open_followup_copilot_pr.py +136 -0
  73. package/skills/pr-converge/scripts/post-bugbot-run.helpers.ps1 +49 -0
  74. package/skills/pr-converge/scripts/post-bugbot-run.ps1 +33 -0
  75. package/skills/pr-converge/scripts/reflow_skill_md.py +288 -0
  76. package/skills/pr-converge/scripts/reply_to_inline_comment.py +84 -0
  77. package/skills/pr-converge/scripts/request_copilot_review.py +71 -0
  78. package/skills/pr-converge/scripts/resolve_pr_head.py +58 -0
  79. package/skills/pr-converge/scripts/review_field_helpers.py +43 -0
  80. package/skills/pr-converge/scripts/test_check_pr_mergeability.py +126 -0
  81. package/skills/pr-converge/scripts/test_evict_cached_config_modules.py +22 -0
  82. package/skills/pr-converge/scripts/test_fetch_bugbot_inline_comments.py +342 -0
  83. package/skills/pr-converge/scripts/test_fetch_bugbot_reviews.py +220 -0
  84. package/skills/pr-converge/scripts/test_fetch_copilot_inline_comments.py +372 -0
  85. package/skills/pr-converge/scripts/test_fetch_copilot_reviews.py +280 -0
  86. package/skills/pr-converge/scripts/test_mark_pr_ready.py +69 -0
  87. package/skills/pr-converge/scripts/test_open_followup_copilot_pr.py +236 -0
  88. package/skills/pr-converge/scripts/test_post_bugbot_run.py +195 -0
  89. package/skills/pr-converge/scripts/test_reply_to_inline_comment.py +159 -0
  90. package/skills/pr-converge/scripts/test_request_copilot_review.py +101 -0
  91. package/skills/pr-converge/scripts/test_resolve_pr_head.py +79 -0
  92. package/skills/pr-converge/scripts/test_review_field_helpers.py +80 -0
  93. package/skills/pr-converge/scripts/test_trigger_bugbot.py +139 -0
  94. package/skills/pr-converge/scripts/test_view_pr_context.py +111 -0
  95. package/skills/pr-converge/scripts/trigger_bugbot.py +77 -0
  96. package/skills/pr-converge/scripts/view_pr_context.py +47 -0
  97. package/skills/pr-converge/test_team_lifecycle.py +56 -0
  98. package/skills/pr-converge/workflows/ahk-auto-continue-loop.md +108 -0
  99. package/skills/pr-converge/workflows/schedule-wakeup-loop.md +37 -0
  100. package/skills/qbug/SKILL.md +4 -4
  101. package/skills/qbug/test_qbug_skill_post_fix_audit.py +2 -2
  102. package/skills/resume-review/SKILL.md +261 -0
  103. package/skills/bugteam/scripts/README.md +0 -58
  104. package/skills/bugteam/scripts/_claude_permissions_common.py +0 -219
  105. package/skills/bugteam/scripts/bugteam_code_rules_gate.py +0 -633
  106. package/skills/bugteam/scripts/bugteam_fix_hookspath.py +0 -260
  107. package/skills/bugteam/scripts/bugteam_preflight.py +0 -201
  108. package/skills/bugteam/scripts/config/bugteam_fix_hookspath_constants.py +0 -17
  109. package/skills/bugteam/scripts/grant_project_claude_permissions.py +0 -109
  110. package/skills/bugteam/scripts/revoke_project_claude_permissions.py +0 -135
  111. package/skills/bugteam/scripts/test_bugteam_code_rules_gate.py +0 -271
  112. package/skills/bugteam/scripts/test_bugteam_fix_hookspath.py +0 -267
  113. package/skills/bugteam/scripts/test_bugteam_preflight.py +0 -189
  114. package/skills/bugteam/scripts/test_claude_permissions_common.py +0 -44
  115. /package/skills/{bugteam → pr-converge}/scripts/config/__init__.py +0 -0
@@ -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 replaced with a fix that resolves the underlying type issue.
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 uninstall() {
538
- console.log(`\nUninstalling ${PACKAGE_NAME}...\n`);
547
+ function purgeManagedInstallation({ requireManifest }) {
539
548
  if (!existsSync(MANIFEST_FILE)) {
540
- console.error('No installation manifest found. Nothing to uninstall.');
541
- process.exit(1);
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 args = process.argv.slice(2);
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
  }
@@ -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 {
@@ -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.