claude-dev-env 1.50.1 → 1.50.3

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.
Files changed (91) hide show
  1. package/_shared/pr-loop/audit-contract.md +3 -3
  2. package/audit-rubrics/category_rubrics/category-e-dead-code.md +3 -2
  3. package/audit-rubrics/prompts/category-a-api-contracts.md +1 -1
  4. package/audit-rubrics/prompts/category-b-selector-engine-compat.md +2 -2
  5. package/audit-rubrics/prompts/category-c-resource-cleanup.md +2 -2
  6. package/audit-rubrics/prompts/category-d-scoping-and-ordering.md +2 -2
  7. package/audit-rubrics/prompts/category-e-dead-code.md +5 -4
  8. package/audit-rubrics/prompts/category-f-silent-failures.md +2 -2
  9. package/audit-rubrics/prompts/category-g-bounds-and-overflow.md +2 -2
  10. package/audit-rubrics/prompts/category-h-security-boundaries.md +2 -2
  11. package/audit-rubrics/prompts/category-i-concurrency.md +2 -2
  12. package/audit-rubrics/prompts/category-j-code-rules-compliance.md +2 -2
  13. package/audit-rubrics/prompts/category-k-codebase-conflicts.md +2 -2
  14. package/docs/CODE_RULES.md +1 -1
  15. package/hooks/blocking/code_rules_annotations_length.py +167 -0
  16. package/hooks/blocking/code_rules_banned_identifiers.py +385 -0
  17. package/hooks/blocking/code_rules_boolean_mustcheck.py +350 -0
  18. package/hooks/blocking/code_rules_comments.py +337 -0
  19. package/hooks/blocking/code_rules_constants_config.py +252 -0
  20. package/hooks/blocking/code_rules_docstrings.py +308 -0
  21. package/hooks/blocking/code_rules_enforcer.py +98 -5807
  22. package/hooks/blocking/code_rules_imports_logging.py +276 -0
  23. package/hooks/blocking/code_rules_magic_values.py +180 -0
  24. package/hooks/blocking/code_rules_mock_completeness.py +295 -0
  25. package/hooks/blocking/code_rules_naming_collection.py +264 -0
  26. package/hooks/blocking/code_rules_optional_params.py +288 -0
  27. package/hooks/blocking/code_rules_paths_syspath.py +186 -0
  28. package/hooks/blocking/code_rules_probe_chains.py +305 -0
  29. package/hooks/blocking/code_rules_probe_detection.py +257 -0
  30. package/hooks/blocking/code_rules_probe_recording.py +225 -0
  31. package/hooks/blocking/code_rules_scope_binding.py +151 -0
  32. package/hooks/blocking/code_rules_shared.py +301 -0
  33. package/hooks/blocking/code_rules_string_magic.py +207 -0
  34. package/hooks/blocking/code_rules_test_assertions.py +226 -0
  35. package/hooks/blocking/code_rules_test_branching_except.py +181 -0
  36. package/hooks/blocking/code_rules_test_isolation.py +341 -0
  37. package/hooks/blocking/code_rules_type_escape.py +341 -0
  38. package/hooks/blocking/code_rules_typeddict_stub.py +305 -0
  39. package/hooks/blocking/code_rules_unused_imports.py +256 -0
  40. package/hooks/blocking/tdd_enforcer.py +31 -0
  41. package/hooks/blocking/test_code_rules_constants_config.py +26 -0
  42. package/hooks/blocking/test_code_rules_enforcer_banned_noun_word.py +5 -2
  43. package/hooks/blocking/test_code_rules_enforcer_cap_meta.py +0 -5
  44. package/hooks/blocking/test_code_rules_enforcer_comment_string_awareness.py +21 -15
  45. package/hooks/blocking/test_code_rules_enforcer_config_path.py +20 -16
  46. package/hooks/blocking/test_code_rules_enforcer_exempt_marker_chained.py +4 -2
  47. package/hooks/blocking/test_code_rules_enforcer_function_length.py +18 -13
  48. package/hooks/blocking/test_code_rules_enforcer_hardcoded_user_path.py +1 -2
  49. package/hooks/blocking/test_code_rules_enforcer_ignored_must_check_return.py +22 -12
  50. package/hooks/blocking/test_code_rules_enforcer_split_annotations_length.py +55 -0
  51. package/hooks/blocking/test_code_rules_enforcer_split_banned.py +170 -0
  52. package/hooks/blocking/test_code_rules_enforcer_split_comments.py +60 -0
  53. package/hooks/blocking/test_code_rules_enforcer_split_config_path.py +52 -0
  54. package/hooks/blocking/test_code_rules_enforcer_split_constants_config.py +236 -0
  55. package/hooks/blocking/test_code_rules_enforcer_split_entry_1.py +296 -0
  56. package/hooks/blocking/test_code_rules_enforcer_split_entry_2.py +238 -0
  57. package/hooks/blocking/test_code_rules_enforcer_split_isolation_1.py +271 -0
  58. package/hooks/blocking/test_code_rules_enforcer_split_isolation_2.py +283 -0
  59. package/hooks/blocking/test_code_rules_enforcer_split_isolation_3.py +268 -0
  60. package/hooks/blocking/test_code_rules_enforcer_split_isolation_4.py +85 -0
  61. package/hooks/blocking/test_code_rules_enforcer_split_mocks_1.py +303 -0
  62. package/hooks/blocking/test_code_rules_enforcer_split_mocks_2.py +111 -0
  63. package/hooks/blocking/test_code_rules_enforcer_split_mustcheck.py +87 -0
  64. package/hooks/blocking/test_code_rules_enforcer_split_naming.py +107 -0
  65. package/hooks/blocking/test_code_rules_enforcer_split_optional_params.py +325 -0
  66. package/hooks/blocking/test_code_rules_enforcer_split_paths_syspath.py +110 -0
  67. package/hooks/blocking/test_code_rules_enforcer_split_shared.py +44 -0
  68. package/hooks/blocking/test_code_rules_enforcer_split_string_magic.py +55 -0
  69. package/hooks/blocking/test_code_rules_enforcer_split_test_assertions.py +56 -0
  70. package/hooks/blocking/test_code_rules_enforcer_todo_markers.py +21 -15
  71. package/hooks/blocking/test_code_rules_paths_syspath.py +26 -0
  72. package/hooks/blocking/test_tdd_enforcer.py +116 -0
  73. package/hooks/hooks_constants/blocking_check_limits.py +3 -0
  74. package/hooks/hooks_constants/code_rules_enforcer_constants.py +8 -0
  75. package/hooks/hooks_constants/sys_path_insert_constants.py +1 -0
  76. package/package.json +1 -1
  77. package/skills/_shared/pr-loop/scripts/build_audit_prompt.py +13 -7
  78. package/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/path_resolver_constants.py +21 -11
  79. package/skills/_shared/pr-loop/scripts/test_build_audit_prompt.py +92 -0
  80. package/skills/bugteam/CONSTRAINTS.md +1 -1
  81. package/skills/bugteam/PROMPTS.md +20 -48
  82. package/skills/bugteam/SKILL.md +5 -5
  83. package/skills/bugteam/reference/audit-and-teammates.md +1 -1
  84. package/skills/bugteam/reference/audit-contract.md +4 -4
  85. package/skills/bugteam/reference/design-rationale.md +1 -1
  86. package/skills/findbugs/SKILL.md +21 -12
  87. package/skills/fixbugs/SKILL.md +1 -1
  88. package/skills/qbug/SKILL.md +5 -5
  89. package/skills/qbug/test_qbug_skill_audit_schema.py +13 -23
  90. package/skills/refine/SKILL.md +1 -1
  91. package/hooks/blocking/test_code_rules_enforcer.py +0 -2669
