crackerjack 0.37.8__py3-none-any.whl → 0.38.0__py3-none-any.whl

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.

Potentially problematic release.


This version of crackerjack might be problematic. Click here for more details.

@@ -397,13 +397,29 @@ class ReferenceGenerator:
397
397
 
398
398
  arg_index = func_node.args.args.index(arg)
399
399
  if arg_index >= defaults_start:
400
- default_index = arg_index - defaults_start
401
- default_node = func_node.args.defaults[default_index]
402
- if isinstance(default_node, ast.Constant):
403
- return default_node.value, False
400
+ return self._extract_argument_default(arg_index, defaults_start, func_node)
404
401
 
405
402
  return None, True
406
403
 
404
+ def _extract_argument_default(
405
+ self, arg_index: int, defaults_start: int, func_node: ast.FunctionDef
406
+ ) -> tuple[t.Any, bool]:
407
+ """Extract default value for a specific argument.
408
+
409
+ Args:
410
+ arg_index: Index of the argument
411
+ defaults_start: Index where defaults start
412
+ func_node: Parent function node
413
+
414
+ Returns:
415
+ Tuple of (default_value, required)
416
+ """
417
+ default_index = arg_index - defaults_start
418
+ default_node = func_node.args.defaults[default_index]
419
+ if isinstance(default_node, ast.Constant):
420
+ return default_node.value, False
421
+ return None, True
422
+
407
423
  async def _enhance_with_examples(
408
424
  self, commands: dict[str, CommandInfo]
409
425
  ) -> dict[str, CommandInfo]:
@@ -416,35 +432,47 @@ class ReferenceGenerator:
416
432
  Enhanced commands with examples
417
433
  """
418
434
  for command in commands.values():
419
- # Generate basic examples
420
- basic_example = f"python -m crackerjack --{command.name}"
421
-
422
- # Add parameter examples
423
- param_examples = []
424
- for param in command.parameters:
425
- if not param.required and param.default_value is not None:
426
- if isinstance(param.default_value, bool):
427
- param_examples.append(f"--{param.name}")
428
- else:
429
- param_examples.append(f"--{param.name} {param.default_value}")
430
-
431
- if param_examples:
432
- enhanced_example = f"{basic_example} {' '.join(param_examples)}"
433
- command.examples.append(
434
- {
435
- "description": f"Using {command.name} with parameters",
436
- "command": enhanced_example,
437
- }
438
- )
435
+ self._add_basic_example(command)
436
+ self._add_parameter_examples(command)
437
+ return commands
439
438
 
439
+ def _add_basic_example(self, command: CommandInfo) -> None:
440
+ """Add a basic example for a command."""
441
+ basic_example = f"python -m crackerjack --{command.name}"
442
+ command.examples.append(
443
+ {
444
+ "description": f"Basic {command.name} usage",
445
+ "command": basic_example,
446
+ }
447
+ )
448
+
449
+ def _add_parameter_examples(self, command: CommandInfo) -> None:
450
+ """Add parameter examples for a command."""
451
+ # Generate basic examples
452
+ basic_example = f"python -m crackerjack --{command.name}"
453
+
454
+ # Add parameter examples
455
+ param_examples = []
456
+ for param in command.parameters:
457
+ if not param.required and param.default_value is not None:
458
+ param_example = self._format_parameter_example(param)
459
+ if param_example:
460
+ param_examples.append(param_example)
461
+
462
+ if param_examples:
463
+ enhanced_example = f"{basic_example} {' '.join(param_examples)}"
440
464
  command.examples.append(
441
465
  {
442
- "description": f"Basic {command.name} usage",
443
- "command": basic_example,
466
+ "description": f"Using {command.name} with parameters",
467
+ "command": enhanced_example,
444
468
  }
445
469
  )
446
470
 
447
- return commands
471
+ def _format_parameter_example(self, param: ParameterInfo) -> str | None:
472
+ """Format a parameter example."""
473
+ if isinstance(param.default_value, bool):
474
+ return f"--{param.name}"
475
+ return f"--{param.name} {param.default_value}"
448
476
 
449
477
  async def _enhance_with_workflows(
450
478
  self, commands: dict[str, CommandInfo]
@@ -457,44 +485,74 @@ class ReferenceGenerator:
457
485
  Returns:
458
486
  Enhanced commands with workflow info
459
487
  """
