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
|
@@ -506,3 +506,174 @@ def test_dead_constant_stays_denied_on_every_attempt(neutral_root: Path) -> None
|
|
|
506
506
|
"re-issue this write" in each_issue for each_issue in each_attempt
|
|
507
507
|
), f"A dead constant stays denied with the retry escape, got: {each_attempt}"
|
|
508
508
|
|
|
509
|
+
|
|
510
|
+
ALIAS_CONSTANTS_BODY = 'BASE_PROMPTS_DIRECTORY_NAME = "prompts"\nSTYLE_SHEET_FILENAME = "style.css"\n'
|
|
511
|
+
|
|
512
|
+
ALIASED_IMPORT_CONSUMER_BODY = (
|
|
513
|
+
"from style_guide_gen_constants.config import constants as sg\n"
|
|
514
|
+
"\n"
|
|
515
|
+
"def prompts_directory() -> str:\n"
|
|
516
|
+
" return sg.BASE_PROMPTS_DIRECTORY_NAME\n"
|
|
517
|
+
"\n"
|
|
518
|
+
"def style_sheet() -> str:\n"
|
|
519
|
+
" return sg.STYLE_SHEET_FILENAME\n"
|
|
520
|
+
)
|
|
521
|
+
|
|
522
|
+
|
|
523
|
+
def _build_alias_repository(
|
|
524
|
+
package_parent: Path,
|
|
525
|
+
consumer_directory: Path,
|
|
526
|
+
consumer_body: str,
|
|
527
|
+
) -> Path:
|
|
528
|
+
package_directory = package_parent / "style_guide_gen_constants"
|
|
529
|
+
config_directory = package_directory / "config"
|
|
530
|
+
config_directory.mkdir(parents=True)
|
|
531
|
+
(package_directory / "__init__.py").write_text("", encoding="utf-8")
|
|
532
|
+
(config_directory / "__init__.py").write_text("", encoding="utf-8")
|
|
533
|
+
constants_path = config_directory / "constants.py"
|
|
534
|
+
constants_path.write_text(ALIAS_CONSTANTS_BODY, encoding="utf-8")
|
|
535
|
+
consumer_directory.mkdir(parents=True, exist_ok=True)
|
|
536
|
+
(consumer_directory / "style_guide_gen.py").write_text(consumer_body, encoding="utf-8")
|
|
537
|
+
return constants_path
|
|
538
|
+
|
|
539
|
+
|
|
540
|
+
def test_does_not_flag_constant_read_through_an_aliased_module_import(
|
|
541
|
+
neutral_root: Path,
|
|
542
|
+
) -> None:
|
|
543
|
+
constants_path = _build_alias_repository(
|
|
544
|
+
neutral_root,
|
|
545
|
+
neutral_root / "style_guide_gen_constants",
|
|
546
|
+
ALIASED_IMPORT_CONSUMER_BODY,
|
|
547
|
+
)
|
|
548
|
+
issues = _check(ALIAS_CONSTANTS_BODY, str(constants_path))
|
|
549
|
+
assert issues == [], (
|
|
550
|
+
f"A constant read as an attribute on an aliased module import stays live, got: {issues}"
|
|
551
|
+
)
|
|
552
|
+
|
|
553
|
+
|
|
554
|
+
def test_does_not_flag_constant_read_through_an_alias_outside_the_package_tree(
|
|
555
|
+
neutral_root: Path,
|
|
556
|
+
) -> None:
|
|
557
|
+
constants_path = _build_alias_repository(
|
|
558
|
+
neutral_root / "scripts",
|
|
559
|
+
neutral_root / "scripts",
|
|
560
|
+
ALIASED_IMPORT_CONSUMER_BODY,
|
|
561
|
+
)
|
|
562
|
+
issues = _check(ALIAS_CONSTANTS_BODY, str(constants_path))
|
|
563
|
+
assert issues == [], (
|
|
564
|
+
"A constant an aliased module import reads from outside the package tree "
|
|
565
|
+
f"stays live through the widened scan, got: {issues}"
|
|
566
|
+
)
|
|
567
|
+
|
|
568
|
+
|
|
569
|
+
def test_does_not_flag_constant_read_through_an_unaliased_module_import_outside_the_tree(
|
|
570
|
+
neutral_root: Path,
|
|
571
|
+
) -> None:
|
|
572
|
+
consumer_body = (
|
|
573
|
+
"from style_guide_gen_constants.config import constants\n"
|
|
574
|
+
"\n"
|
|
575
|
+
"def prompts_directory() -> str:\n"
|
|
576
|
+
" return constants.BASE_PROMPTS_DIRECTORY_NAME\n"
|
|
577
|
+
"\n"
|
|
578
|
+
"def style_sheet() -> str:\n"
|
|
579
|
+
" return constants.STYLE_SHEET_FILENAME\n"
|
|
580
|
+
)
|
|
581
|
+
constants_path = _build_alias_repository(
|
|
582
|
+
neutral_root / "scripts",
|
|
583
|
+
neutral_root / "scripts",
|
|
584
|
+
consumer_body,
|
|
585
|
+
)
|
|
586
|
+
issues = _check(ALIAS_CONSTANTS_BODY, str(constants_path))
|
|
587
|
+
assert issues == [], (
|
|
588
|
+
f"A constant read on an unaliased imported module object stays live, got: {issues}"
|
|
589
|
+
)
|
|
590
|
+
|
|
591
|
+
|
|
592
|
+
def test_does_not_flag_constant_read_through_a_dotted_module_path_outside_the_tree(
|
|
593
|
+
neutral_root: Path,
|
|
594
|
+
) -> None:
|
|
595
|
+
consumer_body = (
|
|
596
|
+
"import style_guide_gen_constants.config.constants\n"
|
|
597
|
+
"\n"
|
|
598
|
+
"def prompts_directory() -> str:\n"
|
|
599
|
+
" return style_guide_gen_constants.config.constants.BASE_PROMPTS_DIRECTORY_NAME\n"
|
|
600
|
+
"\n"
|
|
601
|
+
"def style_sheet() -> str:\n"
|
|
602
|
+
" return style_guide_gen_constants.config.constants.STYLE_SHEET_FILENAME\n"
|
|
603
|
+
)
|
|
604
|
+
constants_path = _build_alias_repository(
|
|
605
|
+
neutral_root / "scripts",
|
|
606
|
+
neutral_root / "scripts",
|
|
607
|
+
consumer_body,
|
|
608
|
+
)
|
|
609
|
+
issues = _check(ALIAS_CONSTANTS_BODY, str(constants_path))
|
|
610
|
+
assert issues == [], (
|
|
611
|
+
f"A constant read through the full dotted module path stays live, got: {issues}"
|
|
612
|
+
)
|
|
613
|
+
|
|
614
|
+
|
|
615
|
+
def test_does_not_flag_constant_read_through_an_alias_in_a_sibling_tree(
|
|
616
|
+
neutral_root: Path,
|
|
617
|
+
) -> None:
|
|
618
|
+
constants_body = 'CROSS_TREE_CONSTANT = "cross"\nLOCALLY_DEAD_CONSTANT = "dead"\n'
|
|
619
|
+
sibling_consumer_body = (
|
|
620
|
+
"from shared.theme_db.config import constants as theme_constants\n"
|
|
621
|
+
"\n"
|
|
622
|
+
"def tally() -> str:\n"
|
|
623
|
+
" return theme_constants.CROSS_TREE_CONSTANT\n"
|
|
624
|
+
)
|
|
625
|
+
constants_path = _build_cross_tree_repository(
|
|
626
|
+
neutral_root, constants_body, sibling_consumer_body
|
|
627
|
+
)
|
|
628
|
+
issues = _check(constants_body, str(constants_path))
|
|
629
|
+
assert not any("CROSS_TREE_CONSTANT" in each_issue for each_issue in issues), (
|
|
630
|
+
"A constant a sibling tree reads as an attribute on an aliased module import "
|
|
631
|
+
f"must not be flagged, got: {issues}"
|
|
632
|
+
)
|
|
633
|
+
assert any("LOCALLY_DEAD_CONSTANT" in each_issue for each_issue in issues), (
|
|
634
|
+
f"A constant referenced nowhere in the repository stays flagged, got: {issues}"
|
|
635
|
+
)
|
|
636
|
+
|
|
637
|
+
|
|
638
|
+
def _build_alias_collision_repository(
|
|
639
|
+
repository_root: Path,
|
|
640
|
+
promoter_constants_body: str,
|
|
641
|
+
harness_constants_body: str,
|
|
642
|
+
harness_consumer_body: str,
|
|
643
|
+
) -> Path:
|
|
644
|
+
promoter_config = repository_root / "promoter" / "config"
|
|
645
|
+
promoter_config.mkdir(parents=True)
|
|
646
|
+
promoter_constants_path = promoter_config / "constants.py"
|
|
647
|
+
promoter_constants_path.write_text(promoter_constants_body, encoding="utf-8")
|
|
648
|
+
harness_config = repository_root / "harness" / "config"
|
|
649
|
+
harness_config.mkdir(parents=True)
|
|
650
|
+
(harness_config / "other_constants.py").write_text(harness_constants_body, encoding="utf-8")
|
|
651
|
+
(repository_root / "harness" / "lockfile.py").write_text(
|
|
652
|
+
harness_consumer_body, encoding="utf-8"
|
|
653
|
+
)
|
|
654
|
+
return promoter_constants_path
|
|
655
|
+
|
|
656
|
+
|
|
657
|
+
def test_alias_attribute_read_on_an_unrelated_module_does_not_mask_a_dead_constant(
|
|
658
|
+
neutral_root: Path,
|
|
659
|
+
) -> None:
|
|
660
|
+
promoter_constants_body = 'NEVER_READ_CONSTANT = "dead"\n'
|
|
661
|
+
harness_constants_body = 'NEVER_READ_CONSTANT = "twin"\n'
|
|
662
|
+
harness_consumer_body = (
|
|
663
|
+
"from harness.config import other_constants as oc\n"
|
|
664
|
+
"\n"
|
|
665
|
+
"def acquire() -> str:\n"
|
|
666
|
+
" return oc.NEVER_READ_CONSTANT\n"
|
|
667
|
+
)
|
|
668
|
+
promoter_constants_path = _build_alias_collision_repository(
|
|
669
|
+
neutral_root,
|
|
670
|
+
promoter_constants_body,
|
|
671
|
+
harness_constants_body,
|
|
672
|
+
harness_consumer_body,
|
|
673
|
+
)
|
|
674
|
+
issues = _check(promoter_constants_body, str(promoter_constants_path))
|
|
675
|
+
assert any("NEVER_READ_CONSTANT" in each_issue for each_issue in issues), (
|
|
676
|
+
"A constant whose only same-named twin is read as an attribute on an "
|
|
677
|
+
f"unrelated aliased module must still be flagged dead, got: {issues}"
|
|
678
|
+
)
|
|
679
|
+
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"""Tests for magic-value allowlist alignment with CODE_RULES §HOOK-ENFORCED.
|
|
2
2
|
|
|
3
|
-
CODE_RULES.md
|
|
3
|
+
CODE_RULES.md states that only
|
|
4
4
|
0, 1, and -1 (plus their float forms 0.0, 1.0) are exempt from the
|
|
5
5
|
magic-value check. Prior to this change, the hook silently allowed 2
|
|
6
6
|
and 100 as well, making the hook more permissive than the written rule.
|
|
@@ -422,7 +422,8 @@ def test_main_logs_high_level_failure_when_restore_switch_fails(
|
|
|
422
422
|
|
|
423
423
|
assert exit_code == 0
|
|
424
424
|
assert state_file.exists()
|
|
425
|
-
assert "[gh-pr-author-restore]
|
|
425
|
+
assert "[gh-pr-author-restore] Restore the gh account" in captured_streams.err
|
|
426
|
+
assert "gh account switch requires attention" in captured_streams.err
|
|
426
427
|
assert "'jl-cmd'" in captured_streams.err
|
|
427
428
|
assert str(state_file) in captured_streams.err
|
|
428
429
|
|
|
@@ -111,6 +111,10 @@ def test_intent_ending_message_emits_block_with_short_user_notice():
|
|
|
111
111
|
assert parsed_response["systemMessage"] == USER_FACING_INTENT_ENDING_NOTICE
|
|
112
112
|
assert parsed_response["suppressOutput"] is True
|
|
113
113
|
assert "long-horizon-autonomy" in parsed_response["reason"]
|
|
114
|
+
assert "This turn ends with a promise of future work." in parsed_response["reason"]
|
|
115
|
+
assert "Complete that work with tool calls during this turn." in parsed_response[
|
|
116
|
+
"reason"
|
|
117
|
+
]
|
|
114
118
|
|
|
115
119
|
|
|
116
120
|
def test_next_steps_lead_in_emits_block():
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# hooks/diagnostic
|
|
2
|
+
|
|
3
|
+
Hooks and scripts that collect, store, and query hook-firing records. The pipeline reads session JSONL transcripts, extracts hook attachment records, and writes them as rows into a Neon (Postgres) `hook_events` table.
|
|
4
|
+
|
|
5
|
+
## Subdirectories
|
|
6
|
+
|
|
7
|
+
| Directory | Role |
|
|
8
|
+
|---|---|
|
|
9
|
+
| `migrations/` | SQL migration files for the `hook_events` schema |
|
|
10
|
+
| `queries/` | Parameterized SQL queries for inspecting blocked commands |
|
|
11
|
+
|
|
12
|
+
## Key files
|
|
13
|
+
|
|
14
|
+
| File | What it does |
|
|
15
|
+
|---|---|
|
|
16
|
+
| `hook_log_init.py` | One-time setup: creates the Neon schema (runs `schema.sql`), then verifies read-write parity with a sentinel round-trip |
|
|
17
|
+
| `hook_log_extractor.py` | Disabled CLI: exits 0 without reading transcripts or rewriting offset state; body remains for a re-enable path that ingests `hook_*` attachments into Neon |
|
|
18
|
+
| `hook_log_stop_wrapper.py` | Disabled Stop wrapper stub: exits 0; not on the Stop dispatcher roster |
|
|
19
|
+
| `schema.sql` | DDL for the `hook_events` table, `blocked_commands` view, and supporting indexes |
|
|
20
|
+
| `requirements-hook-logs.txt` | Runtime dependencies (`psycopg`) for the extractor |
|
|
21
|
+
| `requirements-hook-logs-dev.txt` | Dev/test dependencies |
|
|
22
|
+
| `test_hook_log_extractor.py` | Tests for the extractor |
|
|
23
|
+
| `test_hook_log_init.py` | Tests for the schema-init script |
|
|
24
|
+
| `test_hook_log_stop_wrapper.py` | Tests for the Stop wrapper |
|
|
25
|
+
|
|
26
|
+
## Schema overview (`schema.sql`)
|
|
27
|
+
|
|
28
|
+
The `hook_events` table captures one row per hook firing:
|
|
29
|
+
|
|
30
|
+
- `hook_event`, `hook_name`, `hook_category` — what fired
|
|
31
|
+
- `outcome` — `allowed`, `blocked`, or `ask`
|
|
32
|
+
- `tool_name`, `command_excerpt` — what tool was called
|
|
33
|
+
- `session_id`, `git_branch`, `cwd` — context
|
|
34
|
+
- `duration_ms`, `exit_code` — timing and result
|
|
35
|
+
- `source_jsonl_path`, `source_line_number` — idempotency key
|
|
36
|
+
|
|
37
|
+
The `blocked_commands` view filters to `outcome = 'blocked'`.
|
|
38
|
+
|
|
39
|
+
## Conventions
|
|
40
|
+
|
|
41
|
+
- Extractor and Stop wrapper mains are disabled and exit 0 with no work.
|
|
42
|
+
- Constants for the extractor (table name, offset state file, timeout) live in `hooks_constants/hook_log_extractor_constants.py`.
|
|
43
|
+
- Tests run with `python -m pytest diagnostic/test_hook_log_*.py`.
|
|
44
|
+
|
|
45
|
+
## Schema init
|
|
46
|
+
|
|
47
|
+
Run `hook_log_init.py` once per machine, or after rotating the Neon project.
|
|
48
|
+
|
|
49
|
+
Prerequisites: Bitwarden Secrets Manager CLI (`bws`) on PATH; a machine-account
|
|
50
|
+
token in `BWS_ACCESS_TOKEN` for the user environment (`setx` on Windows, shell
|
|
51
|
+
profile on macOS/Linux); Neon connection string stored as
|
|
52
|
+
`NEON_HOOK_LOGS_DATABASE_URL`; Python deps from `requirements-hook-logs.txt`.
|
|
53
|
+
|
|
54
|
+
```
|
|
55
|
+
bws run -- python packages/claude-dev-env/hooks/diagnostic/hook_log_init.py
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
`bws run` strips `BWS_ACCESS_TOKEN` from the child environment so the Python
|
|
59
|
+
process never sees it. The script verifies `NEON_HOOK_LOGS_DATABASE_URL`,
|
|
60
|
+
connects with a 5-second timeout, applies `schema.sql` with idempotent DDL,
|
|
61
|
+
runs a sentinel insert/select/delete round-trip, and prints the Neon host,
|
|
62
|
+
table name, and row count.
|
|
63
|
+
|
|
64
|
+
## Operator CLI flags
|
|
65
|
+
|
|
66
|
+
`hook_log_extractor.py` and `hook_log_stop_wrapper.py` mains exit 0 with no work.
|
|
67
|
+
The extractor body still documents these flags for a re-enable path:
|
|
68
|
+
|
|
69
|
+
- default / `--incremental`: resume from `~/.claude/logs/hooks/.state/offsets.json`
|
|
70
|
+
- `--full-rebuild`: clear offsets, truncate `hook_events`, re-read every JSONL
|
|
71
|
+
- `--summary`: print the top-10 blockers of the last 24 hours
|
|
72
|
+
- `--query <name>`: run `queries/<name>.sql` (`top_blockers_overall`,
|
|
73
|
+
`top_blockers_last_24_hours`, `blocks_last_7_days`, `blocks_by_category`,
|
|
74
|
+
`blocks_by_tool`, `block_details_for_hook`)
|
|
75
|
+
|
|
@@ -1,43 +1 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
Hooks and scripts that collect, store, and query hook-firing records. The pipeline reads session JSONL transcripts, extracts hook attachment records, and writes them as rows into a Neon (Postgres) `hook_events` table.
|
|
4
|
-
|
|
5
|
-
## Subdirectories
|
|
6
|
-
|
|
7
|
-
| Directory | Role |
|
|
8
|
-
|---|---|
|
|
9
|
-
| `migrations/` | SQL migration files for the `hook_events` schema |
|
|
10
|
-
| `queries/` | Parameterized SQL queries for inspecting blocked commands |
|
|
11
|
-
|
|
12
|
-
## Key files
|
|
13
|
-
|
|
14
|
-
| File | What it does |
|
|
15
|
-
|---|---|
|
|
16
|
-
| `hook_log_init.py` | One-time setup: creates the Neon schema (runs `schema.sql`), then verifies read-write parity with a sentinel round-trip |
|
|
17
|
-
| `hook_log_extractor.py` | Disabled CLI: exits 0 without reading transcripts or rewriting offset state; body remains for a re-enable path that ingests `hook_*` attachments into Neon |
|
|
18
|
-
| `hook_log_stop_wrapper.py` | Disabled Stop wrapper stub: exits 0; not on the Stop dispatcher roster |
|
|
19
|
-
| `schema.sql` | DDL for the `hook_events` table, `blocked_commands` view, and supporting indexes |
|
|
20
|
-
| `requirements-hook-logs.txt` | Runtime dependencies (`psycopg`) for the extractor |
|
|
21
|
-
| `requirements-hook-logs-dev.txt` | Dev/test dependencies |
|
|
22
|
-
| `test_hook_log_extractor.py` | Tests for the extractor |
|
|
23
|
-
| `test_hook_log_init.py` | Tests for the schema-init script |
|
|
24
|
-
| `test_hook_log_stop_wrapper.py` | Tests for the Stop wrapper |
|
|
25
|
-
|
|
26
|
-
## Schema overview (`schema.sql`)
|
|
27
|
-
|
|
28
|
-
The `hook_events` table captures one row per hook firing:
|
|
29
|
-
|
|
30
|
-
- `hook_event`, `hook_name`, `hook_category` — what fired
|
|
31
|
-
- `outcome` — `allowed`, `blocked`, or `ask`
|
|
32
|
-
- `tool_name`, `command_excerpt` — what tool was called
|
|
33
|
-
- `session_id`, `git_branch`, `cwd` — context
|
|
34
|
-
- `duration_ms`, `exit_code` — timing and result
|
|
35
|
-
- `source_jsonl_path`, `source_line_number` — idempotency key
|
|
36
|
-
|
|
37
|
-
The `blocked_commands` view filters to `outcome = 'blocked'`.
|
|
38
|
-
|
|
39
|
-
## Conventions
|
|
40
|
-
|
|
41
|
-
- Extractor and Stop wrapper mains are disabled and exit 0 with no work.
|
|
42
|
-
- Constants for the extractor (table name, offset state file, timeout) live in `hooks_constants/hook_log_extractor_constants.py`.
|
|
43
|
-
- Tests run with `python -m pytest diagnostic/test_hook_log_*.py`.
|
|
1
|
+
@AGENTS.md
|
|
@@ -99,7 +99,7 @@ from hooks_constants.hook_log_extractor_constants import (
|
|
|
99
99
|
STDERR_EXCERPT_MAX_CHARACTERS,
|
|
100
100
|
STDOUT_EXCERPT_MAX_CHARACTERS,
|
|
101
101
|
SUMMARY_COLUMN_HEADINGS,
|
|
102
|
-
|
|
102
|
+
SUMMARY_ZERO_BLOCKED_EVENTS_MESSAGE,
|
|
103
103
|
SUMMARY_TABLE_COLUMN_GAP,
|
|
104
104
|
TOP_BLOCKED_COMMAND_PREVIEW_MAX_CHARACTERS,
|
|
105
105
|
TOP_BLOCKERS_LAST_24_HOURS_SQL,
|
|
@@ -780,7 +780,7 @@ def run_summary() -> int:
|
|
|
780
780
|
except Exception:
|
|
781
781
|
pass
|
|
782
782
|
if not all_result_rows:
|
|
783
|
-
print(
|
|
783
|
+
print(SUMMARY_ZERO_BLOCKED_EVENTS_MESSAGE)
|
|
784
784
|
return EXIT_CODE_SUCCESS
|
|
785
785
|
_print_summary_table(all_result_rows)
|
|
786
786
|
return EXIT_CODE_SUCCESS
|
|
@@ -66,7 +66,7 @@ def verify_environment_variables() -> list[str]:
|
|
|
66
66
|
child process invoked via ``bws run -- python hook_log_init.py``
|
|
67
67
|
would therefore always fail even when the machine is configured
|
|
68
68
|
correctly. The one-time ``setx BWS_ACCESS_TOKEN`` prerequisite is
|
|
69
|
-
documented in ``packages/claude-dev-env/
|
|
69
|
+
documented in ``packages/claude-dev-env/hooks/diagnostic/AGENTS.md``.
|
|
70
70
|
"""
|
|
71
71
|
all_missing_variable_names: list[str] = []
|
|
72
72
|
raw_database_url_value = os.environ.get(NEON_DATABASE_URL_ENVIRONMENT_VARIABLE)
|
|
@@ -172,7 +172,7 @@ def _print_missing_environment_variables(all_missing_variable_names: list[str])
|
|
|
172
172
|
|
|
173
173
|
|
|
174
174
|
def main() -> int:
|
|
175
|
-
"""
|
|
175
|
+
"""CLI entry for one-time Neon schema init."""
|
|
176
176
|
all_missing_variable_names = verify_environment_variables()
|
|
177
177
|
if all_missing_variable_names:
|
|
178
178
|
_print_missing_environment_variables(all_missing_variable_names)
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# hooks/diagnostic/migrations
|
|
2
|
+
|
|
3
|
+
SQL migration files for the `hook_events` Neon schema. Each file applies a schema change to the `hook_events` table or its indexes.
|
|
4
|
+
|
|
5
|
+
## Files
|
|
6
|
+
|
|
7
|
+
| File | What it does |
|
|
8
|
+
|---|---|
|
|
9
|
+
| `2026-04-25-drop-themes-hook-events.sql` | Drops the `themes` hook-events table variant |
|
|
10
|
+
| `README.md` | Notes on the migration approach |
|
|
11
|
+
|
|
12
|
+
## Conventions
|
|
13
|
+
|
|
14
|
+
- Run migrations manually against the Neon database using `psql` or the Neon console.
|
|
15
|
+
- The baseline schema lives in `diagnostic/schema.sql`.
|
|
16
|
+
- File names follow `YYYY-MM-DD-<description>.sql` for chronological ordering.
|
|
@@ -1,16 +1 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
SQL migration files for the `hook_events` Neon schema. Each file applies a schema change to the `hook_events` table or its indexes.
|
|
4
|
-
|
|
5
|
-
## Files
|
|
6
|
-
|
|
7
|
-
| File | What it does |
|
|
8
|
-
|---|---|
|
|
9
|
-
| `2026-04-25-drop-themes-hook-events.sql` | Drops the `themes` hook-events table variant |
|
|
10
|
-
| `README.md` | Notes on the migration approach |
|
|
11
|
-
|
|
12
|
-
## Conventions
|
|
13
|
-
|
|
14
|
-
- Run migrations manually against the Neon database using `psql` or the Neon console.
|
|
15
|
-
- The baseline schema lives in `diagnostic/schema.sql`.
|
|
16
|
-
- File names follow `YYYY-MM-DD-<description>.sql` for chronological ordering.
|
|
1
|
+
@AGENTS.md
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# hooks/diagnostic/queries
|
|
2
|
+
|
|
3
|
+
Parameterized SQL queries for inspecting the `hook_events` Neon table. Run these directly against the Neon database to analyze hook-firing patterns.
|
|
4
|
+
|
|
5
|
+
## Files
|
|
6
|
+
|
|
7
|
+
| File | What it returns |
|
|
8
|
+
|---|---|
|
|
9
|
+
| `block_details_for_hook.sql` | Full details for all blocked events matching a given hook name |
|
|
10
|
+
| `blocks_by_category.sql` | Count of blocks grouped by hook category |
|
|
11
|
+
| `blocks_by_tool.sql` | Count of blocks grouped by tool name |
|
|
12
|
+
| `blocks_last_7_days.sql` | All blocked events from the last 7 days |
|
|
13
|
+
| `top_blockers_last_24_hours.sql` | Hook names with the most blocks in the last 24 hours |
|
|
14
|
+
| `top_blockers_overall.sql` | Hook names with the most blocks across all time |
|
|
15
|
+
|
|
16
|
+
## Conventions
|
|
17
|
+
|
|
18
|
+
- Queries target the `hook_events` table and `blocked_commands` view defined in `diagnostic/schema.sql`.
|
|
19
|
+
- Run with `psql $DATABASE_URL -f <query>.sql` or paste into the Neon console.
|
|
@@ -1,19 +1 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
Parameterized SQL queries for inspecting the `hook_events` Neon table. Run these directly against the Neon database to analyze hook-firing patterns.
|
|
4
|
-
|
|
5
|
-
## Files
|
|
6
|
-
|
|
7
|
-
| File | What it returns |
|
|
8
|
-
|---|---|
|
|
9
|
-
| `block_details_for_hook.sql` | Full details for all blocked events matching a given hook name |
|
|
10
|
-
| `blocks_by_category.sql` | Count of blocks grouped by hook category |
|
|
11
|
-
| `blocks_by_tool.sql` | Count of blocks grouped by tool name |
|
|
12
|
-
| `blocks_last_7_days.sql` | All blocked events from the last 7 days |
|
|
13
|
-
| `top_blockers_last_24_hours.sql` | Hook names with the most blocks in the last 24 hours |
|
|
14
|
-
| `top_blockers_overall.sql` | Hook names with the most blocks across all time |
|
|
15
|
-
|
|
16
|
-
## Conventions
|
|
17
|
-
|
|
18
|
-
- Queries target the `hook_events` table and `blocked_commands` view defined in `diagnostic/schema.sql`.
|
|
19
|
-
- Run with `psql $DATABASE_URL -f <query>.sql` or paste into the Neon console.
|
|
1
|
+
@AGENTS.md
|
|
@@ -617,7 +617,7 @@ def test_tool_name_extracted_from_hook_name_prefix() -> None:
|
|
|
617
617
|
assert hook_log_extractor.extract_tool_name("UserPromptSubmit") is None
|
|
618
618
|
|
|
619
619
|
|
|
620
|
-
def
|
|
620
|
+
def test_run_summary_prints_zero_blocked_events_when_cursor_empty(
|
|
621
621
|
capsys: pytest.CaptureFixture[str],
|
|
622
622
|
) -> None:
|
|
623
623
|
fake_cursor = MagicMock()
|
|
@@ -632,7 +632,7 @@ def test_run_summary_prints_no_new_blocks_when_cursor_empty(
|
|
|
632
632
|
|
|
633
633
|
captured = capsys.readouterr()
|
|
634
634
|
assert exit_code == 0
|
|
635
|
-
assert "
|
|
635
|
+
assert "Blocked events recorded in the last 24 hours: 0." in captured.out
|
|
636
636
|
|
|
637
637
|
|
|
638
638
|
def test_run_summary_prints_table_when_rows_returned(
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# hooks/git-hooks
|
|
2
|
+
|
|
3
|
+
Native git hooks that run outside the Claude Code lifecycle — invoked directly by git at commit and push time. The installer copies these scripts into the user's shared git-hooks directory (`core.hooksPath`).
|
|
4
|
+
|
|
5
|
+
## Key files
|
|
6
|
+
|
|
7
|
+
| File | Git hook | What it does |
|
|
8
|
+
|---|---|---|
|
|
9
|
+
| `pre_commit.py` | `pre-commit` | Runs the CODE_RULES gate (`precommit_code_rules_gate.py`) over staged changes; exits 1 when any staged file has a blocking violation |
|
|
10
|
+
| `pre_push.py` | `pre-push` | Blocks a push that would land a non-`main` local branch onto remote `main` (or `master`), then runs the CODE_RULES gate. An existing branch's gate base is the merge base with the remote default branch; the gate process still diffs that base against checkout HEAD, so the surface matches the pushed tip only when HEAD is that tip. |
|
|
11
|
+
| `pre_push_base_reference.py` | — | Resolves a usable gate base for `pre_push.py`: reads the pushed remote name from git's arguments, then turns a symbolic default-branch head into a reference that git can resolve |
|
|
12
|
+
| `post_commit.py` | `post-commit` | Runs after a commit lands; performs any post-commit bookkeeping |
|
|
13
|
+
| `gate_utils.py` | — | Shared helpers: resolves the gate script path, checks that the path is a safe regular file |
|
|
14
|
+
| `test_config.py` | — | Test configuration helpers |
|
|
15
|
+
| `test_gate_utils.py` | — | Tests for `gate_utils.py` |
|
|
16
|
+
| `test_pre_commit.py` | — | Tests for `pre_commit.py` |
|
|
17
|
+
| `test_pre_push.py` | — | Tests for `pre_push.py` |
|
|
18
|
+
| `test_pre_push_base_reference.py` | — | Tests for the gate base resolution in `pre_push.py` |
|
|
19
|
+
| `conftest.py` | — | Puts this directory on `sys.path` and clears cached `config` modules, so each test module imports the hooks at the top of the file |
|
|
20
|
+
|
|
21
|
+
## Subdirectory
|
|
22
|
+
|
|
23
|
+
| Directory | Role |
|
|
24
|
+
|---|---|
|
|
25
|
+
| `git_hooks_constants/` | Shared constants imported by the git-hook scripts |
|
|
26
|
+
|
|
27
|
+
## Conventions
|
|
28
|
+
|
|
29
|
+
- The installer strips the `_` and `.py` suffix when copying into the live git-hooks path (e.g. `pre_commit.py` becomes `pre-commit`).
|
|
30
|
+
- Constants (exit codes, argument names, error messages) live in `git_hooks_constants/` and are imported at the top of each script.
|
|
31
|
+
- Run tests with `python -m pytest git-hooks/test_<name>.py`.
|
|
@@ -1,31 +1 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
Native git hooks that run outside the Claude Code lifecycle — invoked directly by git at commit and push time. The installer copies these scripts into the user's shared git-hooks directory (`core.hooksPath`).
|
|
4
|
-
|
|
5
|
-
## Key files
|
|
6
|
-
|
|
7
|
-
| File | Git hook | What it does |
|
|
8
|
-
|---|---|---|
|
|
9
|
-
| `pre_commit.py` | `pre-commit` | Runs the CODE_RULES gate (`precommit_code_rules_gate.py`) over staged changes; exits 1 when any staged file has a blocking violation |
|
|
10
|
-
| `pre_push.py` | `pre-push` | Blocks a push that would land a non-`main` local branch onto remote `main` (or `master`), then runs the CODE_RULES gate. An existing branch's gate base is the merge base with the remote default branch; the gate process still diffs that base against checkout HEAD, so the surface matches the pushed tip only when HEAD is that tip. |
|
|
11
|
-
| `pre_push_base_reference.py` | — | Resolves a usable gate base for `pre_push.py`: reads the pushed remote name from git's arguments, then turns a symbolic default-branch head into a reference that git can resolve |
|
|
12
|
-
| `post_commit.py` | `post-commit` | Runs after a commit lands; performs any post-commit bookkeeping |
|
|
13
|
-
| `gate_utils.py` | — | Shared helpers: resolves the gate script path, checks that the path is a safe regular file |
|
|
14
|
-
| `test_config.py` | — | Test configuration helpers |
|
|
15
|
-
| `test_gate_utils.py` | — | Tests for `gate_utils.py` |
|
|
16
|
-
| `test_pre_commit.py` | — | Tests for `pre_commit.py` |
|
|
17
|
-
| `test_pre_push.py` | — | Tests for `pre_push.py` |
|
|
18
|
-
| `test_pre_push_base_reference.py` | — | Tests for the gate base resolution in `pre_push.py` |
|
|
19
|
-
| `conftest.py` | — | Puts this directory on `sys.path` and clears cached `config` modules, so each test module imports the hooks at the top of the file |
|
|
20
|
-
|
|
21
|
-
## Subdirectory
|
|
22
|
-
|
|
23
|
-
| Directory | Role |
|
|
24
|
-
|---|---|
|
|
25
|
-
| `git_hooks_constants/` | Shared constants imported by the git-hook scripts |
|
|
26
|
-
|
|
27
|
-
## Conventions
|
|
28
|
-
|
|
29
|
-
- The installer strips the `_` and `.py` suffix when copying into the live git-hooks path (e.g. `pre_commit.py` becomes `pre-commit`).
|
|
30
|
-
- Constants (exit codes, argument names, error messages) live in `git_hooks_constants/` and are imported at the top of each script.
|
|
31
|
-
- Run tests with `python -m pytest git-hooks/test_<name>.py`.
|
|
1
|
+
@AGENTS.md
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# hooks/git-hooks/git_hooks_constants
|
|
2
|
+
|
|
3
|
+
Shared constants imported by the git-hook scripts in `git-hooks/`. Centralizes exit codes, argument names, and error messages so every tunable lives in one place.
|
|
4
|
+
|
|
5
|
+
## Files
|
|
6
|
+
|
|
7
|
+
| File | Contents |
|
|
8
|
+
|---|---|
|
|
9
|
+
| `__init__.py` | Exports all constants; marks this as a package so `from git_hooks_constants import ...` resolves |
|
|
10
|
+
|
|
11
|
+
## Key constants (defined in `__init__.py`)
|
|
12
|
+
|
|
13
|
+
- `GATE_INFRASTRUCTURE_FAILURE_EXIT_CODE` — exit code when the gate script cannot be found or launched
|
|
14
|
+
- `GATE_SCRIPT_NOT_FOUND_MESSAGE` — error message when the gate script path does not exist
|
|
15
|
+
- `INVOKE_GATE_FAILURE_MESSAGE` — error message when the gate subprocess fails to start
|
|
16
|
+
- `STAGED_SCOPE_ARGUMENT` — CLI argument passed to the gate script to scope it to staged changes
|
|
17
|
+
|
|
18
|
+
## Conventions
|
|
19
|
+
|
|
20
|
+
- Import with `from git_hooks_constants import <CONSTANT>` from within the `git-hooks/` directory.
|
|
21
|
+
- Add new constants here rather than inline in the hook scripts.
|
|
@@ -1,21 +1 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
Shared constants imported by the git-hook scripts in `git-hooks/`. Centralizes exit codes, argument names, and error messages so every tunable lives in one place.
|
|
4
|
-
|
|
5
|
-
## Files
|
|
6
|
-
|
|
7
|
-
| File | Contents |
|
|
8
|
-
|---|---|
|
|
9
|
-
| `__init__.py` | Exports all constants; marks this as a package so `from git_hooks_constants import ...` resolves |
|
|
10
|
-
|
|
11
|
-
## Key constants (defined in `__init__.py`)
|
|
12
|
-
|
|
13
|
-
- `GATE_INFRASTRUCTURE_FAILURE_EXIT_CODE` — exit code when the gate script cannot be found or launched
|
|
14
|
-
- `GATE_SCRIPT_NOT_FOUND_MESSAGE` — error message when the gate script path does not exist
|
|
15
|
-
- `INVOKE_GATE_FAILURE_MESSAGE` — error message when the gate subprocess fails to start
|
|
16
|
-
- `STAGED_SCOPE_ARGUMENT` — CLI argument passed to the gate script to scope it to staged changes
|
|
17
|
-
|
|
18
|
-
## Conventions
|
|
19
|
-
|
|
20
|
-
- Import with `from git_hooks_constants import <CONSTANT>` from within the `git-hooks/` directory.
|
|
21
|
-
- Add new constants here rather than inline in the hook scripts.
|
|
1
|
+
@AGENTS.md
|
|
@@ -49,12 +49,9 @@ NO_PARSEABLE_STDIN_LINES_MESSAGE: str = (
|
|
|
49
49
|
NO_PARSEABLE_STDIN_LINES_SENTINEL: str = "__no_parseable_stdin_lines__"
|
|
50
50
|
UNRESOLVABLE_MERGE_BASE_SENTINEL: str = "__unresolvable_merge_base__"
|
|
51
51
|
UNRESOLVABLE_MERGE_BASE_MESSAGE: str = (
|
|
52
|
-
"claude-dev-env pre-push:
|
|
53
|
-
"and
|
|
54
|
-
"
|
|
55
|
-
"default branch hold unrelated histories, or when a default-branch ref "
|
|
56
|
-
"resolved and merge-base still could not name a shared commit "
|
|
57
|
-
"(for example a shallow clone missing the connecting history)."
|
|
52
|
+
"claude-dev-env pre-push: CODE_RULES validation is pending because the pushed "
|
|
53
|
+
"object and default branch have no shared merge base. Restore shared history "
|
|
54
|
+
"or provide a reachable default branch, then run the push again."
|
|
58
55
|
)
|
|
59
56
|
LOCAL_BRANCH_REFERENCE_PREFIX: str = "refs/heads/"
|
|
60
57
|
ORIGIN_HEAD_SYMBOLIC_REFERENCE: str = "refs/remotes/origin/HEAD"
|
|
@@ -63,6 +60,7 @@ ALL_DEFAULT_BRANCH_FALLBACK_REFERENCES: tuple[str, ...] = (
|
|
|
63
60
|
"refs/remotes/origin/main",
|
|
64
61
|
"refs/remotes/origin/master",
|
|
65
62
|
)
|
|
63
|
+
REMOTE_BRANCH_REFERENCE_TEMPLATE: str = "refs/remotes/{remote}/{branch}"
|
|
66
64
|
ALL_GIT_SYMBOLIC_REFERENCE_COMMAND_PREFIX: tuple[str, ...] = (
|
|
67
65
|
"git",
|
|
68
66
|
"symbolic-ref",
|