@@ -574,3 +574,119 @@ def test_is_inside_dotclaude_segment_helper_matches_only_exact_segments() -> Non
574
574
  assert _PRODUCTION_MODULE._is_inside_dotclaude_segment("C:\\Users\\dev\\.claude\\agent.py") is True
575
575
  assert _PRODUCTION_MODULE._is_inside_dotclaude_segment("/src/my.claude.helpers.py") is False
576
576
  assert _PRODUCTION_MODULE._is_inside_dotclaude_segment("/src/app/service.py") is False
577
+
578
+
579
+ def test_should_offer_split_family_test_files_as_candidates_for_code_rules_module(
580
+ tmp_path: Path,
581
+ ) -> None:
582
+ sandbox = _sandbox(tmp_path)
583
+ production_module = sandbox / "code_rules_magic_values.py"
584
+ string_magic_family_test = sandbox / "test_code_rules_enforcer_split_string_magic.py"
585
+ string_magic_family_test.write_text("def test_string_magic(): pass\n")
586
+ banned_family_test = sandbox / "test_code_rules_enforcer_split_banned.py"
587
+ banned_family_test.write_text("def test_banned(): pass\n")
588
+
589
+ all_candidates = _PRODUCTION_MODULE.candidate_test_paths_for(production_module)
590
+
591
+ assert string_magic_family_test in all_candidates
592
+ assert banned_family_test in all_candidates
593
+
594
+
595
+ def test_should_keep_plain_stem_candidates_first_for_code_rules_module(
596
+ tmp_path: Path,
597
+ ) -> None:
598
+ sandbox = _sandbox(tmp_path)
599
+ production_module = sandbox / "code_rules_magic_values.py"
600
+ family_test = sandbox / "test_code_rules_enforcer_split_string_magic.py"
601
+ family_test.write_text("def test_string_magic(): pass\n")
602
+
603
+ all_candidates = _PRODUCTION_MODULE.candidate_test_paths_for(production_module)
604
+
605
+ assert all_candidates[0] == sandbox / "test_code_rules_magic_values.py"
606
+ assert all_candidates[1] == sandbox / "code_rules_magic_values_test.py"
607
+
608
+
609
+ def test_should_not_offer_split_family_candidates_for_non_code_rules_module(
610
+ tmp_path: Path,
611
+ ) -> None:
612
+ sandbox = _sandbox(tmp_path)
613
+ production_module = sandbox / "orders.py"
614
+ family_test = sandbox / "test_code_rules_enforcer_split_string_magic.py"
615
+ family_test.write_text("def test_string_magic(): pass\n")
616
+
617
+ all_candidates = _PRODUCTION_MODULE.candidate_test_paths_for(production_module)
618
+
619
+ assert family_test not in all_candidates
620
+
621
+
622
+ def test_should_add_no_split_family_candidates_when_directory_has_none(
623
+ tmp_path: Path,
624
+ ) -> None:
625
+ sandbox = _sandbox(tmp_path)
626
+ production_module = sandbox / "code_rules_magic_values.py"
627
+
628
+ all_candidates = _PRODUCTION_MODULE.candidate_test_paths_for(production_module)
629
+
630
+ expected_stem_candidates = [
631
+ sandbox / "test_code_rules_magic_values.py",
632
+ sandbox / "code_rules_magic_values_test.py",
633
+ ]
634
+ assert all_candidates == expected_stem_candidates
635
+
636
+
637
+ def test_should_not_offer_family_candidates_for_code_ruleset_stem(
638
+ tmp_path: Path,
639
+ ) -> None:
640
+ sandbox = _sandbox(tmp_path)
641
+ production_module = sandbox / "code_ruleset.py"
642
+ family_test = sandbox / "test_code_rules_enforcer_split_example.py"
643
+ family_test.write_text("def test_detects_example(): pass\n")
644
+
645
+ all_candidates = _PRODUCTION_MODULE.candidate_test_paths_for(production_module)
646
+
647
+ expected_stem_candidates = [
648
+ sandbox / "test_code_ruleset.py",
649
+ sandbox / "code_ruleset_test.py",
650
+ ]
651
+ assert all_candidates == expected_stem_candidates
652
+ assert family_test not in all_candidates
653
+
654
+
655
+ def test_should_allow_code_rules_edit_when_fresh_split_family_sibling_exists(
656
+ tmp_path: Path,
657
+ ) -> None:
658
+ sandbox = _sandbox(tmp_path)
659
+ production_module = sandbox / "code_rules_example.py"
660
+ production_module.write_text("def detect() -> None:\n return None\n")
661
+ family_test = sandbox / "test_code_rules_enforcer_split_example_concern.py"
662
+ family_test.write_text("def test_detects_example(): pass\n")
663
+
664
+ payload = _make_edit_payload(
665
+ production_module,
666
+ old_string="return None",
667
+ new_string="return None # adjusted",
668
+ )
669
+ completed = _run_hook_with_payload(payload)
670
+
671
+ assert _decision_from(completed) == "allow"
672
+
673
+
674
+ def test_should_deny_code_rules_edit_when_split_family_sibling_is_stale(
675
+ tmp_path: Path,
676
+ ) -> None:
677
+ sandbox = _sandbox(tmp_path)
678
+ production_module = sandbox / "code_rules_example.py"
679
+ production_module.write_text("def detect() -> None:\n return None\n")
680
+ family_test = sandbox / "test_code_rules_enforcer_split_example_concern.py"
681
+ family_test.write_text("def test_detects_example(): pass\n")
682
+ stale_timestamp = time.time() - STALE_MTIME_OFFSET_SECONDS
683
+ os.utime(family_test, (stale_timestamp, stale_timestamp))
684
+
685
+ payload = _make_edit_payload(
686
+ production_module,
687
+ old_string="return None",
688
+ new_string="return None # adjusted",
689
+ )
690
+ completed = _run_hook_with_payload(payload)
691
+
692
+ assert _decision_from(completed) == "deny"
@@ -20,6 +20,9 @@ MAX_DOCSTRING_ARGS_SIGNATURE_ISSUES: int = 5
20
20
  MAX_IGNORED_MUST_CHECK_RETURN_ISSUES: int = 5