460
- # Define common workflow patterns
461
- workflow_patterns = {
488
+ workflow_patterns = self._get_workflow_patterns()
489
+
490
+ for command in commands.values():
491
+ self._assign_command_workflows(command, workflow_patterns)
492
+ self._add_ai_context_to_command(command)
493
+
494
+ return commands
495
+
496
+ def _get_workflow_patterns(self) -> dict[str, list[str]]:
497
+ """Get workflow patterns for command categorization.
498
+
499
+ Returns:
500
+ Dictionary mapping workflow names to pattern lists
501
+ """
502
+ return {
462
503
  "development": ["test", "format", "lint", "type-check"],
463
504
  "release": ["version", "build", "publish", "tag"],
464
505
  "maintenance": ["clean", "update", "optimize", "backup"],
465
506
  "monitoring": ["status", "health", "metrics", "logs"],
466
507
  }
467
508
 
468
- for command in commands.values():
469
- # Assign workflows based on command name patterns
470
- for workflow, patterns in workflow_patterns.items():
471
- if any(pattern in command.name for pattern in patterns):
472
- command.common_workflows.append(workflow)
473
-
474
- # Add AI context based on command purpose
475
- if "test" in command.name:
476
- command.ai_context.update(
477
- {
478
- "purpose": "quality_assurance",
479
- "automation_level": "high",
480
- "ai_agent_compatible": True,
481
- }
482
- )
483
- command.success_patterns.append("All tests passed")
484
- command.failure_patterns.append("Test failures detected")
485
-
486
- elif "format" in command.name or "lint" in command.name:
487
- command.ai_context.update(
488
- {
489
- "purpose": "code_quality",
490
- "automation_level": "high",
491
- "ai_agent_compatible": True,
492
- }
493
- )
494
- command.success_patterns.append("No formatting issues")
495
- command.failure_patterns.append("Style violations found")
509
+ def _assign_command_workflows(
510
+ self, command: CommandInfo, workflow_patterns: dict[str, list[str]]
511
+ ) -> None:
512
+ """Assign workflows to a command based on name patterns.
496
513
 
497
- return commands
514
+ Args:
515
+ command: Command to assign workflows to
516
+ workflow_patterns: Workflow patterns to match against
517
+ """
518
+ for workflow, patterns in workflow_patterns.items():
519
+ if any(pattern in command.name for pattern in patterns):
520
+ command.common_workflows.append(workflow)
521
+
522
+ def _add_ai_context_to_command(self, command: CommandInfo) -> None:
523
+ """Add AI context to a command based on its purpose.
524
+
525
+ Args:
526
+ command: Command to enhance with AI context
527
+ """
528
+ if "test" in command.name:
529
+ self._add_test_ai_context(command)
530
+ elif "format" in command.name or "lint" in command.name:
531
+ self._add_quality_ai_context(command)
532
+
533
+ def _add_test_ai_context(self, command: CommandInfo) -> None:
534
+ """Add AI context for test-related commands."""
535
+ command.ai_context.update(
536
+ {
537
+ "purpose": "quality_assurance",
538
+ "automation_level": "high",
539
+ "ai_agent_compatible": True,
540
+ }
541
+ )
542
+ command.success_patterns.append("All tests passed")
543
+ command.failure_patterns.append("Test failures detected")
544
+
545
+ def _add_quality_ai_context(self, command: CommandInfo) -> None:
546
+ """Add AI context for code quality commands."""
547
+ command.ai_context.update(
548
+ {
549
+ "purpose": "code_quality",
550
+ "automation_level": "high",
551
+ "ai_agent_compatible": True,
552
+ }
553
+ )
554
+ command.success_patterns.append("No formatting issues")
555
+ command.failure_patterns.append("Style violations found")
498
556
 
499
557
  def _categorize_commands(
500
558
  self, commands: dict[str, CommandInfo]
@@ -508,8 +566,18 @@ class ReferenceGenerator:
508
566
  Dictionary of category to command names
509
567
  """
510
568
  categories: dict[str, list[str]] = {}
569
+ category_patterns = self._get_category_patterns()
570
+
571
+ for command in commands.values():
572
+ category = self._determine_command_category(command, category_patterns)
573
+ command.category = category
574
+ self._add_command_to_category(categories, category, command.name)
511
575
 
512
- category_patterns = {
576
+ return categories
577
+
578
+ def _get_category_patterns(self) -> dict[str, list[str]]:
579
+ """Get category patterns for command classification."""
580
+ return {
513
581
  "development": ["test", "format", "lint", "check", "run"],
514
582
  "server": ["server", "start", "stop", "restart", "monitor"],
515
583
  "release": ["version", "bump", "publish", "build", "tag"],
@@ -517,27 +585,22 @@ class ReferenceGenerator:
517
585
  "utilities": ["clean", "help", "info", "status"],
518
586
  }
519
587
 
520
- for command in commands.values():
521
- assigned = False
522
-
523
- # Assign based on patterns
524
- for category, patterns in category_patterns.items():
525
- if any(pattern in command.name for pattern in patterns):
526
- command.category = category
527
- if category not in categories:
528
- categories[category] = []
529
- categories[category].append(command.name)
530
- assigned = True
531
- break
532
-
533
- # Default category
534
- if not assigned:
535
- command.category = "general"
536
- if "general" not in categories:
537
- categories["general"] = []
538
- categories["general"].append(command.name)
539
-
540
- return categories
588
+ def _determine_command_category(
589
+ self, command: CommandInfo, category_patterns: dict[str, list[str]]
590
+ ) -> str:
591
+ """Determine the category for a command based on patterns."""
592
+ for category, patterns in category_patterns.items():
593
+ if any(pattern in command.name for pattern in patterns):
594
+ return category
595
+ return "general"
596
+
597
+ def _add_command_to_category(
598
+ self, categories: dict[str, list[str]], category: str, command_name: str
599
+ ) -> None:
600
+ """Add command to the specified category."""
601
+ if category not in categories:
602
+ categories[category] = []
603
+ categories[category].append(command_name)
541
604
 
542
605
  def _generate_workflows(
543
606
  self, commands: dict[str, CommandInfo]
@@ -632,26 +695,28 @@ class ReferenceGenerator:
632
695
  ]
633
696
 
634
697
  def _render_markdown_categories(self, reference: CommandReference) -> list[str]:
635
- """Render command categories for markdown.
636
-
637
- Args:
638
- reference: Command reference
639
-
640
- Returns:
641
- List of category section lines
642
- """
698
+ """Render command categories for markdown."""
643
699
  category_lines = []
644
700
  for category, command_names in reference.categories.items():
645
- category_lines.extend(
646
- [
647
- f"## {category.title()}",
648
- "",
649
- ]
701
+ category_section = self._render_markdown_category(
702
+ category, reference.commands, command_names
650
703
  )
704
+ category_lines.extend(category_section)
705
+ return category_lines
651
706
 
652
- for command_name in command_names:
653
- command = reference.commands[command_name]
654
- category_lines.extend(self._render_command_markdown(command))
707
+ def _render_markdown_category(
708
+ self, category: str, commands: dict[str, CommandInfo], command_names: list[str]
709
+ ) -> list[str]:
710
+ """Render markdown for a single category."""
711
+ category_lines = [
712
+ f"## {category.title()}",
713
+ "",
714
+ ]
715
+
716
+ for command_name in command_names:
717
+ command = commands[command_name]
718
+ command_lines = self._render_command_markdown(command)
719
+ category_lines.extend(command_lines)
655
720
 
656
721
  return category_lines
657
722
 
@@ -685,14 +750,7 @@ class ReferenceGenerator:
685
750
  return workflow_lines
686
751
 
687
752
  def _render_command_markdown(self, command: CommandInfo) -> list[str]:
688
- """Render single command as Markdown.
689
-
690
- Args:
691
- command: Command to render
692
-
693
- Returns:
694
- List of markdown lines
695
- """
753
+ """Render single command as Markdown."""
696
754
  lines = [
697
755
  f"### `{command.name}`",
698
756
  "",
@@ -702,50 +760,49 @@ class ReferenceGenerator:
702
760
 
703
761
  # Add parameters section
704
762
  if command.parameters:
705
- lines.extend(self._render_command_parameters_markdown(command.parameters))
763
+ param_lines = self._render_command_parameters_markdown(command.parameters)
764
+ lines.extend(param_lines)
706
765
 
707
766
  # Add examples section
708
767
  if command.examples:
709
- lines.extend(self._render_command_examples_markdown(command.examples))
768
+ example_lines = self._render_command_examples_markdown(command.examples)
769
+ lines.extend(example_lines)
710
770
 
711
771
  # Add related commands section
712
772
  if command.related_commands:
713
- lines.extend(
714
- self._render_command_related_markdown(command.related_commands)
773
+ related_lines = self._render_command_related_markdown(
774
+ command.related_commands
715
775
  )
776
+ lines.extend(related_lines)
716
777
 
717
778
  return lines
718
779
 
719
780
  def _render_command_parameters_markdown(
720
781
  self, parameters: list[ParameterInfo]
721
782
  ) -> list[str]:
722
- """Render command parameters for markdown.
723
-
724
- Args:
725
- parameters: List of parameters to render
726
-
727
- Returns:
728
- List of parameter section lines
729
- """
783
+ """Render command parameters for markdown."""
730
784
  param_lines = [
731
785
  "**Parameters:**",
732
786
  "",
733
787
  ]
734
788
 
735
789
  for param in parameters:
736
- required_str = " (required)" if param.required else ""
737
- default_str = (
738
- f" (default: {param.default_value})"
739
- if param.default_value is not None
740
- else ""
741
- )
742
- param_lines.append(
743
- f"- `--{param.name}` ({param.type_hint}){required_str}{default_str}: {param.description}"
744
- )
790
+ param_line = self._format_parameter_line(param)
791
+ param_lines.append(param_line)
745
792
 
746
793
  param_lines.append("")
747
794
  return param_lines
748
795
 
796
+ def _format_parameter_line(self, param: ParameterInfo) -> str:
797
+ """Format a single parameter line."""
798
+ required_str = " (required)" if param.required else ""
799
+ default_str = (
800
+ f" (default: {param.default_value})"
801
+ if param.default_value is not None
802
+ else ""
803
+ )
804
+ return f"- `--{param.name}` ({param.type_hint}){required_str}{default_str}: {param.description}"
805
+
749
806
  def _render_command_examples_markdown(
750
807
  self, examples: list[dict[str, str]]
751
808
  ) -> list[str]:
@@ -794,12 +851,14 @@ class ReferenceGenerator:
794
851
 
795
852
  def _render_html(self, reference: CommandReference) -> str:
796
853
  """Render reference as HTML."""
797
- html = self._render_html_header(
798
- reference.generated_at.strftime("%Y-%m-%d %H:%M:%S")
799
- )
800
- html += self._render_html_commands(reference)
801
- html += "</body></html>"
802
- return html
854
+ html_parts = [
855
+ self._render_html_header(
856
+ reference.generated_at.strftime("%Y-%m-%d %H:%M:%S")
857
+ ),
858
+ self._render_html_commands(reference),
859
+ "</body></html>",
860
+ ]
861
+ return "".join(html_parts)
803
862
 
804
863
  def _render_html_header(self, generated_at: str) -> str:
805
864
  """Render HTML header with styles and metadata."""
@@ -822,26 +881,40 @@ class ReferenceGenerator:
822
881
 
823
882
  def _render_html_commands(self, reference: CommandReference) -> str:
824
883
  """Render HTML commands by category."""
825
- html = ""
884
+ html_parts = []
826
885
  for category, command_names in reference.categories.items():
827
- html += f"<h2>{category.title()}</h2>"
828
- html += self._render_html_category_commands(
829
- reference.commands, command_names
886
+ category_html = self._render_html_category(
887
+ category, reference.commands, command_names
830
888
  )
889
+ html_parts.append(category_html)
890
+ return "".join(html_parts)
891
+
892
+ def _render_html_category(
893
+ self, category: str, commands: dict[str, CommandInfo], command_names: list[str]
894
+ ) -> str:
895
+ """Render HTML for a single category."""
896
+ html = f"<h2>{category.title()}</h2>"
897
+ html += self._render_html_category_commands(commands, command_names)
831
898
  return html
832
899
 
833
900
  def _render_html_category_commands(
834
901
  self, commands: dict[str, CommandInfo], command_names: list[str]
835
902
  ) -> str:
836
903
  """Render HTML for commands in a category."""
837
- html = ""
904
+ html_parts = []
838
905
  for command_name in command_names:
839
906
  command = commands[command_name]
840
- html += '<div class="command">'
841
- html += f"<h3><code>{command.name}</code></h3>"
842
- html += f"<p>{command.description}</p>"
843
- html += self._render_html_command_parameters(command.parameters)
844
- html += "</div>"
907
+ command_html = self._render_single_html_command(command)
908
+ html_parts.append(command_html)
909
+ return "".join(html_parts)
910
+
911
+ def _render_single_html_command(self, command: CommandInfo) -> str:
912
+ """Render HTML for a single command."""
913
+ html = '<div class="command">'
914
+ html += f"<h3><code>{command.name}</code></h3>"
915
+ html += f"<p>{command.description}</p>"
916
+ html += self._render_html_command_parameters(command.parameters)
917
+ html += "</div>"
845
918
  return html
846
919
 
847
920
  def _render_html_command_parameters(self, parameters: list[ParameterInfo]) -> str:
@@ -864,31 +937,46 @@ class ReferenceGenerator:
864
937
  "version": reference.version,
865
938
  "categories": reference.categories,
866
939
  "workflows": reference.workflows,
867
- "commands": {},
940
+ "commands": self._serialize_commands(reference.commands),
868
941
  }
869
942
 
870
- for name, command in reference.commands.items():
871
- data["commands"][name] = {
872
- "name": command.name,
873
- "description": command.description,
874
- "category": command.category,
875
- "parameters": [
876
- {
877
- "name": param.name,
878
- "type": param.type_hint,
879
- "default": param.default_value,
880
- "description": param.description,
881
- "required": param.required,
882
- }
883
- for param in command.parameters
884
- ],
885
- "examples": command.examples,
886
- "related_commands": command.related_commands,
887
- "aliases": command.aliases,
888
- }
889
-
890
943
  return json.dumps(data, indent=2, default=str)
891
944
 
945
+ def _serialize_commands(self, commands: dict[str, CommandInfo]) -> dict[str, t.Any]:
946
+ """Serialize commands for JSON output."""
947
+ serialized_commands = {}
948
+ for name, command in commands.items():
949
+ serialized_commands[name] = self._serialize_command(command)
950
+ return serialized_commands
951
+
952
+ def _serialize_command(self, command: CommandInfo) -> dict[str, t.Any]:
953
+ """Serialize a single command for JSON output."""
954
+ return {
955
+ "name": command.name,
956
+ "description": command.description,
957
+ "category": command.category,
958
+ "parameters": self._serialize_parameters(command.parameters),
959
+ "examples": command.examples,
960
+ "related_commands": command.related_commands,
961
+ "aliases": command.aliases,
962
+ }
963
+
964
+ def _serialize_parameters(
965
+ self, parameters: list[ParameterInfo]
966
+ ) -> list[dict[str, t.Any]]:
967
+ """Serialize parameters for JSON output."""
968
+ return [self._serialize_parameter(param) for param in parameters]
969
+
970
+ def _serialize_parameter(self, param: ParameterInfo) -> dict[str, t.Any]:
971
+ """Serialize a single parameter for JSON output."""
972
+ return {
973
+ "name": param.name,
974
+ "type": param.type_hint,
975
+ "default": param.default_value,
976
+ "description": param.description,
977
+ "required": param.required,
978
+ }
979
+
892
980
  def _render_yaml(self, reference: CommandReference) -> str:
893
981
  """Render reference as YAML."""
894
982
  import yaml
@@ -1,5 +1,6 @@
1
1
  import tempfile
2
2
  import typing as t
3
+ from contextlib import suppress
3
4
  from pathlib import Path
4
5
 
5
6
  import jinja2
@@ -191,7 +192,7 @@ HOOKS_REGISTRY: dict[str, list[HookMetadata]] = {
191
192
  "stages": ["pre-push", "manual"],
192
193
  "args": ["-c", "pyproject.toml", "-r", "-ll"],
193
194
  "files": "^crackerjack/.*\\.py$",
194
- "exclude": None,
195
+ "exclude": r"^tests/",
195
196
  "additional_dependencies": None,
196
197
  "types_or": None,
197
198
  "language": None,
@@ -283,9 +284,9 @@ HOOKS_REGISTRY: dict[str, list[HookMetadata]] = {
283
284
  "tier": 3,
284
285
  "time_estimate": 0.1,
285
286
  "stages": ["pre-push", "manual"],
286
- "args": ["crackerjack"],
287
+ "args": ["crackerjack", "--exclude", "tests"],
287
288
  "files": None,
288
- "exclude": None,
289
+ "exclude": r"^tests/",
289
290
  "additional_dependencies": None,
290
291
  "types_or": None,
291
292
  "language": "system",
@@ -337,9 +338,9 @@ HOOKS_REGISTRY: dict[str, list[HookMetadata]] = {
337
338
  "tier": 3,
338
339
  "time_estimate": 3.0,
339
340
  "stages": ["pre-push", "manual"],
340
- "args": ["--ignore", "FURB184", "--ignore", "FURB120"],
341
+ "args": [],
341
342
  "files": "^crackerjack/.*\\.py$",
342
- "exclude": r"^tests/.*\.py$",
343
+ "exclude": r"^tests/",
343
344
  "additional_dependencies": None,
344
345
  "types_or": None,
345
346
  "language": None,
@@ -357,7 +358,7 @@ HOOKS_REGISTRY: dict[str, list[HookMetadata]] = {
357
358
  "stages": ["pre-push", "manual"],
358
359
  "args": ["--config-file", "mypy.ini", "./crackerjack"],
359
360
  "files": None,
360
- "exclude": None,
361
+ "exclude": r"^tests/",
361
362
  "additional_dependencies": None,
362
363
  "types_or": None,
363
364
  "language": "system",
@@ -469,29 +470,26 @@ class DynamicConfigGenerator:
469
470
 
470
471
  def _detect_package_directory(self) -> str:
471
472
  """Detect the package directory name for the current project."""
472
- import os
473
473
  from pathlib import Path
474
474
 
475
475
  # Check if we're in the crackerjack project itself
476
- current_dir = Path(os.getcwd())
476
+ current_dir = Path.cwd()
477
477
  if (current_dir / "crackerjack").exists() and (
478
478
  current_dir / "pyproject.toml"
479
479
  ).exists():
480
480
  # Check if this is actually the crackerjack project
481
- try:
481
+ with suppress(Exception):
482
482
  import tomllib
483
483
 
484
484
  with (current_dir / "pyproject.toml").open("rb") as f:
485
485
  data = tomllib.load(f)
486
486
  if data.get("project", {}).get("name") == "crackerjack":
487
487
  return "crackerjack"
488
- except Exception:
489
- pass
490
488
 
491
489
  # Try to read package name from pyproject.toml
492
490
  pyproject_path = current_dir / "pyproject.toml"
493
491
  if pyproject_path.exists():
494
- try:
492
+ with suppress(Exception):
495
493
  import tomllib
496
494
 
497
495
  with pyproject_path.open("rb") as f:
@@ -502,8 +500,6 @@ class DynamicConfigGenerator:
502
500
  # Check if package directory exists
503
501
  if (current_dir / package_name).exists():
504
502
  return package_name
505
- except Exception:
506
- pass
507
503
 
508
504
  # Fallback to project directory name
509
505
  if (current_dir / current_dir.name).exists():
@@ -548,7 +544,7 @@ class DynamicConfigGenerator:
548
544
  """Update hook configuration to use the detected package directory."""
549
545
  # Update skylos hook
550
546
  if hook["id"] == "skylos" and hook["args"]:
551
- hook["args"] = [self.package_directory]
547
+ hook["args"] = [self.package_directory, "--exclude", "tests"]
552
548
 
553
549
  # Update zuban hook
554
550
  elif hook["id"] == "zuban" and hook["args"]:
@@ -570,12 +566,24 @@ class DynamicConfigGenerator:
570
566
  "crackerjack", self.package_directory
571
567
  )
572
568
 
573
- # Ensure hooks exclude src directories to avoid JavaScript conflicts
569
+ # Ensure hooks exclude src directories to avoid JavaScript conflicts and tests
574
570
  if hook["exclude"]:
571
+ # Add src exclusion if not present
575
572
  if "src/" not in hook["exclude"]:
576
573
  hook["exclude"] = f"{hook['exclude']}|^src/"
577
574
  else:
578
- hook["exclude"] = "^src/"
575
+ # If no exclusion, add both tests and src
576
+ if hook["id"] in (
577
+ "skylos",
578
+ "zuban",
579
+ "bandit",
580
+ "refurb",
581
+ "complexipy",
582
+ "pyright",
583
+ ):
584
+ hook["exclude"] = r"^tests/|^src/"
585
+ else:
586
+ hook["exclude"] = "^src/"
579
587
 
580
588
  return hook
581
589