claude-dev-env 2.25.0 → 2.27.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/.agents/agents/clean-coder.md +95 -33
- package/.agents/agents/code-quality-agent.md +85 -24
- package/.agents/agents/pr-description-writer.md +2 -2
- package/.agents/agents/test_agent_frontmatter.py +626 -0
- package/.agents/skills/_shared/pr-loop/scripts/test_build_audit_prompt.py +47 -1
- package/.agents/skills/pr-cleanup/SKILL.md +1 -0
- package/.agents/skills/pr-plain-language-cleanup/SKILL.md +93 -0
- package/.agents/skills/pr-title-description/SKILL.md +92 -0
- package/.agents/skills/source-command-sr-loop/SKILL.md +12 -3
- package/bin/ever-shipped-skills.mjs +2 -0
- package/bin/install.agents-home.test.mjs +199 -8
- package/bin/install.mjs +15 -8
- package/bin/install.test.mjs +82 -2
- package/commands/sr-loop.md +10 -1
- package/docs/codex-compatibility.md +19 -0
- package/hooks/blocking/luna_fast_mode_gate.py +160 -0
- package/hooks/blocking/test_luna_fast_mode_gate.py +211 -0
- package/hooks/hooks.json +10 -5
- package/hooks/hooks_constants/AGENTS.md +1 -1
- package/hooks/hooks_constants/luna_fast_mode_gate_constants.py +45 -0
- package/hooks/hooks_constants/mypy_integration_constants.py +14 -3
- package/hooks/hooks_constants/session_start_injector.py +11 -4
- package/hooks/hooks_constants/test_session_start_injector.py +4 -2
- package/hooks/session/AGENTS.md +2 -2
- package/hooks/session/issue_tracker_session_starter.py +3 -2
- package/hooks/session/orchestrator_auto_starter.py +3 -2
- package/hooks/session/task_list_loop_starter.py +10 -1
- package/hooks/session/test_issue_tracker_session_starter.py +3 -1
- package/hooks/session/test_orchestrator_auto_starter.py +5 -3
- package/hooks/session/test_task_list_loop_starter.py +10 -8
- package/hooks/session/test_untracked_repo_detector.py +7 -5
- package/hooks/session/test_working_style_prompt.py +5 -3
- package/hooks/session/untracked_repo_detector.py +10 -1
- package/hooks/session/working_style_prompt.py +10 -1
- package/hooks/validators/AGENTS.md +2 -1
- package/hooks/validators/conftest.py +21 -4
- package/hooks/validators/mypy_integration.py +77 -16
- package/hooks/validators/run_all_validators.py +2 -35
- package/hooks/validators/system_temporary_roots.py +90 -0
- package/hooks/validators/test_directory_exemption_constants.py +1 -1
- package/hooks/validators/test_mypy_integration.py +169 -0
- package/hooks/validators/test_system_temporary_roots.py +93 -0
- package/package.json +1 -1
- package/scripts/codex_compat_materializer.py +4 -2
- package/scripts/tests/test_codex_compat_materializer.py +2 -2
|
@@ -79,6 +79,8 @@ import pytest
|
|
|
79
79
|
import yaml
|
|
80
80
|
|
|
81
81
|
ACCEPTED_FRONTMATTER_KEYS = frozenset({"name", "description", "tools", "color"})
|
|
82
|
+
CODE_QUALITY_AGENT_FILENAME = "code-quality-agent.md"
|
|
83
|
+
CODE_QUALITY_READ_ONLY_TOOLS = ("Read", "Grep", "Glob")
|
|
82
84
|
INSTRUCTION_ALIAS_FILENAMES = frozenset({"AGENTS.md", "CLAUDE.md"})
|
|
83
85
|
FRONTMATTER_FENCE_LINE = "---"
|
|
84
86
|
MATERIALIZER_MODULE_NAME = "codex_compat_materializer"
|
|
@@ -420,6 +422,301 @@ def _clean_coder_body() -> str:
|
|
|
420
422
|
return (Path(__file__).parent / "clean-coder.md").read_text(encoding="utf-8")
|
|
421
423
|
|
|
422
424
|
|
|
425
|
+
SOURCE_LINK_PATTERN = re.compile(
|
|
426
|
+
r"`(?P<installed><(?:managed-root|agents-home)>/[^`]+)` "
|
|
427
|
+
r"\(source fallback: `(?P<source>packages/claude-dev-env/[^`]+)`\)"
|
|
428
|
+
)
|
|
429
|
+
|
|
430
|
+
EXPECTED_SOURCE_LINK_PAIRS = {
|
|
431
|
+
"clean-coder.md": frozenset({
|
|
432
|
+
(
|
|
433
|
+
"<managed-root>/docs/CODE_RULES.md#5-no-abbreviations",
|
|
434
|
+
"packages/claude-dev-env/docs/CODE_RULES.md#5-no-abbreviations",
|
|
435
|
+
),
|
|
436
|
+
(
|
|
437
|
+
"<managed-root>/docs/CODE_RULES.md",
|
|
438
|
+
"packages/claude-dev-env/docs/CODE_RULES.md",
|
|
439
|
+
),
|
|
440
|
+
(
|
|
441
|
+
"<managed-root>/hooks/blocking/code_rules_enforcer.py",
|
|
442
|
+
"packages/claude-dev-env/hooks/blocking/code_rules_enforcer.py",
|
|
443
|
+
),
|
|
444
|
+
(
|
|
445
|
+
"<managed-root>/rules/code-standards.md",
|
|
446
|
+
"packages/claude-dev-env/rules/code-standards.md",
|
|
447
|
+
),
|
|
448
|
+
(
|
|
449
|
+
"<managed-root>/rules/file-global-constants.md",
|
|
450
|
+
"packages/claude-dev-env/rules/file-global-constants.md",
|
|
451
|
+
),
|
|
452
|
+
(
|
|
453
|
+
"<managed-root>/rules/windows-filesystem-safe.md",
|
|
454
|
+
"packages/claude-dev-env/rules/windows-filesystem-safe.md",
|
|
455
|
+
),
|
|
456
|
+
(
|
|
457
|
+
"<managed-root>/rules/gh-cli-conventions.md",
|
|
458
|
+
"packages/claude-dev-env/rules/gh-cli-conventions.md",
|
|
459
|
+
),
|
|
460
|
+
(
|
|
461
|
+
"<managed-root>/rules/plain-illustrative-docstrings.md",
|
|
462
|
+
"packages/claude-dev-env/rules/plain-illustrative-docstrings.md",
|
|
463
|
+
),
|
|
464
|
+
(
|
|
465
|
+
"<managed-root>/rules/testing.md",
|
|
466
|
+
"packages/claude-dev-env/rules/testing.md",
|
|
467
|
+
),
|
|
468
|
+
(
|
|
469
|
+
"<managed-root>/rules/anti-corollary-tests.md",
|
|
470
|
+
"packages/claude-dev-env/rules/anti-corollary-tests.md",
|
|
471
|
+
),
|
|
472
|
+
(
|
|
473
|
+
"<managed-root>/rules/paired-test-coverage.md",
|
|
474
|
+
"packages/claude-dev-env/rules/paired-test-coverage.md",
|
|
475
|
+
),
|
|
476
|
+
(
|
|
477
|
+
"<managed-root>/rules/bdd.md",
|
|
478
|
+
"packages/claude-dev-env/rules/bdd.md",
|
|
479
|
+
),
|
|
480
|
+
(
|
|
481
|
+
"<managed-root>/rules/ask-user-question-required.md",
|
|
482
|
+
"packages/claude-dev-env/rules/ask-user-question-required.md",
|
|
483
|
+
),
|
|
484
|
+
(
|
|
485
|
+
"<managed-root>/rules/verify-before-asking.md",
|
|
486
|
+
"packages/claude-dev-env/rules/verify-before-asking.md",
|
|
487
|
+
),
|
|
488
|
+
(
|
|
489
|
+
"<managed-root>/rules/filesystem-search.md",
|
|
490
|
+
"packages/claude-dev-env/rules/filesystem-search.md",
|
|
491
|
+
),
|
|
492
|
+
(
|
|
493
|
+
"<managed-root>/rules/shell-invocation.md",
|
|
494
|
+
"packages/claude-dev-env/rules/shell-invocation.md",
|
|
495
|
+
),
|
|
496
|
+
(
|
|
497
|
+
"<managed-root>/rules/verify-runtime-state.md",
|
|
498
|
+
"packages/claude-dev-env/rules/verify-runtime-state.md",
|
|
499
|
+
),
|
|
500
|
+
(
|
|
501
|
+
"<managed-root>/rules/doc-inventory-integrity.md",
|
|
502
|
+
"packages/claude-dev-env/rules/doc-inventory-integrity.md",
|
|
503
|
+
),
|
|
504
|
+
(
|
|
505
|
+
"<managed-root>/rules/docstring-prose-matches-implementation.md",
|
|
506
|
+
"packages/claude-dev-env/rules/docstring-prose-matches-implementation.md",
|
|
507
|
+
),
|
|
508
|
+
(
|
|
509
|
+
"<managed-root>/rules/durable-post-artifacts.md",
|
|
510
|
+
"packages/claude-dev-env/rules/durable-post-artifacts.md",
|
|
511
|
+
),
|
|
512
|
+
(
|
|
513
|
+
"<managed-root>/rules/failure-blast-radius.md",
|
|
514
|
+
"packages/claude-dev-env/rules/failure-blast-radius.md",
|
|
515
|
+
),
|
|
516
|
+
(
|
|
517
|
+
"<managed-root>/rules/git-workflow.md",
|
|
518
|
+
"packages/claude-dev-env/rules/git-workflow.md",
|
|
519
|
+
),
|
|
520
|
+
(
|
|
521
|
+
"<managed-root>/rules/re-stage-before-commit.md",
|
|
522
|
+
"packages/claude-dev-env/rules/re-stage-before-commit.md",
|
|
523
|
+
),
|
|
524
|
+
(
|
|
525
|
+
"<managed-root>/rules/agent-spawn-protocol.md",
|
|
526
|
+
"packages/claude-dev-env/rules/agent-spawn-protocol.md",
|
|
527
|
+
),
|
|
528
|
+
(
|
|
529
|
+
"<managed-root>/rules/workers-done-before-complete.md",
|
|
530
|
+
"packages/claude-dev-env/rules/workers-done-before-complete.md",
|
|
531
|
+
),
|
|
532
|
+
}),
|
|
533
|
+
"code-quality-agent.md": frozenset({
|
|
534
|
+
(
|
|
535
|
+
"<managed-root>/audit-rubrics/category_rubrics/category-a-api-contracts.md",
|
|
536
|
+
"packages/claude-dev-env/audit-rubrics/category_rubrics/category-a-api-contracts.md",
|
|
537
|
+
),
|
|
538
|
+
(
|
|
539
|
+
"<managed-root>/audit-rubrics/category_rubrics/category-b-selector-engine-compat.md",
|
|
540
|
+
"packages/claude-dev-env/audit-rubrics/category_rubrics/category-b-selector-engine-compat.md",
|
|
541
|
+
),
|
|
542
|
+
(
|
|
543
|
+
"<managed-root>/audit-rubrics/category_rubrics/category-c-resource-cleanup.md",
|
|
544
|
+
"packages/claude-dev-env/audit-rubrics/category_rubrics/category-c-resource-cleanup.md",
|
|
545
|
+
),
|
|
546
|
+
(
|
|
547
|
+
"<managed-root>/audit-rubrics/category_rubrics/category-d-scoping-and-ordering.md",
|
|
548
|
+
"packages/claude-dev-env/audit-rubrics/category_rubrics/category-d-scoping-and-ordering.md",
|
|
549
|
+
),
|
|
550
|
+
(
|
|
551
|
+
"<managed-root>/audit-rubrics/category_rubrics/category-e-dead-code.md",
|
|
552
|
+
"packages/claude-dev-env/audit-rubrics/category_rubrics/category-e-dead-code.md",
|
|
553
|
+
),
|
|
554
|
+
(
|
|
555
|
+
"<managed-root>/audit-rubrics/category_rubrics/category-f-silent-failures.md",
|
|
556
|
+
"packages/claude-dev-env/audit-rubrics/category_rubrics/category-f-silent-failures.md",
|
|
557
|
+
),
|
|
558
|
+
(
|
|
559
|
+
"<managed-root>/audit-rubrics/category_rubrics/category-g-bounds-and-overflow.md",
|
|
560
|
+
"packages/claude-dev-env/audit-rubrics/category_rubrics/category-g-bounds-and-overflow.md",
|
|
561
|
+
),
|
|
562
|
+
(
|
|
563
|
+
"<managed-root>/audit-rubrics/category_rubrics/category-h-security-boundaries.md",
|
|
564
|
+
"packages/claude-dev-env/audit-rubrics/category_rubrics/category-h-security-boundaries.md",
|
|
565
|
+
),
|
|
566
|
+
(
|
|
567
|
+
"<managed-root>/audit-rubrics/category_rubrics/category-i-concurrency.md",
|
|
568
|
+
"packages/claude-dev-env/audit-rubrics/category_rubrics/category-i-concurrency.md",
|
|
569
|
+
),
|
|
570
|
+
(
|
|
571
|
+
"<managed-root>/audit-rubrics/category_rubrics/category-j-code-rules-compliance.md",
|
|
572
|
+
"packages/claude-dev-env/audit-rubrics/category_rubrics/category-j-code-rules-compliance.md",
|
|
573
|
+
),
|
|
574
|
+
(
|
|
575
|
+
"<managed-root>/audit-rubrics/category_rubrics/category-k-codebase-conflicts.md",
|
|
576
|
+
"packages/claude-dev-env/audit-rubrics/category_rubrics/category-k-codebase-conflicts.md",
|
|
577
|
+
),
|
|
578
|
+
(
|
|
579
|
+
"<managed-root>/audit-rubrics/category_rubrics/category-l-behavior-equivalence.md",
|
|
580
|
+
"packages/claude-dev-env/audit-rubrics/category_rubrics/category-l-behavior-equivalence.md",
|
|
581
|
+
),
|
|
582
|
+
(
|
|
583
|
+
"<managed-root>/audit-rubrics/category_rubrics/category-m-producer-consumer-cardinality.md",
|
|
584
|
+
"packages/claude-dev-env/audit-rubrics/category_rubrics/category-m-producer-consumer-cardinality.md",
|
|
585
|
+
),
|
|
586
|
+
(
|
|
587
|
+
"<managed-root>/audit-rubrics/category_rubrics/category-n-test-name-scenario-verifier.md",
|
|
588
|
+
"packages/claude-dev-env/audit-rubrics/category_rubrics/category-n-test-name-scenario-verifier.md",
|
|
589
|
+
),
|
|
590
|
+
(
|
|
591
|
+
"<managed-root>/audit-rubrics/category_rubrics/category-o-docstring-vs-impl-drift.md",
|
|
592
|
+
"packages/claude-dev-env/audit-rubrics/category_rubrics/category-o-docstring-vs-impl-drift.md",
|
|
593
|
+
),
|
|
594
|
+
(
|
|
595
|
+
"<managed-root>/audit-rubrics/category_rubrics/category-p-name-vs-behavior-contract.md",
|
|
596
|
+
"packages/claude-dev-env/audit-rubrics/category_rubrics/category-p-name-vs-behavior-contract.md",
|
|
597
|
+
),
|
|
598
|
+
(
|
|
599
|
+
"<managed-root>/audit-rubrics/category_rubrics/category-q-cross-surface-claims.md",
|
|
600
|
+
"packages/claude-dev-env/audit-rubrics/category_rubrics/category-q-cross-surface-claims.md",
|
|
601
|
+
),
|
|
602
|
+
}),
|
|
603
|
+
"pr-description-writer.md": frozenset({
|
|
604
|
+
(
|
|
605
|
+
"<agents-home>/skills/pr-title-description/SKILL.md",
|
|
606
|
+
"packages/claude-dev-env/.agents/skills/pr-title-description/SKILL.md",
|
|
607
|
+
),
|
|
608
|
+
(
|
|
609
|
+
"<managed-root>/rules/gh-cli-conventions.md#body-content-goes-in-a-file",
|
|
610
|
+
"packages/claude-dev-env/rules/gh-cli-conventions.md#body-content-goes-in-a-file",
|
|
611
|
+
),
|
|
612
|
+
}),
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
|
|
616
|
+
def _assert_source_link_contract(agent_file_name: str, agent_text: str) -> None:
|
|
617
|
+
agents_directory = Path(__file__).parent
|
|
618
|
+
repository_root = agents_directory.parents[3]
|
|
619
|
+
actual_source_links = frozenset(SOURCE_LINK_PATTERN.findall(agent_text))
|
|
620
|
+
expected_source_links = EXPECTED_SOURCE_LINK_PAIRS[agent_file_name]
|
|
621
|
+
assert actual_source_links == expected_source_links, (
|
|
622
|
+
f"{agent_file_name} source links must match the complete expected set"
|
|
623
|
+
)
|
|
624
|
+
for each_installed_path, each_source_path in actual_source_links:
|
|
625
|
+
installed_suffix = each_installed_path.split(">/", 1)[1]
|
|
626
|
+
source_suffix = each_source_path.removeprefix("packages/claude-dev-env/")
|
|
627
|
+
source_suffix = source_suffix.removeprefix(".agents/")
|
|
628
|
+
assert installed_suffix == source_suffix, (
|
|
629
|
+
f"installed/source suffix mismatch: {each_installed_path} -> "
|
|
630
|
+
f"{each_source_path}"
|
|
631
|
+
)
|
|
632
|
+
source_file_path = repository_root / each_source_path.split("#", 1)[0]
|
|
633
|
+
assert source_file_path.is_file(), (
|
|
634
|
+
f"source fallback must be a file: {each_source_path}"
|
|
635
|
+
)
|
|
636
|
+
|
|
637
|
+
|
|
638
|
+
def test_named_agents_document_installed_paths_and_source_fallbacks() -> None:
|
|
639
|
+
agents_directory = Path(__file__).parent
|
|
640
|
+
for each_agent_file_name in (
|
|
641
|
+
"clean-coder.md",
|
|
642
|
+
"code-quality-agent.md",
|
|
643
|
+
"pr-description-writer.md",
|
|
644
|
+
):
|
|
645
|
+
agent_text = (agents_directory / each_agent_file_name).read_text(
|
|
646
|
+
encoding="utf-8"
|
|
647
|
+
)
|
|
648
|
+
_assert_source_link_contract(each_agent_file_name, agent_text)
|
|
649
|
+
for each_stale_prefix in (
|
|
650
|
+
"../docs/",
|
|
651
|
+
"../hooks/",
|
|
652
|
+
"../rules/",
|
|
653
|
+
"../audit-rubrics/",
|
|
654
|
+
"../skills/descriptions/",
|
|
655
|
+
"../skills/comments/",
|
|
656
|
+
):
|
|
657
|
+
assert each_stale_prefix not in agent_text, (
|
|
658
|
+
f"{each_agent_file_name} must not use stale relative guide "
|
|
659
|
+
f"path {each_stale_prefix}"
|
|
660
|
+
)
|
|
661
|
+
|
|
662
|
+
|
|
663
|
+
def test_source_link_contract_rejects_missing_pair() -> None:
|
|
664
|
+
body = _clean_coder_body()
|
|
665
|
+
missing_pair_text = (
|
|
666
|
+
"`<managed-root>/hooks/blocking/code_rules_enforcer.py` "
|
|
667
|
+
"(source fallback: `packages/claude-dev-env/hooks/blocking/"
|
|
668
|
+
"code_rules_enforcer.py`)"
|
|
669
|
+
)
|
|
670
|
+
mutated_body = body.replace(missing_pair_text, "", 1)
|
|
671
|
+
with pytest.raises(AssertionError):
|
|
672
|
+
_assert_source_link_contract("clean-coder.md", mutated_body)
|
|
673
|
+
|
|
674
|
+
|
|
675
|
+
def test_source_link_contract_rejects_altered_valid_file() -> None:
|
|
676
|
+
body = _clean_coder_body()
|
|
677
|
+
mutated_body = body.replace(
|
|
678
|
+
"`<managed-root>/docs/CODE_RULES.md` (source fallback: "
|
|
679
|
+
"`packages/claude-dev-env/docs/CODE_RULES.md`)",
|
|
680
|
+
"`<managed-root>/docs/CODE_RULES.md` (source fallback: "
|
|
681
|
+
"`packages/claude-dev-env/rules/code-standards.md`)",
|
|
682
|
+
1,
|
|
683
|
+
)
|
|
684
|
+
with pytest.raises(AssertionError):
|
|
685
|
+
_assert_source_link_contract("clean-coder.md", mutated_body)
|
|
686
|
+
|
|
687
|
+
|
|
688
|
+
def test_source_link_contract_rejects_directory_fallback() -> None:
|
|
689
|
+
body = _clean_coder_body()
|
|
690
|
+
mutated_body = body.replace(
|
|
691
|
+
"`<managed-root>/docs/CODE_RULES.md` (source fallback: "
|
|
692
|
+
"`packages/claude-dev-env/docs/CODE_RULES.md`)",
|
|
693
|
+
"`<managed-root>/docs/CODE_RULES.md` (source fallback: "
|
|
694
|
+
"`packages/claude-dev-env/docs`)",
|
|
695
|
+
1,
|
|
696
|
+
)
|
|
697
|
+
with pytest.raises(AssertionError):
|
|
698
|
+
_assert_source_link_contract("clean-coder.md", mutated_body)
|
|
699
|
+
|
|
700
|
+
|
|
701
|
+
def test_named_agents_resolve_active_managed_root_and_agents_home() -> None:
|
|
702
|
+
agents_directory = Path(__file__).parent
|
|
703
|
+
for each_agent_file_name in (
|
|
704
|
+
"clean-coder.md",
|
|
705
|
+
"code-quality-agent.md",
|
|
706
|
+
"pr-description-writer.md",
|
|
707
|
+
):
|
|
708
|
+
agent_text = (agents_directory / each_agent_file_name).read_text(
|
|
709
|
+
encoding="utf-8"
|
|
710
|
+
)
|
|
711
|
+
assert "active managed root" in agent_text.lower()
|
|
712
|
+
assert "active agents home" in agent_text.lower()
|
|
713
|
+
assert "CLAUDE_CONFIG_DIR" in agent_text
|
|
714
|
+
assert "--target" in agent_text
|
|
715
|
+
assert "<managed-root>/" in agent_text
|
|
716
|
+
assert "<agents-home>/" in agent_text
|
|
717
|
+
assert "do not assume" in agent_text.lower()
|
|
718
|
+
|
|
719
|
+
|
|
423
720
|
def test_clean_coder_never_globs_or_reads_dotenv_files() -> None:
|
|
424
721
|
body = _clean_coder_body()
|
|
425
722
|
assert "`**/.env`" not in body
|
|
@@ -436,7 +733,336 @@ def test_clean_coder_uses_task_local_config_discovery() -> None:
|
|
|
436
733
|
assert "Issue all seven Glob calls" not in body
|
|
437
734
|
|
|
438
735
|
|
|
736
|
+
def test_clean_coder_loads_scoped_agents_files_before_editing() -> None:
|
|
737
|
+
body = _clean_coder_body()
|
|
738
|
+
assert "scoped AGENTS.md" in body
|
|
739
|
+
assert body.count("Load scoped AGENTS.md files first.") == 1
|
|
740
|
+
assert "repository root" in body.lower()
|
|
741
|
+
assert "target directory" in body.lower()
|
|
742
|
+
assert "unrelated" in body.lower()
|
|
743
|
+
|
|
744
|
+
|
|
745
|
+
def test_clean_coder_explains_red_green_refactor_steps() -> None:
|
|
746
|
+
body = _clean_coder_body()
|
|
747
|
+
assert "behavior change" in body.lower()
|
|
748
|
+
assert "failing test" in body.lower()
|
|
749
|
+
assert "RED" in body
|
|
750
|
+
assert "GREEN" in body
|
|
751
|
+
assert "REFACTOR" in body
|
|
752
|
+
assert "write production behavior before red" in body.lower()
|
|
753
|
+
|
|
754
|
+
|
|
755
|
+
def test_clean_coder_distinguishes_code_rules_precheck_from_full_quality_gates() -> None:
|
|
756
|
+
body = _clean_coder_body()
|
|
757
|
+
assert "mechanical CODE_RULES" in body
|
|
758
|
+
assert "does not run tests" in body
|
|
759
|
+
assert "full quality gate" in body.lower()
|
|
760
|
+
assert "ruff" in body.lower()
|
|
761
|
+
assert "mypy" in body.lower()
|
|
762
|
+
|
|
763
|
+
|
|
764
|
+
def test_clean_coder_has_hook_specific_workflow() -> None:
|
|
765
|
+
body = _clean_coder_body()
|
|
766
|
+
assert "Hook-specific workflow" in body
|
|
767
|
+
assert "production entry point" in body.lower()
|
|
768
|
+
assert "JSON" in body
|
|
769
|
+
assert "registered" in body.lower()
|
|
770
|
+
|
|
771
|
+
|
|
772
|
+
def test_clean_coder_keeps_warm_session_advisor_triggers() -> None:
|
|
773
|
+
body = _clean_coder_body().lower()
|
|
774
|
+
assert "warm `session-advisor`" in body
|
|
775
|
+
assert "before substantive work" in body
|
|
776
|
+
assert "when you believe the task is complete" in body
|
|
777
|
+
assert "when stuck" in body
|
|
778
|
+
assert "change of approach" in body
|
|
779
|
+
assert "before any commit" in body
|
|
780
|
+
|
|
781
|
+
|
|
782
|
+
|
|
783
|
+
def test_clean_coder_separates_constants_and_caller_search() -> None:
|
|
784
|
+
body = _clean_coder_body()
|
|
785
|
+
assert "task-local constants search" in body
|
|
786
|
+
assert "caller boundary" in body
|
|
787
|
+
|
|
788
|
+
|
|
789
|
+
def test_clean_coder_scopes_task_artifact_guidance() -> None:
|
|
790
|
+
body = _clean_coder_body()
|
|
791
|
+
assert "Follow the target repo's policy for scratch, planning, and image files" in body
|
|
792
|
+
assert "No scratch/planning artifacts" not in body
|
|
793
|
+
|
|
794
|
+
|
|
795
|
+
def test_clean_coder_links_canonical_policy_areas() -> None:
|
|
796
|
+
body = _clean_coder_body()
|
|
797
|
+
required_links = (
|
|
798
|
+
"<managed-root>/docs/CODE_RULES.md#5-no-abbreviations",
|
|
799
|
+
"<managed-root>/rules/testing.md",
|
|
800
|
+
"<managed-root>/rules/ask-user-question-required.md",
|
|
801
|
+
"<managed-root>/rules/verify-runtime-state.md",
|
|
802
|
+
"<managed-root>/rules/doc-inventory-integrity.md",
|
|
803
|
+
"<managed-root>/rules/failure-blast-radius.md",
|
|
804
|
+
"<managed-root>/rules/git-workflow.md",
|
|
805
|
+
"<managed-root>/rules/workers-done-before-complete.md",
|
|
806
|
+
)
|
|
807
|
+
assert all(each_link in body for each_link in required_links)
|
|
808
|
+
|
|
809
|
+
|
|
810
|
+
def test_clean_coder_preserves_existing_comment_instruction() -> None:
|
|
811
|
+
body = _clean_coder_body()
|
|
812
|
+
assert (
|
|
813
|
+
"Note every existing comment so you can leave each one untouched on lines "
|
|
814
|
+
"that remain otherwise unchanged."
|
|
815
|
+
) in body
|
|
816
|
+
|
|
439
817
|
def test_clean_coder_examples_import_constants_from_config() -> None:
|
|
440
818
|
body = _clean_coder_body()
|
|
441
819
|
assert "from config.timing import MAXIMUM_RETRIES" in body
|
|
442
820
|
assert re.search(r"(?m)^MAXIMUM_RETRIES\s*=\s*\d+", body) is None
|
|
821
|
+
|
|
822
|
+
|
|
823
|
+
def test_clean_coder_loads_scoped_agents_before_claude() -> None:
|
|
824
|
+
body = _clean_coder_body()
|
|
825
|
+
agents_instruction_at = body.index("Load scoped AGENTS.md files first")
|
|
826
|
+
claude_instruction_at = body.index("Then read the applicable `CLAUDE.md`")
|
|
827
|
+
assert agents_instruction_at < claude_instruction_at
|
|
828
|
+
assert "every applicable `AGENTS.md`" in body
|
|
829
|
+
assert "closest file wins" in body
|
|
830
|
+
|
|
831
|
+
|
|
832
|
+
def test_clean_coder_requires_red_green_refactor_for_behavior_changes() -> None:
|
|
833
|
+
body = _clean_coder_body()
|
|
834
|
+
assert "every behavior change" in body
|
|
835
|
+
assert "red → green → refactor" in body
|
|
836
|
+
assert "run it red" in body
|
|
837
|
+
|
|
838
|
+
|
|
839
|
+
def test_clean_coder_distinguishes_full_gate_from_candidate_check() -> None:
|
|
840
|
+
body = _clean_coder_body()
|
|
841
|
+
assert "pre-check tests CODE_RULES only" in body
|
|
842
|
+
assert "full project gate" in body
|
|
843
|
+
assert "candidate enforcer check" in body
|
|
844
|
+
assert "not the full gate" in body
|
|
845
|
+
|
|
846
|
+
|
|
847
|
+
def test_clean_coder_defines_a_hook_specific_workflow() -> None:
|
|
848
|
+
body = _clean_coder_body()
|
|
849
|
+
assert "Hook-specific workflow" in body
|
|
850
|
+
assert "hooks/AGENTS.md" in body
|
|
851
|
+
assert "stdin JSON" in body
|
|
852
|
+
assert "exit code" in body
|
|
853
|
+
assert "registration" in body
|
|
854
|
+
assert "constants package" in body
|
|
855
|
+
assert "check.ps1" in body
|
|
856
|
+
assert "every applicable test file and suite" in body
|
|
857
|
+
|
|
858
|
+
|
|
859
|
+
def test_clean_coder_uses_target_package_aware_hook_paths() -> None:
|
|
860
|
+
body = _clean_coder_body()
|
|
861
|
+
hook_workflow = body.split("## Hook-specific workflow", 1)[1].split(
|
|
862
|
+
"## Session advisor", 1
|
|
863
|
+
)[0]
|
|
864
|
+
hook_path_pattern = re.compile(
|
|
865
|
+
r"`(?P<managed><managed-root>/(?P<relative>hooks/AGENTS\.md|scripts/check\.ps1))` "
|
|
866
|
+
r"\(default: `(?P<default>~/.claude/(?P=relative))`; "
|
|
867
|
+
r"source fallback: `(?P<source>packages/claude-dev-env/(?P=relative))`\)"
|
|
868
|
+
)
|
|
869
|
+
path_pairs = {
|
|
870
|
+
(
|
|
871
|
+
each_match.group("managed"),
|
|
872
|
+
each_match.group("default"),
|
|
873
|
+
each_match.group("source"),
|
|
874
|
+
)
|
|
875
|
+
for each_match in hook_path_pattern.finditer(hook_workflow)
|
|
876
|
+
}
|
|
877
|
+
expected_pairs = {
|
|
878
|
+
(
|
|
879
|
+
"<managed-root>/hooks/AGENTS.md",
|
|
880
|
+
"~/.claude/hooks/AGENTS.md",
|
|
881
|
+
"packages/claude-dev-env/hooks/AGENTS.md",
|
|
882
|
+
),
|
|
883
|
+
(
|
|
884
|
+
"<managed-root>/scripts/check.ps1",
|
|
885
|
+
"~/.claude/scripts/check.ps1",
|
|
886
|
+
"packages/claude-dev-env/scripts/check.ps1",
|
|
887
|
+
),
|
|
888
|
+
}
|
|
889
|
+
assert path_pairs == expected_pairs
|
|
890
|
+
assert len(path_pairs) == len(expected_pairs)
|
|
891
|
+
repository_root = Path(__file__).parent.parents[3]
|
|
892
|
+
for each_source_path in (
|
|
893
|
+
"packages/claude-dev-env/hooks/AGENTS.md",
|
|
894
|
+
"packages/claude-dev-env/scripts/check.ps1",
|
|
895
|
+
):
|
|
896
|
+
assert (repository_root / each_source_path).is_file()
|
|
897
|
+
assert "active managed root" in hook_workflow
|
|
898
|
+
assert "`~/.claude`" in hook_workflow
|
|
899
|
+
assert "`--target`" in hook_workflow
|
|
900
|
+
assert "`CLAUDE_CONFIG_DIR`" in hook_workflow
|
|
901
|
+
|
|
902
|
+
|
|
903
|
+
def test_clean_coder_uses_the_callers_warm_advisor_at_approved_triggers() -> None:
|
|
904
|
+
body = _clean_coder_body()
|
|
905
|
+
assert "caller's existing warm `session-advisor`" in body
|
|
906
|
+
assert "before first write" in body
|
|
907
|
+
assert "before locking a plan or interpretation" in body
|
|
908
|
+
assert "before a hard-to-reverse action" in body
|
|
909
|
+
assert "after repeated failure or a stall" in body
|
|
910
|
+
assert "changing approach" in body
|
|
911
|
+
assert "before completion" in body
|
|
912
|
+
assert "do not bind or spawn another advisor" in body
|
|
913
|
+
|
|
914
|
+
|
|
915
|
+
def test_clean_coder_policy_targets_exist_in_source_package() -> None:
|
|
916
|
+
_assert_source_link_contract("clean-coder.md", _clean_coder_body())
|
|
917
|
+
|
|
918
|
+
|
|
919
|
+
def test_code_quality_agent_allows_only_read_and_search_tools() -> None:
|
|
920
|
+
agent_definition_path = Path(__file__).parent / CODE_QUALITY_AGENT_FILENAME
|
|
921
|
+
parsed_frontmatter = yaml.safe_load(_frontmatter_block(agent_definition_path))
|
|
922
|
+
|
|
923
|
+
assert parsed_frontmatter["tools"] == list(CODE_QUALITY_READ_ONLY_TOOLS)
|
|
924
|
+
|
|
925
|
+
|
|
926
|
+
def test_code_quality_agent_contract_forbids_mutating_commands() -> None:
|
|
927
|
+
agent_definition_path = Path(__file__).parent / CODE_QUALITY_AGENT_FILENAME
|
|
928
|
+
body = agent_definition_path.read_text(encoding="utf-8")
|
|
929
|
+
assert "Use only `Read`, `Grep`, and `Glob`." in body
|
|
930
|
+
assert "Author zero edits." in body
|
|
931
|
+
assert "Run zero commits or pushes." in body
|
|
932
|
+
assert "run no commands that write files or create PRs." in body
|
|
933
|
+
|
|
934
|
+
|
|
935
|
+
def test_clean_coder_sets_a_small_complexity_budget() -> None:
|
|
936
|
+
body = _clean_coder_body()
|
|
937
|
+
assert "Complexity budget" in body
|
|
938
|
+
assert "1–2 files" in body
|
|
939
|
+
assert "~50–300 lines" in body
|
|
940
|
+
assert "about 40 executable lines" in body
|
|
941
|
+
assert "nesting level of 2" in body
|
|
942
|
+
|
|
943
|
+
|
|
944
|
+
def test_clean_coder_defines_a_liveness_boundary_for_dead_code_cleanup() -> None:
|
|
945
|
+
body = _clean_coder_body()
|
|
946
|
+
assert "orphaned or dead code" in body.lower()
|
|
947
|
+
assert "liveness boundary" in body
|
|
948
|
+
assert "live entry point" in body
|
|
949
|
+
assert "public API, plugin hook, or reflective dispatch" in body
|
|
950
|
+
|
|
951
|
+
|
|
952
|
+
def test_clean_coder_hands_the_full_diff_to_code_quality_agent() -> None:
|
|
953
|
+
body = _clean_coder_body()
|
|
954
|
+
assert "Full Code Quality Agent review handoff" in body
|
|
955
|
+
assert "code-quality-agent" in body
|
|
956
|
+
assert "full diff" in body
|
|
957
|
+
assert "all A–Q categories" in body
|
|
958
|
+
|
|
959
|
+
|
|
960
|
+
assert "Repair each actionable finding" in body
|
|
961
|
+
assert "rerun focused checks and the full project gate on the post-repair diff" in body
|
|
962
|
+
assert "Record both results" in body
|
|
963
|
+
|
|
964
|
+
|
|
965
|
+
def test_clean_coder_uses_evidence_based_completion_language() -> None:
|
|
966
|
+
body = _clean_coder_body()
|
|
967
|
+
assert "Evidence-Based Code Generation" in body
|
|
968
|
+
assert "recorded check results" in body
|
|
969
|
+
assert "Do not claim defect-free code" in body
|
|
970
|
+
assert "Zero-Defect" not in body
|
|
971
|
+
assert "zero-defect" not in body
|
|
972
|
+
|
|
973
|
+
|
|
974
|
+
def test_clean_coder_example_contains_real_code() -> None:
|
|
975
|
+
body = _clean_coder_body()
|
|
976
|
+
example = body.split("```python", 1)[1].split("```", 1)[0]
|
|
977
|
+
assert "..." not in example
|
|
978
|
+
assert "pass" not in example
|
|
979
|
+
assert "NotImplementedError" not in example
|
|
980
|
+
|
|
981
|
+
|
|
982
|
+
def test_clean_coder_requires_evidence_based_completion_wording() -> None:
|
|
983
|
+
body = _clean_coder_body()
|
|
984
|
+
assert "# Clean Coder — Evidence-Based Code Generation" in body
|
|
985
|
+
assert "reviewers find nothing" not in body
|
|
986
|
+
assert "Write clear code. Provide test and review evidence." in body
|
|
987
|
+
assert "candidate check" in body
|
|
988
|
+
assert "focused tests" in body
|
|
989
|
+
assert "full project gate" in body
|
|
990
|
+
assert "recorded check results" in body
|
|
991
|
+
assert "review evidence" in body
|
|
992
|
+
assert "open questions" in body
|
|
993
|
+
assert "Do not claim defect-free code" in body
|
|
994
|
+
|
|
995
|
+
|
|
996
|
+
def test_clean_coder_follows_target_package_constant_layouts() -> None:
|
|
997
|
+
body = _clean_coder_body()
|
|
998
|
+
assert "target package's existing constants layout" in body
|
|
999
|
+
assert "Reuse first" in body
|
|
1000
|
+
assert "shared policy" in body
|
|
1001
|
+
assert "multiple consumers" in body
|
|
1002
|
+
assert "file-global-constants" in body
|
|
1003
|
+
assert "add the constant to the appropriate config file" not in body
|
|
1004
|
+
|
|
1005
|
+
|
|
1006
|
+
def test_clean_coder_allows_requested_plan_packets_and_product_assets() -> None:
|
|
1007
|
+
body = _clean_coder_body()
|
|
1008
|
+
assert "No unasked scratch files" in body
|
|
1009
|
+
assert "valid plan packets" in body
|
|
1010
|
+
assert "uncommitted working files" in body
|
|
1011
|
+
assert "durable artifacts release" in body
|
|
1012
|
+
assert "not the repository tree" in body
|
|
1013
|
+
assert "docs/plans/*.md" not in body
|
|
1014
|
+
assert "or image assets" not in body
|
|
1015
|
+
|
|
1016
|
+
|
|
1017
|
+
def test_clean_coder_links_canonical_naming_guidance() -> None:
|
|
1018
|
+
body = _clean_coder_body()
|
|
1019
|
+
assert "canonical naming guidance" in body
|
|
1020
|
+
assert "CODE_RULES.md §5" in body
|
|
1021
|
+
assert "`each_` loops" not in body
|
|
1022
|
+
assert "`is_`/`has_`/`should_`/`can_`" not in body
|
|
1023
|
+
|
|
1024
|
+
|
|
1025
|
+
def test_clean_coder_groups_session_policy_references() -> None:
|
|
1026
|
+
body = _clean_coder_body()
|
|
1027
|
+
assert "Session policy map" in body
|
|
1028
|
+
for each_policy_group in (
|
|
1029
|
+
"Tests",
|
|
1030
|
+
"Questions",
|
|
1031
|
+
"Search and shell",
|
|
1032
|
+
"Runtime checks",
|
|
1033
|
+
"Documentation",
|
|
1034
|
+
"Batch failures",
|
|
1035
|
+
"Git",
|
|
1036
|
+
"Worker coordination",
|
|
1037
|
+
):
|
|
1038
|
+
assert each_policy_group in body
|
|
1039
|
+
assert "ask-user-question-required.md" in body
|
|
1040
|
+
assert "workers-done-before-complete.md" in body
|
|
1041
|
+
assert "verify-runtime-state.md" in body
|
|
1042
|
+
assert "Material implementation questions must return to the caller" in body
|
|
1043
|
+
assert "AskUserQuestion" in body
|
|
1044
|
+
assert "do not ask in plain text or guess" in body
|
|
1045
|
+
|
|
1046
|
+
expected_session_policy_files = (
|
|
1047
|
+
"testing.md",
|
|
1048
|
+
"anti-corollary-tests.md",
|
|
1049
|
+
"ask-user-question-required.md",
|
|
1050
|
+
"verify-before-asking.md",
|
|
1051
|
+
"filesystem-search.md",
|
|
1052
|
+
"shell-invocation.md",
|
|
1053
|
+
"verify-runtime-state.md",
|
|
1054
|
+
"doc-inventory-integrity.md",
|
|
1055
|
+
"docstring-prose-matches-implementation.md",
|
|
1056
|
+
"failure-blast-radius.md",
|
|
1057
|
+
"git-workflow.md",
|
|
1058
|
+
"re-stage-before-commit.md",
|
|
1059
|
+
"agent-spawn-protocol.md",
|
|
1060
|
+
"workers-done-before-complete.md",
|
|
1061
|
+
)
|
|
1062
|
+
session_policy_map = body[body.index("## Session policy map") :]
|
|
1063
|
+
session_policy_links = SOURCE_LINK_PATTERN.findall(session_policy_map)
|
|
1064
|
+
for each_policy_file_name in expected_session_policy_files:
|
|
1065
|
+
assert (
|
|
1066
|
+
f"<managed-root>/rules/{each_policy_file_name}",
|
|
1067
|
+
f"packages/claude-dev-env/rules/{each_policy_file_name}",
|
|
1068
|
+
) in session_policy_links
|