21
21
  MAX_TYPE_ESCAPE_HATCH_ISSUES: int = 5
22
22
  MAX_THIN_WRAPPER_ISSUES: int = 1
23
+ MAX_LOGGING_FSTRING_ISSUES: int = 3
24
+ MAX_WINDOWS_API_NONE_ISSUES: int = 3
25
+ MAX_E2E_TEST_NAMING_ISSUES: int = 3
23
26
  DOCSTRING_TRIVIAL_FUNCTION_BODY_LINE_LIMIT: int = 3
24
27
 
25
28
  ALL_BARE_EXCEPT_BANNED_HANDLER_NAMES: frozenset[str] = frozenset({"Exception", "BaseException"})
@@ -79,6 +79,14 @@ NOT_INSIDE_TYPE_CHECKING_BLOCK = -1
79
79
  TRIPLE_QUOTE_PARITY_DIVISOR = 2
80
80
  TRIPLE_DOUBLE_QUOTE_DELIMITER = '"""'
81
81
  TRIPLE_SINGLE_QUOTE_DELIMITER = "'''"
82
+ MAX_MAGIC_VALUE_ISSUES = 3
83
+ STRING_LITERAL_QUOTE_PAIR_LENGTH = 2
84
+ MINIMUM_FSTRING_LITERAL_LENGTH = 2
85
+ MAX_FSTRING_STRUCTURAL_LITERAL_ISSUES = 100
86
+ ALL_ALLOWED_MAGIC_NUMBER_LITERALS: frozenset[str] = frozenset({"0", "1", "-1", "0.0", "1.0"})
87
+ ALL_NON_MAGIC_FSTRING_STRIPPED_VALUES: frozenset[str] = frozenset({"", "True", "False"})
88
+ DUPLICATED_FORMAT_MINIMUM_REPETITION_COUNT = 3
89
+ DUPLICATED_FORMAT_MINIMUM_LITERAL_CHARACTER_COUNT = 5
82
90
  FILE_GLOBAL_UPPER_SNAKE_PATTERN = re.compile(r"^_?[A-Z][A-Z0-9_]*$")
83
91
 
