claude-dev-env 1.82.0 → 1.84.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 +16 -13
- package/_shared/pr-loop/audit-contract.md +24 -12
- package/_shared/pr-loop/scripts/CLAUDE.md +1 -0
- package/_shared/pr-loop/scripts/README.md +1 -0
- package/_shared/pr-loop/scripts/_claude_permissions_common.py +16 -5
- package/_shared/pr-loop/scripts/code_rules_gate.py +2 -3
- package/_shared/pr-loop/scripts/pr_loop_shared_constants/CLAUDE.md +1 -0
- package/_shared/pr-loop/scripts/pr_loop_shared_constants/reviewer_availability_constants.py +12 -0
- package/_shared/pr-loop/scripts/pr_loop_shared_constants/terminology_sweep_constants.py +0 -2
- package/_shared/pr-loop/scripts/reviewer_availability.py +182 -0
- package/_shared/pr-loop/scripts/reviews_disabled.py +2 -0
- package/_shared/pr-loop/scripts/terminology_sweep.py +9 -33
- package/_shared/pr-loop/scripts/tests/CLAUDE.md +2 -0
- package/_shared/pr-loop/scripts/tests/test__claude_permissions_common.py +212 -0
- package/_shared/pr-loop/scripts/tests/test_agent_config_carveout.py +18 -0
- package/_shared/pr-loop/scripts/tests/test_claude_permissions_common.py +18 -0
- package/_shared/pr-loop/scripts/tests/test_code_rules_gate.py +37 -0
- package/_shared/pr-loop/scripts/tests/test_reviewer_availability.py +159 -0
- package/_shared/pr-loop/scripts/tests/test_reviewer_availability_constants.py +36 -0
- package/_shared/pr-loop/scripts/tests/test_reviews_disabled.py +14 -0
- package/_shared/pr-loop/scripts/tests/test_terminology_sweep.py +14 -4
- package/agents/clean-coder.md +3 -0
- package/audit-rubrics/category_rubrics/category-o-docstring-vs-impl-drift.md +1 -1
- package/bin/install.mjs +16 -8
- package/bin/install.test.mjs +17 -6
- package/commands/CLAUDE.md +0 -1
- package/docs/CODE_RULES.md +1 -1
- package/hooks/blocking/CLAUDE.md +2 -4
- package/hooks/blocking/code_rules_constants_config.py +164 -1
- package/hooks/blocking/code_rules_docstrings.py +428 -15
- package/hooks/blocking/code_rules_enforcer.py +33 -0
- package/hooks/blocking/code_rules_imports_logging.py +867 -1
- package/hooks/blocking/code_rules_magic_values.py +5 -0
- package/hooks/blocking/code_rules_naming_collection.py +152 -6
- package/hooks/blocking/code_rules_shared.py +34 -0
- package/hooks/blocking/code_rules_string_magic.py +68 -0
- package/hooks/blocking/duplicate_rmtree_helper_blocker.py +4 -4
- package/hooks/blocking/pre_tool_use_dispatcher.py +3 -3
- package/hooks/blocking/reviewer_spawn_gate.py +182 -0
- package/hooks/blocking/stale_comment_reference_blocker.py +267 -0
- package/hooks/blocking/state_description_blocker.py +96 -5
- package/hooks/blocking/test_code_rules_config_duplicate_path_anchor.py +132 -0
- package/hooks/blocking/test_code_rules_enforcer_cap_meta.py +2 -0
- package/hooks/blocking/test_code_rules_enforcer_docstring_delegation_summary.py +385 -0
- package/hooks/blocking/test_code_rules_enforcer_docstring_prose_wall_illustration.py +197 -0
- package/hooks/blocking/test_code_rules_enforcer_docstring_runon_sentence.py +27 -0
- package/hooks/blocking/test_code_rules_enforcer_join_separator_magic.py +67 -0
- package/hooks/blocking/test_code_rules_enforcer_module_docstring_roster.py +40 -0
- package/hooks/blocking/test_code_rules_enforcer_naive_datetime.py +213 -0
- package/hooks/blocking/test_code_rules_enforcer_referenced_underscore_loop.py +169 -0
- package/hooks/blocking/test_code_rules_enforcer_split_constants_config.py +33 -0
- package/hooks/blocking/test_code_rules_enforcer_split_shared.py +23 -0
- package/hooks/blocking/test_code_rules_js_bare_flag_return_directive.py +266 -0
- package/hooks/blocking/test_code_rules_js_sibling_return_object_key_drift.py +490 -0
- package/hooks/blocking/test_code_rules_logging_adjacent_literals.py +171 -0
- package/hooks/blocking/test_code_rules_magic_values.py +54 -0
- package/hooks/blocking/test_duplicate_rmtree_helper_blocker.py +0 -6
- package/hooks/blocking/test_pr_converge_bugteam_enforcer_state_tolerance.py +184 -0
- package/hooks/blocking/test_pre_tool_use_dispatcher.py +9 -3
- package/hooks/blocking/test_reviewer_spawn_gate.py +230 -0
- package/hooks/blocking/test_shared_stdin_adoption.py +5 -30
- package/hooks/blocking/test_stale_comment_reference_blocker.py +236 -0
- package/hooks/blocking/test_state_description_blocker.py +135 -0
- package/hooks/hooks.json +5 -0
- package/hooks/hooks_constants/CLAUDE.md +3 -4
- package/hooks/hooks_constants/blocking_check_limits.py +46 -0
- package/hooks/hooks_constants/code_rules_enforcer_constants.py +41 -0
- package/hooks/hooks_constants/code_rules_path_utils_constants.py +1 -1
- package/hooks/hooks_constants/duplicate_rmtree_helper_blocker_constants.py +0 -1
- package/hooks/hooks_constants/post_tool_use_dispatcher_constants.py +0 -5
- package/hooks/hooks_constants/pre_tool_use_dispatcher_constants.py +4 -0
- package/hooks/hooks_constants/reviewer_spawn_gate_constants.py +41 -0
- package/hooks/hooks_constants/stale_comment_reference_blocker_constants.py +76 -0
- package/hooks/hooks_constants/state_description_blocker_constants.py +8 -0
- package/hooks/validation/post_tool_use_dispatcher.py +1 -1
- package/hooks/validation/test_post_tool_use_dispatcher.py +51 -35
- package/hooks/workflow/CLAUDE.md +2 -8
- package/package.json +1 -1
- package/rules/CLAUDE.md +5 -2
- package/rules/claude-md-orphan-file.md +5 -0
- package/rules/docstring-prose-matches-implementation.md +10 -1
- package/rules/env-var-table-code-drift.md +5 -0
- package/rules/es-exe-file-search.md +17 -0
- package/rules/no-historical-clutter.md +12 -1
- package/rules/orphan-css-class.md +5 -0
- package/rules/package-inventory-stale-entry.md +10 -0
- package/rules/paired-test-coverage.md +5 -0
- package/rules/plain-illustrative-docstrings.md +40 -1
- package/rules/verify-before-asking.md +7 -0
- package/rules/verify-runtime-state.md +40 -0
- package/rules/windows-filesystem-safe.md +8 -0
- package/rules/workers-done-before-complete.md +33 -0
- package/rules/workflow-substitution-slots.md +5 -0
- package/skills/CLAUDE.md +7 -2
- package/skills/autoconverge/SKILL.md +48 -61
- package/skills/autoconverge/reference/closing-report.md +6 -6
- package/skills/autoconverge/reference/convergence.md +17 -15
- package/skills/autoconverge/reference/gotchas.md +6 -3
- package/skills/autoconverge/workflow/autoconverge_report_constants/render_report_constants.py +2 -9
- package/skills/autoconverge/workflow/converge.contract.test.mjs +153 -10
- package/skills/autoconverge/workflow/converge.copilot-gate.test.mjs +55 -19
- package/skills/autoconverge/workflow/converge.merge-conflict.test.mjs +16 -9
- package/skills/autoconverge/workflow/converge.mjs +167 -106
- package/skills/autoconverge/workflow/render_report.py +7 -11
- package/skills/bugteam/CLAUDE.md +1 -1
- package/skills/bugteam/PROMPTS.md +7 -6
- package/skills/bugteam/SKILL.md +25 -72
- package/skills/bugteam/reference/CLAUDE.md +1 -3
- package/skills/bugteam/reference/README.md +1 -1
- package/skills/bugteam/reference/audit-and-teammates.md +1 -1
- package/skills/bugteam/reference/obstacles/fix-publish-summary.md +1 -1
- package/skills/bugteam/reference/team-setup.md +8 -7
- package/skills/bugteam/scripts/CLAUDE.md +0 -6
- package/skills/bugteam/scripts/README.md +0 -4
- package/skills/bugteam/scripts/bugteam_scripts_constants/CLAUDE.md +0 -1
- package/skills/code/SKILL.md +2 -0
- package/skills/copilot-review/CLAUDE.md +1 -1
- package/skills/copilot-review/SKILL.md +25 -23
- package/skills/everything-search/SKILL.md +5 -0
- package/skills/findbugs/CLAUDE.md +2 -2
- package/skills/findbugs/SKILL.md +22 -83
- package/skills/fixbugs/SKILL.md +2 -4
- package/skills/log-audit/CLAUDE.md +20 -0
- package/skills/log-audit/SKILL.md +68 -0
- package/skills/log-audit/reference/CLAUDE.md +9 -0
- package/skills/log-audit/reference/charter.md +52 -0
- package/skills/log-audit/scripts/CLAUDE.md +27 -0
- package/skills/log-audit/scripts/cluster_recurrences.py +261 -0
- package/skills/log-audit/scripts/collect_log_window.py +199 -0
- package/skills/log-audit/scripts/log_audit_constants/CLAUDE.md +12 -0
- package/skills/log-audit/scripts/log_audit_constants/cluster_recurrences_constants.py +23 -0
- package/skills/log-audit/scripts/log_audit_constants/collect_log_window_constants.py +24 -0
- package/skills/log-audit/scripts/log_audit_constants/mine_copilot_findings_constants.py +49 -0
- package/skills/log-audit/scripts/mine_copilot_findings.py +302 -0
- package/skills/log-audit/scripts/test_cluster_recurrences.py +160 -0
- package/skills/log-audit/scripts/test_collect_log_window.py +111 -0
- package/skills/log-audit/scripts/test_mine_copilot_findings.py +126 -0
- package/skills/monitor-open-prs/SKILL.md +2 -2
- package/skills/post-audit-findings/SKILL.md +84 -0
- package/skills/pr-converge/CLAUDE.md +2 -0
- package/skills/pr-converge/SKILL.md +72 -59
- package/skills/pr-converge/reference/CLAUDE.md +1 -1
- package/skills/pr-converge/reference/convergence-gates.md +16 -19
- package/skills/pr-converge/reference/examples.md +5 -5
- package/skills/pr-converge/reference/fix-protocol.md +16 -43
- package/skills/pr-converge/reference/obstacles/fix-publish-summary.md +1 -1
- package/skills/pr-converge/reference/per-tick.md +24 -45
- package/skills/pr-converge/reference/state-schema.md +15 -0
- package/skills/pr-converge/scripts/README.md +3 -5
- package/skills/pr-fix-protocol/SKILL.md +70 -0
- package/skills/pr-loop-lifecycle/SKILL.md +73 -0
- package/skills/{bugteam → pr-loop-lifecycle}/reference/teardown-publish-permissions.md +22 -24
- package/skills/pr-scope-resolve/SKILL.md +48 -0
- package/skills/qbug/CLAUDE.md +4 -4
- package/skills/qbug/SKILL.md +46 -144
- package/skills/qbug/test_qbug_skill_audit_schema.py +2 -2
- package/skills/qbug/test_qbug_skill_post_fix_audit.py +1 -1
- package/skills/reviewer-gates/SKILL.md +96 -0
- package/skills/session-log/CLAUDE.md +7 -7
- package/skills/session-log/SKILL.md +27 -44
- package/skills/test_markdown_link_integrity.py +103 -0
- package/commands/doc-gist.md +0 -16
- package/hooks/blocking/_md_to_html_blocker_test_support.py +0 -65
- package/hooks/blocking/conftest.py +0 -30
- package/hooks/blocking/md_path_exemptions.py +0 -224
- package/hooks/blocking/md_to_html_blocker.py +0 -155
- package/hooks/blocking/test_md_to_html_blocker_exemptions.py +0 -434
- package/hooks/blocking/test_md_to_html_blocker_extensions.py +0 -157
- package/hooks/blocking/test_md_to_html_blocker_path_resolution.py +0 -336
- package/hooks/hooks_constants/doc_gist_auto_publish_constants.py +0 -18
- package/hooks/hooks_constants/html_companion_constants.py +0 -20
- package/hooks/hooks_constants/md_to_html_blocker_constants.py +0 -76
- package/hooks/hooks_constants/test_md_to_html_blocker_constants.py +0 -125
- package/hooks/workflow/doc_gist_auto_publish.py +0 -144
- package/hooks/workflow/md_to_html_companion.py +0 -358
- package/hooks/workflow/test_doc_gist_auto_publish.py +0 -117
- package/hooks/workflow/test_md_to_html_companion.py +0 -613
- package/skills/bugteam/reference/audit-contract.md +0 -163
- package/skills/bugteam/scripts/_bugteam_permissions_common.py +0 -455
- package/skills/bugteam/scripts/bugteam_scripts_constants/claude_permissions_common_constants.py +0 -69
- package/skills/bugteam/scripts/grant_project_claude_permissions.py +0 -280
- package/skills/bugteam/scripts/revoke_project_claude_permissions.py +0 -266
- package/skills/bugteam/scripts/test__bugteam_permissions_common.py +0 -160
- package/skills/bugteam/scripts/test_agent_config_carveout.py +0 -356
- package/skills/bugteam/scripts/test_bugteam_permissions_common.py +0 -140
- package/skills/doc-gist/CLAUDE.md +0 -25
- package/skills/doc-gist/SKILL.md +0 -97
- package/skills/doc-gist/references/CLAUDE.md +0 -9
- package/skills/doc-gist/references/examples/01-exploration-code-approaches.html +0 -453
- package/skills/doc-gist/references/examples/02-exploration-visual-designs.html +0 -515
- package/skills/doc-gist/references/examples/03-code-review-pr.html +0 -638
- package/skills/doc-gist/references/examples/04-code-understanding.html +0 -491
- package/skills/doc-gist/references/examples/05-design-system.html +0 -629
- package/skills/doc-gist/references/examples/06-component-variants.html +0 -605
- package/skills/doc-gist/references/examples/07-prototype-animation.html +0 -455
- package/skills/doc-gist/references/examples/08-prototype-interaction.html +0 -396
- package/skills/doc-gist/references/examples/09-slide-deck.html +0 -592
- package/skills/doc-gist/references/examples/10-svg-illustrations.html +0 -492
- package/skills/doc-gist/references/examples/11-status-report.html +0 -528
- package/skills/doc-gist/references/examples/12-incident-report.html +0 -596
- package/skills/doc-gist/references/examples/13-flowchart-diagram.html +0 -395
- package/skills/doc-gist/references/examples/14-research-feature-explainer.html +0 -381
- package/skills/doc-gist/references/examples/15-research-concept-explainer.html +0 -368
- package/skills/doc-gist/references/examples/16-implementation-plan.html +0 -702
- package/skills/doc-gist/references/examples/17-pr-writeup.html +0 -595
- package/skills/doc-gist/references/examples/18-editor-triage-board.html +0 -573
- package/skills/doc-gist/references/examples/19-editor-feature-flags.html +0 -663
- package/skills/doc-gist/references/examples/20-editor-prompt-tuner.html +0 -722
- package/skills/doc-gist/references/examples/21-decision-signoff.html +0 -546
- package/skills/doc-gist/references/examples/CLAUDE.md +0 -25
- package/skills/doc-gist/references/examples/README.md +0 -5
- package/skills/doc-gist/scripts/CLAUDE.md +0 -27
- package/skills/doc-gist/scripts/doc_gist_scripts_constants/CLAUDE.md +0 -10
- package/skills/doc-gist/scripts/doc_gist_scripts_constants/gist_upload_constants.py +0 -16
- package/skills/doc-gist/scripts/gist_upload.py +0 -177
- package/skills/doc-gist/scripts/test_gist_upload.py +0 -51
- /package/skills/{doc-gist/scripts/doc_gist_scripts_constants → log-audit/scripts/log_audit_constants}/__init__.py +0 -0
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
"""Configuration constants for the PreToolUse hook stale_comment_reference_blocker."""
|
|
2
|
+
|
|
3
|
+
from re import Pattern, compile
|
|
4
|
+
|
|
5
|
+
COMMENT_IDENTIFIER_PATTERN: Pattern[str] = compile(r"[A-Za-z_][A-Za-z0-9_]{2,}")
|
|
6
|
+
|
|
7
|
+
ALL_COMMENT_STOPWORDS: frozenset[str] = frozenset(
|
|
8
|
+
{
|
|
9
|
+
"the",
|
|
10
|
+
"and",
|
|
11
|
+
"for",
|
|
12
|
+
"not",
|
|
13
|
+
"with",
|
|
14
|
+
"from",
|
|
15
|
+
"into",
|
|
16
|
+
"onto",
|
|
17
|
+
"out",
|
|
18
|
+
"off",
|
|
19
|
+
"this",
|
|
20
|
+
"that",
|
|
21
|
+
"these",
|
|
22
|
+
"those",
|
|
23
|
+
"each",
|
|
24
|
+
"all",
|
|
25
|
+
"any",
|
|
26
|
+
"are",
|
|
27
|
+
"was",
|
|
28
|
+
"were",
|
|
29
|
+
"has",
|
|
30
|
+
"have",
|
|
31
|
+
"had",
|
|
32
|
+
"its",
|
|
33
|
+
"use",
|
|
34
|
+
"uses",
|
|
35
|
+
"used",
|
|
36
|
+
"using",
|
|
37
|
+
"when",
|
|
38
|
+
"then",
|
|
39
|
+
"than",
|
|
40
|
+
"before",
|
|
41
|
+
"after",
|
|
42
|
+
"only",
|
|
43
|
+
"also",
|
|
44
|
+
"but",
|
|
45
|
+
"per",
|
|
46
|
+
"via",
|
|
47
|
+
"one",
|
|
48
|
+
"two",
|
|
49
|
+
"new",
|
|
50
|
+
"old",
|
|
51
|
+
"now",
|
|
52
|
+
}
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
PYTHON_FILE_SUFFIX: str = ".py"
|
|
56
|
+
|
|
57
|
+
COMMENT_LINE_PREFIX: str = "#"
|
|
58
|
+
|
|
59
|
+
STALE_COMMENT_DENY_TEMPLATE: str = (
|
|
60
|
+
"The comment above the changed line in {file_path} names "
|
|
61
|
+
"'{orphaned_name}', which this edit removes from the line below. "
|
|
62
|
+
"The comment left in place reads: {contradicted_comment}. "
|
|
63
|
+
"Update or remove the comment in the same edit."
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
STALE_COMMENT_ADDITIONAL_CONTEXT: str = (
|
|
67
|
+
"A standalone comment directly above an edited line describes that "
|
|
68
|
+
"line. When the edit drops a name the comment carries, rewrite the "
|
|
69
|
+
"comment to describe the rewritten line, or delete the comment, inside "
|
|
70
|
+
"the same Edit call."
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
STALE_COMMENT_SYSTEM_MESSAGE: str = (
|
|
74
|
+
"Agent edited a code line without updating the comment above it that "
|
|
75
|
+
"names what the edit removed"
|
|
76
|
+
)
|
|
@@ -41,6 +41,14 @@ ALL_COMMENT_TRANSITION_PATTERNS: list[Pattern[str]] = [
|
|
|
41
41
|
|
|
42
42
|
CODE_FENCE_PATTERN: Pattern[str] = compile(r"```[\s\S]*?```")
|
|
43
43
|
INLINE_CODE_PATTERN: Pattern[str] = compile(r"``[^`]+``|`[^`]+`")
|
|
44
|
+
DOUBLE_QUOTED_SPAN_PATTERN: Pattern[str] = compile(r'"[^"\n]*"')
|
|
45
|
+
TRIPLE_QUOTED_BLOCK_PATTERN: Pattern[str] = compile(
|
|
46
|
+
r'"""([\s\S]*?)"""|\'\'\'([\s\S]*?)\'\'\''
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
PYTHON_EXTENSION: str = ".py"
|
|
50
|
+
|
|
51
|
+
ALL_DEFINITION_HEADER_PREFIXES: tuple[str, ...] = ("def ", "async def ", "class ")
|
|
44
52
|
|
|
45
53
|
ALL_MARKDOWN_EXTENSIONS: frozenset[str] = frozenset(
|
|
46
54
|
{".md", ".mdx", ".markdown", ".rmd"}
|
|
@@ -168,7 +168,7 @@ class PostDispatcherDecision:
|
|
|
168
168
|
all_block_reasons: All block reasons from blocking hooks, in run order.
|
|
169
169
|
all_non_block_stdout: Stdout from hooks that did not emit a block
|
|
170
170
|
decision, concatenated in run order. Preserved so informational
|
|
171
|
-
output (such as
|
|
171
|
+
output (such as an auto-formatter notice) reaches the harness
|
|
172
172
|
on both the allow and block paths.
|
|
173
173
|
"""
|
|
174
174
|
|
|
@@ -5,11 +5,11 @@ hook as its own subprocess (the production path), records each hook's block
|
|
|
5
5
|
decision, computes the expected aggregate, then runs the dispatcher on the same
|
|
6
6
|
payload and asserts an equal block-or-allow decision and the union of reasons.
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
Two focused tests pin the side-effecting behavior the dispatcher must not
|
|
9
9
|
change: the formatter formats only on a Write of a file git does not yet track
|
|
10
|
-
and never blocks; the type-checker still blocks on a real type error when
|
|
11
|
-
through the dispatcher
|
|
12
|
-
|
|
10
|
+
and never blocks; and the type-checker still blocks on a real type error when
|
|
11
|
+
run through the dispatcher. Two more tests pin that non-block stdout from a
|
|
12
|
+
side-effect hook survives on both the allow and block paths.
|
|
13
13
|
|
|
14
14
|
Crash and early-exit tests exercise the aggregator directly: an early hook
|
|
15
15
|
crash before mypy does not drop mypy's block, a non-blocking hook crash leaves
|
|
@@ -20,6 +20,7 @@ from __future__ import annotations
|
|
|
20
20
|
|
|
21
21
|
import importlib.util
|
|
22
22
|
import json
|
|
23
|
+
import os
|
|
23
24
|
import subprocess
|
|
24
25
|
import sys
|
|
25
26
|
from pathlib import Path
|
|
@@ -33,9 +34,6 @@ _VALIDATION_DIR_STR = str(Path(__file__).resolve().parent)
|
|
|
33
34
|
if _VALIDATION_DIR_STR not in sys.path:
|
|
34
35
|
sys.path.insert(0, _VALIDATION_DIR_STR)
|
|
35
36
|
|
|
36
|
-
from hooks_constants.doc_gist_auto_publish_constants import ( # noqa: E402, I001
|
|
37
|
-
HOOK_SUBPROCESS_TIMEOUT_SECONDS as DOC_GIST_TIMEOUT_SECONDS,
|
|
38
|
-
)
|
|
39
37
|
from hooks_constants.post_tool_use_dispatcher_constants import ( # noqa: E402, I001
|
|
40
38
|
ALL_POST_HOSTED_HOOK_ENTRIES,
|
|
41
39
|
BLOCK_DECISION,
|
|
@@ -95,6 +93,26 @@ def _resolve_extra_arguments(each_entry: PostHostedHookEntry) -> list[str]:
|
|
|
95
93
|
return resolved_arguments
|
|
96
94
|
|
|
97
95
|
|
|
96
|
+
def _environment_without_git_hook_variables() -> dict[str, str]:
|
|
97
|
+
"""Return the process environment with git's hook-scoped variables removed.
|
|
98
|
+
|
|
99
|
+
Git exports variables such as GIT_DIR and GIT_INDEX_FILE to hook
|
|
100
|
+
processes. When this suite runs under a git hook (the pre-commit gate
|
|
101
|
+
replays staged test files), those variables leak into the hook
|
|
102
|
+
subprocesses and point their project discovery at the wrong checkout.
|
|
103
|
+
Stripping them restores the cwd-based discovery the hooks use in
|
|
104
|
+
production.
|
|
105
|
+
|
|
106
|
+
Returns:
|
|
107
|
+
A copy of the environment without any GIT_-prefixed variable.
|
|
108
|
+
"""
|
|
109
|
+
return {
|
|
110
|
+
each_name: each_value
|
|
111
|
+
for each_name, each_value in os.environ.items()
|
|
112
|
+
if not each_name.startswith("GIT_")
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
|
|
98
116
|
def _run_hook_subprocess(
|
|
99
117
|
each_entry: PostHostedHookEntry, payload_text: str
|
|
100
118
|
) -> subprocess.CompletedProcess[str]:
|
|
@@ -116,6 +134,7 @@ def _run_hook_subprocess(
|
|
|
116
134
|
capture_output=True,
|
|
117
135
|
text=True,
|
|
118
136
|
encoding="utf-8",
|
|
137
|
+
env=_environment_without_git_hook_variables(),
|
|
119
138
|
)
|
|
120
139
|
|
|
121
140
|
|
|
@@ -135,6 +154,7 @@ def _run_dispatcher(payload_text: str) -> subprocess.CompletedProcess[str]:
|
|
|
135
154
|
capture_output=True,
|
|
136
155
|
text=True,
|
|
137
156
|
encoding="utf-8",
|
|
157
|
+
env=_environment_without_git_hook_variables(),
|
|
138
158
|
)
|
|
139
159
|
|
|
140
160
|
|
|
@@ -267,10 +287,10 @@ def _post_tool_use_dispatcher_harness_timeout_seconds() -> int:
|
|
|
267
287
|
def _hosted_hooks_worst_case_internal_seconds() -> int:
|
|
268
288
|
"""Return the summed worst-case internal subprocess budget of the hosted hooks.
|
|
269
289
|
|
|
270
|
-
The
|
|
271
|
-
type-checker
|
|
272
|
-
|
|
273
|
-
|
|
290
|
+
The hosted hooks run sequentially in one dispatcher process. The
|
|
291
|
+
type-checker and the formatter (its slower JS path) each carry their own
|
|
292
|
+
internal subprocess timeout; their sum is the dispatcher process's
|
|
293
|
+
worst-case runtime when each hook runs to its own ceiling.
|
|
274
294
|
|
|
275
295
|
Returns:
|
|
276
296
|
The summed worst-case internal subprocess seconds across the hosted hooks.
|
|
@@ -281,21 +301,17 @@ def _hosted_hooks_worst_case_internal_seconds() -> int:
|
|
|
281
301
|
auto_formatter.PYTHON_FORMAT_TIMEOUT_SECONDS,
|
|
282
302
|
auto_formatter.JS_FORMAT_TIMEOUT_SECONDS,
|
|
283
303
|
)
|
|
284
|
-
return
|
|
285
|
-
mypy_validator.MYPY_TIMEOUT_SECONDS
|
|
286
|
-
+ slowest_formatter_seconds
|
|
287
|
-
+ DOC_GIST_TIMEOUT_SECONDS
|
|
288
|
-
)
|
|
304
|
+
return mypy_validator.MYPY_TIMEOUT_SECONDS + slowest_formatter_seconds
|
|
289
305
|
|
|
290
306
|
|
|
291
307
|
def test_dispatcher_harness_timeout_clears_summed_hosted_hook_budgets() -> None:
|
|
292
308
|
"""The dispatcher harness timeout exceeds the summed worst-case hosted-hook budget.
|
|
293
309
|
|
|
294
|
-
The
|
|
310
|
+
The hosted hooks run sequentially under one harness timeout. When the
|
|
295
311
|
harness timeout does not clear their summed worst-case internal budgets, a
|
|
296
312
|
near-full slow type-check run consumes the budget and the harness kills the
|
|
297
|
-
dispatcher before the formatter
|
|
298
|
-
|
|
313
|
+
dispatcher before the formatter gets its turn. The harness timeout must
|
|
314
|
+
exceed the sum so every hosted hook runs to completion.
|
|
299
315
|
"""
|
|
300
316
|
harness_timeout_seconds = _post_tool_use_dispatcher_harness_timeout_seconds()
|
|
301
317
|
summed_internal_seconds = _hosted_hooks_worst_case_internal_seconds()
|
|
@@ -303,7 +319,7 @@ def test_dispatcher_harness_timeout_clears_summed_hosted_hook_budgets() -> None:
|
|
|
303
319
|
"PostToolUse dispatcher harness timeout "
|
|
304
320
|
f"({harness_timeout_seconds}s) must exceed the summed worst-case internal "
|
|
305
321
|
f"budgets of the hosted hooks ({summed_internal_seconds}s), or a slow "
|
|
306
|
-
"type-check run starves the formatter
|
|
322
|
+
"type-check run starves the formatter."
|
|
307
323
|
)
|
|
308
324
|
|
|
309
325
|
|
|
@@ -315,14 +331,14 @@ def test_clean_edit_of_plain_text_allows() -> None:
|
|
|
315
331
|
|
|
316
332
|
|
|
317
333
|
def test_clean_write_of_nonexistent_path_allows() -> None:
|
|
318
|
-
"""Dispatcher allows a Write whose path does not exist (mypy
|
|
334
|
+
"""Dispatcher allows a Write whose path does not exist (mypy skips)."""
|
|
319
335
|
missing_path = str(_VALIDATION_DIR / "does_not_exist_dispatcher_probe.txt")
|
|
320
336
|
payload_text = _write_payload(missing_path, "hello world\n")
|
|
321
337
|
_assert_dispatcher_matches_individual_hooks(payload_text)
|
|
322
338
|
|
|
323
339
|
|
|
324
|
-
def
|
|
325
|
-
"""Dispatcher allows an Edit of an existing non-HTML file
|
|
340
|
+
def test_edit_of_non_html_allows() -> None:
|
|
341
|
+
"""Dispatcher allows an Edit of an existing non-HTML file."""
|
|
326
342
|
existing_path = str(Path(__file__).resolve())
|
|
327
343
|
payload_text = _edit_payload(existing_path, "old", "new")
|
|
328
344
|
_assert_dispatcher_matches_individual_hooks(payload_text)
|
|
@@ -433,10 +449,10 @@ def test_formatter_formats_only_untracked_write_and_never_blocks(tmp_path: Path)
|
|
|
433
449
|
def test_non_block_stdout_preserved_in_aggregator_allow_path() -> None:
|
|
434
450
|
"""Aggregator preserves non-block hook stdout on the allow path.
|
|
435
451
|
|
|
436
|
-
A side-effect hook
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
452
|
+
A side-effect hook writes informational text to stdout without emitting a
|
|
453
|
+
block decision. The aggregator must carry that text into
|
|
454
|
+
all_non_block_stdout so the dispatcher can write it to the real stdout on
|
|
455
|
+
the allow path.
|
|
440
456
|
"""
|
|
441
457
|
informational_text = "https://htmlpreview.github.io/?https://gist.github.com/abc/123"
|
|
442
458
|
all_results = [
|
|
@@ -459,7 +475,7 @@ def test_non_block_stdout_preserved_in_aggregator_allow_path() -> None:
|
|
|
459
475
|
def test_non_block_stdout_preserved_in_aggregator_block_path() -> None:
|
|
460
476
|
"""Aggregator preserves non-block hook stdout even when another hook blocks.
|
|
461
477
|
|
|
462
|
-
When mypy_validator blocks and
|
|
478
|
+
When mypy_validator blocks and a side-effect hook wrote informational
|
|
463
479
|
text, both the block reason and the informational text survive in the
|
|
464
480
|
aggregated decision so _emit_block_decision can forward both to stdout.
|
|
465
481
|
"""
|
|
@@ -512,9 +528,9 @@ def test_early_hook_crash_does_not_drop_later_blocking_hook_block() -> None:
|
|
|
512
528
|
def test_non_blocking_hook_crash_leaves_decision_allow() -> None:
|
|
513
529
|
"""A crash in a non-blocking hook does not change an allow to a block.
|
|
514
530
|
|
|
515
|
-
A side-effect hook such as auto_formatter
|
|
516
|
-
|
|
517
|
-
|
|
531
|
+
A side-effect hook such as auto_formatter carries is_blocking=False. Its
|
|
532
|
+
crash must not surface a blocking signal — the aggregated decision stays
|
|
533
|
+
allow.
|
|
518
534
|
"""
|
|
519
535
|
all_results = [
|
|
520
536
|
PostHostedHookResult(captured_stdout="", did_crash=True, is_blocking=False),
|
|
@@ -566,12 +582,12 @@ def test_block_path_emits_single_parseable_json_object_on_stdout() -> None:
|
|
|
566
582
|
)
|
|
567
583
|
try:
|
|
568
584
|
payload_text = _write_payload(str(type_error_file), type_error_file.read_text())
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
)
|
|
585
|
+
completed_direct = _run_hook_subprocess(ALL_POST_HOSTED_HOOK_ENTRIES[0], payload_text)
|
|
586
|
+
direct_block, _direct_reason = _parse_block_decision(completed_direct)
|
|
572
587
|
assert direct_block, (
|
|
573
588
|
"Precondition failed: mypy_validator did not block a real type error directly. "
|
|
574
|
-
"Is mypy installed and the file inside the git project
|
|
589
|
+
"Is mypy installed and the file inside the git project?\n"
|
|
590
|
+
f"stdout: {completed_direct.stdout!r}\nstderr: {completed_direct.stderr!r}"
|
|
575
591
|
)
|
|
576
592
|
|
|
577
593
|
dispatcher_result = _run_dispatcher(payload_text)
|
package/hooks/workflow/CLAUDE.md
CHANGED
|
@@ -1,22 +1,16 @@
|
|
|
1
1
|
# hooks/workflow
|
|
2
2
|
|
|
3
|
-
PostToolUse hooks that
|
|
3
|
+
PostToolUse hooks that run after Claude writes a file. They do not block the write; they run follow-up side effects — reformatting the written file and resetting the investigation tracker.
|
|
4
4
|
|
|
5
5
|
## Key files
|
|
6
6
|
|
|
7
7
|
| File | Event | What it does |
|
|
8
8
|
|---|---|---|
|
|
9
9
|
| `auto_formatter.py` | PostToolUse (Write/Edit) | Runs the project's auto-formatter (ruff, prettier, etc.) on the written file and sends a desktop notification when formatting changes are applied |
|
|
10
|
-
| `doc_gist_auto_publish.py` | PostToolUse (Write on `.html` files) | When an `.html` file has the `<!-- @publish-as-gist -->` marker, uploads it as a secret GitHub Gist and prints the htmlpreview URL into the harness output |
|
|
11
|
-
| `md_to_html_companion.py` | PostToolUse (Write/Edit on `.md` files) | Generates a styled `.html` companion file from the `.md` source with dark-mode styling |
|
|
12
10
|
| `investigation_tracker_reset.py` | PostToolUse | Resets the investigation tracker state after a tool call |
|
|
13
11
|
| `test_auto_formatter.py` | — | Tests for `auto_formatter.py` |
|
|
14
|
-
| `test_doc_gist_auto_publish.py` | — | Tests for `doc_gist_auto_publish.py` |
|
|
15
|
-
| `test_md_to_html_companion.py` | — | Tests for `md_to_html_companion.py` |
|
|
16
12
|
|
|
17
13
|
## Conventions
|
|
18
14
|
|
|
19
|
-
-
|
|
20
|
-
- `doc_gist_auto_publish.py` checks for the `<!-- @publish-as-gist -->` marker before doing any work; HTML files without it are ignored.
|
|
21
|
-
- Constants (marker text, blocked URL schemes, gist upload script path) live in `hooks_constants/doc_gist_auto_publish_constants.py` and `hooks_constants/html_companion_constants.py`.
|
|
15
|
+
- `auto_formatter.py` exits 0 even on failure — it logs warnings to stderr but does not break Claude's flow.
|
|
22
16
|
- Tests run with `python -m pytest workflow/test_<name>.py`.
|
package/package.json
CHANGED
package/rules/CLAUDE.md
CHANGED
|
@@ -28,11 +28,12 @@ Rule files installed into `~/.claude/rules/` by `bin/install.mjs`. Claude Code l
|
|
|
28
28
|
| `no-historical-clutter.md` | Documentation describes current state only; no historical or transitional language |
|
|
29
29
|
| `no-inline-destructive-literals.md` | No destructive-command literals in Bash tool command strings, even as data |
|
|
30
30
|
| `env-var-table-code-drift.md` | Every env-var summary table row in a `.md` file names a code file whose source references the variable |
|
|
31
|
+
| `es-exe-file-search.md` | File search on Windows routes through the `es.exe` CLI with a scoped query; the Everything HTTP server stays off |
|
|
31
32
|
| `orphan-css-class.md` | Every `class="..."` attribute in Python-generated markup has a matching selector in the `<style>` block |
|
|
32
33
|
| `package-inventory-stale-entry.md` | A new production code file added to a directory carries an entry in that directory's `README.md`/`CLAUDE.md` file inventory |
|
|
33
34
|
| `paired-test-coverage.md` | A public function omitted by a module's established paired test suite must get a behavioral test |
|
|
34
35
|
| `parallel-tools.md` | Make all independent tool calls in a single response |
|
|
35
|
-
| `plain-illustrative-docstrings.md` | Public docstring narrative reads plainly and
|
|
36
|
+
| `plain-illustrative-docstrings.md` | Public docstring narrative reads plainly and shows behavior with a diagram block (a `::` example or a doctest), painting a concrete scene a general developer follows on first read; a run-on backstop hook, a prose-wall backstop hook, and Category O9 audit enforce it |
|
|
36
37
|
| `plain-language.md` | Everyday words, short active sentences, lead with the answer |
|
|
37
38
|
| `prompt-workflow-context-controls.md` | Keep prompt-workflow instruction layers small and stable; load heavy skills on demand |
|
|
38
39
|
| `research-mode.md` | Three anti-hallucination constraints: say "I don't know", verify with citations, quote for factual grounding |
|
|
@@ -42,8 +43,10 @@ Rule files installed into `~/.claude/rules/` by `bin/install.mjs`. Claude Code l
|
|
|
42
43
|
| `tdd.md` | Test-driven development: red → green → refactor, no production code before a failing test |
|
|
43
44
|
| `testing.md` | Test quality and infrastructure standards |
|
|
44
45
|
| `vault-context.md` | Search Obsidian vault for prior sessions and decisions before substantive project work |
|
|
45
|
-
| `verify-before-asking.md` | Answer questions by inspecting files or running tools before asking
|
|
46
|
+
| `verify-before-asking.md` | Answer questions by inspecting files or running tools before asking; recalled facts expire until re-checked this session |
|
|
47
|
+
| `verify-runtime-state.md` | A "component is fine / not at fault" verdict rests on a live probe this session, never code reading or prior-session memory |
|
|
46
48
|
| `windows-filesystem-safe.md` | Use safe `rmtree` patterns on Windows; `mkdirSync` with `recursive: true` on possibly-existing paths |
|
|
49
|
+
| `workers-done-before-complete.md` | A task reaches `completed` only when every spawned worker has finished and its results are merged into run state |
|
|
47
50
|
| `workflow-substitution-slots.md` | Per-iteration values in `.workflow.js` templates use angle-bracket slots |
|
|
48
51
|
|
|
49
52
|
## Hook enforcement
|
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
---
|
|
2
|
+
paths:
|
|
3
|
+
- "**/CLAUDE.md"
|
|
4
|
+
---
|
|
5
|
+
|
|
1
6
|
# Orphan File Reference in a Per-Directory CLAUDE.md
|
|
2
7
|
|
|
3
8
|
**When this applies:** Any Write, Edit, or MultiEdit to a file named `CLAUDE.md` that lists files in a markdown table whose first column names each file in backticks, or that shows run commands invoking those files inside fenced code blocks.
|
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
---
|
|
2
|
+
paths:
|
|
3
|
+
- "**/*.py"
|
|
4
|
+
- "**/*.md"
|
|
5
|
+
---
|
|
6
|
+
|
|
1
7
|
# Docstring Prose Matches Implementation
|
|
2
8
|
|
|
3
9
|
**When this applies:** Any Write or Edit to a public function, method, class, or module whose docstring prose makes an enumerable claim about behavior — a list of inputs the code handles, the conditions it treats as a match, the cases it skips, or the order of its steps. It applies equally to a skill's companion `SKILL.md` (or any sibling `.md`) that describes a producer the skill's `scripts/` carry out: a doc sentence that claims a produced artifact's ordering or content is the prose this rule governs, and it tracks the producer function's own docstring and body.
|
|
@@ -6,7 +12,7 @@
|
|
|
6
12
|
|
|
7
13
|
When a docstring enumerates the behaviors a body applies, the enumeration covers every behavior the body applies. A reader trusts the list to be complete: an item the code applies but the prose omits is a silent gap that misleads every future reader and reviewer.
|
|
8
14
|
|
|
9
|
-
The gate validator `check_docstring_args_match_signature` covers the `Args:` section parameter names.
|
|
15
|
+
The gate validator `check_docstring_args_match_signature` covers the `Args:` section parameter names. Eleven more gate validators each cover one deterministic slice of the free-form prose. `check_docstring_delegation_summary_enumeration_drift` covers a thin delegating wrapper's docstring summary enumerating actions that the same-named function's summary in the named sibling file omits, compared from both save directions. `check_docstring_names_absent_type_checking_gate` covers a module or function docstring that names a `TYPE_CHECKING` gate-detection step, or a `type-checking-gate` helper family, while no identifier in the module's code carries the `type_checking` marker — the drift where the prose points a reader at a gate the body never performs; drop the `TYPE_CHECKING` gate wording, or add the detection the prose describes. `check_docstring_length_constant_superlative_vs_exact_gate` covers a length-constant module whose docstring describes an integer `*_LENGTH` constant with a superlative or range word (`the longest color string the swatch accepts`) while the only code consuming the constant compares `len(...)` against it with `==`/`!=` — an exact-length gate that rejects every other length — and never with an ordered operator; the check scans the constant module's package tree (its own directory, or the parent package when the module sits in a `config/` subdirectory), so the mismatching consumer may sit in a sibling module. `check_docstring_fallback_branch_coverage` covers a summary that scopes a fallback to a single condition (`only when`, `falls back to ... when`) while the body routes to that same fallback call from two or more distinct early-return guards. `check_class_docstring_names_public_methods` covers a class whose docstring is a single summary line while the class exposes two or more public methods whose names the summary never spells out — the drift where a one-line class summary keeps naming its first feature after the class grows a second public entry point. `check_docstring_no_consumer_claim` covers a producer docstring asserting that no consumer reads its output yet (`producer-only artifact`, `no submission-run consumer reads it yet`) — a transitional claim that drifts the moment a reader lands and contradicts any companion `SKILL.md` that documents the consumer; this is the deterministic slice of the O8 companion-doc producer/consumer drift below. `check_docstring_returns_plural_cardinality` covers a `Returns:` clause that names a dict-key prefix family with a plural noun (`the sheen stops`) while the returned dict literal holds exactly one key in that family (`sheen_mid`) — the drift where a single-key family carries a plural noun, so the prose claims a cardinality of two or more that the dict does not hold. `check_docstring_args_single_line_scope_vs_span` covers an `Args:` entry whose prose scopes a finding to a single named line (`only when its block-anchor line is among the changed lines`) while the body builds a `range(...)` span over the finding's source lines and scopes it through a span-intersection scoper — the drift where the Args entry claims a narrower single-line scope than the span-intersection body applies, so an edit touching any non-anchor line of the span still blocks. `check_docstring_cardinal_count_matches_constant_family` covers a docstring that states a cardinal count of an outcome family (`Covers the four outcome branches: ...`) and lists those members, while the module references more members of the same `UPPER_SNAKE` constant family than the count names (`OUTCOME_OFFENDER_UNREADABLE` is imported and exercised, yet the summary stops at four) — the drift where a summary keeps the old count after the code grows another branch; this gate runs on test modules as well as production modules. `check_docstring_raises_unraisable_largezipfile` covers a `Raises:` clause that names `zipfile.LargeZipFile` while the function opens its `zipfile.ZipFile` writer in a write mode (`w`/`a`/`x`) with `allowZip64` left at its default of True — `zipfile` raises `LargeZipFile` only when an entry needs ZIP64 and `allowZip64` is False, so a writer that allows ZIP64 documents an exception the body cannot produce. `check_docstring_no_network_claim_with_metadata_access` covers a function docstring promising a code path returns `without touching the network` (or a sibling no-network phrase) while the body calls a path-metadata method (`is_file`, `is_dir`, `exists`, `stat`, `lstat`) — on a network share each metadata call is a round-trip over the wire, so a cache-hit path the docstring swore avoids the network still pays a stat on every call; reword the claim to state the path is stat-checked on every call, or short-circuit to the cached path before the share is touched. The remaining free-form prose — `"a field counts as read when ..."`, `"resolves to shared temp only"`, `"strip ceremony, then drop blockquotes"`, and the broader module-responsibility paragraph outside the user-facing-text-scope slice the checklist below names — has no signature, method roster, or single structural shape to compare against, so the gate cannot catch its drift. This rule is the judgment standard for that prose; the audit lane below is the enforcement for everything outside the twelve gated slices.
|
|
10
16
|
|
|
11
17
|
## What to check before you write the docstring
|
|
12
18
|
|
|
@@ -16,8 +22,11 @@ Read the body and the docstring side by side:
|
|
|
16
22
|
- **Suppressor / skip lists.** A body with several early returns that suppress the check names each suppressor in the prose.
|
|
17
23
|
- **Shared fallback routes.** A summary that scopes a fallback call to one condition names every condition that reaches that call. When the body routes to the same fallback from two or more early-return guards (`if a is None: fallback(); return` and `if random() < p: fallback(); return`), the prose enumerates both guards. The `check_docstring_fallback_branch_coverage` gate blocks the single-condition form of this drift at Write/Edit time.
|
|
18
24
|
- **Step order.** A docstring that says `A then B then C` matches the call order in the body. A step enumeration that names the body's linear steps also names every corrective step the body guards inside an `if`/`elif` branch (`if not await cancel_and_reinitiate_update(...): return`). The `check_docstring_step_enumeration_dispatch_coverage` gate blocks the branch-guarded-dispatch form of this drift — a step-enumeration docstring that omits a two-or-more-token dispatch step the body guards inside a branch — at Write/Edit time.
|
|
25
|
+
- **Delegation pointer summaries.** A thin delegating method whose docstring names its actions and points at the home of the real body (Apply Russia, review note, publication edits; full doc on `listing_edit_flow`) lists the same actions the delegated function's own summary lists. When an edit moves one action out of the delegated body, the same edit rewords both summaries. The `check_docstring_delegation_summary_enumeration_drift` gate blocks this drift as either side lands: a saved wrapper docstring gets compared against the delegated body beside it, and a saved delegated body gets compared against every neighboring wrapper docstring pointing at it. A conditional bullet in the delegated prose (`re-apply the privacy policy after the upload`) also names every exception the body honors (`unless the reopened button reads Re-register`) — that conditional-completeness slice stays a judgment call for the audit lane.
|
|
19
26
|
- **JS/`.mjs` resume-task enumerations.** A `spawn<Role>Agent` JSDoc that enumerates its sibling `resume<Role>Agent`'s resume tasks in a parenthetical `resume (repair-verify, hardening-verify)` list names every `task === '<name>'` branch the resume body dispatches on. The `check_js_resume_task_enumeration_coverage` gate blocks the JavaScript form of this drift — a spawn JSDoc whose resume enumeration omits a dispatched task — at Write/Edit time. This is the `.mjs` slice of the same Category O6 standard the Python gates carry; the Python AST docstring gates never inspect JavaScript source.
|
|
20
27
|
- **JS/`.mjs` `@returns` object with a schema-less branch.** A `function` whose JSDoc `@returns {Promise<object>}` promises a structured object names a return type every branch honors. When the body returns one agent-spawn helper both with a `schema` options object and without one, the schema-less branch resolves to a transcript string, not the object the JSDoc claims. The `check_js_returns_object_schemaless_branch` gate blocks this drift — a `Promise<object>` JSDoc whose body returns the same helper with and without a `schema` key — at Write/Edit time. This is the `.mjs` slice of the same Category O6 standard the Python gates carry; the Python AST docstring gates never inspect JavaScript source.
|
|
28
|
+
- **JS/`.mjs` sibling return-object key drift.** Every return path of a function that yields the same record carries the same keys. When an early return in a workflow body omits exactly one key its sibling tail return carries (`allDeferredPrs`), a caller reading that key off the short path gets undefined where the documented contract and the sibling return both promise it. The `check_js_sibling_return_object_key_drift` gate blocks this drift — a `return { ... }` object literal whose key set misses exactly one key of a sibling return in the same function or module scope, with discriminated-union variants and two-or-more-key exit shapes left alone — at Write/Edit time. This is the `.mjs` slice of the same Category O standard the Python gates carry; the Python AST docstring gates never inspect JavaScript source.
|
|
29
|
+
- **JS/`.mjs` bare-flag return directive vs a stated full-result contract.** A converge-workflow preamble that states a full result-object contract (`{sha, clean:false, down:true, findings:[]}`) and adds "never a bare down flag" matches any `return <flag>: true`/`false` prose directive elsewhere in the file that repeats a status flag the stated contract rules out. When a step's prose says to "return down: true" once a budget runs out, that directive repeats the bare flag the preamble rules out — a StructuredOutput run whose schema needs every field would reject a lone `{down:true}`. The `check_js_bare_flag_return_directive` gate blocks this drift — a `return <name>: true`/`false` prose directive anywhere in the file that repeats a status flag a stated contract rules out, with no proximity or ordering check between the two — at Write/Edit time. This is the `.mjs` slice of the same Category O6 standard the Python gates carry; the Python AST docstring gates never inspect JavaScript source.
|
|
21
30
|
- **Returns-clause cardinality.** A `Returns:` clause that names a dict-key prefix family with a plural noun (`the sheen stops`) matches the count of keys in that family in the returned dict literal. When the dict holds one key in the family (`sheen_mid`), the noun is singular (`the sheen stop`); a plural noun there claims two or more entries the dict does not hold. The `check_docstring_returns_plural_cardinality` gate blocks the single-key-with-plural-noun form of this drift at Write/Edit time.
|
|
22
31
|
- **Length-constant superlative vs exact gate.** A module docstring that describes an integer `*_LENGTH` constant with a superlative or range word (`the longest color string the swatch accepts`, `no longer than`) matches how the code consumes the constant. When the only consumer compares `len(...)` against the constant with `==`/`!=` — an exact-length gate where every other length is rejected, not accepted at a shorter length — the superlative prose claims a range of accepted lengths the code never allows. State the exact required length (`the exact #AARRGGBB length`), not a longest/range form. The `check_docstring_length_constant_superlative_vs_exact_gate` gate blocks this drift at Write/Edit time, scanning the constant module's package tree so it sees a consumer that lives in a sibling module; a constant genuinely used as a ceiling (`len(x) <= LIMIT`) is left alone.
|
|
23
32
|
- **Args single-line scope vs span body.** An `Args:` entry that scopes a finding to one named line (`a finding blocks only when its block-anchor line is among the changed lines`) matches the line breadth the body scopes by. When the body builds a `range(...)` span over the finding's source lines and scopes it through a span-intersection scoper that blocks when any line of the span is among the changed lines, the single-line Args wording understates the scope: an edit touching a non-anchor line of the span still blocks. State the Args entry on the same span breadth the body uses (`a finding blocks when any line of its block span is among the changed lines`). The `check_docstring_args_single_line_scope_vs_span` gate blocks the single-line-Args-over-span-body form of this drift at Write/Edit time.
|
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
---
|
|
2
|
+
paths:
|
|
3
|
+
- "**/*.md"
|
|
4
|
+
---
|
|
5
|
+
|
|
1
6
|
# Env-Var Summary Table Names a Code File That Reads the Variable
|
|
2
7
|
|
|
3
8
|
**When this applies:** Any Write, Edit, or MultiEdit to a markdown (`.md`) file that carries an environment-variable summary table — a markdown table whose rows pair a `` `VARIABLE` `` name with the `` `code/file.py` `` that reads it.
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# File Search Through the es.exe CLI
|
|
2
|
+
|
|
3
|
+
**When this applies:** Any file-system search on Windows — finding files by name, path, extension, size, or date modified.
|
|
4
|
+
|
|
5
|
+
## Rule
|
|
6
|
+
|
|
7
|
+
`es.exe` (the Everything command-line tool) is the file-search tool. Every search carries a scope: a project path or registry token, an `ext:` filter, a `dm:` date filter, a `size:` filter, or a name pattern. A bare whole-drive scan or a network-share sweep is out of bounds — narrow the search to what you need.
|
|
8
|
+
|
|
9
|
+
When `es.exe` fails or returns nothing, try to self heal. If self-healing fails, prompt user with askuserquestion with an analysis, plus next steps questions. ie: fall back to `Glob` (name and path patterns) or `Grep` (file contents), and report the outage so the reader knows the index was unavailable.
|
|
10
|
+
|
|
11
|
+
## Registry tokens
|
|
12
|
+
|
|
13
|
+
The `es_exe_path_rewriter` hook resolves scope tokens before the command runs. A `{project-name}` placeholder or a bare registry key from `~/.claude/project-paths.json` becomes its quoted absolute path in the command. The hook allows and rewrites — it never blocks — so a search scoped to a registered project names the project token and lets the hook fill in the path.
|
|
14
|
+
|
|
15
|
+
## Operator syntax
|
|
16
|
+
|
|
17
|
+
`skills/everything-search/SKILL.md` holds the full operator reference: `ext:`, `dm:`, `size:`, wildcards, OR/AND/NOT, output flags, and the junction/drive-mapping note.
|
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
---
|
|
2
|
-
paths:
|
|
2
|
+
paths:
|
|
3
|
+
- "**/*.md"
|
|
4
|
+
- "**/*.py"
|
|
5
|
+
- "**/*.mjs"
|
|
6
|
+
- "**/*.js"
|
|
7
|
+
- "**/*.ts"
|
|
8
|
+
- "**/*.ps1"
|
|
9
|
+
- "**/*.sh"
|
|
3
10
|
---
|
|
4
11
|
|
|
5
12
|
# No Historical Clutter in Documentation or Comments
|
|
@@ -8,10 +15,14 @@ paths: **/*
|
|
|
8
15
|
|
|
9
16
|
**Hook enforcement:** `state-description-blocker` (PreToolUse on Write|Edit) blocks historical/comparative language automatically. See `hooks.json` for registration.
|
|
10
17
|
|
|
18
|
+
Coverage spans `.md` prose, code comments, and Python module/class/function docstrings; a phrase wrapped in double quotes or backticks inside a docstring counts as a mention and is skipped.
|
|
19
|
+
|
|
11
20
|
## Rule
|
|
12
21
|
|
|
13
22
|
Never reference removed implementations, old defaults, prior behaviors, or how something `"used to be"` when updating documentation. The current state is all that matters.
|
|
14
23
|
|
|
24
|
+
A module or function docstring carries the same describe-current-state-only contract as a `.md` file.
|
|
25
|
+
|
|
15
26
|
## Examples of prohibited patterns
|
|
16
27
|
|
|
17
28
|
### In documentation (.md files)
|
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
---
|
|
2
|
+
paths:
|
|
3
|
+
- "**/*.py"
|
|
4
|
+
---
|
|
5
|
+
|
|
1
6
|
# Orphan CSS Class in Generated Markup
|
|
2
7
|
|
|
3
8
|
**When this applies:** Any Write or Edit to a production `.py` file that builds HTML by emitting `class="..."` attributes inside string literals and pairs them with a `<style>` block — in the same file or in a companion module beside it.
|
|
@@ -1,3 +1,13 @@
|
|
|
1
|
+
---
|
|
2
|
+
paths:
|
|
3
|
+
- "**/*.py"
|
|
4
|
+
- "**/*.mjs"
|
|
5
|
+
- "**/*.js"
|
|
6
|
+
- "**/*.ts"
|
|
7
|
+
- "**/*.ps1"
|
|
8
|
+
- "**/*.sh"
|
|
9
|
+
---
|
|
10
|
+
|
|
1
11
|
# New Production File Absent From Its Package Inventory
|
|
2
12
|
|
|
3
13
|
**When this applies:** Any Write that creates a new production code file (`.py`, `.mjs`, `.js`, `.ts`, `.ps1`, `.sh`) in a directory whose sibling `README.md` or `CLAUDE.md` already names two or more of the directory's files in backticks, or in a skill's `scripts/` subdirectory whose parent `SKILL.md` Layout table already names two or more of those scripts.
|
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
---
|
|
2
|
+
paths:
|
|
3
|
+
- "**/*.py"
|
|
4
|
+
---
|
|
5
|
+
|
|
1
6
|
# Plain, Illustrative Docstrings
|
|
2
7
|
|
|
3
8
|
**When this applies:** Any Write or Edit to a public function, method, class, or module docstring whose narrative prose — the summary and description before the first `Args:` / `Returns:` / `Raises:` / `Yields:` section — says what the code is for or how it behaves. The standard governs the prose a reader meets first, not the structured `Args:` / `Returns:` entries below it.
|
|
@@ -14,6 +19,39 @@ Two shapes break the standard. Hold the prose clear of both:
|
|
|
14
19
|
- **No machinery nouns stacked into a wall.** A sentence that chains abstract machinery terms (`the SIGINT install/restore/installability check, the atexit terminal-record registration, and the interrupted-run finalizer`) names parts without painting a scene. Name what the reader sees and why it matters.
|
|
15
20
|
- **No defining by negation.** Prose that explains a thing by what it is not (`the non-promoter-specific machinery`) leaves the reader without a picture. Say what the thing is.
|
|
16
21
|
|
|
22
|
+
## Shape: a summary line, then a diagram
|
|
23
|
+
|
|
24
|
+
The clearest way to be illustrative is to show a worked example, not describe one. A docstring that lands well reads in four parts:
|
|
25
|
+
|
|
26
|
+
1. **One summary line** that says what the code does.
|
|
27
|
+
2. **A diagram block** that carries the explanation by sight — a reStructuredText literal block (a line ending in `::`, then an indented example) or a doctest (`>>>`). Give the concrete input, mark the outcome, and add `ok:` / `flag:` contrast lines where a pass-and-fail pair makes the point. Keep the diagram clear of a bare number or an ALL-CAPS `NAME = value` line, which read as a magic value or a stray constant to a line-based lint pass.
|
|
28
|
+
3. **A couple of short narrative lines** after the block — two or three at most.
|
|
29
|
+
4. **The Google `Args:` / `Returns:` sections.**
|
|
30
|
+
|
|
31
|
+
Keep the wording neutral in the diagram and the prose: two names "contradict" or "clash", two names "agree". Skip words that pass judgment.
|
|
32
|
+
|
|
33
|
+
Canonical example:
|
|
34
|
+
|
|
35
|
+
```
|
|
36
|
+
Flag a boolean assignment whose target and callee assert opposite polarity.
|
|
37
|
+
|
|
38
|
+
::
|
|
39
|
+
|
|
40
|
+
is_inside_allowed = _point_hits_any_forbidden(...)
|
|
41
|
+
^^^^^^^ ^^^^^^^^^
|
|
42
|
+
allowed vs. forbidden ⚠ the two names clash
|
|
43
|
+
ok: is_inside_allowed = _point_inside_allowed_region(...)
|
|
44
|
+
flag: is_inside_allowed = _point_hits_any_forbidden(...)
|
|
45
|
+
|
|
46
|
+
The target token and the callee token contradict each other, so the reader
|
|
47
|
+
cannot tell which name states the truth. Rename the callee to a neutral form
|
|
48
|
+
the two names agree on at every call site.
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
The live version of this docstring sits on `check_polarity_name_contradiction` in `packages/claude-dev-env/hooks/blocking/code_rules_naming_collection.py`.
|
|
52
|
+
|
|
53
|
+
A short narrative with no diagram is fine when a couple of plain sentences carry the whole picture. The diagram earns its place once the explanation grows past what two or three lines hold — the moment a wall of prose starts to form.
|
|
54
|
+
|
|
17
55
|
## What to check before you write the docstring
|
|
18
56
|
|
|
19
57
|
Read the narrative back as a stranger would:
|
|
@@ -48,7 +86,8 @@ one — and you're debugging blind.
|
|
|
48
86
|
|
|
49
87
|
Two surfaces carry this standard:
|
|
50
88
|
|
|
51
|
-
- **Hook (the run-on backstop).** `check_docstring_runon_sentence` in `packages/claude-dev-env/hooks/blocking/code_rules_docstrings.py` flags the one mechanical mark of a wall: a single narrative sentence that is both over the word limit and joined by an em-dash or a semicolon. A hook cannot judge whether prose paints a picture, so it catches only this structural mark.
|
|
89
|
+
- **Hook (the run-on backstop).** `check_docstring_runon_sentence` in `packages/claude-dev-env/hooks/blocking/code_rules_docstrings.py` flags the one mechanical mark of a wall: a single narrative sentence that is both over the word limit and joined by an em-dash or a semicolon. A hook cannot judge whether prose paints a picture, so it catches only this structural mark. It reads the narrative through a shared partition that sets aside any `::` literal block and any doctest, so a diagram's own arrows and dashes never count against the sentence.
|
|
90
|
+
- **Hook (the prose-wall backstop).** `check_docstring_prose_wall_without_illustration` in the same module flags a narrative that runs more than six prose lines with no diagram block. It marks the wall so the writer shows the behavior with a `::` example or a doctest and trims the prose to a few short lines. It cannot judge whether the diagram illustrates well; that stays with the audit lane.
|
|
52
91
|
- **Audit (the judgment lane).** Category O sub-bucket O9 in `packages/claude-dev-env/audit-rubrics/category_rubrics/category-o-docstring-vs-impl-drift.md` carries the illustrative-and-brief judgment the hook cannot. The audit teammate reads each changed docstring's narrative and asks whether a general developer follows it on the first read.
|
|
53
92
|
|
|
54
93
|
## Why
|
|
@@ -22,6 +22,13 @@ Before writing any AskUserQuestion or asking a clarifying question in chat, eval
|
|
|
22
22
|
|
|
23
23
|
Only after confirming the answer cannot be obtained through any available tool, ask the user.
|
|
24
24
|
|
|
25
|
+
## Prior-session facts expire
|
|
26
|
+
|
|
27
|
+
A path, port, branch name, or config value you recall from an earlier session counts as unanswered until a tool re-checks it this session. Memory records what was true when it was written; the file may have moved, the port may be down, the branch may have merged. Treat every recalled fact as a claim to re-ground, not an answer to reuse.
|
|
28
|
+
|
|
29
|
+
- When a tool can settle it, re-check in silence and act on the fresh result — no question to the user.
|
|
30
|
+
- When no tool can settle it and the user has a stake in the answer, ask through `AskUserQuestion` (see [`confirm-implementation-forks`](confirm-implementation-forks.md)).
|
|
31
|
+
|
|
25
32
|
## Questions That Belong to the User
|
|
26
33
|
|
|
27
34
|
Reserve user questions for:
|