claude-dev-env 2.13.0 → 2.14.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +135 -0
- package/CLAUDE.md +1 -57
- package/_shared/AGENTS.md +15 -0
- package/_shared/CLAUDE.md +1 -15
- package/_shared/advisor/AGENTS.md +22 -0
- package/_shared/advisor/CLAUDE.md +1 -22
- package/_shared/pr-loop/AGENTS.md +27 -0
- package/_shared/pr-loop/CLAUDE.md +1 -27
- package/_shared/pr-loop/scripts/AGENTS.md +35 -0
- package/_shared/pr-loop/scripts/CLAUDE.md +1 -35
- package/_shared/pr-loop/scripts/code_rules_gate_parts/AGENTS.md +42 -0
- package/_shared/pr-loop/scripts/code_rules_gate_parts/CLAUDE.md +1 -42
- package/_shared/pr-loop/scripts/pr_loop_shared_constants/AGENTS.md +26 -0
- package/_shared/pr-loop/scripts/pr_loop_shared_constants/CLAUDE.md +1 -26
- package/_shared/pr-loop/scripts/tests/AGENTS.md +44 -0
- package/_shared/pr-loop/scripts/tests/CLAUDE.md +1 -44
- package/_shared/process-tree/AGENTS.md +41 -0
- package/_shared/process-tree/CLAUDE.md +1 -41
- package/agents/AGENTS.md +26 -0
- package/agents/CLAUDE.md +1 -31
- package/agents/clean-coder.md +6 -6
- package/agents/test_agent_frontmatter.py +11 -11
- package/audit-rubrics/AGENTS.md +43 -0
- package/audit-rubrics/CLAUDE.md +1 -43
- package/audit-rubrics/category_rubrics/AGENTS.md +37 -0
- package/audit-rubrics/category_rubrics/CLAUDE.md +1 -37
- package/audit-rubrics/prompts/AGENTS.md +37 -0
- package/audit-rubrics/prompts/CLAUDE.md +1 -37
- package/bin/AGENTS.md +112 -0
- package/bin/CLAUDE.md +1 -112
- package/bin/ever-shipped-skills.mjs +1 -0
- package/bin/install-constants.mjs +6 -7
- package/bin/install.mjs +24 -5
- package/bin/install.test.mjs +34 -0
- package/commands/AGENTS.md +13 -0
- package/commands/CLAUDE.md +1 -23
- package/docs/AGENTS.md +32 -0
- package/docs/CLAUDE.md +1 -32
- package/docs/CODE_RULES.md +1 -1
- package/docs/imagegen.md +22 -0
- package/docs/references/AGENTS.md +17 -0
- package/docs/references/CLAUDE.md +1 -17
- package/hooks/AGENTS.md +31 -0
- package/hooks/CLAUDE.md +1 -31
- package/hooks/_gh_pr_author_swap_utils.py +14 -0
- package/hooks/advisory/AGENTS.md +16 -0
- package/hooks/advisory/CLAUDE.md +1 -16
- package/hooks/blocking/AGENTS.md +123 -0
- package/hooks/blocking/CLAUDE.md +1 -123
- package/hooks/blocking/claude_md_orphan_file_blocker_parts/AGENTS.md +28 -0
- package/hooks/blocking/claude_md_orphan_file_blocker_parts/CLAUDE.md +1 -28
- package/hooks/blocking/code_rules_banned_identifiers.py +1 -1
- package/hooks/blocking/code_rules_dead_module_constant.py +149 -37
- package/hooks/blocking/code_rules_probe_chains.py +6 -2
- package/hooks/blocking/config/AGENTS.md +10 -0
- package/hooks/blocking/config/CLAUDE.md +1 -10
- package/hooks/blocking/gh_pr_author_restore.py +7 -2
- package/hooks/blocking/intent_only_ending_blocker.py +11 -16
- package/hooks/blocking/inventory_intent_records/AGENTS.md +26 -0
- package/hooks/blocking/inventory_intent_records/CLAUDE.md +1 -26
- package/hooks/blocking/package_inventory_stale_blocker_parts/AGENTS.md +26 -0
- package/hooks/blocking/package_inventory_stale_blocker_parts/CLAUDE.md +1 -26
- package/hooks/blocking/pii_prevention_blocker_parts/AGENTS.md +24 -0
- package/hooks/blocking/pii_prevention_blocker_parts/CLAUDE.md +1 -24
- package/hooks/blocking/tdd_enforcer_parts/AGENTS.md +30 -0
- package/hooks/blocking/tdd_enforcer_parts/CLAUDE.md +1 -30
- package/hooks/blocking/test_claude_md_orphan_file_blocker.py +1 -1
- package/hooks/blocking/test_code_rules_enforcer_banned_prefixes.py +1 -1
- package/hooks/blocking/test_code_rules_enforcer_dead_module_constant.py +171 -0
- package/hooks/blocking/test_code_rules_enforcer_magic_allowlist.py +1 -1
- package/hooks/blocking/test_gh_pr_author_restore.py +2 -1
- package/hooks/blocking/test_intent_only_ending_blocker.py +4 -0
- package/hooks/diagnostic/AGENTS.md +75 -0
- package/hooks/diagnostic/CLAUDE.md +1 -43
- package/hooks/diagnostic/hook_log_extractor.py +2 -2
- package/hooks/diagnostic/hook_log_init.py +2 -2
- package/hooks/diagnostic/migrations/AGENTS.md +16 -0
- package/hooks/diagnostic/migrations/CLAUDE.md +1 -16
- package/hooks/diagnostic/queries/AGENTS.md +19 -0
- package/hooks/diagnostic/queries/CLAUDE.md +1 -19
- package/hooks/diagnostic/test_hook_log_extractor.py +2 -2
- package/hooks/git-hooks/AGENTS.md +31 -0
- package/hooks/git-hooks/CLAUDE.md +1 -31
- package/hooks/git-hooks/git_hooks_constants/AGENTS.md +21 -0
- package/hooks/git-hooks/git_hooks_constants/CLAUDE.md +1 -21
- package/hooks/git-hooks/git_hooks_constants/__init__.py +4 -6
- package/hooks/git-hooks/pre_push.py +47 -34
- package/hooks/git-hooks/test_pre_push.py +111 -7
- package/hooks/hooks_constants/AGENTS.md +104 -0
- package/hooks/hooks_constants/CLAUDE.md +1 -104
- package/hooks/hooks_constants/hook_log_extractor_constants.py +1 -2
- package/hooks/hooks_constants/task_list_loop_starter_constants.py +4 -3
- package/hooks/hooks_constants/working_style_prompt_constants.py +29 -20
- package/hooks/lifecycle/AGENTS.md +18 -0
- package/hooks/lifecycle/CLAUDE.md +1 -18
- package/hooks/observability/AGENTS.md +20 -0
- package/hooks/observability/CLAUDE.md +1 -20
- package/hooks/session/AGENTS.md +33 -0
- package/hooks/session/CLAUDE.md +1 -33
- package/hooks/session/gh_pr_author_session_cleanup.py +7 -2
- package/hooks/session/task_list_loop_starter.py +3 -3
- package/hooks/session/test_gh_pr_author_session_cleanup.py +2 -1
- package/hooks/session/test_task_list_loop_starter.py +10 -2
- package/hooks/session/test_working_style_prompt.py +9 -4
- package/hooks/validation/AGENTS.md +20 -0
- package/hooks/validation/CLAUDE.md +1 -20
- package/hooks/validators/AGENTS.md +51 -0
- package/hooks/validators/CLAUDE.md +1 -51
- package/hooks/validators/health_check.py +52 -19
- package/hooks/validators/python_style_checks.py +1 -1
- package/hooks/validators/test_health_check.py +112 -27
- package/hooks/workflow/AGENTS.md +16 -0
- package/hooks/workflow/CLAUDE.md +1 -16
- package/installable-surfaces.manifest.json +1 -0
- package/output-styles/AGENTS.md +15 -0
- package/output-styles/CLAUDE.md +1 -15
- package/package.json +2 -1
- package/rules/AGENTS.md +61 -0
- package/rules/CLAUDE.md +1 -61
- package/rules/code-standards.md +5 -9
- package/rules/doc-prose-cuts.md +1 -1
- package/scripts/AGENTS.md +62 -0
- package/scripts/CLAUDE.md +1 -62
- package/scripts/codex_compat_materializer.py +4 -0
- package/scripts/dev_env_scripts_constants/AGENTS.md +21 -0
- package/scripts/dev_env_scripts_constants/CLAUDE.md +1 -21
- package/scripts/sync_to_cursor/AGENTS.md +23 -0
- package/scripts/sync_to_cursor/CLAUDE.md +1 -23
- package/scripts/test_active_capability_references.py +2 -2
- package/scripts/test_validate_instruction_pairs.py +90 -0
- package/scripts/test_verify_installable_package.py +5 -1
- package/scripts/tests/AGENTS.md +33 -0
- package/scripts/tests/CLAUDE.md +1 -33
- package/scripts/tests/test_codex_compat_materializer.py +36 -1
- package/scripts/validate_instruction_pairs.py +183 -0
- package/skills/AGENTS.md +72 -0
- package/skills/CLAUDE.md +1 -72
- package/skills/_shared/AGENTS.md +44 -0
- package/skills/_shared/CLAUDE.md +1 -44
- package/skills/_shared/advisor/AGENTS.md +9 -0
- package/skills/_shared/advisor/CLAUDE.md +1 -9
- package/skills/_shared/pr-loop/AGENTS.md +50 -0
- package/skills/_shared/pr-loop/CLAUDE.md +1 -50
- package/skills/_shared/pr-loop/prompts/AGENTS.md +9 -0
- package/skills/_shared/pr-loop/prompts/CLAUDE.md +1 -9
- package/skills/_shared/pr-loop/scripts/AGENTS.md +35 -0
- package/skills/_shared/pr-loop/scripts/CLAUDE.md +1 -35
- package/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/AGENTS.md +24 -0
- package/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/CLAUDE.md +1 -24
- package/skills/anthropic-plan/AGENTS.md +34 -0
- package/skills/anthropic-plan/CLAUDE.md +1 -34
- package/skills/anthropic-plan/SKILL.md +1 -1
- package/skills/anthropic-plan/scripts/AGENTS.md +11 -0
- package/skills/anthropic-plan/scripts/CLAUDE.md +1 -11
- package/skills/anthropic-plan/scripts/anthropic_plan_scripts_constants/AGENTS.md +16 -0
- package/skills/anthropic-plan/scripts/anthropic_plan_scripts_constants/CLAUDE.md +1 -16
- package/skills/anthropic-plan/templates/AGENTS.md +13 -0
- package/skills/anthropic-plan/templates/CLAUDE.md +1 -13
- package/skills/anthropic-plan/test_skill_contract.py +8 -6
- package/skills/anthropic-plan/workflow/AGENTS.md +14 -0
- package/skills/anthropic-plan/workflow/CLAUDE.md +1 -14
- package/skills/auditing-claude-config/AGENTS.md +21 -0
- package/skills/auditing-claude-config/CLAUDE.md +1 -21
- package/skills/autoconverge/AGENTS.md +36 -0
- package/skills/autoconverge/CLAUDE.md +1 -36
- package/skills/autoconverge/reference/AGENTS.md +16 -0
- package/skills/autoconverge/reference/CLAUDE.md +1 -16
- package/skills/autoconverge/workflow/AGENTS.md +26 -0
- package/skills/autoconverge/workflow/CLAUDE.md +1 -26
- package/skills/autoconverge/workflow/autoconverge_report_constants/AGENTS.md +16 -0
- package/skills/autoconverge/workflow/autoconverge_report_constants/CLAUDE.md +1 -16
- package/skills/bugteam/AGENTS.md +30 -0
- package/skills/bugteam/CLAUDE.md +1 -30
- package/skills/bugteam/reference/AGENTS.md +20 -0
- package/skills/bugteam/reference/CLAUDE.md +1 -20
- package/skills/bugteam/reference/obstacles/AGENTS.md +24 -0
- package/skills/bugteam/reference/obstacles/CLAUDE.md +1 -24
- package/skills/bugteam/scripts/AGENTS.md +30 -0
- package/skills/bugteam/scripts/CLAUDE.md +1 -30
- package/skills/bugteam/scripts/bugteam_scripts_constants/AGENTS.md +18 -0
- package/skills/bugteam/scripts/bugteam_scripts_constants/CLAUDE.md +1 -18
- package/skills/closeout/reference/handoff-prompt-template.md +1 -1
- package/skills/codex-review/AGENTS.md +46 -0
- package/skills/codex-review/CLAUDE.md +1 -46
- package/skills/codex-review/reference/AGENTS.md +15 -0
- package/skills/codex-review/reference/CLAUDE.md +1 -15
- package/skills/codex-review/scripts/codex_review_scripts_constants/AGENTS.md +18 -0
- package/skills/codex-review/scripts/codex_review_scripts_constants/CLAUDE.md +1 -18
- package/skills/codex-review/test_skill_scaffold.py +3 -3
- package/skills/copilot-review/AGENTS.md +18 -0
- package/skills/copilot-review/CLAUDE.md +1 -18
- package/skills/everything-search/AGENTS.md +17 -0
- package/skills/everything-search/CLAUDE.md +1 -17
- package/skills/fresh-branch/AGENTS.md +14 -0
- package/skills/fresh-branch/CLAUDE.md +1 -14
- package/skills/grok-spawn/AGENTS.md +28 -0
- package/skills/grok-spawn/CLAUDE.md +1 -28
- package/skills/pr-converge/AGENTS.md +32 -0
- package/skills/pr-converge/CLAUDE.md +1 -32
- package/skills/pr-converge/pr_converge_skill_constants/AGENTS.md +26 -0
- package/skills/pr-converge/pr_converge_skill_constants/CLAUDE.md +1 -26
- package/skills/pr-converge/reference/AGENTS.md +28 -0
- package/skills/pr-converge/reference/CLAUDE.md +1 -28
- package/skills/pr-converge/reference/obstacles/AGENTS.md +23 -0
- package/skills/pr-converge/reference/obstacles/CLAUDE.md +1 -23
- package/skills/pr-converge/scripts/AGENTS.md +46 -0
- package/skills/pr-converge/scripts/CLAUDE.md +1 -46
- package/skills/pr-converge/scripts/pr_converge_scripts_constants/AGENTS.md +18 -0
- package/skills/pr-converge/scripts/pr_converge_scripts_constants/CLAUDE.md +1 -18
- package/skills/pr-converge/workflows/AGENTS.md +16 -0
- package/skills/pr-converge/workflows/CLAUDE.md +1 -16
- package/skills/prototype/SKILL.md +1 -2
- package/skills/prototype/reference/honest-limitations.md +1 -1
- package/skills/prototype/reference/promotion-tasks.md +1 -1
- package/skills/prototype/workflows/promotion.md +1 -1
- package/skills/rebase/AGENTS.md +32 -0
- package/skills/rebase/CLAUDE.md +1 -32
- package/skills/recall/AGENTS.md +30 -0
- package/skills/recall/CLAUDE.md +1 -30
- package/skills/remember/AGENTS.md +31 -0
- package/skills/remember/CLAUDE.md +1 -31
- package/skills/reviews/SKILL.md +1 -1
- package/skills/session-log/AGENTS.md +32 -0
- package/skills/session-log/CLAUDE.md +1 -32
- package/skills/session-tidy/AGENTS.md +36 -0
- package/skills/session-tidy/CLAUDE.md +1 -36
- package/skills/skill-builder/AGENTS.md +49 -0
- package/skills/skill-builder/CLAUDE.md +1 -49
- package/skills/skill-builder/references/AGENTS.md +25 -0
- package/skills/skill-builder/references/CLAUDE.md +1 -25
- package/skills/skill-builder/references/deterministic-elements.md +1 -1
- package/skills/skill-builder/templates/AGENTS.md +14 -0
- package/skills/skill-builder/templates/CLAUDE.md +1 -14
- package/skills/skill-builder/workflows/AGENTS.md +19 -0
- package/skills/skill-builder/workflows/CLAUDE.md +1 -19
- package/skills/task-build/AGENTS.md +29 -0
- package/skills/task-build/CLAUDE.md +1 -29
- package/skills/update/AGENTS.md +38 -0
- package/skills/update/CLAUDE.md +1 -38
- package/system-prompts/AGENTS.md +17 -0
- package/system-prompts/CLAUDE.md +1 -17
- package/system-prompts/software-engineer.xml +3 -3
- package/agents/caveman.md +0 -73
- package/agents/clasp-deployment-orchestrator.md +0 -608
- package/agents/code-advisor.md +0 -23
- package/agents/deep-research.md +0 -152
- package/agents/docs-agent.md +0 -85
- package/commands/commit.md +0 -28
- package/commands/docupdate.md +0 -322
- package/commands/hook-log-extract.md +0 -70
- package/commands/hook-log-init.md +0 -76
- package/commands/implement.md +0 -102
- package/commands/plan.md +0 -14
- package/commands/pr-comments.md +0 -47
- package/commands/review-plan.md +0 -5
- package/commands/right-size.md +0 -15
- package/commands/sum.md +0 -30
- package/skills/imagegen/SKILL.md +0 -21
- package/skills/imagegen/scripts/config/__init__.py +0 -1
- package/skills/imagegen/scripts/config/constants.py +0 -28
- package/skills/imagegen/scripts/imagegen.py +0 -45
- package/skills/imagegen/scripts/imagegen_core.py +0 -528
- package/skills/imagegen/scripts/test_imagegen.py +0 -204
|
@@ -29,13 +29,20 @@ The scan is deliberately conservative to keep false positives near zero:
|
|
|
29
29
|
exported constant live.
|
|
30
30
|
- When the package-tree scan leaves a constant unreferenced, the scan widens to
|
|
31
31
|
the repository root (the nearest ``.git`` ancestor). The widened pass counts a
|
|
32
|
-
sibling-tree reference
|
|
33
|
-
``from <module> import`` whose final dotted segment equals the
|
|
34
|
-
module's filename stem
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
32
|
+
sibling-tree reference in two shapes. The first is an import of the name
|
|
33
|
+
through a ``from <module> import`` whose final dotted segment equals the
|
|
34
|
+
written module's filename stem. The second is an attribute read on a binding
|
|
35
|
+
of the written module itself: a consumer that writes
|
|
36
|
+
``import pkg.config.constants``, the same with an alias,
|
|
37
|
+
``from pkg.config import constants``, or the same with an alias, and then
|
|
38
|
+
reads each constant as an attribute on that binding (``alias.NAME``,
|
|
39
|
+
``pkg.config.constants.NAME``), keeps every name it reads that way live. Both
|
|
40
|
+
shapes are bound to the written module's stem, so a genuine cross-tree
|
|
41
|
+
consumer of this constants module keeps the constant live while a same-named
|
|
42
|
+
constant exported by an unrelated module never masks a dead one. The widened
|
|
43
|
+
pass reads a repository file only to test whether its text names the written
|
|
44
|
+
module's filename stem; neither shape can appear in a file that never spells
|
|
45
|
+
the stem, so such a file is
|
|
39
46
|
skipped without spending scan-cap budget, keeping the widened pass bounded to
|
|
40
47
|
the handful of candidate importer files even in a large repository. A module
|
|
41
48
|
outside any repository is
|
|
@@ -65,6 +72,7 @@ if _blocking_directory not in sys.path:
|
|
|
65
72
|
if _hooks_directory not in sys.path:
|
|
66
73
|
sys.path.insert(0, _hooks_directory)
|
|
67
74
|
|
|
75
|
+
from code_rules_probe_chains import _dotted_attribute_chain # noqa: E402
|
|
68
76
|
from code_rules_shared import ( # noqa: E402
|
|
69
77
|
is_migration_file,
|
|
70
78
|
is_test_file,
|
|
@@ -268,13 +276,116 @@ def _module_final_segment(module_path: str | None) -> str:
|
|
|
268
276
|
return module_path.rsplit(".", 1)[-1]
|
|
269
277
|
|
|
270
278
|
|
|
271
|
-
def
|
|
272
|
-
"""Return
|
|
279
|
+
def _stem_import_member_names(import_node: ast.ImportFrom, module_stem: str) -> set[str]:
|
|
280
|
+
"""Return the member names a stem-matched ``from ... import`` brings in.
|
|
273
281
|
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
282
|
+
::
|
|
283
|
+
|
|
284
|
+
from pkg.config.constants import NAME -> "NAME"
|
|
285
|
+
from pkg.config.other import NAME -> nothing (stem mismatch)
|
|
286
|
+
|
|
287
|
+
Args:
|
|
288
|
+
import_node: A ``from ... import`` statement node.
|
|
289
|
+
module_stem: The filename stem of the constants module being judged.
|
|
290
|
+
|
|
291
|
+
Returns:
|
|
292
|
+
The imported member names when the module path's final dotted segment
|
|
293
|
+
equals ``module_stem``, empty otherwise.
|
|
294
|
+
"""
|
|
295
|
+
if _module_final_segment(import_node.module) != module_stem:
|
|
296
|
+
return set()
|
|
297
|
+
return {each_alias.name for each_alias in import_node.names}
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
def _import_module_bindings(all_import_aliases: list[ast.alias], module_stem: str) -> set[str]:
|
|
301
|
+
"""Return the names an import's aliases bind to the constants module object.
|
|
302
|
+
|
|
303
|
+
::
|
|
304
|
+
|
|
305
|
+
import pkg.config.constants -> "pkg.config.constants"
|
|
306
|
+
import pkg.config.constants as sg -> "sg"
|
|
307
|
+
from pkg.config import constants -> "constants"
|
|
308
|
+
from . import constants as sg -> "sg"
|
|
309
|
+
import pkg.config.other -> nothing (stem mismatch)
|
|
310
|
+
|
|
311
|
+
A plain ``import`` names the module by its dotted path, so the final
|
|
312
|
+
segment is matched against the stem. A ``from ... import`` alias name is a
|
|
313
|
+
single identifier, which the same final-segment match covers unchanged.
|
|
314
|
+
|
|
315
|
+
Args:
|
|
316
|
+
all_import_aliases: The ``names`` list of an ``Import`` or
|
|
317
|
+
``ImportFrom`` node.
|
|
318
|
+
module_stem: The filename stem of the constants module being judged.
|
|
319
|
+
|
|
320
|
+
Returns:
|
|
321
|
+
The dotted paths and aliases through which the module object is
|
|
322
|
+
reachable.
|
|
323
|
+
"""
|
|
324
|
+
binding_names: set[str] = set()
|
|
325
|
+
for each_alias in all_import_aliases:
|
|
326
|
+
if _module_final_segment(each_alias.name) != module_stem:
|
|
327
|
+
continue
|
|
328
|
+
binding_names.add(each_alias.asname or each_alias.name)
|
|
329
|
+
return binding_names
|
|
330
|
+
|
|
331
|
+
|
|
332
|
+
def _collect_widened_scan_facts(
|
|
333
|
+
tree: ast.Module, module_stem: str
|
|
334
|
+
) -> tuple[set[str], set[str], list[ast.Attribute]]:
|
|
335
|
+
"""Collect a candidate module's import and attribute facts in one walk.
|
|
336
|
+
|
|
337
|
+
Args:
|
|
338
|
+
tree: The parsed candidate module.
|
|
339
|
+
module_stem: The filename stem of the constants module being judged.
|
|
340
|
+
|
|
341
|
+
Returns:
|
|
342
|
+
The member names imported through a stem-matched ``from ... import``,
|
|
343
|
+
the names bound to the constants module object itself, and every
|
|
344
|
+
attribute node in the module.
|
|
345
|
+
"""
|
|
346
|
+
member_names: set[str] = set()
|
|
347
|
+
binding_names: set[str] = set()
|
|
348
|
+
all_attribute_nodes: list[ast.Attribute] = []
|
|
349
|
+
for each_node in ast.walk(tree):
|
|
350
|
+
if isinstance(each_node, ast.ImportFrom):
|
|
351
|
+
member_names |= _stem_import_member_names(each_node, module_stem)
|
|
352
|
+
if isinstance(each_node, (ast.Import, ast.ImportFrom)):
|
|
353
|
+
binding_names |= _import_module_bindings(each_node.names, module_stem)
|
|
354
|
+
if isinstance(each_node, ast.Attribute):
|
|
355
|
+
all_attribute_nodes.append(each_node)
|
|
356
|
+
return member_names, binding_names, all_attribute_nodes
|
|
357
|
+
|
|
358
|
+
|
|
359
|
+
def _attribute_read_names(
|
|
360
|
+
all_attribute_nodes: list[ast.Attribute], all_binding_names: set[str]
|
|
361
|
+
) -> set[str]:
|
|
362
|
+
"""Return the constant names read as attributes on a module-object binding.
|
|
363
|
+
|
|
364
|
+
The expression an attribute is read on is spelled back to its dotted source
|
|
365
|
+
form and matched against the bindings, so an alias read (``sg.NAME``) and a
|
|
366
|
+
full dotted path (``pkg.config.constants.NAME``) both resolve, while a read
|
|
367
|
+
on a binding of some other module counts for nothing.
|
|
368
|
+
|
|
369
|
+
Args:
|
|
370
|
+
all_attribute_nodes: Every attribute node in the candidate module.
|
|
371
|
+
all_binding_names: The names bound to the constants module object.
|
|
372
|
+
|
|
373
|
+
Returns:
|
|
374
|
+
The attribute names read on a binding of the constants module.
|
|
375
|
+
"""
|
|
376
|
+
if not all_binding_names:
|
|
377
|
+
return set()
|
|
378
|
+
return {
|
|
379
|
+
each_attribute.attr
|
|
380
|
+
for each_attribute in all_attribute_nodes
|
|
381
|
+
if _dotted_attribute_chain(each_attribute.value) in all_binding_names
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
|
|
385
|
+
def _widened_scan_reference_names(source: str, module_stem: str) -> set[str]:
|
|
386
|
+
"""Return the names a repository-wide candidate contributes, parsing it once.
|
|
387
|
+
|
|
388
|
+
Both counted shapes are bound to the written module's filename stem, so a
|
|
278
389
|
same-named constant exported by an unrelated module never masks a dead one.
|
|
279
390
|
|
|
280
391
|
Args:
|
|
@@ -282,22 +393,18 @@ def _qualified_import_member_names(source: str, module_stem: str) -> set[str]:
|
|
|
282
393
|
module_stem: The filename stem of the constants module being judged.
|
|
283
394
|
|
|
284
395
|
Returns:
|
|
285
|
-
The
|
|
286
|
-
|
|
396
|
+
The members imported through a stem-matched ``from <module> import``,
|
|
397
|
+
plus the constant names read as attributes on an imported binding of the
|
|
398
|
+
constants module. A module that fails to parse contributes no names.
|
|
287
399
|
"""
|
|
288
400
|
try:
|
|
289
401
|
tree = ast.parse(source)
|
|
290
402
|
except SyntaxError:
|
|
291
403
|
return set()
|
|
292
|
-
member_names
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
if _module_final_segment(each_node.module) != module_stem:
|
|
297
|
-
continue
|
|
298
|
-
for each_alias in each_node.names:
|
|
299
|
-
member_names.add(each_alias.name)
|
|
300
|
-
return member_names
|
|
404
|
+
member_names, binding_names, all_attribute_nodes = _collect_widened_scan_facts(
|
|
405
|
+
tree, module_stem
|
|
406
|
+
)
|
|
407
|
+
return member_names | _attribute_read_names(all_attribute_nodes, binding_names)
|
|
301
408
|
|
|
302
409
|
|
|
303
410
|
def _scan_root_for_constants_module(file_path: str) -> Path:
|
|
@@ -350,11 +457,13 @@ def _read_candidate_source(file_path: Path, required_substring: str | None) -> s
|
|
|
350
457
|
required_substring = None -> read and keep every file
|
|
351
458
|
required_substring = the module stem -> keep only files naming it
|
|
352
459
|
from pkg.foo_constants import BAR -> names the stem -> candidate
|
|
460
|
+
from pkg import foo_constants as c -> names the stem -> candidate
|
|
353
461
|
def unrelated() -> int: ... -> no stem mention -> skipped
|
|
354
462
|
|
|
355
|
-
The widened repository pass looks only for
|
|
356
|
-
|
|
357
|
-
|
|
463
|
+
The widened repository pass looks only for an import bound to the constants
|
|
464
|
+
module's filename stem — a ``from <module> import`` whose final dotted segment
|
|
465
|
+
equals the stem, or an import of the module object itself — and every such
|
|
466
|
+
import spells that stem in the file's text. A file whose text never
|
|
358
467
|
mentions the stem cannot carry the import, so returning None for it lets the
|
|
359
468
|
caller skip the file without spending scan-cap budget, which keeps the widened
|
|
360
469
|
pass bounded to the candidate importer files even under a large repository.
|
|
@@ -422,7 +531,9 @@ def _collect_names_under_root(
|
|
|
422
531
|
in before the walk begins.
|
|
423
532
|
extract_names: Maps one module's source text to the set of names it
|
|
424
533
|
contributes — the generous reference collector for the package-tree
|
|
425
|
-
pass, the stem-bound
|
|
534
|
+
pass, and for the widened pass the stem-bound collector that counts
|
|
535
|
+
both a stem-matched from-import and an attribute read on a binding
|
|
536
|
+
of the written module.
|
|
426
537
|
already_scanned_count: The parsed-file count accumulated by a prior
|
|
427
538
|
pass, so the parse cap bounds the combined work of the
|
|
428
539
|
package-tree and widened passes.
|
|
@@ -552,12 +663,13 @@ def check_dead_module_constants(
|
|
|
552
663
|
are governed by the use-count rule instead. A constant is dead when its name
|
|
553
664
|
appears in no ``.py`` module under the enclosing package tree — not imported,
|
|
554
665
|
not read, not listed in another module's ``__all__`` literal, not named in a
|
|
555
|
-
string annotation
|
|
556
|
-
the
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
unrelated module
|
|
666
|
+
string annotation. When that leaves a constant unreferenced, the scan widens
|
|
667
|
+
to the repository, where two shapes count: a ``from <module> import`` whose
|
|
668
|
+
final dotted segment equals this module's filename stem, and an attribute
|
|
669
|
+
read on a binding of this module (``import pkg.config.constants as alias``
|
|
670
|
+
then the alias followed by the constant name). Binding both to the stem
|
|
671
|
+
keeps a same-named constant in an unrelated module from masking a dead one.
|
|
672
|
+
A module declaring ``__all__``
|
|
561
673
|
narrows the check to the constants its ``__all__`` list names: each must be
|
|
562
674
|
imported or read by another module, and the module's own ``__all__`` entry
|
|
563
675
|
never counts as that consumer, so an exported constant no module consumes is
|
|
@@ -616,14 +728,14 @@ def check_dead_module_constants(
|
|
|
616
728
|
if has_unreferenced_constant:
|
|
617
729
|
repository_root = _repository_root_for(written_path)
|
|
618
730
|
if repository_root is not None and repository_root != scan_root:
|
|
619
|
-
|
|
620
|
-
|
|
731
|
+
collect_widened_scan_names = partial(
|
|
732
|
+
_widened_scan_reference_names, module_stem=written_path.stem
|
|
621
733
|
)
|
|
622
734
|
widened_names, _widened_count, widened_cap_was_hit = _collect_names_under_root(
|
|
623
735
|
repository_root,
|
|
624
736
|
written_path,
|
|
625
737
|
set(),
|
|
626
|
-
|
|
738
|
+
collect_widened_scan_names,
|
|
627
739
|
already_scanned_count=scanned_file_count,
|
|
628
740
|
excluded_subtree=scan_root,
|
|
629
741
|
required_substring=written_path.stem,
|
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
"""Attribute-chain and os.environ alias-resolution primitives
|
|
1
|
+
"""Attribute-chain and os.environ alias-resolution primitives.
|
|
2
|
+
|
|
3
|
+
Serves the test-isolation check, and lends ``_dotted_attribute_chain`` to the
|
|
4
|
+
dead-module-constant check's widened attribute-read scan.
|
|
5
|
+
"""
|
|
2
6
|
|
|
3
7
|
import ast
|
|
4
8
|
import sys
|
|
@@ -143,7 +147,7 @@ def _attribute_chain_resolves_to_os_environ(
|
|
|
143
147
|
return canonical_chain == OS_ENVIRON_DOTTED_NAME
|
|
144
148
|
|
|
145
149
|
|
|
146
|
-
def _dotted_attribute_chain(attribute_node: ast.
|
|
150
|
+
def _dotted_attribute_chain(attribute_node: ast.expr) -> str | None:
|
|
147
151
|
chain_parts: list[str] = []
|
|
148
152
|
walker: ast.expr = attribute_node
|
|
149
153
|
while isinstance(walker, ast.Attribute):
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# hooks/blocking/config
|
|
2
|
+
|
|
3
|
+
A Python package that holds shared constants for blocking hooks. Opinionated prose-style gates import from here.
|
|
4
|
+
|
|
5
|
+
## Key files
|
|
6
|
+
|
|
7
|
+
| File | Contents |
|
|
8
|
+
|---|---|
|
|
9
|
+
| `__init__.py` | Declares this as a regular package (not a namespace package) so it resolves first on `sys.path` |
|
|
10
|
+
| `prose_style_enforcement_constants.py` | `CLAUDE_PROSE_STYLE_ENFORCEMENT` opt-in (default off) for opinionated prose gates |
|
|
@@ -1,10 +1 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
A Python package that holds shared constants for blocking hooks. Opinionated prose-style gates import from here.
|
|
4
|
-
|
|
5
|
-
## Key files
|
|
6
|
-
|
|
7
|
-
| File | Contents |
|
|
8
|
-
|---|---|
|
|
9
|
-
| `__init__.py` | Declares this as a regular package (not a namespace package) so it resolves first on `sys.path` |
|
|
10
|
-
| `prose_style_enforcement_constants.py` | `CLAUDE_PROSE_STYLE_ENFORCEMENT` opt-in (default off) for opinionated prose gates |
|
|
1
|
+
@AGENTS.md
|
|
@@ -36,6 +36,7 @@ if hooks_parent_directory not in sys.path:
|
|
|
36
36
|
sys.path.insert(0, hooks_parent_directory)
|
|
37
37
|
|
|
38
38
|
from _gh_pr_author_swap_utils import ( # noqa: E402
|
|
39
|
+
_build_restore_failure_message,
|
|
39
40
|
_command_invokes_gh_pr_create_in_stripped,
|
|
40
41
|
_delete_state_file,
|
|
41
42
|
_preprocess_command_for_matching,
|
|
@@ -89,8 +90,12 @@ def main() -> None:
|
|
|
89
90
|
_delete_state_file(state_file)
|
|
90
91
|
else:
|
|
91
92
|
_write_line(
|
|
92
|
-
|
|
93
|
-
|
|
93
|
+
_build_restore_failure_message(
|
|
94
|
+
"gh-pr-author-restore",
|
|
95
|
+
original_account,
|
|
96
|
+
state_file,
|
|
97
|
+
"state file",
|
|
98
|
+
),
|
|
94
99
|
sys.stderr,
|
|
95
100
|
)
|
|
96
101
|
sys.exit(0)
|
|
@@ -1,12 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
|
-
"""
|
|
3
|
-
Stop hook that blocks Claude responses ending on a promise about undone work.
|
|
2
|
+
"""Stop hook that blocks a turn ending with a promise of future work.
|
|
4
3
|
|
|
5
|
-
When a turn ends
|
|
6
|
-
tests", "Let me implement the fix")
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
cleanly. The rule name is long-horizon-autonomy.
|
|
4
|
+
When a turn ends with a forward-looking statement of intent ("I'll now run the
|
|
5
|
+
tests", "Let me implement the fix"), complete the work with tool calls during
|
|
6
|
+
the turn. Route work requiring user-only input through AskUserQuestion. The
|
|
7
|
+
rule name is long-horizon-autonomy.
|
|
10
8
|
"""
|
|
11
9
|
|
|
12
10
|
import json
|
|
@@ -104,7 +102,7 @@ def find_intent_only_ending(text: str) -> bool:
|
|
|
104
102
|
|
|
105
103
|
|
|
106
104
|
def main() -> None:
|
|
107
|
-
"""Read the stop-hook payload and block turns that
|
|
105
|
+
"""Read the stop-hook payload and block turns that promise future work."""
|
|
108
106
|
try:
|
|
109
107
|
hook_input = json.load(sys.stdin)
|
|
110
108
|
except json.JSONDecodeError:
|
|
@@ -125,14 +123,11 @@ def main() -> None:
|
|
|
125
123
|
sys.exit(0)
|
|
126
124
|
|
|
127
125
|
block_reason = (
|
|
128
|
-
"LONG-HORIZON-AUTONOMY GUARDRAIL:
|
|
129
|
-
"that
|
|
130
|
-
"
|
|
131
|
-
"
|
|
132
|
-
"
|
|
133
|
-
"carry out the stated action this turn.\n\n"
|
|
134
|
-
"You MUST re-output the complete response with the work actually performed, "
|
|
135
|
-
"per the long-horizon-autonomy rule."
|
|
126
|
+
"LONG-HORIZON-AUTONOMY GUARDRAIL: This turn ends with a promise of future "
|
|
127
|
+
"work. Complete that work with tool calls during this turn. Route questions "
|
|
128
|
+
"requiring user-only input through AskUserQuestion, then end the turn cleanly.\n\n"
|
|
129
|
+
"Re-output the complete response with the work performed, per the "
|
|
130
|
+
"long-horizon-autonomy rule."
|
|
136
131
|
)
|
|
137
132
|
block_response = {
|
|
138
133
|
"decision": "block",
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# inventory_intent_records
|
|
2
|
+
|
|
3
|
+
The shared pending-intent store that lets `package_inventory_stale_blocker.py`
|
|
4
|
+
and `claude_md_orphan_file_blocker.py` add a new file and its inventory row in
|
|
5
|
+
one change, in either order. When one blocker denies, it records a note; the
|
|
6
|
+
sibling blocker reads that note to allow the matching second write.
|
|
7
|
+
|
|
8
|
+
## Modules
|
|
9
|
+
|
|
10
|
+
| File | Purpose |
|
|
11
|
+
|---|---|
|
|
12
|
+
| `records.py` | Read, record, peek, and consume the per-session file and row intents; a missing or corrupt store reads as no notes |
|
|
13
|
+
| `__init__.py` | Package marker |
|
|
14
|
+
|
|
15
|
+
## Subdirectories
|
|
16
|
+
|
|
17
|
+
| Entry | Description |
|
|
18
|
+
|---|---|
|
|
19
|
+
| `config/` | The records-file name shape, the freshness window, the list keys, and the note field names (`intent_records_constants.py`) |
|
|
20
|
+
| `tests/` | pytest suite for the records store |
|
|
21
|
+
|
|
22
|
+
## Running tests
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
python -m pytest packages/claude-dev-env/hooks/blocking/inventory_intent_records/tests/
|
|
26
|
+
```
|
|
@@ -1,26 +1 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
The shared pending-intent store that lets `package_inventory_stale_blocker.py`
|
|
4
|
-
and `claude_md_orphan_file_blocker.py` add a new file and its inventory row in
|
|
5
|
-
one change, in either order. When one blocker denies, it records a note; the
|
|
6
|
-
sibling blocker reads that note to allow the matching second write.
|
|
7
|
-
|
|
8
|
-
## Modules
|
|
9
|
-
|
|
10
|
-
| File | Purpose |
|
|
11
|
-
|---|---|
|
|
12
|
-
| `records.py` | Read, record, peek, and consume the per-session file and row intents; a missing or corrupt store reads as no notes |
|
|
13
|
-
| `__init__.py` | Package marker |
|
|
14
|
-
|
|
15
|
-
## Subdirectories
|
|
16
|
-
|
|
17
|
-
| Entry | Description |
|
|
18
|
-
|---|---|
|
|
19
|
-
| `config/` | The records-file name shape, the freshness window, the list keys, and the note field names (`intent_records_constants.py`) |
|
|
20
|
-
| `tests/` | pytest suite for the records store |
|
|
21
|
-
|
|
22
|
-
## Running tests
|
|
23
|
-
|
|
24
|
-
```bash
|
|
25
|
-
python -m pytest packages/claude-dev-env/hooks/blocking/inventory_intent_records/tests/
|
|
26
|
-
```
|
|
1
|
+
@AGENTS.md
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# package_inventory_stale_blocker_parts
|
|
2
|
+
|
|
3
|
+
The concern modules `package_inventory_stale_blocker.py` wires together to block a
|
|
4
|
+
new production file its package inventory omits. The entry hook imports them and
|
|
5
|
+
re-exports their surface for the test suite.
|
|
6
|
+
|
|
7
|
+
## Modules
|
|
8
|
+
|
|
9
|
+
| File | Purpose |
|
|
10
|
+
|---|---|
|
|
11
|
+
| `inventory_detection.py` | Surveys a directory's `README.md`/`CLAUDE.md`/`SKILL.md`, collects the filenames they name, and reports whether a maintained inventory omits the file being written |
|
|
12
|
+
| `decision.py` | Builds the deny payload naming the omitted file and closing with the retry hint, and emits the decision JSON |
|
|
13
|
+
| `__init__.py` | Package marker |
|
|
14
|
+
|
|
15
|
+
## Subdirectories
|
|
16
|
+
|
|
17
|
+
| Entry | Description |
|
|
18
|
+
|---|---|
|
|
19
|
+
| `config/` | The retry hint the deny reason closes with and the inventory-name join separator (`inventory_blocker_constants.py`) |
|
|
20
|
+
| `tests/` | pytest suite with one test module per module above |
|
|
21
|
+
|
|
22
|
+
## Running tests
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
python -m pytest packages/claude-dev-env/hooks/blocking/package_inventory_stale_blocker_parts/tests/
|
|
26
|
+
```
|
|
@@ -1,26 +1 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
The concern modules `package_inventory_stale_blocker.py` wires together to block a
|
|
4
|
-
new production file its package inventory omits. The entry hook imports them and
|
|
5
|
-
re-exports their surface for the test suite.
|
|
6
|
-
|
|
7
|
-
## Modules
|
|
8
|
-
|
|
9
|
-
| File | Purpose |
|
|
10
|
-
|---|---|
|
|
11
|
-
| `inventory_detection.py` | Surveys a directory's `README.md`/`CLAUDE.md`/`SKILL.md`, collects the filenames they name, and reports whether a maintained inventory omits the file being written |
|
|
12
|
-
| `decision.py` | Builds the deny payload naming the omitted file and closing with the retry hint, and emits the decision JSON |
|
|
13
|
-
| `__init__.py` | Package marker |
|
|
14
|
-
|
|
15
|
-
## Subdirectories
|
|
16
|
-
|
|
17
|
-
| Entry | Description |
|
|
18
|
-
|---|---|
|
|
19
|
-
| `config/` | The retry hint the deny reason closes with and the inventory-name join separator (`inventory_blocker_constants.py`) |
|
|
20
|
-
| `tests/` | pytest suite with one test module per module above |
|
|
21
|
-
|
|
22
|
-
## Running tests
|
|
23
|
-
|
|
24
|
-
```bash
|
|
25
|
-
python -m pytest packages/claude-dev-env/hooks/blocking/package_inventory_stale_blocker_parts/tests/
|
|
26
|
-
```
|
|
1
|
+
@AGENTS.md
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# pii_prevention_blocker_parts
|
|
2
|
+
|
|
3
|
+
The concern modules `pii_prevention_blocker.py` wires together. The entry hook
|
|
4
|
+
imports them and re-exports their surface for the test suite.
|
|
5
|
+
|
|
6
|
+
## Modules
|
|
7
|
+
|
|
8
|
+
| File | Purpose |
|
|
9
|
+
|---|---|
|
|
10
|
+
| `repository_exemption.py` | Resolves a commit's origin owner/repo slug, decides whether the repository skips the staged PII scan, and resolves the exact values that repository allows past the scan |
|
|
11
|
+
| `repository_resolution.py` | Reads the repository a commit command targets — composing multiple `-C` values and a leading `cd`/`pushd` — and builds the deny reason naming the path when the repository root does not resolve |
|
|
12
|
+
| `__init__.py` | Package marker |
|
|
13
|
+
|
|
14
|
+
## Subdirectories
|
|
15
|
+
|
|
16
|
+
| Entry | Description |
|
|
17
|
+
|---|---|
|
|
18
|
+
| `config/` | The repository-resolution deny-message template and the session-cwd label (`repository_resolution_constants.py`) |
|
|
19
|
+
|
|
20
|
+
## Running tests
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
python -m pytest packages/claude-dev-env/hooks/blocking/tests/
|
|
24
|
+
```
|
|
@@ -1,24 +1 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
The concern modules `pii_prevention_blocker.py` wires together. The entry hook
|
|
4
|
-
imports them and re-exports their surface for the test suite.
|
|
5
|
-
|
|
6
|
-
## Modules
|
|
7
|
-
|
|
8
|
-
| File | Purpose |
|
|
9
|
-
|---|---|
|
|
10
|
-
| `repository_exemption.py` | Resolves a commit's origin owner/repo slug, decides whether the repository skips the staged PII scan, and resolves the exact values that repository allows past the scan |
|
|
11
|
-
| `repository_resolution.py` | Reads the repository a commit command targets — composing multiple `-C` values and a leading `cd`/`pushd` — and builds the deny reason naming the path when the repository root does not resolve |
|
|
12
|
-
| `__init__.py` | Package marker |
|
|
13
|
-
|
|
14
|
-
## Subdirectories
|
|
15
|
-
|
|
16
|
-
| Entry | Description |
|
|
17
|
-
|---|---|
|
|
18
|
-
| `config/` | The repository-resolution deny-message template and the session-cwd label (`repository_resolution_constants.py`) |
|
|
19
|
-
|
|
20
|
-
## Running tests
|
|
21
|
-
|
|
22
|
-
```bash
|
|
23
|
-
python -m pytest packages/claude-dev-env/hooks/blocking/tests/
|
|
24
|
-
```
|
|
1
|
+
@AGENTS.md
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# tdd_enforcer_parts
|
|
2
|
+
|
|
3
|
+
The concern modules `tdd_enforcer.py` wires together to run the TDD gate. Each
|
|
4
|
+
module owns one concern; the entry hook imports them and re-exports their
|
|
5
|
+
surface for the test suite.
|
|
6
|
+
|
|
7
|
+
## Modules
|
|
8
|
+
|
|
9
|
+
| File | Purpose |
|
|
10
|
+
|---|---|
|
|
11
|
+
| `path_classification.py` | Classifies a write target (docs, tests, `.claude` trees) the gate skips, and extracts the written text from a Write, Edit, or MultiEdit payload |
|
|
12
|
+
| `content_analysis.py` | Decides whether a payload is constants-only, and whether an Edit or MultiEdit merely removes or reorders imports |
|
|
13
|
+
| `candidate_paths.py` | Resolves the candidate test files whose freshness can satisfy the gate for a production file |
|
|
14
|
+
| `freshness.py` | Checks whether a candidate test was modified within the window and holds a real test function |
|
|
15
|
+
| `git_tracking.py` | Detects a write that restores a git-tracked file absent on disk, so a remove-then-Write rewrite is exempt from the gate |
|
|
16
|
+
| `decisions.py` | Builds the deny reason and writes the allow or deny decision JSON to stdout |
|
|
17
|
+
| `__init__.py` | Package marker |
|
|
18
|
+
|
|
19
|
+
## Subdirectories
|
|
20
|
+
|
|
21
|
+
| Entry | Description |
|
|
22
|
+
|---|---|
|
|
23
|
+
| `config/` | The freshness window, the ancestor-walk limit, the git-tracking command tokens, the source-file extension sets, and the join separator (`tdd_enforcer_constants.py`) |
|
|
24
|
+
| `tests/` | pytest suite with one test module per module above |
|
|
25
|
+
|
|
26
|
+
## Running tests
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
python -m pytest packages/claude-dev-env/hooks/blocking/tdd_enforcer_parts/tests/
|
|
30
|
+
```
|
|
@@ -1,30 +1 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
The concern modules `tdd_enforcer.py` wires together to run the TDD gate. Each
|
|
4
|
-
module owns one concern; the entry hook imports them and re-exports their
|
|
5
|
-
surface for the test suite.
|
|
6
|
-
|
|
7
|
-
## Modules
|
|
8
|
-
|
|
9
|
-
| File | Purpose |
|
|
10
|
-
|---|---|
|
|
11
|
-
| `path_classification.py` | Classifies a write target (docs, tests, `.claude` trees) the gate skips, and extracts the written text from a Write, Edit, or MultiEdit payload |
|
|
12
|
-
| `content_analysis.py` | Decides whether a payload is constants-only, and whether an Edit or MultiEdit merely removes or reorders imports |
|
|
13
|
-
| `candidate_paths.py` | Resolves the candidate test files whose freshness can satisfy the gate for a production file |
|
|
14
|
-
| `freshness.py` | Checks whether a candidate test was modified within the window and holds a real test function |
|
|
15
|
-
| `git_tracking.py` | Detects a write that restores a git-tracked file absent on disk, so a remove-then-Write rewrite is exempt from the gate |
|
|
16
|
-
| `decisions.py` | Builds the deny reason and writes the allow or deny decision JSON to stdout |
|
|
17
|
-
| `__init__.py` | Package marker |
|
|
18
|
-
|
|
19
|
-
## Subdirectories
|
|
20
|
-
|
|
21
|
-
| Entry | Description |
|
|
22
|
-
|---|---|
|
|
23
|
-
| `config/` | The freshness window, the ancestor-walk limit, the git-tracking command tokens, the source-file extension sets, and the join separator (`tdd_enforcer_constants.py`) |
|
|
24
|
-
| `tests/` | pytest suite with one test module per module above |
|
|
25
|
-
|
|
26
|
-
## Running tests
|
|
27
|
-
|
|
28
|
-
```bash
|
|
29
|
-
python -m pytest packages/claude-dev-env/hooks/blocking/tdd_enforcer_parts/tests/
|
|
30
|
-
```
|
|
1
|
+
@AGENTS.md
|
|
@@ -47,7 +47,7 @@ TABLE_WITH_SLASH_COMMAND_AND_SUBDIR = (
|
|
|
47
47
|
"# example\n\n"
|
|
48
48
|
"| Entry | Description |\n"
|
|
49
49
|
"|---|---|\n"
|
|
50
|
-
"| `/
|
|
50
|
+
"| `/sr-loop` | Slash command |\n"
|
|
51
51
|
"| `scripts/` | A subdirectory |\n"
|
|
52
52
|
"| Plain prose, no backticks | Not a file |\n"
|
|
53
53
|
)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"""Tests for check_banned_prefixes — flags function names with generic prefixes.
|
|
2
2
|
|
|
3
|
-
CODE_RULES.md §5
|
|
3
|
+
CODE_RULES.md §5 "Naming → Function names use specific verbs:
|
|
4
4
|
parse_invoice, dispatch_event, migrate_schema. Generic prefixes to replace:
|
|
5
5
|
handle_, process_, manage_, do_."
|
|
6
6
|
|