84
92
  ALL_COLLECTION_TYPE_NAMES: frozenset[str] = frozenset({
@@ -2,3 +2,4 @@
2
2
 
3
3
  MAX_SYS_PATH_INSERT_ISSUES: int = 25
4
4
  SYS_PATH_INSERT_GUIDANCE: str = "guard with `if <path> not in sys.path:` to avoid pushing the same entry on every reload"
5
+ SYS_PATH_INSERT_MINIMUM_ARGUMENT_COUNT: int = 2
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-dev-env",
3
- "version": "1.50.1",
3
+ "version": "1.50.3",
4
4
  "description": "Claude Code development standards — rules, hooks, agents, commands, and skills",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,7 +1,9 @@
1
1
  """Emit the complete AUDIT spawn prompt XML to stdout.
2
2
 
3
- Substitutes <context>, <scope>, <bug_categories>, <constraints>,
4
- <comment_posting>, and <output_format> blocks from CLI args.
3
+ Builds <context> and <scope> from CLI args; <bug_categories>,
4
+ <rubric_reference>, and <constraints> come from the shared constants in
5
+ skills_pr_loop_constants; <comment_posting> and <output_format> are built
6
+ inline.
5
7
 
6
8
  Usage:
7
9
  python scripts/build_audit_prompt.py --owner jl-cmd --repo claude-code-config --pr-number 422 --loop 1 --head-ref feat/branch --base-ref main --worktree-path <PATH> --run-temp-dir <PATH>
@@ -22,6 +24,7 @@ from _xml_utils import emit_pretty_xml
22
24
  from skills_pr_loop_constants.path_resolver_constants import (
23
25
  ALL_AUDIT_CATEGORY_ENTRIES,
24
26
  ALL_AUDIT_CONSTRAINT_TEXTS,
27
+ AUDIT_RUBRIC_REFERENCE_TEXT,
25
28
  )
26
29
 
27
30
 
@@ -74,6 +77,9 @@ def build_audit_prompt_xml(
74
77
  cat_elem = SubElement(bug_categories, "category", {"id": each_category_id})
75
78
  cat_elem.text = each_category_label
76
79
 
80
+ rubric_reference = SubElement(root, "rubric_reference")
81
+ rubric_reference.text = AUDIT_RUBRIC_REFERENCE_TEXT
82
+
77
83
  constraints = SubElement(root, "constraints")
78
84
  for each_constraint in ALL_AUDIT_CONSTRAINT_TEXTS:
79
85
  SubElement(constraints, "constraint").text = each_constraint
@@ -167,12 +173,12 @@ def main(all_arguments: list[str]) -> int:
167
173
  xml_output = emit_audit_prompt(
168
174
  owner=arguments.owner,
169
175
  repo=arguments.repo,
170
- pr_number=getattr(arguments, "pr_number"),
176
+ pr_number=arguments.pr_number,
171
177
  loop=arguments.loop,
172
- head_ref=getattr(arguments, "head_ref"),
173
- base_ref=getattr(arguments, "base_ref"),
174
- worktree_path=getattr(arguments, "worktree_path"),
175
- run_temp_dir=getattr(arguments, "run_temp_dir"),
178
+ head_ref=arguments.head_ref,
179
+ base_ref=arguments.base_ref,
180
+ worktree_path=arguments.worktree_path,
181
+ run_temp_dir=arguments.run_temp_dir,
176
182
  )
177
183
  sys.stdout.write(xml_output)
178
184
  return 0
@@ -29,19 +29,29 @@ ALL_AUDIT_CONSTRAINT_TEXTS = [
29
29
  ]
30
30
 
31
31
  ALL_AUDIT_CATEGORY_ENTRIES = [
32
- ("A", "Documentation / API call accuracy"),
33
- ("B", "Type safety / boundary types"),
34
- ("C", "Magic values / hardcoded constants"),
35
- ("D", "Naming / banned identifiers"),
36
- ("E", "Orphans / dead code"),
37
- ("F", "Error handling / bare except"),
38
- ("G", "Bounds / silent cap exits"),
39
- ("H", "Testing / test quality"),
40
- ("I", "Control flow / logic errors"),
41
- ("J", "Architecture / SOLID violations"),
42
- ("K", "Codebase conflicts / DRY"),
32
+ ("A", "API contract verification"),
33
+ ("B", "Selector / query / engine compatibility"),
34
+ ("C", "Resource cleanup and lifecycle"),
35
+ ("D", "Variable scoping, ordering, and unbound references"),
36
+ ("E", "Dead code and unused imports"),
37
+ ("F", "Silent failures"),
38
+ ("G", "Off-by-one, bounds, integer overflow"),
39
+ ("H", "Security boundaries"),
40
+ ("I", "Concurrency hazards"),
41
+ ("J", "CODE_RULES.md compliance"),
42
+ ("K", "Codebase conflicts (incomplete propagation)"),
43
+ ("L", "Behavior-equivalence for refactors"),
44
+ ("M", "Producer/consumer cardinality vs collection-type contract"),
45
+ ("N", "Test-name scenario verifier"),
43
46
  ]
44
47
 
48
+ AUDIT_RUBRIC_REFERENCE_TEXT = (
49
+ "The category list above is a summary. The binding definition of each "
50
+ "category is its rubric file under $HOME/.claude/audit-rubrics/category_rubrics/ "
51
+ "(ready-to-send prompt variants under $HOME/.claude/audit-rubrics/prompts/). "
52
+ "Read the rubric files before auditing."
53
+ )
54
+
45
55
  ALL_FIX_EXECUTION_STEPS = [
46
56
  "Read the finding and verify it against the current file at file:line.",
47
57
  "Write a failing test that reproduces the bug.",
@@ -0,0 +1,92 @@
1
+ """Tests pinning build_audit_prompt's emitted A-N category taxonomy."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import importlib.util
6
+ import re
7
+ import sys
8
+ from pathlib import Path
9
+ from types import ModuleType
10
+ from xml.etree.ElementTree import Element
11
+
12
+ _SCRIPTS_DIR = Path(__file__).resolve().parent
13
+ if str(_SCRIPTS_DIR) not in sys.path:
14
+ sys.path.insert(0, str(_SCRIPTS_DIR))
15
+
16
+ from skills_pr_loop_constants.path_resolver_constants import (
17
+ ALL_AUDIT_CATEGORY_ENTRIES,
18
+ )
19
+
20
+ _CATEGORY_RUBRICS_DIR = _SCRIPTS_DIR.parents[3] / "audit-rubrics" / "category_rubrics"
21
+ _HEADING_PATTERN = re.compile(r"^# Category ([A-N]) — (.+)$")
22
+
23
+
24
+ def _load_build_audit_prompt() -> ModuleType:
25
+ module_path = _SCRIPTS_DIR / "build_audit_prompt.py"
26
+ spec = importlib.util.spec_from_file_location("build_audit_prompt", module_path)
27
+ assert spec is not None
28
+ assert spec.loader is not None
29
+ module = importlib.util.module_from_spec(spec)
30
+ sys.modules["build_audit_prompt"] = module
31
+ spec.loader.exec_module(module)
32
+ return module
33
+
34
+
35
+ build_audit_prompt = _load_build_audit_prompt()
36
+
37
+
38
+ def _rubric_label_by_letter() -> dict[str, str]:
39
+ assert _CATEGORY_RUBRICS_DIR.is_dir(), f"Missing rubric directory: {_CATEGORY_RUBRICS_DIR}"
40
+ all_labels: dict[str, str] = {}
41
+ for each_rubric_file in sorted(_CATEGORY_RUBRICS_DIR.glob("category-*.md")):
42
+ all_rubric_lines = each_rubric_file.read_text(encoding="utf-8").splitlines()
43
+ assert all_rubric_lines, f"Empty rubric file: {each_rubric_file}"
44
+ each_match = _HEADING_PATTERN.match(all_rubric_lines[0])
45
+ assert each_match is not None, f"Heading pattern not matched in {each_rubric_file}"
46
+ all_labels[each_match.group(1)] = each_match.group(2)
47
+ return all_labels
48
+
49
+
50
+ def _build_audit_root() -> Element:
51
+ return build_audit_prompt.build_audit_prompt_xml(
52
+ owner="jl-cmd",
53
+ repo="claude-code-config",
54
+ pr_number=422,
55
+ loop=1,
56
+ head_ref="feat/branch",
57
+ base_ref="main",
58
+ worktree_path=Path("/tmp/bugteam-pr-422/worktree"),
59
+ run_temp_dir=Path("/tmp/bugteam-pr-422"),
60
+ )
61
+
62
+
63
+ def test_bug_categories_carry_ids_a_through_n_in_order() -> None:
64
+ root = _build_audit_root()
65
+ bug_categories = root.find("bug_categories")
66
+ assert bug_categories is not None
67
+ all_emitted_ids = [each_category.get("id") for each_category in bug_categories]
68
+ all_expected_ids = list("ABCDEFGHIJKLMN")
69
+ assert all_emitted_ids == all_expected_ids
70
+
71
+
72
+ def test_emitted_category_labels_match_constant_entries() -> None:
73
+ root = _build_audit_root()
74
+ bug_categories = root.find("bug_categories")
75
+ assert bug_categories is not None
76
+ label_by_id = {
77
+ each_category.get("id"): each_category.text for each_category in bug_categories
78
+ }
79
+ for each_category_id, each_category_label in ALL_AUDIT_CATEGORY_ENTRIES:
80
+ assert label_by_id[each_category_id] == each_category_label
81
+
82
+
83
+ def test_category_labels_match_rubric_file_headings() -> None:
84
+ assert dict(ALL_AUDIT_CATEGORY_ENTRIES) == _rubric_label_by_letter()
85
+
86
+
87
+ def test_rubric_reference_element_names_category_rubrics_directory() -> None:
88
+ root = _build_audit_root()
89
+ rubric_reference = root.find("rubric_reference")
90
+ assert rubric_reference is not None
91
+ assert rubric_reference.text is not None
92
+ assert "audit-rubrics/category_rubrics" in rubric_reference.text
@@ -2,7 +2,7 @@
2
2
 
3
3
  ## Constraints
4
4
 
5
- - **Full A–K audit every loop, no exceptions.** PR size, "focused audit," "team overhead," "CODE_RULES already passed" — not valid reasons. Empty `<findings/>` for any category is a valid result. The audit agent walks all A–K rubrics each loop.
5
+ - **Full A–N audit every loop, no exceptions.** PR size, "focused audit," "team overhead," "CODE_RULES already passed" — not valid reasons. Empty `<findings/>` for any category is a valid result. The audit agent walks all A–N rubrics each loop.
6
6
  - **One run per invocation, multi-PR supported.** All PRs in a single /bugteam invocation share one `run_temp_dir`. Per-PR identity lives in the subagent name prefix (`bugfind-pr<N>-loop<L>` / `bugfix-pr<N>-loop<L>`) and the `<run_temp_dir>/pr-<N>/` subfolder containing that PR's git worktree, diff patches, and outcome XML files.
7
7
  - **Grant before any spawn, revoke before any return.** Step 0 grants project `.claude/**` permissions; Step 5 revokes. Both are mandatory. Revoke runs on every exit path including error, cap-reached, and stuck.
8
8
  - **Fresh subagent per loop.** Both bugfind and bugfix are spawned new each loop. Reusing a subagent across loops accumulates context inside that subagent's window — defeats clean-room.
@@ -44,58 +44,30 @@ cd into `<worktree_path>` before any git or file operation.
44
44
  a `---` separator and a worked example against an authentic PR below —
45
45
  are in `$HOME/.claude/audit-rubrics/prompts/`):
46
46
 
47
- A. API contract verification (signatures, return types, async/await correctness)
47
+ A. API contract verification
48
48
  B. Selector / query / engine compatibility
49
- C. Resource cleanup and lifecycle (file handles, connections, processes, locks)
49
+ C. Resource cleanup and lifecycle
50
50
  D. Variable scoping, ordering, and unbound references
51
- E. Dead code: dead parameters, dead locals, dead imports, dead branches, dead returns, and unused imports
52
- F. Silent failures (catch-all excepts, unconditional success returns, missing error propagation)
53
- G. Off-by-one, bounds, and integer overflow
54
- H. Security boundaries (injection, path traversal, auth bypass, secret leakage)
55
- I. Concurrency hazards (race conditions, missing awaits, shared mutable state)
56
- J. Magic values and configuration drift
57
- K. Codebase conflicts a change updates one site of a pattern but a parallel
58
- site in unchanged code stays stale, producing contradictory behavior;
59
- the diff is internally consistent, the bug emerges only against unchanged
60
- code (canonical example: jl-cmd/claude-code-config PR #397 r3210166636)
61
- L. Behavior-equivalence for refactors. When the PR rewrites an existing
62
- function (especially an enforcement check, parser, or path classifier),
63
- compare the rewrite's edge-case handling against the sibling implementation
64
- at the same git commit base. Pin the historically-valid inputs in a
65
- `KNOWN_GOOD_INPUTS` table and assert each still passes. Cited in audits:
66
- ccc#479 F1 (`#noqa` no-space variant dropped after a tokenize-based
67
- refactor); ccc#479 F4 (bare `#` lookalike misclassified after refactor);
68
- ccc#479 F5 (inline `#!` lookalike misclassified); ccc#479 F6 (early-exit
69
- invariant dropped); ccc#472 F44 (`startswith('## Problem')` too loose vs
70
- the sibling regex shape).
71
- M. Producer/consumer cardinality vs collection-type contract. For each new
72
- function returning `list[X]`, `Sequence[X]`, or `Iterable[X]`, ask
73
- whether the return can contain duplicates and whether any downstream
74
- consumer treats the value as a set. Subprocess-stdout parsers must return
75
- `frozenset[Path]` or `dict.fromkeys`-deduplicated `list[Path]`.
76
- Functions whose consumer is itself an `extend(...)` into a list pass;
77
- functions with explicit "duplicates preserved" docstring text pass.
78
- Cited in audits: pa#143 F10 (`_extract_paths_from_everything_cli_stdout`
79
- duplicates → `RuntimeError` — the only High-severity crash bug in the
80
- audit set); pa#136 F30 / F32 (duplicate content_id rows submit twice;
81
- writeback ignores content_id key).
82
- N. Test-name claims a scenario the body does not enter. Tests named
83
- `test_*_at_*`, `test_*_under_*`, `test_*_when_*`, and `test_*_with_*`
84
- must, via monkeypatch / fixture inspection, demonstrate the named
85
- condition is in effect when the system under test runs. Path-decision
86
- functions (registered in `*_path_exemptions.py` / `is_*_path` /
87
- `_resolve_*_path` modules) must ship with a parametric matrix of
88
- canonical edge cases (empty string, single filename, tilde, UNC,
89
- drive-letter, symlinked, `..`-containing, trailing-slash). Tests with
90
- neutral names (`test_returns_empty_list_on_x`) are unaffected. Cited
91
- in audits: ccc#476 F5 / F21 / F23 / F26 / F27 (cross-platform
92
- scenarios never exercised under the claimed conditions); pa#135 F11 /
93
- F15 (string-shape and integration tests that exercise only the no-op
94
- branch); pa#136 F50 (`<substring> not in executed_sql` assertion that
95
- cannot fail because the substring shape never matches the real
96
- fragment).
51
+ E. Dead code and unused imports
52
+ F. Silent failures
53
+ G. Off-by-one, bounds, integer overflow
54
+ H. Security boundaries
55
+ I. Concurrency hazards
56
+ J. CODE_RULES.md compliance
57
+ K. Codebase conflicts (incomplete propagation)
58
+ L. Behavior-equivalence for refactors
59
+ M. Producer/consumer cardinality vs collection-type contract
60
+ N. Test-name scenario verifier
97
61
  </bug_categories>
