claude-dev-env 1.44.0 → 1.46.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.
Files changed (44) hide show
  1. package/CLAUDE.md +9 -0
  2. package/_shared/pr-loop/scripts/code_rules_gate.py +426 -85
  3. package/_shared/pr-loop/scripts/pr_loop_shared_constants/code_rules_gate_constants.py +20 -0
  4. package/_shared/pr-loop/scripts/pr_loop_shared_constants/reviews_disabled_constants.py +1 -0
  5. package/_shared/pr-loop/scripts/reviews_disabled.py +82 -9
  6. package/_shared/pr-loop/scripts/tests/test_code_rules_gate.py +630 -21
  7. package/_shared/pr-loop/scripts/tests/test_code_rules_gate_constants.py +15 -0
  8. package/_shared/pr-loop/scripts/tests/test_reviews_disabled.py +57 -0
  9. package/agents/clean-coder.md +7 -1
  10. package/agents/code-quality-agent.md +8 -5
  11. package/hooks/blocking/code_rules_enforcer.py +1562 -37
  12. package/hooks/blocking/content_search_zoekt_redirect_guidance.py +19 -0
  13. package/hooks/blocking/open_questions_in_plans_blocker.py +249 -0
  14. package/hooks/blocking/test_code_rules_enforcer.py +1389 -0
  15. package/hooks/blocking/test_code_rules_enforcer_banned_noun_word.py +292 -0
  16. package/hooks/blocking/test_code_rules_enforcer_cap_meta.py +46 -8
  17. package/hooks/blocking/test_code_rules_enforcer_exempt_marker_chained.py +189 -0
  18. package/hooks/blocking/test_code_rules_enforcer_function_length.py +210 -0
  19. package/hooks/blocking/test_code_rules_enforcer_tests_isolate_home_temp.py +1512 -0
  20. package/hooks/blocking/test_code_rules_enforcer_unused_imports.py +9 -5
  21. package/hooks/blocking/test_content_search_to_zoekt_redirector_unit.py +30 -0
  22. package/hooks/blocking/test_open_questions_in_plans_blocker.py +790 -0
  23. package/hooks/hooks.json +10 -0
  24. package/hooks/hooks_constants/banned_identifiers_constants.py +19 -0
  25. package/hooks/hooks_constants/code_rules_enforcer_constants.py +129 -2
  26. package/hooks/hooks_constants/open_questions_in_plans_blocker_constants.py +35 -0
  27. package/hooks/hooks_constants/test_open_questions_in_plans_blocker_constants.py +125 -0
  28. package/package.json +1 -1
  29. package/skills/_shared/pr-loop/scripts/_path_resolver.py +34 -13
  30. package/skills/_shared/pr-loop/scripts/init_loop_state.py +1 -2
  31. package/skills/_shared/pr-loop/scripts/teardown_worktrees.py +1 -4
  32. package/skills/_shared/pr-loop/scripts/test__path_resolver.py +57 -0
  33. package/skills/_shared/pr-loop/scripts/test_init_loop_state.py +48 -0
  34. package/skills/_shared/pr-loop/scripts/test_teardown_worktrees.py +59 -0
  35. package/skills/bugteam/PROMPTS.md +48 -12
  36. package/skills/bugteam/reference/team-setup.md +4 -2
  37. package/skills/bugteam/scripts/bugteam_code_rules_gate.py +487 -76
  38. package/skills/bugteam/scripts/bugteam_scripts_constants/bugteam_code_rules_gate_constants.py +22 -1
  39. package/skills/bugteam/scripts/test_bugteam_code_rules_gate.py +602 -12
  40. package/skills/pr-converge/SKILL.md +5 -0
  41. package/skills/pr-converge/reference/per-tick.md +14 -5
  42. package/skills/pr-converge/reference/state-schema.md +7 -3
  43. package/skills/pr-converge/scripts/check_convergence.py +27 -1
  44. package/skills/pr-converge/scripts/test_check_convergence.py +28 -0
@@ -30,7 +30,7 @@ assert _hook_spec.loader is not None
30
30
  _hook_module = importlib.util.module_from_spec(_hook_spec)
31
31
  _hook_spec.loader.exec_module(_hook_module)
32
32
  check_unused_module_level_imports = _hook_module.check_unused_module_level_imports
33
- _reconstruct_post_edit_file_content = _hook_module._reconstruct_post_edit_file_content
33
+ prior_and_post_edit_content = _hook_module.prior_and_post_edit_content
34
34
 
35
35
 
36
36
  PRODUCTION_FILE_PATH = "packages/app/services/loader.py"
@@ -414,9 +414,10 @@ def test_should_fall_back_when_full_file_content_has_syntax_error() -> None:
414
414
  def test_reconstruct_post_edit_returns_replaced_content(tmp_path: pathlib.Path) -> None:
415
415
  target_file = tmp_path / "module.py"
416
416
  target_file.write_text("ALPHA = 1\nBETA = 2\n", encoding="utf-8")
