claude-dev-env 1.50.3 → 1.51.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLAUDE.md +0 -8
- package/_shared/pr-loop/audit-contract.md +3 -3
- package/_shared/pr-loop/scripts/pr_loop_shared_constants/preflight_self_heal_constants.py +28 -0
- package/_shared/pr-loop/scripts/preflight.py +18 -6
- package/_shared/pr-loop/scripts/preflight_self_heal.py +164 -0
- package/_shared/pr-loop/scripts/tests/test_preflight.py +39 -0
- package/_shared/pr-loop/scripts/tests/test_preflight_self_heal.py +273 -0
- package/agents/clean-coder.md +1 -1
- package/agents/code-quality-agent.md +7 -5
- package/audit-rubrics/category_rubrics/category-a-api-contracts.md +3 -0
- package/audit-rubrics/category_rubrics/category-f-silent-failures.md +3 -0
- package/audit-rubrics/category_rubrics/category-k-codebase-conflicts.md +8 -2
- package/audit-rubrics/category_rubrics/category-n-test-name-scenario-verifier.md +3 -0
- package/audit-rubrics/category_rubrics/category-o-docstring-vs-impl-drift.md +39 -0
- package/audit-rubrics/category_rubrics/category-p-name-vs-behavior-contract.md +40 -0
- package/audit-rubrics/prompts/category-a-api-contracts.md +11 -4
- package/audit-rubrics/prompts/category-b-selector-engine-compat.md +2 -2
- package/audit-rubrics/prompts/category-c-resource-cleanup.md +1 -1
- package/audit-rubrics/prompts/category-d-scoping-and-ordering.md +1 -1
- package/audit-rubrics/prompts/category-e-dead-code.md +1 -1
- package/audit-rubrics/prompts/category-f-silent-failures.md +13 -2
- package/audit-rubrics/prompts/category-g-bounds-and-overflow.md +1 -1
- package/audit-rubrics/prompts/category-h-security-boundaries.md +1 -1
- package/audit-rubrics/prompts/category-i-concurrency.md +1 -1
- package/audit-rubrics/prompts/category-j-code-rules-compliance.md +1 -1
- package/audit-rubrics/prompts/category-k-codebase-conflicts.md +15 -5
- package/audit-rubrics/prompts/category-l-behavior-equivalence.md +1 -1
- package/audit-rubrics/prompts/category-m-producer-consumer-cardinality.md +1 -1
- package/audit-rubrics/prompts/category-n-test-name-scenario-verifier.md +10 -3
- package/audit-rubrics/prompts/category-o-docstring-vs-impl-drift.md +74 -0
- package/audit-rubrics/prompts/category-p-name-vs-behavior-contract.md +75 -0
- package/docs/CODE_RULES.md +24 -346
- package/package.json +1 -1
- package/rules/ask-user-question-required.md +2 -41
- package/rules/confirm-implementation-forks.md +3 -44
- package/rules/gh-body-file.md +2 -78
- package/rules/gh-paginate.md +2 -78
- package/rules/plain-language.md +2 -41
- package/rules/prompt-workflow-context-controls.md +9 -38
- package/rules/shell-invocation-policy.md +2 -141
- package/rules/testing.md +10 -0
- package/rules/vault-context.md +3 -32
- package/rules/windows-filesystem-safe.md +3 -87
- package/scripts/sync_to_cursor/rules.py +201 -79
- package/scripts/tests/test_sync_to_cursor.py +122 -26
- package/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/path_resolver_constants.py +2 -0
- package/skills/_shared/pr-loop/scripts/test_build_audit_prompt.py +51 -4
- package/skills/auditing-claude-config/SKILL.md +6 -1
- package/skills/bugteam/CONSTRAINTS.md +1 -1
- package/skills/bugteam/PROMPTS.md +8 -6
- package/skills/bugteam/SKILL.md +5 -5
- package/skills/bugteam/reference/audit-and-teammates.md +1 -1
- package/skills/bugteam/reference/audit-contract.md +4 -4
- package/skills/bugteam/reference/design-rationale.md +1 -1
- package/skills/bugteam/reference/obstacles/audit-walk-categories.md +1 -1
- package/skills/bugteam/reference/team-setup.md +17 -5
- package/skills/bugteam/scripts/bugteam_preflight.py +22 -10
- package/skills/bugteam/scripts/test_bugteam_preflight.py +32 -0
- package/skills/copilot-review/SKILL.md +5 -8
- package/skills/doc-gist/SKILL.md +5 -8
- package/skills/fixbugs/SKILL.md +1 -1
- package/skills/gh-paginate/SKILL.md +84 -0
- package/skills/pr-converge/SKILL.md +28 -1
- package/skills/pr-converge/reference/per-tick.md +24 -8
- package/skills/pre-compact/SKILL.md +4 -9
- package/skills/refine/SKILL.md +8 -2
- package/skills/structure-prompt/SKILL.md +5 -10
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
Audit [REPO/ARTIFACT] [TARGET_ID] for **Category P only** (name / regex / word-list vs behavior-contract precision). Skip A–O. Sub-bucket forced-exhaustion mode: Category P is decomposed into 7 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 — every newly-added or renamed identifier plus the body code that implements its contract]
|
|
4
|
+
|
|
5
|
+
- Title / one-line summary: [TITLE]
|
|
6
|
+
- Head ref / SHA at audit time: [HEAD_SHA]
|
|
7
|
+
- New / renamed boolean flags (file:line + identifier + lifecycle sites): [NEW_FLAGS]
|
|
8
|
+
- New / renamed predicate helpers (file:line + identifier + body + return contract): [NEW_PREDICATES]
|
|
9
|
+
- New / renamed regex constants (file:line + identifier + regex source + anchors): [NEW_REGEXES]
|
|
10
|
+
- New / renamed word-lists or replacement tables (file:line + identifier + entries): [NEW_LISTS]
|
|
11
|
+
- New / renamed helper-function names (file:line + identifier + signature + return): [NEW_HELPERS]
|
|
12
|
+
- Stated intent of each new identifier: [INTENT]
|
|
13
|
+
|
|
14
|
+
ID prefix: `find`.
|
|
15
|
+
|
|
16
|
+
[ONE-PARAGRAPH FRAME: list every fresh identifier introduced by the diff. State the audit goal: for each identifier, verify the body's behavior matches the contract the name asserts — not broader, not narrower.]
|
|
17
|
+
|
|
18
|
+
## Source material ([N] files/sections, all lines in scope)
|
|
19
|
+
|
|
20
|
+
[INLINE each fresh identifier in context — the constant declaration with the value, the function signature with the body, the flag with every assignment / reset site, the regex source with surrounding usage.]
|
|
21
|
+
|
|
22
|
+
## Sub-buckets (each requires Shape A finding OR Shape B with ≥3 adversarial probes)
|
|
23
|
+
|
|
24
|
+
**P1. Boolean / flag names assert state the body keeps**
|
|
25
|
+
- For every new `is_*` / `has_*` / `was_*` / `should_*` flag, trace the body. Every set site must be paired with a reset site that fires when the named condition becomes false.
|
|
26
|
+
- Adversarial probes: (a) grep every assignment to the flag — count set-true vs set-false; (b) for AST-driven flags, walk the visit method and confirm scope-exit semantics (def visited → flag set; dedent / function-exit → flag reset); (c) construct an input where the flag should be false after a prior true region — does the code agree.
|
|
27
|
+
|
|
28
|
+
**P2. Predicate-name breadth matches body coverage**
|
|
29
|
+
- For every new `_is_*` / `_has_*` / `_can_*` predicate function, list the body's `return True` branches. The named predicate must hold on the union of those branches and nothing else.
|
|
30
|
+
- Adversarial probes: (a) construct an input that satisfies the name but returns False — that is a P2 narrowness finding; (b) construct an input that does not satisfy the name but returns True — that is a P2 breadth finding; (c) check neighbor predicates — is the body's actual contract the responsibility of a different helper.
|
|
31
|
+
|
|
32
|
+
**P3. Regex name vs regex shape** ⭐ canonical P case
|
|
33
|
+
- For every new `*_PATTERN` / `*_REGEX` constant, read the regex source. Confirm the anchors (`^`, `$`, `\b`, lookarounds) match the name's promised shape. `FILE_PATH_PATTERN` implies path shape; the regex must include path-shape anchors (slash count, segment shape, length bounds) enough to reject non-path inputs.
|
|
34
|
+
- Adversarial probes: (a) construct 3 non-matching inputs that the regex's name says it should reject — does the regex reject them; (b) construct 3 inputs that satisfy the regex but violate the name — those are P3 findings; (c) check sibling regexes in the same module — is the new regex anchored consistently with them.
|
|
35
|
+
|
|
36
|
+
**P4. Helper-function name vs return contract**
|
|
37
|
+
- For every new helper function, compare the name to the return shape AND the matching semantics. `_split_module_stem_prefix` should split on a stem-prefix boundary; if the returned prefix substring-matches unrelated stems, the matching semantics diverge from the name.
|
|
38
|
+
- Adversarial probes: (a) feed the helper an input class the name names — is the return what the name implies; (b) feed it an input the name excludes — does the helper still produce output; (c) check downstream callers — do they consume the return on the contract the name implies.
|
|
39
|
+
|
|
40
|
+
**P5. Word-list / replacement-table precision**
|
|
41
|
+
- For every new reference list, walk each entry. Every entry must satisfy the named class. A list named `HEAVY_WORDS_TO_REPLACE` may not contain words that are ordinary in legitimate user inputs.
|
|
42
|
+
- Adversarial probes: (a) for each entry, construct 3 legitimate-looking inputs containing it — would the gate fire on legitimate writing; (b) check entry density against the named class — is the list curated or kitchen-sink; (c) propose 3 entries that fit the named class better and 3 entries that do not fit — does the list match the proposed-good or the proposed-bad set.
|
|
43
|
+
|
|
44
|
+
**P6. Class / module name vs scope**
|
|
45
|
+
- For every new class or module, list the responsibilities (exported symbols, dispatched calls). Confirm each fits the named scope.
|
|
46
|
+
- Adversarial probes: (a) list exported symbols and ask whether each matches the named scope; (b) check imports — does the module pull in unrelated subsystems; (c) check call-graph centrality — does the class own one thing or many.
|
|
47
|
+
|
|
48
|
+
**P7. Reverse: name understates what the body does**
|
|
49
|
+
- For every new identifier, ask whether the body produces effects the name does not promise (side effects, more return values, broader input acceptance, hidden state mutation). A future caller relying on the narrow name will be surprised.
|
|
50
|
+
- Adversarial probes: (a) walk side effects (writes, network calls, global mutation) — are they named in the contract; (b) walk return tuples / objects — is every component named; (c) walk input acceptance — does the helper silently accept inputs outside its named class.
|
|
51
|
+
|
|
52
|
+
## Cross-bucket questions to answer at the end
|
|
53
|
+
|
|
54
|
+
Q1: Across all 7 sub-buckets, which identifier's name-vs-body gap is most likely to cause false positives in a production gate? Cite the identifier and the input class that trips it.
|
|
55
|
+
|
|
56
|
+
Q2: Which identifier is at highest risk of being relied on by a future caller based on the name alone, with the broader / narrower body causing a regression? Cite the identifier and the assumed contract.
|
|
57
|
+
|
|
58
|
+
Q3: Which identifier most clearly shows the body should be renamed (the name is honest about intent and the body is wrong) vs the name should be tightened (the body is the contract and the name overpromises)? Cite the identifier and the recommended direction.
|
|
59
|
+
|
|
60
|
+
## Output
|
|
61
|
+
|
|
62
|
+
Lead: `Total: N (P0=N, P1=N, P2=N)`. For each sub-bucket P1-P7, produce Shape A or Shape B (with ≥3 probes). Each Shape A finding must cite (a) the identifier file:line, (b) one concrete input that shows the name-vs-body gap, and (c) the recommended fix direction (rename vs body-tighten). Cross-bucket Q1-Q3 answers after the per-sub-bucket walk. Adversarial second pass: "assume your first pass missed at least 3 identifiers whose names overstate what the body actually does — find them." Open Questions section for ambiguities. Read-only. No edits, no commits.
|
|
63
|
+
|
|
64
|
+
---
|
|
65
|
+
|
|
66
|
+
# Worked example: jl-cmd/claude-code-config PR #508
|
|
67
|
+
|
|
68
|
+
Audit jl-cmd/claude-code-config PR #508 for **Category P only** (name / regex / word-list vs behavior-contract precision). Skip A-O.
|
|
69
|
+
|
|
70
|
+
PR #508 ships the plain-language blocker hook. Two fresh identifiers in `plain_language_blocker_constants.py` show the canonical P shapes:
|
|
71
|
+
|
|
72
|
+
- `FILE_PATH_PATTERN` (line 286) — a regex of shape `(\S+/\S+)` named for file paths but unanchored. Probes: `client/server`, `and/or`, `TCP/IP`, `lookup/replace` all match and are silently exempted from the plain-language scan even though none is a file path. **P3 finding.**
|
|
73
|
+
- `HARD_DENY_REPLACEMENT_TERMS` (line 247) — a hard-deny replacement-by-term list named for ban-worthy heavy words but containing `command`, `address`, `function`, `subject`, `same`, `such`, `said`, `it is`, `there is`, `however`, `forward`. Each entry trips on ordinary technical English (`run the command`, `the address bar`, `validate the function`). **P5 finding.**
|
|
74
|
+
|
|
75
|
+
Expected output: two P-class findings with cited file:line of the constant, cited example inputs (`client/server`, `run the command`), and recommended direction (anchor the regex to need repo-relative paths; curate the deny-list to drop dev-domain false positives).
|
package/docs/CODE_RULES.md
CHANGED
|
@@ -1,419 +1,97 @@
|
|
|
1
1
|
# Code Rules Reference
|
|
2
2
|
|
|
3
|
-
Compact reference for agents.
|
|
3
|
+
Compact reference for agents. ⚡ marks rules enforced by `code_rules_enforcer.py` — the hook blocks the Write/Edit and returns the corrective detail at violation time, so this document lists those rules by name only.
|
|
4
4
|
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
## COMMENT PRESERVATION (ABSOLUTE RULE)
|
|
8
8
|
|
|
9
|
-
**NEVER remove existing comments.**
|
|
10
|
-
|
|
11
|
-
- Existing comments are SACRED — never delete, rewrite, or "clean up" existing comments
|
|
12
|
-
- New inline comments are not needed — write self-documenting code instead
|
|
13
|
-
- Module-level docstrings are allowed in all files
|
|
14
|
-
- Docstrings for new files/methods/classes are allowed
|
|
15
|
-
- **Test files are exempt:** comments and docstrings inside test functions are allowed
|
|
16
|
-
- The hook enforces BOTH directions: blocks new inline comments AND blocks deletion of existing comments
|
|
17
|
-
|
|
18
|
-
**Scope:** Only evaluate comments on lines YOU are actively changing. If code is untouched, its comments are untouched.
|
|
9
|
+
**NEVER remove existing comments.** Existing comments are SACRED. Only evaluate comments on lines you are actively changing. Do not add new inline comments in production code — write self-documenting code. Docstrings (module/class/function) are always allowed. Test files are exempt. The hook enforces both directions: it blocks new inline comments AND blocks deletion of existing ones.
|
|
19
10
|
|
|
20
11
|
---
|
|
21
12
|
|
|
22
13
|
## CORE PRINCIPLES
|
|
23
14
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
### Centralized Configuration
|
|
30
|
-
One source of truth. Every constant lives in ONE place (`config/`).
|
|
31
|
-
|
|
32
|
-
### Reuse Before Create
|
|
33
|
-
Search first. Import second. Create last.
|
|
34
|
-
|
|
35
|
-
### Encapsulation Enables Cleaner Naming
|
|
36
|
-
Expose constants via helper functions: `isMaxLevel(level)` > `level >= MAXIMUM_LEVEL`
|
|
15
|
+
- **Self-documenting code** — naming over comments. Full 8-dimension rubric: `~/.claude/skills/readability-review/SKILL.md` (`/check` for parallel team review, `/readability-review` standalone).
|
|
16
|
+
- **Centralized configuration** — every constant lives in ONE place (`config/`).
|
|
17
|
+
- **Reuse before create** — search first, import second, create last.
|
|
18
|
+
- **Encapsulation enables cleaner naming** — `isMaxLevel(level)` > `level >= MAXIMUM_LEVEL`.
|
|
37
19
|
|
|
38
20
|
---
|
|
39
21
|
|
|
40
22
|
## ⚡ HOOK-ENFORCED RULES
|
|
41
23
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
| Rule | What's Checked |
|
|
45
|
-
|------|----------------|
|
|
46
|
-
| No NEW comments | `#` / `//` in new production code only (existing comments NEVER removed; exempt markers: shebangs, `# type:`, `# noqa`, `# pylint:`, `# pragma:`, `// @ts-`, `// eslint-`, `// prettier-`, `/// `; docstrings and module docstrings are always allowed; all test files are exempt) |
|
|
47
|
-
| Imports at top | No `import` inside function bodies |
|
|
48
|
-
| Logging format args | No `log_*(f"...")` - use `log_*("...", arg)` |
|
|
49
|
-
| File line count | Advisory only — see [File length guidance](#65-file-length-guidance) |
|
|
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
|
-
| 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.** |
|
|
55
|
-
| Banned identifiers | Single-letter or 2–4 char abbreviations in production code: `ctx`, `cfg`, `msg`, `btn`, `idx`, `cnt`, `tmp`, `elem`, `val`. Test files exempt; loop counters `i`/`j`/`k` and exception `e` exempt. See §5 for the full list and rationale. |
|
|
56
|
-
| Banned function prefixes | Function names starting with `handle_`, `process_`, `manage_`, or `do_` are flagged — these prefixes describe nothing about behavior. Name functions after the noun they produce or the verb they perform (`fetch_user`, `validate_payload`). **Test files exempt.** |
|
|
57
|
-
| Type escape hatches | `from typing import Any`, `cast()`, and inline `Any` in production are flagged outside boundary files (`__init__.py`, `protocols.py`, `types.py`, `conftest.py`). Name the concrete shape instead. |
|
|
58
|
-
| Bare except | `except:` and `except BaseException:` swallow KeyboardInterrupt/SystemExit; `except Exception:` hides bugs by catching nearly every error class. Name the specific exception(s) you intend to catch (tuple form `except (ValueError, KeyError):` is fine). **Test files and hook infrastructure exempt.** |
|
|
59
|
-
| Boundary types — Any in signatures | `Any` appearing directly or nested inside a generic in a function signature (parameters, return type) or class attribute annotation is flagged. Local variable annotations are exempt. Files named `protocols.py` or `types.py` are interface-declaration surfaces and exempt. |
|
|
60
|
-
| Stub implementations | Functions whose body is `pass`, `...`, or `raise NotImplementedError` in production code are flagged unless declared abstract (`@abstractmethod`) or part of a `Protocol`. Implement the function or remove it. |
|
|
61
|
-
| TypedDict encode/decode pairs | Every `class FooPayload(TypedDict):` in production code must have companion `_encode_foo_payload(...)` and `_decode_foo_payload(...)` functions in the same module — boundary serialization should not leak to callers. |
|
|
62
|
-
| Test-mode branching in production | Reading `TESTING`, `PYTEST_CURRENT_TEST`, `IS_TEST`, etc. from production code creates two parallel implementations. Use dependency injection so production stays single-path. **Test files and hook infrastructure exempt.** |
|
|
63
|
-
| Thin wrapper files | A non-`__init__.py` module whose body is only imports (optionally with an `__all__` assignment) is a re-export indirection with no payload. Callers should import from the real module. `__init__.py` is the canonical re-export surface and is exempt. |
|
|
64
|
-
| Docstring format (Google-style) | Public functions/methods (no leading underscore, not dunder, body > 3 lines, not `@property`/`@abstractmethod`) require Google-style `Args:` / `Returns:` (or `Yields:`) / `Raises:` sections matching the signature. **Test files exempt.** |
|
|
65
|
-
| Docstring Args match signature | A public function whose docstring `Args:` section names a parameter the signature does not declare is flagged — a rename that left the adjacent `Args:` line stale. Only the `Args:` section is compared against the signature; `Raises:` is left alone because callee-propagated exceptions cause false positives. **Test files and hook infrastructure exempt.** |
|
|
66
|
-
| Ignored must-check return | A bare-statement call to a function whose return value is its only failure signal (the curated `find_and_click`, `write_outcome` set) is flagged — the discarded boolean lets the caller move on silently after a failure. Assign the return and check it. Assigned (`clicked = …`) and branched-on (`if …:`) calls are exempt. Attribute calls are matched by their terminal method name alone (the receiver type is not resolved), so an unrelated `obj.write_outcome()` or `widget.find_and_click()` whose method name collides with a curated name is also flagged. **Test files exempt.** |
|
|
67
|
-
|
|
68
|
-
### Where UPPER_SNAKE is allowed
|
|
69
|
-
|
|
70
|
-
The "Constants location" rule is enforced at Write time. The hook exempts these path families where UPPER_SNAKE identifiers are either the canonical home or the native convention rather than misplaced scalar constants:
|
|
71
|
-
|
|
72
|
-
| Path pattern | Why it is exempt |
|
|
73
|
-
|---|---|
|
|
74
|
-
| `config/*` | Canonical home for scalar constants. |
|
|
75
|
-
| `/migrations/` (Django migrations) | Migration files are self-contained by framework convention; their UPPER_SNAKE identifiers are operation names, not misplaced configuration. |
|
|
76
|
-
| `/workflow/`, `_tab.py`, `/states.py`, `/modules.py` (path normalized to forward slashes, matched as substrings) | Workflow state and module registries declare `StateDefinition` / `WorkflowModule` instances as module-level singletons using UPPER_SNAKE names. These are registry entries, not constants to hoist. |
|
|
77
|
-
| Test files (`test_*.py`, `*_test.py`, `*.spec.*`, `conftest.py`, paths under `/tests/`) | Test files may define local constants without using `config/`. |
|
|
78
|
-
|
|
79
|
-
Any production file outside these families that defines an UPPER_SNAKE at module scope is still flagged and must be moved to `config/`.
|
|
80
|
-
|
|
81
|
-
> See also: [File-global constants use-count rule](../rules/file-global-constants.md) for the use-count requirement on file-global constants outside `config/`.
|
|
82
|
-
|
|
83
|
-
---
|
|
84
|
-
|
|
85
|
-
## 3. REUSE CONSTANTS (DRY CONFIG)
|
|
86
|
-
|
|
87
|
-
**Before writing ANY constant:**
|
|
24
|
+
`code_rules_enforcer.py` blocks each of these at Write/Edit and explains the specific violation when it fires; exact patterns and exemption lists live in the hook:
|
|
88
25
|
|
|
89
|
-
|
|
90
|
-
# Find config files
|
|
91
|
-
# Search your project for existing config files before creating new ones
|
|
26
|
+
no new comments · imports at top · logging format args (`log_*("...", arg)`) · no magic values in production bodies (0, 1, -1 exempt) · UPPER_SNAKE constants only in `config/` (exempt: `config/*`, `/migrations/`, workflow registries `/workflow/` + `_tab.py` + `/states.py` + `/modules.py`, test files) · no hardcoded user home paths · guarded `sys.path.insert` · no unused module-level imports · banned identifiers (`ctx`, `cfg`, `msg`, `btn`, `idx`, `cnt`, `tmp`, `elem`, `val`) · banned function prefixes (`handle_`, `process_`, `manage_`, `do_`) · no type escape hatches (`Any` import, `cast()`, inline `Any`) outside boundary files · no bare/broad `except` · no `Any` in signatures or class attributes · no stub bodies (`pass`/`...`/`raise NotImplementedError`) outside abstract/Protocol · TypedDict `_encode_*`/`_decode_*` companions in the same module · no test-mode branching in production (use dependency injection) · no thin wrapper modules · Google-style docstrings on public functions with `Args:` matching the signature · boolean names prefixed `is_`/`has_`/`should_`/`can_`/`was_`/`did_` (assignments AND bool-typed parameters) · must-check returns (`find_and_click`, `write_outcome`) assigned and checked
|
|
92
27
|
|
|
93
|
-
|
|
94
|
-
grep -r "VALUE" config/
|
|
95
|
-
```
|
|
96
|
-
|
|
97
|
-
**Decision tree:**
|
|
98
|
-
1. Search exact value → Found? → IMPORT IT
|
|
99
|
-
2. Search semantic match → Found? → USE EXISTING NAME
|
|
100
|
-
3. Config file exists? → ADD TO EXISTING
|
|
101
|
-
4. Create new (rare)
|
|
28
|
+
Test files are exempt from most checks. See also the file-global constants use-count rule: [`rules/file-global-constants.md`](../rules/file-global-constants.md).
|
|
102
29
|
|
|
103
30
|
---
|
|
104
31
|
|
|
105
|
-
## 4. CONFIG LOCATIONS
|
|
32
|
+
## 3. REUSE CONSTANTS / 4. CONFIG LOCATIONS
|
|
106
33
|
|
|
107
|
-
|
|
108
|
-
|---------------|----------|
|
|
109
|
-
| Timeouts, delays, retries | `config/timing.py` |
|
|
110
|
-
| Ports, URLs, thresholds | `config/constants.py` |
|
|
111
|
-
| CSS selectors | `config/selectors.py` |
|
|
34
|
+
Before writing ANY constant: search `config/` for the exact value → semantic match → add to the existing config file → create new (rare). Locations: timeouts/delays/retries → `config/timing.py`; ports/URLs/thresholds → `config/constants.py`; CSS selectors → `config/selectors.py`.
|
|
112
35
|
|
|
113
36
|
---
|
|
114
37
|
|
|
115
38
|
## 5. NO ABBREVIATIONS
|
|
116
39
|
|
|
117
|
-
Full words only.
|
|
118
|
-
|
|
119
|
-
| Bad | Good |
|
|
120
|
-
|-----|------|
|
|
121
|
-
| `ctx`, `cfg`, `msg` | `context`, `configuration`, `message` |
|
|
122
|
-
| `btn`, `idx`, `cnt` | `button`, `index`, `count` |
|
|
123
|
-
| `tmp`, `elem`, `val` | `temporary_value`, `element`, `value` |
|
|
124
|
-
|
|
125
|
-
**Exception:** `i`, `j`, `k` in loops; `e` for exception.
|
|
126
|
-
|
|
127
|
-
**Extended naming rules** :
|
|
128
|
-
- Loop vars: `each_order`, `each_user` (prefix `each_`)
|
|
129
|
-
- Booleans: `is_valid`, `has_permission`, `should_retry`, `was_clicked`, `did_succeed` (prefix `is_`/`has_`/`should_`/`can_`/`was_`/`did_`). The hook covers both boolean assignments and boolean-typed function parameters (a parameter annotated `bool` or defaulting to a boolean literal); `self`/`cls` and single-character names are exempt.
|
|
130
|
-
- Collections: `all_orders`, `all_users` (prefix `all_`)
|
|
131
|
-
- Maps: `price_by_product`, `user_by_id` (pattern `X_by_Y`)
|
|
132
|
-
- Preposition params: `from_path=`, `to=`, `into=`
|
|
133
|
-
- **Banned names:** `result`, `data`, `output`, `response`, `value`, `item`, `temp`
|
|
134
|
-
- **Banned prefixes:** `handle`, `process`, `manage`, `do`
|
|
40
|
+
Full words only (`context`, not `ctx`). Exceptions: `i`/`j`/`k` in loops, `e` for exception. Naming patterns: loop vars `each_*`; booleans `is_/has_/should_/can_/was_/did_`; collections `all_*`; maps `X_by_Y`; preposition params (`from_path=`, `to=`, `into=`). Banned names: `result`, `data`, `output`, `response`, `value`, `item`, `temp`. Banned prefixes: `handle`, `process`, `manage`, `do`.
|
|
135
41
|
|
|
136
42
|
---
|
|
137
43
|
|
|
138
44
|
## 6. COMPLETE TYPE HINTS
|
|
139
45
|
|
|
140
|
-
|
|
141
|
-
def function_name(
|
|
142
|
-
parameter: str,
|
|
143
|
-
optional: Optional[str] = None,
|
|
144
|
-
) -> ReturnType:
|
|
145
|
-
```
|
|
146
|
-
|
|
147
|
-
- ALL parameters typed
|
|
148
|
-
- ALL returns typed
|
|
149
|
-
- No `Any` type
|
|
150
|
-
- No `# type: ignore`
|
|
151
|
-
|
|
152
|
-
*(Also enforced by mypy_validator.py hook)*
|
|
153
|
-
|
|
154
|
-
---
|
|
46
|
+
ALL parameters typed, ALL returns typed. No `Any`, no `# type: ignore` (also enforced by the mypy_validator.py hook).
|
|
155
47
|
|
|
156
48
|
## 6.5 FILE LENGTH GUIDANCE
|
|
157
49
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
**Two advisory thresholds (non-blocking, stderr only):**
|
|
161
|
-
|
|
162
|
-
| Threshold | Source basis | Hook behavior |
|
|
163
|
-
|-----------|--------------|---------------|
|
|
164
|
-
| `>= 400` lines | Robert C. Martin, *Clean Code* (2008), Ch. 5 "Formatting" — small files preferred; Martin Fowler, *Refactoring* — "Large Class" code smell | Soft advisory: "consider splitting" |
|
|
165
|
-
| `>= 1000` lines | pylint default `max-module-lines=1000`; SonarQube rule S104 default `1000` | Strong nudge: "exceeds widely-used static-analysis defaults" |
|
|
166
|
-
|
|
167
|
-
**What we deliberately reject:**
|
|
168
|
-
|
|
169
|
-
- **Hard numeric blocks** — Google's Python Style Guide imposes no file-length cap (only a ~40-line function review hint at https://google.github.io/styleguide/pyguide.html). A blocking rule produces false positives on legitimate cases.
|
|
170
|
-
- **A single magic number** — Different sources land at 200 (*Clean Code* preference), 750 (some SonarQube language profiles), or 1000 (pylint, Sonar Java). No source justifies a single universal cap.
|
|
171
|
-
|
|
172
|
-
**When to actually split:**
|
|
173
|
-
|
|
174
|
-
The size signal matters *because* of what it usually indicates: multiple responsibilities (Single Responsibility Principle — Robert C. Martin, *Agile Software Development*, 2002), poor cohesion (Steve McConnell, *Code Complete 2e*, 2004, Ch. 5–6), or the "Large Class" / "Long Function" smells (Fowler). Use the readability rubric (`~/.claude/skills/readability-review/SKILL.md`) when an advisory fires — split based on cohesion, not line count.
|
|
50
|
+
Advisory only, never blocking: soft advisory at >= 400 lines, strong nudge at >= 1000 (pylint / SonarQube defaults). Split on cohesion (SRP, "Large Class" smell), not line count — run the readability rubric when an advisory fires.
|
|
175
51
|
|
|
176
52
|
---
|
|
177
53
|
|
|
178
54
|
## 7. RIGHT-SIZED ENGINEERING
|
|
179
55
|
|
|
180
56
|
**Simple > Clever. Functions > Classes. Concrete > Abstract.**
|
|
181
|
-
|
|
182
|
-
Never: ABC for single impl, DI frameworks, factory for single type
|
|
183
|
-
Always: Functions when no state, concrete classes, simple imports
|
|
184
|
-
|
|
185
|
-
---
|
|
57
|
+
Never: ABC for single impl, DI frameworks, factory for single type. Always: functions when no state, concrete classes, simple imports.
|
|
186
58
|
|
|
187
59
|
## 7.5 SOLID PRINCIPLES
|
|
188
60
|
|
|
189
|
-
**
|
|
190
|
-
|
|
191
|
-
Reference: Robert C. Martin, *Agile Software Development: Principles, Patterns, and Practices* (2002), Ch. 8–12.
|
|
192
|
-
|
|
193
|
-
| Letter | Principle | What it means here |
|
|
194
|
-
|--------|-----------|--------------------|
|
|
195
|
-
| **S** | Single Responsibility Principle | A class, function, or module has one reason to change. One unit = one axis of variation. Ties to §6.5 (file length as smell signal) and Fowler's "Large Class" / "Long Function" smells. |
|
|
196
|
-
| **O** | Open/Closed Principle | Extend behavior by adding new code. Favor a new branch/handler/subclass over editing the same switch in five places. |
|
|
197
|
-
| **L** | Liskov Substitution Principle | A subtype must be usable anywhere its parent type is expected without surprising the caller. If a subclass override breaks caller assumptions, flatten the hierarchy or prefer composition. |
|
|
198
|
-
| **I** | Interface Segregation Principle | Each client depends on exactly the methods it calls. Split one fat interface into several role-specific ones so each caller imports only the role it needs. |
|
|
199
|
-
| **D** | Dependency Inversion Principle | When two or more concretions exist or are imminent, depend on the shared abstraction. With exactly one concretion, import the concrete type directly (see §7). |
|
|
200
|
-
|
|
201
|
-
### Reconciling SOLID with Right-Sized Engineering (§7)
|
|
202
|
-
|
|
203
|
-
SOLID was written for OO codebases where most abstract types have two or more concrete subclasses. In this codebase:
|
|
204
|
-
|
|
205
|
-
- **SRP always applies.** Functions, classes, and modules must have one reason to change regardless of paradigm. This is the only SOLID letter that applies immediately, without waiting for a second implementation.
|
|
206
|
-
- **OCP, LSP, ISP, DIP apply where two or more concrete implementations already share a contract.** A single concrete class satisfies SOLID by default. Introduce interfaces, ABCs, or DI containers only when the second concretion lands.
|
|
207
|
-
- **For code with fewer than two concretions, §7 wins:** concrete classes, functions when no state, direct imports. Refactor toward OCP/DIP at the commit that introduces the second concrete implementation (YAGNI).
|
|
208
|
-
|
|
209
|
-
### Signals that SOLID is being misapplied
|
|
210
|
-
|
|
211
|
-
- Creating an interface or ABC with exactly one implementation (violates §7 DIP guard)
|
|
212
|
-
- Splitting a cohesive 80-line class with one reason to change into four 20-line classes because "SRP" — SRP counts distinct change reasons; size is a separate signal tracked in §6.5
|
|
213
|
-
- Abstract factories for types that have exactly one concrete product
|
|
214
|
-
- Dependency-injection containers where every injected type has exactly one concrete implementation across production and tests
|
|
215
|
-
|
|
216
|
-
### When SOLID adds value
|
|
217
|
-
|
|
218
|
-
- Two or more concrete implementations already exist → DIP and ISP earn their keep
|
|
219
|
-
- A class shows multiple unrelated change reasons in git history → SRP split is justified
|
|
220
|
-
- Subclass overrides break caller assumptions → LSP violation; fix or flatten the hierarchy
|
|
221
|
-
- Editing the same `if`/`switch` block every time a new case is added → OCP refactor is justified
|
|
61
|
+
**SRP always applies** — one reason to change per function/class/module. **OCP, LSP, ISP, DIP apply only where two or more concrete implementations already share a contract**; with a single concretion §7 wins (concrete classes, direct imports, YAGNI — introduce the abstraction at the commit that adds the second concretion). Misapplication signals: interface/ABC with exactly one implementation, SRP-splitting a cohesive class by size alone, abstract factories for one product, DI containers where every injected type has one concretion.
|
|
222
62
|
|
|
223
63
|
---
|
|
224
64
|
|
|
225
65
|
## 8. TDD PROCESS
|
|
226
66
|
|
|
227
|
-
1. **RED**
|
|
228
|
-
2. **GREEN** - Minimum code to pass
|
|
229
|
-
3. **REFACTOR** - Only if valuable
|
|
230
|
-
|
|
231
|
-
---
|
|
67
|
+
1. **RED** — failing test first. 2. **GREEN** — minimum code to pass. 3. **REFACTOR** — only if valuable.
|
|
232
68
|
|
|
233
69
|
## 9. SELF-CONTAINED COMPONENTS
|
|
234
70
|
|
|
235
|
-
Components own their complete feature. Parents just render `<Child />`.
|
|
236
|
-
|
|
237
|
-
Child handles: state, modals, overlays, toasts
|
|
238
|
-
Parent knows: nothing about child's internals
|
|
239
|
-
|
|
240
|
-
---
|
|
71
|
+
Components own their complete feature (state, modals, overlays, toasts). Parents just render `<Child />`.
|
|
241
72
|
|
|
242
73
|
## 9.5 NO THIN WRAPPER MODULES
|
|
243
74
|
|
|
244
|
-
A non-`__init__.py` module whose body is only imports (optionally
|
|
245
|
-
|
|
246
|
-
`__init__.py` is the canonical re-export surface and is exempt; package surface aggregation is its job.
|
|
247
|
-
|
|
248
|
-
---
|
|
75
|
+
A non-`__init__.py` module whose body is only imports (optionally `__all__`) is indirection without payload — callers import the real module. `__init__.py` is the canonical re-export surface and is exempt.
|
|
249
76
|
|
|
250
77
|
## 9.6 NO BACKWARDS-COMPATIBILITY SHIMS
|
|
251
78
|
|
|
252
|
-
Removed code is removed.
|
|
253
|
-
|
|
254
|
-
- Renamed functions that re-export the old name with a `# deprecated` comment
|
|
255
|
-
- `_old_*` aliases pointing to the new implementation
|
|
256
|
-
- Wrapper modules whose only purpose is to keep an old import path alive
|
|
257
|
-
- `// removed in vX` comment markers next to deleted blocks
|
|
258
|
-
|
|
259
|
-
When a symbol moves or its signature changes, update the call sites in the same commit. The git log records what changed; the codebase records what exists now.
|
|
260
|
-
|
|
261
|
-
> **See also:** [`skills/code/SKILL.md`](../skills/code/SKILL.md) §5 ("No fallback paths, no back-compat shims, no legacy code") states the same principle as a session-start prompt directive. This section is the authoritative wording; the skill amplifies it for `/code` sessions.
|
|
262
|
-
|
|
263
|
-
---
|
|
79
|
+
Removed code is removed: no renamed re-export aliases, no `_old_*` aliases, no keep-alive wrapper modules, no tombstone comment markers. When a symbol's name or signature changes, update the call sites in the same commit. Git history records change; the codebase records what exists.
|
|
264
80
|
|
|
265
81
|
## 9.7 NO FALLBACK / BEST-EFFORT WRAPPERS
|
|
266
82
|
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
```python
|
|
270
|
-
# BAD — silently hides every failure mode
|
|
271
|
-
def fetch_user(user_id: int) -> User | None:
|
|
272
|
-
try:
|
|
273
|
-
return _registry[user_id]
|
|
274
|
-
except Exception:
|
|
275
|
-
return None
|
|
276
|
-
```
|
|
277
|
-
|
|
278
|
-
```python
|
|
279
|
-
# GOOD — names the specific failure and propagates the rest
|
|
280
|
-
def fetch_user(user_id: int) -> User | None:
|
|
281
|
-
try:
|
|
282
|
-
return _registry[user_id]
|
|
283
|
-
except KeyError:
|
|
284
|
-
return None
|
|
285
|
-
```
|
|
286
|
-
|
|
287
|
-
Fallback values mask programming errors (KeyError vs RuntimeError vs AttributeError all collapse to "None"), making debugging impossible. Production code names the specific failure mode it intends to handle.
|
|
288
|
-
|
|
289
|
-
> **See also:** [`skills/code/SKILL.md`](../skills/code/SKILL.md) §2 ("No `try`/`except` in core logic that recovers, softens, or best-efforts a failure") states the same principle as a session-start prompt directive. The hook check `check_bare_except` enforces the narrowest case (bare/`Exception`/`BaseException` handlers); this section + the `code` skill cover the broader "any swallowing handler" intent.
|
|
290
|
-
|
|
291
|
-
---
|
|
83
|
+
Never swallow a failure into a default unless the caller explicitly opted in at the boundary. Name the specific exception (`except KeyError:`) and propagate the rest — collapsing every error class to `None` masks programming errors and makes debugging impossible.
|
|
292
84
|
|
|
293
85
|
## 9.8 REMOVE CODE YOU ORPHAN (Dead Code Elimination)
|
|
294
86
|
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
After deleting or rewriting code, trace what it referenced and remove whatever is unreachable as a result:
|
|
298
|
-
|
|
299
|
-
- **Variables** with no remaining readers after the change
|
|
300
|
-
- **Functions / methods** with no remaining call sites
|
|
301
|
-
- **Parameters** that no caller passes
|
|
302
|
-
- **Branches** made unreachable (dead `if`/`else`, conditions that are always true or always false)
|
|
303
|
-
- **Imports** left unused (also caught by the unused-import hook)
|
|
304
|
-
- **Helper files** whose only consumer you just deleted
|
|
305
|
-
|
|
306
|
-
**Confirm the orphan is truly unreferenced first.** "No remaining call sites" means none *anywhere in the codebase*, not just in the file you edited. Before removing a function, method, class, or module-level name, run Serena's `find_referencing_symbols` on it: the language server resolves call sites, `import` statements, and re-exports across files far more reliably and cheaply than a text sweep. Back it with a plain text search for string-based dynamic lookups (`getattr`, entry-point names) that the language server cannot see. A reference is not proof of life — the referrer can be dead too. The symbol is live only when some chain of references reaches a live entry point: a CLI command, route, public API, test, or any caller outside the code you are removing. Trace the referrers upward. If every chain dead-ends in code that is itself unreferenced, the whole cluster is dead — remove it together in the same commit (§9.6). If any chain reaches a live entry point, the symbol is in use; leave it. Deleting something still reachable breaks its importers; treating a self-referential dead cluster as alive leaves litter.
|
|
307
|
-
|
|
308
|
-
**When liveness is uncertain, ask — never guess.** A tool cannot always tell what is live: a chain may leave the repository (a public API, plugin hook, or module other projects import), or run through dynamic or reflective dispatch that no search resolves. If you cannot conclusively prove a symbol is unreachable, do not delete it. Surface the specific ambiguity to the user through AskUserQuestion and let them decide. Removing live production code is never an acceptable risk — when in doubt, keep it and ask.
|
|
309
|
-
|
|
310
|
-
This is the inverse of comment preservation: existing **comments** are sacred and never removed, but dead **code** is removed in the same edit that orphans it. A function left with no callers is not "preserved" — it is litter.
|
|
311
|
-
|
|
312
|
-
> **See also:** §9.6 (a renamed alias is dead code by another name), the `file_global_constants_use_count` rule (zero references → delete), and the unused-import hook check — each enforces a specific slice of this principle automatically.
|
|
313
|
-
|
|
314
|
-
> **Standard terms (shorthand for this section):** the mechanical part is *dead code elimination* (compilers; Aho et al., *Compilers: Principles, Techniques, and Tools*) and *tree-shaking* (bundlers like Rollup/Webpack) — retain only what is reachable from a live entry point. The ask-when-uncertain overlay guards against the *Lava Flow* anti-pattern — dead code kept because removing it feels risky (Brown et al., *AntiPatterns*, 1998). Compare Fowler's "Remove Dead Code" refactoring (*Refactoring*, 2nd ed., 2018). Direct source links: [`references/dead-code-elimination.md`](references/dead-code-elimination.md).
|
|
315
|
-
|
|
316
|
-
---
|
|
87
|
+
An edit that deletes or rewrites code also removes everything it makes dead: unread variables, uncalled functions, unpassed parameters, dead branches, unused imports, helper files whose only consumer that edit deleted. Prove unreachability first: Serena `find_referencing_symbols` plus a text search for dynamic lookups (`getattr`, entry-point names). A symbol is live only when a reference chain reaches a live entry point (CLI command, route, public API, test); a self-referential dead cluster is removed together in the same commit. **When liveness is uncertain (public API, plugin hook, reflective dispatch), do NOT delete — surface the ambiguity via AskUserQuestion.** Source links: [`references/dead-code-elimination.md`](references/dead-code-elimination.md).
|
|
317
88
|
|
|
318
89
|
## 10. NO REDUNDANT DATA FETCHES
|
|
319
90
|
|
|
320
|
-
If you already have data, don't fetch again.
|
|
321
|
-
|
|
322
|
-
```typescript
|
|
323
|
-
// BAD
|
|
324
|
-
const profile = await getProfile();
|
|
325
|
-
const localProfile = await db.profile.first(); // same data!
|
|
326
|
-
|
|
327
|
-
// GOOD
|
|
328
|
-
const profile = await db.profile.first();
|
|
329
|
-
// ... use profile throughout ...
|
|
330
|
-
```
|
|
91
|
+
If you already have the data, don't fetch it again.
|
|
331
92
|
|
|
332
93
|
---
|
|
333
94
|
|
|
334
95
|
## 11. ENFORCEMENT SURFACES
|
|
335
96
|
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
| Surface | What it catches | When it runs | Failure mode |
|
|
339
|
-
|---------|-----------------|--------------|--------------|
|
|
340
|
-
| **⚡ Hook** | Pattern-matchable violations: comments, magic values, banned identifiers, bare except, boundary `Any`, thin wrappers, docstring shape, etc. | PreToolUse on Write/Edit (and PostToolUse advisories) | Blocks the write — Claude must fix and retry |
|
|
341
|
-
| **🤖 Prompt** | Judgment-driven principles: SRP, Right-Sized Engineering, KISS, conservative-action, BDD discovery; strict-mode standards via the `/code` skill (no `Any`/`cast`, immutable TypedDicts, DI hooks, 100% branch coverage, no fallback `try/except`) | Read into the model context at session start (CLAUDE.md, rules/*.md, skill prepends) | Influences Claude's decisions; no automatic block |
|
|
342
|
-
| **👥 Audit rubric** | Cross-file architectural concerns: SOLID misapplication, abstraction leaks, multi-file coupling | Run on demand via `/check`, `/readability-review`, agent-driven audits | Surfaces in audit output; humans decide whether to act |
|
|
343
|
-
|
|
344
|
-
### Prompt-enforced rules (no hook coverage)
|
|
345
|
-
|
|
346
|
-
These principles cannot be reduced to a regex or AST visitor. They live in user-private `~/.claude/rules/` and `~/.claude/CLAUDE.md`, and are read into context every session:
|
|
347
|
-
|
|
348
|
-
- **Right-Sized Engineering** — concrete classes for single concretions, functions when no state, no DI frameworks for solo-scale code
|
|
349
|
-
- **SRP / SOLID misapplication signals** — see §7.5
|
|
350
|
-
- **conservative-action** — when intent is ambiguous, ask before acting
|
|
351
|
-
- **explore-thoroughly** — investigate before committing to an approach
|
|
352
|
-
- **agent-spawn-protocol** — verify context before delegating to a subagent
|
|
353
|
-
- **`code` skill ([`skills/code/SKILL.md`](../skills/code/SKILL.md))** — invoked via `/code`, prepends strict-mode standards for the entire session: no `Any`/`cast()`/`# type: ignore`, immutable TypedDicts with manual `_encode_*`/`_decode_*` and `require_*` validation, per-module `_test_hooks.py` for DI, 100% statement + branch coverage, zero mocks. Several criteria overlap with §9.6/§9.7 and the ⚡ B-series checks; the skill is the canonical entry point when the user explicitly opts into strict mode for an implementation task.
|
|
354
|
-
|
|
355
|
-
### Audit-rubric reference
|
|
356
|
-
|
|
357
|
-
For multi-file architectural reviews see [`packages/claude-dev-env/audit-rubrics/`](../audit-rubrics/). Categories A–N are maintained as agent rubrics. Category J (CODE_RULES.md compliance) mirrors the ⚡ hook-enforced rules as an audit-side rubric; the other categories stay agent rubrics because they rest on multi-file reasoning beyond a single-file hook's reach.
|
|
358
|
-
|
|
359
|
-
---
|
|
360
|
-
|
|
361
|
-
## 12. DEFERRED RULES (P1 — TRACKED, NOT ENFORCED)
|
|
362
|
-
|
|
363
|
-
The following rules are documented in `~/.claude/rules/` and applied by Claude through prompt context, but have no hook coverage in `code_rules_enforcer.py`. Promotion to ⚡ blocking enforcement is on the backlog and will land as separate hardening commits.
|
|
364
|
-
|
|
365
|
-
| Rule | Source | Promotion path |
|
|
366
|
-
|------|--------|----------------|
|
|
367
|
-
| Context7 before web search for library/SDK questions | [`rules/context7.md`](../rules/context7.md) | New PreToolUse hook on WebSearch when query mentions a library name |
|
|
368
|
-
| Verify-before-asking checklist | [`rules/verify-before-asking.md`](../rules/verify-before-asking.md) | Stop hook scanning AskUserQuestion calls for "where is", "what file", etc. |
|
|
369
|
-
| BDD naming (`should_…` / `describe…it…`) | [`rules/bdd.md`](../rules/bdd.md) | `code_rules_enforcer.py::check_test_naming` (new) — flag test functions starting with `test_` lacking corresponding `should_…` form |
|
|
370
|
-
| `gh` pagination requires `--paginate --slurp` plus external `jq` | [`rules/gh-paginate.md`](../rules/gh-paginate.md) | PreToolUse Bash hook matching `gh api .*/(reviews\|comments)` without `--paginate --slurp` |
|
|
371
|
-
| Self-contained documentation (no "as discussed", "Option A", session refs) | [`rules/self-contained-docs.md`](../rules/self-contained-docs.md) | PostToolUse Write hook scanning new `.md` content for conversational refs |
|
|
372
|
-
| Temp file cleanup at end of task | [`rules/cleanup-temp-files.md`](../rules/cleanup-temp-files.md) | Stop hook scanning for files Claude created and did not delete |
|
|
373
|
-
| No credentials committed to git | [`rules/git-workflow.md`](../rules/git-workflow.md) | PreToolUse Bash hook on `git commit` scanning staged diff for high-entropy strings, `.env` patterns, and known credential file extensions |
|
|
374
|
-
| Per-module DI hooks (`_test_hooks.py` sibling) instead of `if TESTING:` branching | [`skills/code/SKILL.md`](../skills/code/SKILL.md) §4 | New `check_test_hooks_sibling()` — when a module imports a side-effect dependency and a test exists, require a `<module>_test_hooks.py` sibling exposing the injection points |
|
|
375
|
-
| 100% statement + branch coverage with zero mocks | [`skills/code/SKILL.md`](../skills/code/SKILL.md) §3 | CI check (not write-time) — `pytest --cov-branch --cov-fail-under=100` on touched packages; covers only what the diff added |
|
|
376
|
-
| `TypedDict` `_decode_*` calls `require_*` on every field | [`skills/code/SKILL.md`](../skills/code/SKILL.md) §6 | Extend `check_typed_dict_encode_decode` (B2) — when the decoder is present, AST-verify every TypedDict field appears as a `require_*` call before the return |
|
|
377
|
-
| `Protocol` signatures match the real implementation exactly | [`skills/code/SKILL.md`](../skills/code/SKILL.md) Gotchas | mypy `--strict` over the protocol module catches this; promotion = add `[tool.mypy] strict = true` per-module override in `pyproject.toml` for files declaring `Protocol` subclasses |
|
|
378
|
-
|
|
379
|
-
---
|
|
380
|
-
|
|
381
|
-
## QUICK CHECKLIST
|
|
382
|
-
|
|
383
|
-
```
|
|
384
|
-
Before ANY code:
|
|
385
|
-
[ ] Searched existing configs?
|
|
386
|
-
[ ] Importing from centralized config?
|
|
387
|
-
|
|
388
|
-
Hook will enforce:
|
|
389
|
-
[⚡] No NEW comments (existing comments NEVER removed)
|
|
390
|
-
[⚡] No magic values
|
|
391
|
-
[⚡] Imports at top
|
|
392
|
-
[⚡] Logging format args
|
|
393
|
-
[ ] File length reasonable (advisory at 400, strong nudge at 1000 — see §6.5)
|
|
394
|
-
[⚡] Constants in config/
|
|
395
|
-
[⚡] No banned identifiers (ctx, cfg, msg, btn, idx, cnt, tmp, elem, val)
|
|
396
|
-
[⚡] No banned function prefixes (handle_, process_, manage_, do_)
|
|
397
|
-
[⚡] No type escape hatches (Any imports, cast(), inline Any)
|
|
398
|
-
[⚡] No bare except / except Exception / except BaseException
|
|
399
|
-
[⚡] No Any in function signatures or class attributes (boundary types)
|
|
400
|
-
[⚡] No stub bodies (pass / ... / raise NotImplementedError) outside abstract methods
|
|
401
|
-
[⚡] TypedDict has companion _encode_*/_decode_* in same module
|
|
402
|
-
[⚡] No test-mode branching in production (TESTING / PYTEST_CURRENT_TEST)
|
|
403
|
-
[⚡] No thin wrapper modules (imports only, optionally with __all__, outside __init__.py)
|
|
404
|
-
[⚡] Public functions have Google-style Args:/Returns:/Raises: when warranted
|
|
405
|
-
[⚡] Docstring Args: names match the signature (a stale renamed param is flagged)
|
|
406
|
-
[⚡] Boolean names prefixed is_/has_/should_/can_/was_/did_ (assignments AND bool-typed parameters)
|
|
407
|
-
[⚡] No discarded must-check return (assign and check find_and_click/write_outcome outcomes)
|
|
408
|
-
|
|
409
|
-
Manual check:
|
|
410
|
-
[ ] No abbreviations?
|
|
411
|
-
[ ] Complete type hints?
|
|
412
|
-
[ ] Self-contained components?
|
|
413
|
-
[ ] SRP holds (one reason to change per function/class/module)?
|
|
414
|
-
[ ] OCP/LSP/ISP/DIP only applied where abstractions already earn their keep (see §7.5)?
|
|
415
|
-
[ ] No backwards-compatibility shims (§9.6)?
|
|
416
|
-
[ ] No fallback/best-effort wrappers (§9.7)?
|
|
417
|
-
[ ] No code orphaned by an edit (§9.8 — dead vars, uncalled functions, unused imports, dead branches)?
|
|
418
|
-
[ ] Readability: /check
|
|
419
|
-
```
|
|
97
|
+
⚡ **Hooks** block pattern-matchable violations at Write/Edit time. 🤖 **Prompt context** carries judgment principles (SRP, Right-Sized Engineering, conservative-action, BDD discovery; the `/code` skill prepends strict mode for a session: no `Any`/`cast()`, immutable TypedDicts with `_encode_*`/`_decode_*` + `require_*` validation, per-module `_test_hooks.py` DI, 100% statement + branch coverage, zero mocks). 👥 **Audit rubrics** (`/check`, `packages/claude-dev-env/audit-rubrics/` categories A–P) cover cross-file architectural concerns. Rules with documented-but-pending hook coverage live in `~/.claude/rules/*.md` and `skills/code/SKILL.md`; each names its own promotion path.
|
package/package.json
CHANGED
|
@@ -1,44 +1,5 @@
|
|
|
1
1
|
# AskUserQuestion Required
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Route every user-directed question through the `AskUserQuestion` tool — never a plain-text question in a response's final paragraph. Structure: concise `question`, `header` of 12 chars or fewer, 2-4 options (the UI adds the "Other" fallback), `multiSelect` only when choices genuinely combine.
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
Route every user-directed question through the `AskUserQuestion` tool. Embedded plain-text questions in the final paragraph of an assistant message are blocked by a Stop hook, and the response must be re-output with the ask moved into an `AskUserQuestion` tool call.
|
|
8
|
-
|
|
9
|
-
## Detection Criteria
|
|
10
|
-
|
|
11
|
-
The `question_to_user_enforcer` Stop hook inspects the last non-empty paragraph of the response after stripping fenced code blocks, inline code (backticks), and blockquoted lines (`> …`). The response is blocked when either signal is present:
|
|
12
|
-
|
|
13
|
-
- The final paragraph's last sentence ends with a question mark.
|
|
14
|
-
- The final paragraph contains any of these preamble phrases (case-insensitive, word-boundary matched): `would you like`, `should I`, `do you want`, `which would you prefer`, `let me know if`, `let me know which`, `let me know whether`, `please confirm`, `please let me know`, `want me to`.
|
|
15
|
-
|
|
16
|
-
## Acceptable Plain-Text Question Patterns
|
|
17
|
-
|
|
18
|
-
These remain allowed and do not trigger the hook:
|
|
19
|
-
|
|
20
|
-
- **Rhetorical questions answered in the same paragraph.** `"What happens if the queue is empty? The handler short-circuits cleanly."` The question frames its own answer; the reader never has to respond.
|
|
21
|
-
- **Questions inside code, diffs, or documentation excerpts.** Code fences, inline backticks, and `>` blockquotes are stripped before detection. Quoting a GitHub issue title, a user's prior message, or a log line inside a blockquote is fine.
|
|
22
|
-
- **Middle-paragraph questions when the closing paragraph is declarative.** Only the final paragraph is scanned.
|
|
23
|
-
|
|
24
|
-
## AskUserQuestion Structure
|
|
25
|
-
|
|
26
|
-
When a question is genuinely for the user, call the tool with:
|
|
27
|
-
|
|
28
|
-
- A concise `question` string stating what is needed.
|
|
29
|
-
- A `header` of twelve characters or fewer summarizing the decision.
|
|
30
|
-
- Two to four `options`, each with a short `label` the user can pick. An "Other" free-text fallback is already provided by the UI; do not add one manually.
|
|
31
|
-
- `multiSelect: false` unless the user can genuinely combine choices.
|
|
32
|
-
|
|
33
|
-
## Why
|
|
34
|
-
|
|
35
|
-
- **Structured options reduce re-reading friction.** The user sees labeled choices directly rather than scanning prose for the ask.
|
|
36
|
-
- **Transcript clarity.** Tool-use entries are easy to locate in the JSONL transcript; prose questions disappear into the response text.
|
|
37
|
-
- **Reduced drift.** Claude's next turn cannot move past an unanswered structured question; prose questions can be silently bypassed.
|
|
38
|
-
|
|
39
|
-
## Enforcement
|
|
40
|
-
|
|
41
|
-
- Hook: `packages/claude-dev-env/hooks/blocking/question_to_user_enforcer.py`, registered on the `Stop` matcher in `packages/claude-dev-env/hooks/hooks.json`.
|
|
42
|
-
- Loop prevention: the hook honors Claude Code's `stop_hook_active` flag and does not re-block on retry.
|
|
43
|
-
- User-facing notice: `USER_FACING_ASKUSERQUESTION_NOTICE` in `packages/claude-dev-env/hooks/hooks_constants/messages.py`.
|
|
44
|
-
- Related rule: `packages/claude-dev-env/rules/verify-before-asking.md` gates whether the question belongs to the user in the first place.
|
|
5
|
+
The `question_to_user_enforcer` Stop hook blocks a response whose final paragraph (after stripping code fences, inline code, and blockquotes) ends in a question mark or contains ask-phrases ("would you like", "should I", "let me know if", ...). Rhetorical questions answered in the same paragraph, and questions inside code or blockquotes, pass. `verify-before-asking` gates whether the question belongs to the user at all.
|