98
62
 
63
+ <rubric_reference>
64
+ The category list above is a summary. The binding definition of each
65
+ category is its rubric file under
66
+ `$HOME/.claude/audit-rubrics/category_rubrics/` (ready-to-send prompt
67
+ variants under `$HOME/.claude/audit-rubrics/prompts/`). Read the rubric
68
+ files before auditing.
69
+ </rubric_reference>
70
+
99
71
  <constraints>
100
72
  - Read-only on source code: the audit does not modify any source file.
101
73
  - Cite file:line for every finding.
@@ -11,10 +11,10 @@ description: >-
11
11
  # Bugteam
12
12
 
13
13
  Audit–fix until convergence. Bugfind: `code-quality-agent`, fresh context each
14
- loop, auditing all A–K categories. Bugfix: `clean-coder`. Hard cap: 20 audit
14
+ loop, auditing all A–N categories. Bugfix: `clean-coder`. Hard cap: 20 audit
15
15
  loops. Grant `.claude/**` at start, revoke always at end.
16
16
 
17
- The audit agent loads the A–K category rubrics from
17
+ The audit agent loads the A–N category rubrics from
18
18
  `$HOME/.claude/audit-rubrics/{category_rubrics,prompts}/` alongside
19
19
  [`PROMPTS.md`](PROMPTS.md) and produces a single outcome XML per loop.