417
- post_edit = _reconstruct_post_edit_file_content(
417
+ prior_content, post_edit = prior_and_post_edit_content(
418
418
  str(target_file), "BETA = 2", "BETA = 22\nGAMMA = 3",
419
419
  )
420
+ assert prior_content == "ALPHA = 1\nBETA = 2\n"
420
421
  assert post_edit == "ALPHA = 1\nBETA = 22\nGAMMA = 3\n", (
421
422
  f"Helper must return the file body with the first occurrence replaced, got: {post_edit!r}"
422
423
  )
@@ -424,9 +425,10 @@ def test_reconstruct_post_edit_returns_replaced_content(tmp_path: pathlib.Path)
424
425
 
425
426
  def test_reconstruct_post_edit_returns_none_when_file_missing(tmp_path: pathlib.Path) -> None:
426
427
  missing_file = tmp_path / "does_not_exist.py"
427
- post_edit = _reconstruct_post_edit_file_content(
428
+ prior_content, post_edit = prior_and_post_edit_content(
428
429
  str(missing_file), "any_old", "any_new",
429
430
  )
431
+ assert prior_content is None
430
432
  assert post_edit is None, (
431
433
  f"Missing file must yield None so the caller treats it as 'no full-file context', got: {post_edit!r}"
432
434
  )
@@ -435,9 +437,10 @@ def test_reconstruct_post_edit_returns_none_when_file_missing(tmp_path: pathlib.
435
437
  def test_reconstruct_post_edit_returns_none_when_old_string_absent(tmp_path: pathlib.Path) -> None:
436
438
  target_file = tmp_path / "module.py"
437
439
  target_file.write_text("ALPHA = 1\n", encoding="utf-8")
438
- post_edit = _reconstruct_post_edit_file_content(
440
+ prior_content, post_edit = prior_and_post_edit_content(
439
441
  str(target_file), "ZETA = 9", "OMEGA = 0",
440
442
  )
443
+ assert prior_content is None
441
444
  assert post_edit is None, (
442
445
  f"Absent old_string means the Edit will not apply cleanly — return None, got: {post_edit!r}"
443
446
  )
@@ -446,9 +449,10 @@ def test_reconstruct_post_edit_returns_none_when_old_string_absent(tmp_path: pat
446
449
  def test_reconstruct_post_edit_replaces_only_first_occurrence(tmp_path: pathlib.Path) -> None:
447
450
  target_file = tmp_path / "module.py"
448
451
  target_file.write_text("X = 1\nX = 1\n", encoding="utf-8")
449
- post_edit = _reconstruct_post_edit_file_content(
452
+ prior_content, post_edit = prior_and_post_edit_content(
450
453
  str(target_file), "X = 1", "X = 2",
451
454
  )
455
+ assert prior_content == "X = 1\nX = 1\n"
452
456
  assert post_edit == "X = 2\nX = 1\n", (
453
457
  "Edit replaces only the first occurrence; helper must mirror that, got: "
454
458
  f"{post_edit!r}"
@@ -14,6 +14,8 @@ from content_search_zoekt_block_payload import build_block_payload
14
14
  from content_search_zoekt_redirect_guidance import (
15
15
  get_zoekt_redirect_guidance,
16
16
  get_zoekt_redirect_reason_brief,
17
+ worktree_path_display_fragment,
18
+ worktree_path_filter_fragment,
17
19
  )
18
20
 
19
21
 
@@ -52,5 +54,33 @@ class BuildBlockPayloadTests(unittest.TestCase):
52
54
  )
53
55
 
54
56
 
57
+ class RedirectGuidanceWorktreeTests(unittest.TestCase):
58
+ def test_guidance_defaults_to_excluding_worktrees(self) -> None:
59
+ guidance = get_zoekt_redirect_guidance()
60
+ expected_default_exclusion = f"-file:{worktree_path_filter_fragment()}"
61
+ self.assertIn(expected_default_exclusion, guidance)
62
+
63
+ def test_guidance_explains_how_to_search_a_worktree(self) -> None:
64
+ guidance = get_zoekt_redirect_guidance()
65
+ positive_filter_example = (
66
+ f'query="your pattern file:{worktree_path_filter_fragment()}<branch>/'
67
+ )
68
+ self.assertIn(positive_filter_example, guidance)
69
+ self.assertIn("worktree", guidance.lower())
70
+
71
+ def test_worktree_filter_fragment_is_a_regex_escaped_path(self) -> None:
72
+ self.assertEqual(worktree_path_filter_fragment(), "\\.claude/worktrees/")
73
+
74
+ def test_guidance_describes_worktree_path_unescaped_in_prose(self) -> None:
75
+ guidance = get_zoekt_redirect_guidance()
76
+ prose_substring = (
77
+ f"git worktrees under {worktree_path_display_fragment()} that"
78
+ )
79
+ self.assertIn(prose_substring, guidance)
80
+
81
+ def test_worktree_display_fragment_is_the_unescaped_path(self) -> None:
82
+ self.assertEqual(worktree_path_display_fragment(), ".claude/worktrees/")
83
+
84
+
55
85
  if __name__ == "__main__":
56
86
  unittest.main()