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.
- crackerjack/__init__.py +1 -1
- crackerjack/api.py +1 -2
- crackerjack/core/workflow_orchestrator.py +323 -171
- crackerjack/documentation/reference_generator.py +250 -162
- crackerjack/dynamic_config.py +25 -17
- crackerjack/hooks/lsp_hook.py +39 -24
- crackerjack/interactive.py +13 -22
- crackerjack/managers/publish_manager.py +1 -1
- crackerjack/mcp/tools/execution_tools_backup.py +34 -36
- crackerjack/plugins/hooks.py +1 -1
- crackerjack/services/config_merge.py +33 -30
- crackerjack/services/secure_subprocess.py +43 -25
- crackerjack/tools/validate_regex_patterns.py +1 -0
- {crackerjack-0.37.8.dist-info → crackerjack-0.38.0.dist-info}/METADATA +2 -2
- {crackerjack-0.37.8.dist-info → crackerjack-0.38.0.dist-info}/RECORD +18 -18
- {crackerjack-0.37.8.dist-info → crackerjack-0.38.0.dist-info}/WHEEL +0 -0
- {crackerjack-0.37.8.dist-info → crackerjack-0.38.0.dist-info}/entry_points.txt +0 -0
- {crackerjack-0.37.8.dist-info → crackerjack-0.38.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -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
|
-
|
|
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
|
-
|
|
420
|
-
|
|
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"
|
|
443
|
-
"command":
|
|
466
|
+
"description": f"Using {command.name} with parameters",
|
|
467
|
+
"command": enhanced_example,
|
|
444
468
|
}
|
|
445
469
|
)
|
|
446
470
|
|
|
447
|
-
|
|
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
|
-
|
|
461
|
-
|
|
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
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
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
|
-
|
|
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
|
-
|
|
653
|
-
|
|
654
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
714
|
-
|
|
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
|
-
|
|
737
|
-
|
|
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
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
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
|
-
|
|
884
|
+
html_parts = []
|
|
826
885
|
for category, command_names in reference.categories.items():
|
|
827
|
-
|
|
828
|
-
|
|
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
|
-
|
|
904
|
+
html_parts = []
|
|
838
905
|
for command_name in command_names:
|
|
839
906
|
command = commands[command_name]
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
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
|
crackerjack/dynamic_config.py
CHANGED
|
@@ -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":
|
|
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":
|
|
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": [
|
|
341
|
+
"args": [],
|
|
341
342
|
"files": "^crackerjack/.*\\.py$",
|
|
342
|
-
"exclude": r"^tests
|
|
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":
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|