20
20
 
@@ -146,7 +146,7 @@ end-to-end mental model before starting Step 0.
146
146
  | Posting the end-of-pass audit review via `post_audit_thread.py` (APPROVE on CLEAN — the request event; GitHub stores it as `state=APPROVED` — REQUEST_CHANGES with inline anchored comments on DIRTY) | [§ Audit posting](#audit-posting) |
147
147
  | Posting per-finding fix replies via GitHub MCP `add_reply_to_pull_request_comment` (rendered with the unified template at [`_shared/pr-loop/audit-reply-template.md`](../../_shared/pr-loop/audit-reply-template.md)) | [reference/github-pr-reviews.md](reference/github-pr-reviews.md) |
148
148
  | Teardown, PR description rewrite via `pr-description-writer`, permission revoke, final report | [reference/teardown-publish-permissions.md](reference/teardown-publish-permissions.md) |
149
- | Spawn-prompt XML, A–K category bindings, outcome XML schemas | [PROMPTS.md](PROMPTS.md) |
149
+ | Spawn-prompt XML, A–N category bindings, outcome XML schemas | [PROMPTS.md](PROMPTS.md) |
150
150
  | Per-category audit content (sub-buckets, decision criteria, ready-to-send Variant C templates) | `$HOME/.claude/audit-rubrics/{category_rubrics,prompts}/` |
151
151
  | Invariants and design rationale | [CONSTRAINTS.md](CONSTRAINTS.md), [reference/design-rationale.md](reference/design-rationale.md) |
152
152
  | Audit-contract finding shape (Shape A / B), Haiku secondary, post-fix self-audit | [reference/audit-contract.md](reference/audit-contract.md) |
@@ -159,11 +159,11 @@ end-to-end mental model before starting Step 0.
159
159
  - `SKILL.md` — this hub.
160
160
  - `reference/` — workflow detail per situation.
161
161
  - `scripts/` — utility scripts executed, not loaded as primary context.
162
- - `PROMPTS.md` — spawn XML, A–K category bindings, outcome schemas.
162
+ - `PROMPTS.md` — spawn XML, A–N category bindings, outcome schemas.
163
163
  - `CONSTRAINTS.md` — invariants.
164
164
  - `EXAMPLES.md` — exit scenarios.
165
165
  - `sources.md` — doc URLs and verbatim quotes.
166
166
  - `~/.claude/audit-rubrics/` — installed by `npx claude-dev-env` from
167
- `packages/claude-dev-env/audit-rubrics/`; the audit agent reads all A–K
167
+ `packages/claude-dev-env/audit-rubrics/`; the audit agent reads all A–N
168
168
  rubrics under `category_rubrics/` and prompts under `prompts/`. Required
169
169
  at audit time alongside `PROMPTS.md`.
@@ -116,7 +116,7 @@ Repeat until an exit condition fires.
116
116
 
117
117
  ## AUDIT action
118
118
 
119
- Spawn one audit agent that walks all A–K categories:
119
+ Spawn one audit agent that walks all A–N categories:
120
120
 
121
121
  ```
122
122
  Agent(
@@ -21,7 +21,7 @@ Each finding an audit produces MUST be one of exactly two shapes.
21
21
  "id": "loop<L>-<K>",
22
22
  "file": "path/relative/to/repo/root.py",
23
23
  "line": 123,
24
- "category": "A | B | C | D | E | F | G | H | I | J | K",
24
+ "category": "A | B | C | D | E | F | G | H | I | J | K | L | M | N",
25
25
  "severity": "P0 | P1 | P2",
26
26
  "excerpt": "verbatim code snippet from the offending line(s)",
27
27
  "failure_mode": "one sentence describing what goes wrong and when",
@@ -37,7 +37,7 @@ Used when an audit investigates a category and does NOT find a bug. Bare "verifi
37
37
 
38
38
  ```json
39
39
  {
40
- "category": "A | B | C | D | E | F | G | H | I | J | K",
40
+ "category": "A | B | C | D | E | F | G | H | I | J | K | L | M | N",
41
41
  "files_opened": ["file1.py", "file2.py"],
42
42
  "lines_quoted": [
43
43
  {"file": "file1.py", "line": 88, "text": "verbatim line content"}
@@ -89,7 +89,7 @@ After the primary finding list is complete, every audit runs a second pass again
89
89
 
90
90
  The audit must either produce new Shape A findings citing new file:line references not present in the first pass, or cite explicit Shape B adversarial-probe entries for each category it re-examined. An adversarial pass that returns "nothing new, confident first pass was complete" is REJECTED — produce evidence or findings, not confidence.
91
91
 
92
- For `/bugteam`, the single audit agent provides per-category coverage by walking all A–K rubrics in one invocation.
92
+ For `/bugteam`, the single audit agent provides per-category coverage by walking all A–N rubrics in one invocation.
93
93
 
94
94
  ## Merge rules
95
95
 
@@ -113,7 +113,7 @@ Sequence:
113
113
  3. Run `py_compile` (or language-equivalent) on each modified file.
114
114
  4. Compute `fix_diff` against pre-fix contents for the modified set.
115
115
  5. Run `bugteam_code_rules_gate.py` with explicit paths for every modified file.
116
- 6. Spawn a scoped audit of `fix_diff` with full A–K rigor, Shape A/B contract, adversarial pass, AND Haiku secondary in parallel (paranoid mode on post-fix).
116
+ 6. Spawn a scoped audit of `fix_diff` with full A–N rigor, Shape A/B contract, adversarial pass, AND Haiku secondary in parallel (paranoid mode on post-fix).
117
117
  7. Any new findings become same-loop fix-targets. Internal iteration count increments by one.
118
118
  8. After 3 internal iterations with fresh findings each time, exit `stuck: post-fix audit not converging`.
119
119
  9. Only when `gate_findings` empty AND `post_fix_findings` empty: `git add`, commit, push.
@@ -2,7 +2,7 @@
2
2
 
3
3
  ## Core principle (expanded)
4
4
 
5
- One audit agent (`code-quality-agent`, opus) walks all A–K categories per loop. One fix agent (`clean-coder`, opus) addresses the audit's findings.
5
+ One audit agent (`code-quality-agent`, opus) walks all A–N categories per loop. One fix agent (`clean-coder`, opus) addresses the audit's findings.
6
6
 
7
7
  Fresh-spawn clean-room isolation: each `Agent` call creates a new subagent with its own context window and no access to prior conversation. After the subagent writes its outcome XML and self-terminates, the lead reads the file. Results never accumulate in the lead’s context beyond the XML artifact. Verbatim Anthropic quotes and URLs: [`../sources.md`](../sources.md).
8
8
 
@@ -88,16 +88,25 @@ The XML prompt skeleton:
88
88
 
89
89
  <bug_categories>
90
90
  Investigate each explicitly:
91
- A. API contract verification (signatures, return types, async/await correctness)
91
+ A. API contract verification
92
92
  B. Selector / query / engine compatibility
93
- C. Resource cleanup and lifecycle (file handles, connections, processes, locks)
93
+ C. Resource cleanup and lifecycle
94
94
  D. Variable scoping, ordering, and unbound references
95
95
  E. Dead code and unused imports
96
- F. Silent failures (catch-all excepts, unconditional success returns, missing error propagation)
97
- G. Off-by-one, bounds, and integer overflow
98
- H. Security boundaries (injection, path traversal, auth bypass, secret leakage)
99
- I. Concurrency hazards (race conditions, missing awaits, shared mutable state)
100
- J. Magic values and configuration drift
96
+ F. Silent failures
97
+ G. Off-by-one, bounds, integer overflow
98
+ H. Security boundaries
99
+ I. Concurrency hazards
100
+ J. CODE_RULES.md compliance
101
+ K. Codebase conflicts (incomplete propagation)
102
+ L. Behavior-equivalence for refactors
103
+ M. Producer/consumer cardinality vs collection-type contract
104
+ N. Test-name scenario verifier
105
+
106
+ The category list above is a summary. The binding definition of each
107
+ category is its rubric file under $HOME/.claude/audit-rubrics/category_rubrics/
108
+ (ready-to-send prompt variants under $HOME/.claude/audit-rubrics/prompts/).
109
+ Read the rubric files before auditing.
101
110
  </bug_categories>
102
111
 
103
112
  <constraints>
@@ -256,18 +265,18 @@ Want me to run /fixbugs for the P0/P1 findings?
256
265
  User: `/findbugs`
257
266
  Claude: [resolves PR #42 from current branch, fetches full diff, spawns code-quality-agent foreground with self-contained prompt, returns]
258
267
 
259
- `1 P0 / 2 P1 / 0 P2 — 7 categories cleared`
268
+ `1 P0 / 2 P1 / 0 P2 — 11 categories cleared`
260
269
 
261
270
  `P0 — race condition on shared cache write`
262
- ` src/cache.py:88 — concurrent writers can both pass the existence check before either writes (category: concurrency)`
271
+ ` src/cache.py:88 — concurrent writers can both pass the existence check before either writes (category: I — Concurrency hazards)`
263
272
 
264
273
  `P1 — silent paste failure`
265
- ` utils/clipboard.py:33 — validated_paste returns success without verifying the post-paste state (category: silent failure)`
274
+ ` utils/clipboard.py:33 — validated_paste returns success without verifying the post-paste state (category: F — Silent failures)`
266
275
 
267
276
  `P1 — unbound variable on early-exception path`
268
- ` src/processor.py:283 — scheduling_log referenced after try/finally where it may be unbound (category: scoping)`
277
+ ` src/processor.py:283 — scheduling_log referenced after try/finally where it may be unbound (category: D — Variable scoping, ordering, and unbound references)`
269
278
 
270
- `Verified clean: API contract, selector compatibility, resource cleanup, dead code, off-by-one, security boundaries, magic values`
279
+ `Verified clean: API contract, selector compatibility, resource cleanup, dead code, off-by-one, security boundaries, CODE_RULES.md compliance, codebase conflicts, behavior-equivalence, producer/consumer cardinality, Test-name scenario verifier`
271
280
 
272
281
  `Open questions: none`
273
282
 
@@ -33,7 +33,7 @@ Locate the most recent `/findbugs` output in the current conversation. For each
33
33
 
34
34
  - Severity (`P0` / `P1` / `P2`)
35
35
  - `file:line`
36
- - Category (the A–J letter or category name `/findbugs` reported)
36
+ - Category (the A–N letter or category name `/findbugs` reported)
37
37
  - One-sentence description as `/findbugs` wrote it
38
38
 
39
39
  Apply the severity filter from `$ARGUMENTS` if present:
@@ -3,7 +3,7 @@ name: qbug
3
3
  description: >-
4
4
  Required baseline review for every new PR. Runs the /bugteam audit → fix →
5
5
  commit → push cycle via one clean-coder subagent (not a full team), looping
6
- until convergence or stuck. Uses the same CODE_RULES gate, A–J category
6
+ until convergence or stuck. Uses the same CODE_RULES gate, A–N category
7
7
  rubric, and per-loop PR review shape as /bugteam — without TeamCreate,
8
8
  teammates, per-loop clean-room, or a loop cap. Invoke /bugteam instead for
9
9
  larger PRs that need per-loop bias isolation or a hard loop cap. Triggers:
@@ -21,7 +21,7 @@ Shared artifacts with /bugteam are referenced below by path, using the `${CLAUDE
21
21
 
22
22
  - Pre-flight script: `${CLAUDE_SKILL_DIR}/../../_shared/pr-loop/scripts/preflight.py`
23
23
  - Code-rules gate script: `${CLAUDE_SKILL_DIR}/../../_shared/pr-loop/scripts/code_rules_gate.py`
24
- - Bug category rubric A–J: [`bugteam/PROMPTS.md`](../bugteam/PROMPTS.md#audit-spawn-prompt-xml-bugfind-teammate)
24
+ - Bug category rubric A–N: [`bugteam/PROMPTS.md`](../bugteam/PROMPTS.md#audit-spawn-prompt-xml-bugfind-teammate)
25
25
  - **Audit contract** (finding schema, proof-of-absence, adversarial pass, Haiku secondary, post-fix self-audit, diagnostics JSON): [`bugteam/reference/audit-contract.md`](../bugteam/reference/audit-contract.md)
26
26
  - PR comment lifecycle shape: [`bugteam/SKILL.md`](../bugteam/SKILL.md#audit-posting)
27
27
 
@@ -117,7 +117,7 @@ Agent(
117
117
  subagent_type="code-quality-agent",
118
118
  model="haiku",
119
119
  description="qbug Haiku secondary audit for PR <number>",
120
- prompt="<audit-only prompt: read the PR diff, apply A-J categories from <categories_file>, return structured findings. No FIX, no git add, no git commit, no git push.>",
120
+ prompt="<audit-only prompt: read the PR diff, apply A-N categories from <categories_file>, return structured findings. No FIX, no git add, no git commit, no git push.>",
121
121
  run_in_background=False
122
122
  )
123
123
  ```
@@ -188,7 +188,7 @@ The subagent receives this prompt and loops internally — the lead does not re-
188
188
 
189
189
  - Read the patch file.
190
190
  - Audit only added/modified lines. Read <categories_file> for the
191
- A–J category definitions; investigate each category explicitly.
191
+ A–N category definitions; investigate each category explicitly.
192
192
  - Follow the shared audit contract at
193
193
  bugteam/reference/audit-contract.md. Per category: produce
194
194
  either a Shape A structured finding or a Shape B structured
@@ -444,7 +444,7 @@ Delete the resolved `<qbug_temp_dir>` tree and any `.qbug-*.md` temp files in th
444
444
  - **No loop cap.** Cycle runs until `converged`, `stuck`, or `error`. User can interrupt.
445
445
  - **Code rules gate before every AUDIT.** Same `validate_content` logic as /bugteam.
446
446
  - **One commit per FIX action.** Linear branch, fast-forward push only.
447
- - **Categories A–J.** Same rubric as [`bugteam/PROMPTS.md`](../bugteam/PROMPTS.md).
447
+ - **Categories A–N.** Same rubric as [`bugteam/PROMPTS.md`](../bugteam/PROMPTS.md).
448
448
  - **One review per loop.** Anchored findings as `comments[]`; unanchored findings surface in the calling skill's user-facing output (chat reply to the user) rather than in the PR review body.
449
449
  - **PR description rewrite on every exit**, same as /bugteam Step 4.5.
450
450
  - **Temp file cleanup on every exit path.**