py-wlcli 0.8.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.
- py_wlcli-0.8.0.dist-info/METADATA +139 -0
- py_wlcli-0.8.0.dist-info/RECORD +320 -0
- py_wlcli-0.8.0.dist-info/WHEEL +4 -0
- py_wlcli-0.8.0.dist-info/entry_points.txt +2 -0
- py_wlcli-0.8.0.dist-info/licenses/LICENSE +21 -0
- wl_cli/__init__.py +27 -0
- wl_cli/__main__.py +130 -0
- wl_cli/capabilities.py +79 -0
- wl_cli/cc/__init__.py +5 -0
- wl_cli/cc/checks.py +1019 -0
- wl_cli/cc/cli.py +370 -0
- wl_cli/cc/mutate.py +1350 -0
- wl_cli/cli_click.py +156 -0
- wl_cli/commands/__init__.py +84 -0
- wl_cli/commands/base.py +80 -0
- wl_cli/commands/buildcommands/__init__.py +1 -0
- wl_cli/commands/buildcommands/build.py +71 -0
- wl_cli/commands/buildcommands/build_utils.py +47 -0
- wl_cli/commands/buildcommands/builders/__init__.py +18 -0
- wl_cli/commands/buildcommands/builders/cleanup.py +35 -0
- wl_cli/commands/buildcommands/builders/coordinator.py +122 -0
- wl_cli/commands/buildcommands/builders/environment.py +81 -0
- wl_cli/commands/buildcommands/builders/rust_builder.py +39 -0
- wl_cli/commands/buildcommands/builders/stubs.py +58 -0
- wl_cli/commands/buildcommands/dist.py +326 -0
- wl_cli/commands/buildcommands/test.py +89 -0
- wl_cli/commands/clean/__init__.py +1 -0
- wl_cli/commands/clean/build.py +151 -0
- wl_cli/commands/clean/clean.py +294 -0
- wl_cli/commands/clean/clean_utils.py +77 -0
- wl_cli/commands/clean/core.py +236 -0
- wl_cli/commands/clean/dead_code/__init__.py +0 -0
- wl_cli/commands/clean/dead_code/dead_code_command.py +70 -0
- wl_cli/commands/clean/dead_code/detector.py +161 -0
- wl_cli/commands/clean/dead_code/fixer.py +176 -0
- wl_cli/commands/clean/dead_code/reporter.py +295 -0
- wl_cli/commands/clean/dead_code/utils.py +0 -0
- wl_cli/commands/clean/lfs.py +215 -0
- wl_cli/commands/clean/python.py +81 -0
- wl_cli/commands/clean/rust.py +91 -0
- wl_cli/commands/clean/utils/__init__.py +34 -0
- wl_cli/commands/clean/venv.py +91 -0
- wl_cli/commands/cleanup.py +73 -0
- wl_cli/commands/click_base.py +79 -0
- wl_cli/commands/config/__init__.py +1 -0
- wl_cli/commands/config/config.py +291 -0
- wl_cli/commands/debug/__init__.py +6 -0
- wl_cli/commands/debug/debug.py +309 -0
- wl_cli/commands/debug/debug_cache.py +86 -0
- wl_cli/commands/debug/debug_subagent.py +156 -0
- wl_cli/commands/debug/output_formatter.py +165 -0
- wl_cli/commands/debug/source_hasher.py +92 -0
- wl_cli/commands/format/__init__.py +13 -0
- wl_cli/commands/format/format_cache_adapter.py +125 -0
- wl_cli/commands/format/format_command.py +197 -0
- wl_cli/commands/format/format_coordinator.py +191 -0
- wl_cli/commands/format/format_executor.py +470 -0
- wl_cli/commands/format/format_outcome.py +44 -0
- wl_cli/commands/format/format_path_handler.py +1016 -0
- wl_cli/commands/format/format_progress.py +62 -0
- wl_cli/commands/format/format_reporter.py +31 -0
- wl_cli/commands/format/python_formatter.py +506 -0
- wl_cli/commands/format/rust_formatter.py +106 -0
- wl_cli/commands/gate/__init__.py +1 -0
- wl_cli/commands/gate/gate_command.py +862 -0
- wl_cli/commands/gate/preflight.py +445 -0
- wl_cli/commands/git/__init__.py +1 -0
- wl_cli/commands/git/config.py +169 -0
- wl_cli/commands/git/git_facts.py +60 -0
- wl_cli/commands/git/git_flow.py +86 -0
- wl_cli/commands/git/hook.py +112 -0
- wl_cli/commands/git/policy.py +216 -0
- wl_cli/commands/git/push_update.py +82 -0
- wl_cli/commands/initenv/__init__.py +1 -0
- wl_cli/commands/initenv/common/__init__.py +23 -0
- wl_cli/commands/initenv/common/check_git_bash.py +78 -0
- wl_cli/commands/initenv/common/error_handler.py +68 -0
- wl_cli/commands/initenv/common/exceptions.py +52 -0
- wl_cli/commands/initenv/components/__init__.py +4 -0
- wl_cli/commands/initenv/components/cargo_sync.py +240 -0
- wl_cli/commands/initenv/components/config_manager.py +106 -0
- wl_cli/commands/initenv/components/directory_utils.py +71 -0
- wl_cli/commands/initenv/components/git/__init__.py +9 -0
- wl_cli/commands/initenv/components/git/branch_creator.py +143 -0
- wl_cli/commands/initenv/components/git/git_flow_interface.py +24 -0
- wl_cli/commands/initenv/components/git/git_flow_manager.py +159 -0
- wl_cli/commands/initenv/components/git/git_lfs_manager.py +272 -0
- wl_cli/commands/initenv/components/git/git_utility.py +254 -0
- wl_cli/commands/initenv/components/git/gitignore_manager.py +53 -0
- wl_cli/commands/initenv/components/git_initializer.py +212 -0
- wl_cli/commands/initenv/components/i18n_manager.py +104 -0
- wl_cli/commands/initenv/components/log_manager.py +241 -0
- wl_cli/commands/initenv/components/project_structure/__init__.py +23 -0
- wl_cli/commands/initenv/components/project_structure/config_generator.py +360 -0
- wl_cli/commands/initenv/components/project_structure/directory_creator.py +184 -0
- wl_cli/commands/initenv/components/project_structure/hook_source.py +110 -0
- wl_cli/commands/initenv/components/project_structure/hooks_manager.py +995 -0
- wl_cli/commands/initenv/components/project_structure/readme_handler.py +56 -0
- wl_cli/commands/initenv/components/project_structure/rust_files_handler.py +30 -0
- wl_cli/commands/initenv/components/project_structure/setup_handler.py +103 -0
- wl_cli/commands/initenv/components/project_structure/sop_generator.py +73 -0
- wl_cli/commands/initenv/components/project_structure/template_copier.py +104 -0
- wl_cli/commands/initenv/components/project_structure/template_manager.py +195 -0
- wl_cli/commands/initenv/components/pyproject_generator.py +194 -0
- wl_cli/commands/initenv/components/rust_initializer.py +145 -0
- wl_cli/commands/initenv/components/symlink_cleaner.py +99 -0
- wl_cli/commands/initenv/components/symlink_manager.py +52 -0
- wl_cli/commands/initenv/components/venv_manager.py +118 -0
- wl_cli/commands/initenv/initenv.py +169 -0
- wl_cli/commands/initenv/services/__init__.py +11 -0
- wl_cli/commands/initenv/services/environment_initializer.py +288 -0
- wl_cli/commands/initenv/services/git_lfs_initializer.py +26 -0
- wl_cli/commands/initenv/services/project_structure_initializer.py +52 -0
- wl_cli/commands/lfs/README.md +221 -0
- wl_cli/commands/lfs/__init__.py +5 -0
- wl_cli/commands/lfs/cli.py +106 -0
- wl_cli/commands/lfs/config.py +164 -0
- wl_cli/commands/lfs/hook.py +61 -0
- wl_cli/commands/lfs/initializer.py +531 -0
- wl_cli/commands/lfs/manager.py +95 -0
- wl_cli/commands/lfs/policy.py +165 -0
- wl_cli/commands/lfs/watcher.py +294 -0
- wl_cli/commands/lint/__init__.py +1 -0
- wl_cli/commands/lint/lint_command.py +297 -0
- wl_cli/commands/lint/lint_executor.py +409 -0
- wl_cli/commands/lint/lint_reporter.py +579 -0
- wl_cli/commands/lint/lint_run.py +258 -0
- wl_cli/commands/lint/mypy_config.py +90 -0
- wl_cli/commands/registry.py +103 -0
- wl_cli/commands/self/__init__.py +7 -0
- wl_cli/commands/self/common/__init__.py +8 -0
- wl_cli/commands/self/common/capabilities.py +30 -0
- wl_cli/commands/self/common/config.py +27 -0
- wl_cli/commands/self/common/version_cache.py +85 -0
- wl_cli/commands/self/handlers/__init__.py +19 -0
- wl_cli/commands/self/handlers/base_handler.py +28 -0
- wl_cli/commands/self/handlers/install_handler.py +85 -0
- wl_cli/commands/self/handlers/status_handler.py +66 -0
- wl_cli/commands/self/handlers/uninstall_handler.py +58 -0
- wl_cli/commands/self/handlers/update_handler.py +102 -0
- wl_cli/commands/self/handlers/version_handler.py +16 -0
- wl_cli/commands/self/self_command.py +237 -0
- wl_cli/commands/self/services/__init__.py +15 -0
- wl_cli/commands/self/services/installation_manager.py +187 -0
- wl_cli/commands/self/services/local_installer.py +165 -0
- wl_cli/commands/self/services/pypi_updater.py +278 -0
- wl_cli/commands/self/services/windows_update.py +174 -0
- wl_cli/commands/testcommands/__init__.py +8 -0
- wl_cli/commands/testcommands/base_dependency_analyzer.py +198 -0
- wl_cli/commands/testcommands/batch_processor.py +278 -0
- wl_cli/commands/testcommands/cache_manager.py +1156 -0
- wl_cli/commands/testcommands/coverage_analyzer.py +955 -0
- wl_cli/commands/testcommands/coverage_cache_manager.py +183 -0
- wl_cli/commands/testcommands/progress_manager.py +79 -0
- wl_cli/commands/testcommands/python_env_detector.py +207 -0
- wl_cli/commands/testcommands/report_generator.py +101 -0
- wl_cli/commands/testcommands/single_test_executor.py +250 -0
- wl_cli/commands/testcommands/speed_checker.py +118 -0
- wl_cli/commands/testcommands/test_command_builder.py +165 -0
- wl_cli/commands/testcommands/test_commands.py +786 -0
- wl_cli/commands/testcommands/test_reporter.py +181 -0
- wl_cli/commands/testcommands/test_result_handler.py +96 -0
- wl_cli/commands/testcommands/test_runner.py +1343 -0
- wl_cli/commands/testcommands/test_utils.py +197 -0
- wl_cli/commands/testcommands/utils/__init__.py +17 -0
- wl_cli/commands/testcommands/utils/coverage_parser.py +98 -0
- wl_cli/commands/testcommands/utils/env_info.py +36 -0
- wl_cli/commands/testcommands/utils/junit_parser.py +75 -0
- wl_cli/commands/testcommands/utils/markdown_utils.py +20 -0
- wl_cli/commands/testcommands/utils/project_root.py +61 -0
- wl_cli/commands/update/__init__.py +9 -0
- wl_cli/commands/update/services/__init__.py +21 -0
- wl_cli/commands/update/services/backup_manager.py +207 -0
- wl_cli/commands/update/services/component_manager.py +83 -0
- wl_cli/commands/update/services/conflict_resolver.py +189 -0
- wl_cli/commands/update/services/vendors_updater.py +85 -0
- wl_cli/commands/update/services/version_tracker.py +127 -0
- wl_cli/commands/update/update_command.py +270 -0
- wl_cli/commands/update/utils/__init__.py +3 -0
- wl_cli/commands/version/__init__.py +9 -0
- wl_cli/commands/version/version_command.py +155 -0
- wl_cli/commands/workflow.py +477 -0
- wl_cli/env/__init__.py +5 -0
- wl_cli/env/cli.py +167 -0
- wl_cli/env_status.py +157 -0
- wl_cli/envelope.py +91 -0
- wl_cli/exceptions.py +85 -0
- wl_cli/lib/__init__.py +3 -0
- wl_cli/lib/rust_utils.py +35 -0
- wl_cli/mcp/__init__.py +6 -0
- wl_cli/mcp/capabilities.py +19 -0
- wl_cli/mcp/cli.py +22 -0
- wl_cli/mcp/gitlab.py +553 -0
- wl_cli/mcp/k8s.py +344 -0
- wl_cli/mcp/protocol.py +159 -0
- wl_cli/platform.py +139 -0
- wl_cli/quality/__init__.py +18 -0
- wl_cli/utils/__init__.py +7 -0
- wl_cli/utils/cache.py +491 -0
- wl_cli/utils/cache_ledger.py +355 -0
- wl_cli/utils/cache_store.py +546 -0
- wl_cli/utils/config/__init__.py +27 -0
- wl_cli/utils/config/config_loader.py +163 -0
- wl_cli/utils/config/core.py +862 -0
- wl_cli/utils/config/defaults.py +142 -0
- wl_cli/utils/config/env_exporter.py +86 -0
- wl_cli/utils/config/env_parser.py +108 -0
- wl_cli/utils/config/validation.py +280 -0
- wl_cli/utils/error_handler.py +165 -0
- wl_cli/utils/file_copier.py +56 -0
- wl_cli/utils/file_operations.py +506 -0
- wl_cli/utils/language_utils.py +122 -0
- wl_cli/utils/lazy_import.py +240 -0
- wl_cli/utils/log_levels.py +35 -0
- wl_cli/utils/log_rotators.py +74 -0
- wl_cli/utils/logging.py +458 -0
- wl_cli/utils/output_context.py +142 -0
- wl_cli/utils/performance/__init__.py +1 -0
- wl_cli/utils/performance/decorators.py +111 -0
- wl_cli/utils/performance/exporter.py +38 -0
- wl_cli/utils/performance/models.py +16 -0
- wl_cli/utils/performance/monitor.py +161 -0
- wl_cli/utils/performance_monitor.py +32 -0
- wl_cli/utils/platform_adapter.py +131 -0
- wl_cli/utils/pre_commit.py +266 -0
- wl_cli/utils/pre_commit_config.py +72 -0
- wl_cli/utils/root.py +521 -0
- wl_cli/utils/root_fallback.py +256 -0
- wl_cli/utils/run_evidence.py +236 -0
- wl_cli/utils/structured_logger.py +18 -0
- wl_cli/utils/structured_logging/__init__.py +19 -0
- wl_cli/utils/structured_logging/config.py +337 -0
- wl_cli/utils/structured_logging/core.py +177 -0
- wl_cli/utils/structured_logging/env.py +56 -0
- wl_cli/utils/structured_logging/filtering.py +75 -0
- wl_cli/utils/structured_logging/filters.py +55 -0
- wl_cli/utils/structured_logging/formatters.py +251 -0
- wl_cli/utils/structured_logging/handlers.py +308 -0
- wl_cli/utils/structured_logging/logger.py +354 -0
- wl_cli/utils/subprocess.py +71 -0
- wl_cli/utils/subprocess_utils.py +260 -0
- wl_cli/utils/test_utils/__init__.py +5 -0
- wl_cli/utils/test_utils/test_filter.py +36 -0
- wl_cli/utils/uv_run.py +79 -0
- wl_cli/utils/uv_tool.py +117 -0
- wl_cli/utils/uv_tree.py +71 -0
- wl_cli/utils/uv_workspace.py +174 -0
- wl_cli/utils/version/__init__.py +15 -0
- wl_cli/utils/version/pypi_version_checker.py +95 -0
- wl_cli/utils/version/version_comparator.py +133 -0
- wl_cli/utils/version/version_detectors.py +312 -0
- wl_cli/utils/version/version_service.py +131 -0
- wl_cli/utils/version/version_updater.py +198 -0
- wl_cli/utils/wl_dir_updater.py +1440 -0
- wl_cli/utils/workspace_detector/__init__.py +28 -0
- wl_cli/utils/workspace_detector/core.py +161 -0
- wl_cli/utils/workspace_detector/detection_strategies.py +107 -0
- wl_cli/utils/workspace_detector/exceptions.py +5 -0
- wl_cli/utils/workspace_detector/types.py +17 -0
- wl_cli/utils/workspace_detector/venv_resolver.py +84 -0
- wl_cli/vendors/config/.codespellrc +2 -0
- wl_cli/vendors/config/dependencies.toml +10 -0
- wl_cli/vendors/config/mypy.ini +62 -0
- wl_cli/vendors/config/pyproject_defaults.toml +28 -0
- wl_cli/vendors/config/template_config.toml +24 -0
- wl_cli/vendors/config/tool_configs.toml +76 -0
- wl_cli/vendors/git/.gitignore +122 -0
- wl_cli/vendors/git/.pre-commit-config.yaml +37 -0
- wl_cli/vendors/hooks/applypatch-msg.sample +15 -0
- wl_cli/vendors/hooks/commit-msg.sample +24 -0
- wl_cli/vendors/hooks/fsmonitor-watchman.sample +174 -0
- wl_cli/vendors/hooks/post-update.sample +8 -0
- wl_cli/vendors/hooks/pre-applypatch.sample +14 -0
- wl_cli/vendors/hooks/pre-commit +45 -0
- wl_cli/vendors/hooks/pre-commit.legacy +31 -0
- wl_cli/vendors/hooks/pre-commit.sample +49 -0
- wl_cli/vendors/hooks/pre-merge-commit.sample +13 -0
- wl_cli/vendors/hooks/pre-push.sample +60 -0
- wl_cli/vendors/hooks/pre-rebase.sample +169 -0
- wl_cli/vendors/hooks/pre-receive.sample +24 -0
- wl_cli/vendors/hooks/prepare-commit-msg.sample +42 -0
- wl_cli/vendors/hooks/push-to-checkout.sample +78 -0
- wl_cli/vendors/hooks/sendemail-validate.sample +77 -0
- wl_cli/vendors/hooks/unix/applypatch-msg.sample +15 -0
- wl_cli/vendors/hooks/unix/check_lockfile.py +58 -0
- wl_cli/vendors/hooks/unix/check_lockfile.sh +36 -0
- wl_cli/vendors/hooks/unix/commit-msg.sample +24 -0
- wl_cli/vendors/hooks/unix/fsmonitor-watchman.sample +174 -0
- wl_cli/vendors/hooks/unix/post-update.sample +8 -0
- wl_cli/vendors/hooks/unix/pre-applypatch.sample +14 -0
- wl_cli/vendors/hooks/unix/pre-commit +190 -0
- wl_cli/vendors/hooks/unix/pre-commit.legacy +31 -0
- wl_cli/vendors/hooks/unix/pre-commit.py +81 -0
- wl_cli/vendors/hooks/unix/pre-commit.sample +49 -0
- wl_cli/vendors/hooks/unix/pre-merge-commit.sample +13 -0
- wl_cli/vendors/hooks/unix/pre-push +417 -0
- wl_cli/vendors/hooks/unix/pre-push.sample +53 -0
- wl_cli/vendors/hooks/unix/pre-rebase.sample +169 -0
- wl_cli/vendors/hooks/unix/pre-receive.sample +24 -0
- wl_cli/vendors/hooks/unix/prepare-commit-msg.sample +42 -0
- wl_cli/vendors/hooks/update.sample +128 -0
- wl_cli/vendors/hooks/win/work.bat +63 -0
- wl_cli/vendors/project/.gitignore +90 -0
- wl_cli/vendors/project/.python-version +1 -0
- wl_cli/vendors/project/LICENSE +21 -0
- wl_cli/vendors/project/README.md +0 -0
- wl_cli/vendors/project/pyproject.toml +27 -0
- wl_cli/vendors/project/pyproject_no_workspace.toml +16 -0
- wl_cli/vendors/project/pyproject_workspace.toml +14 -0
- wl_cli/vendors/readme/README.md +120 -0
- wl_cli/vendors/rust/Cargo.toml +14 -0
- wl_cli/vendors/rust/lib/__init__.py +3 -0
- wl_cli/vendors/rust/lib/rust_utils.py +31 -0
- wl_cli/vendors/rust/src/lib.rs +13 -0
- wl_cli/vendors/rust/src/main.rs +3 -0
- wl_cli/vendors/rust/tests/test_rust_fallback.py +40 -0
- wl_cli/vendors/sop_doc/git-dev-sop.md +220 -0
- wl_cli/vendors/sop_doc/industrial-standard-sop.md +145 -0
- wl_cli/vendors/sop_doc/python-sop.md +342 -0
- wl_cli/vendors/sop_doc/rust-readme.md +219 -0
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
"""wl-debug command implementation - code quality validation with smart caching."""
|
|
2
|
+
|
|
3
|
+
import subprocess
|
|
4
|
+
import sys
|
|
5
|
+
import time
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
import click
|
|
9
|
+
|
|
10
|
+
from wl_cli.commands.debug.debug_cache import DebugCache
|
|
11
|
+
from wl_cli.commands.debug.debug_subagent import DebugSubagent
|
|
12
|
+
from wl_cli.commands.debug.output_formatter import OutputFormatter
|
|
13
|
+
from wl_cli.commands.debug.source_hasher import SourceHasher
|
|
14
|
+
|
|
15
|
+
from ...utils.root import RootKind
|
|
16
|
+
from ...utils.root_fallback import current_root
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class DebugCommand:
|
|
20
|
+
"""Implementation of the wl-debug command."""
|
|
21
|
+
|
|
22
|
+
def __init__(
|
|
23
|
+
self,
|
|
24
|
+
json_mode: bool = False,
|
|
25
|
+
verbosity: int = 0,
|
|
26
|
+
subagent_mode: bool = False,
|
|
27
|
+
):
|
|
28
|
+
"""Initialize debug command.
|
|
29
|
+
|
|
30
|
+
Args:
|
|
31
|
+
json_mode: Enable JSON output mode
|
|
32
|
+
verbosity: Verbosity level (0-2)
|
|
33
|
+
subagent_mode: Enable subagent auto-fix mode
|
|
34
|
+
"""
|
|
35
|
+
self.source_hasher = SourceHasher()
|
|
36
|
+
self.debug_cache = DebugCache()
|
|
37
|
+
self.formatter = OutputFormatter(json_mode=json_mode, verbosity=verbosity)
|
|
38
|
+
self.subagent_mode = subagent_mode
|
|
39
|
+
self.subagent = DebugSubagent()
|
|
40
|
+
self.subagent.enabled = subagent_mode
|
|
41
|
+
|
|
42
|
+
@property
|
|
43
|
+
def name(self) -> str:
|
|
44
|
+
return "debug"
|
|
45
|
+
|
|
46
|
+
@property
|
|
47
|
+
def help(self) -> str:
|
|
48
|
+
return "Run code quality validation (DEPRECATED: use 'wl test -s' + 'wl format -s')"
|
|
49
|
+
|
|
50
|
+
def _run_command(
|
|
51
|
+
self, cmd: list[str], capture_output: bool = False
|
|
52
|
+
) -> int | tuple[int, str, str]:
|
|
53
|
+
"""Run a command and return exit code. For internal use, also returns stdout/stderr."""
|
|
54
|
+
try:
|
|
55
|
+
result = subprocess.run(
|
|
56
|
+
cmd, capture_output=True, text=True, errors="replace"
|
|
57
|
+
)
|
|
58
|
+
if result.stdout and not capture_output:
|
|
59
|
+
self.formatter.info(result.stdout, verbosity=1)
|
|
60
|
+
if result.stderr and not capture_output:
|
|
61
|
+
# Error messages should be shown even at default verbosity (0)
|
|
62
|
+
self.formatter.error(result.stderr, verbosity=0)
|
|
63
|
+
if capture_output:
|
|
64
|
+
return result.returncode, result.stdout or "", result.stderr or ""
|
|
65
|
+
return result.returncode
|
|
66
|
+
except FileNotFoundError:
|
|
67
|
+
self.formatter.error(f"Command not found: {cmd[0]}")
|
|
68
|
+
if capture_output:
|
|
69
|
+
return 1, "", f"Command not found: {cmd[0]}"
|
|
70
|
+
return 1
|
|
71
|
+
|
|
72
|
+
def _run_command_with_retry(self, cmd: list[str], command_name: str) -> int:
|
|
73
|
+
"""Run a command with retry logic using subagent if enabled.
|
|
74
|
+
|
|
75
|
+
Args:
|
|
76
|
+
cmd: Command to run
|
|
77
|
+
command_name: Name of the command for logging
|
|
78
|
+
|
|
79
|
+
Returns:
|
|
80
|
+
Exit code (0 on success)
|
|
81
|
+
"""
|
|
82
|
+
attempt = 0
|
|
83
|
+
max_total_attempts = 1 + (
|
|
84
|
+
DebugSubagent.MAX_ATTEMPTS if self.subagent_mode else 0
|
|
85
|
+
)
|
|
86
|
+
exit_code = 1
|
|
87
|
+
|
|
88
|
+
while attempt < max_total_attempts:
|
|
89
|
+
# Always use capture_output=True for retry logic to get full output
|
|
90
|
+
result = self._run_command(cmd, capture_output=True)
|
|
91
|
+
if isinstance(result, tuple):
|
|
92
|
+
exit_code, stdout, stderr = result
|
|
93
|
+
else:
|
|
94
|
+
exit_code = result
|
|
95
|
+
stdout = ""
|
|
96
|
+
stderr = ""
|
|
97
|
+
|
|
98
|
+
if exit_code == 0:
|
|
99
|
+
if stdout:
|
|
100
|
+
self.formatter.info(stdout, verbosity=1)
|
|
101
|
+
return 0
|
|
102
|
+
|
|
103
|
+
attempt += 1
|
|
104
|
+
|
|
105
|
+
# When command fails, show both stdout and stderr, at verbosity 0
|
|
106
|
+
if stdout:
|
|
107
|
+
self.formatter.info(stdout, verbosity=0)
|
|
108
|
+
if stderr:
|
|
109
|
+
self.formatter.error(stderr, verbosity=0)
|
|
110
|
+
|
|
111
|
+
if not self.subagent_mode:
|
|
112
|
+
return exit_code
|
|
113
|
+
|
|
114
|
+
error_info = self.subagent.record_error(
|
|
115
|
+
command=" ".join(cmd),
|
|
116
|
+
exit_code=exit_code,
|
|
117
|
+
stdout=stdout,
|
|
118
|
+
stderr=stderr,
|
|
119
|
+
context={"command_name": command_name, "attempt": attempt},
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
if not self.subagent.can_attempt_fix():
|
|
123
|
+
self.formatter.error(
|
|
124
|
+
f"{command_name} failed and max fix attempts reached"
|
|
125
|
+
)
|
|
126
|
+
return exit_code
|
|
127
|
+
|
|
128
|
+
self.formatter.info(
|
|
129
|
+
f"{command_name} failed, attempting fix (attempt {attempt}/{DebugSubagent.MAX_ATTEMPTS})..."
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
fix_result = self.subagent.attempt_fix(
|
|
133
|
+
error_info,
|
|
134
|
+
simulate_success=False,
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
self.formatter.info(fix_result.message)
|
|
138
|
+
|
|
139
|
+
if fix_result.success:
|
|
140
|
+
self.formatter.success("Fix applied, retrying...")
|
|
141
|
+
else:
|
|
142
|
+
self.formatter.info("No fix available, stopping...")
|
|
143
|
+
return exit_code
|
|
144
|
+
|
|
145
|
+
return exit_code
|
|
146
|
+
|
|
147
|
+
def _clean_test_cache(self) -> None:
|
|
148
|
+
"""Clean test cache - delete cache directories directly."""
|
|
149
|
+
import shutil
|
|
150
|
+
|
|
151
|
+
try:
|
|
152
|
+
# Root-anchored (#125): the project root's ``.wl`` (the DebugCache
|
|
153
|
+
# anchor), not the process CWD's.
|
|
154
|
+
project_test_cache = (
|
|
155
|
+
current_root(RootKind.PROJECT).wl_dir() / "cache" / "test-cache"
|
|
156
|
+
)
|
|
157
|
+
if project_test_cache.exists():
|
|
158
|
+
shutil.rmtree(project_test_cache, ignore_errors=True)
|
|
159
|
+
|
|
160
|
+
user_test_cache = Path.home() / ".cache/wl/test-cache"
|
|
161
|
+
if user_test_cache.exists():
|
|
162
|
+
shutil.rmtree(user_test_cache, ignore_errors=True)
|
|
163
|
+
except Exception:
|
|
164
|
+
pass
|
|
165
|
+
|
|
166
|
+
def _clean_format_cache(self) -> None:
|
|
167
|
+
"""Clean format cache - the legacy file and the per-entry store.
|
|
168
|
+
|
|
169
|
+
The format cache is per-entry files under ``cache/format/`` since
|
|
170
|
+
ADR-0011; the single ``format_cache.json`` is the pre-#213 shape. A
|
|
171
|
+
debug run must clear both, or a stale verdict from the live store
|
|
172
|
+
survives the clean (#213 review LOW-4).
|
|
173
|
+
"""
|
|
174
|
+
import shutil
|
|
175
|
+
|
|
176
|
+
try:
|
|
177
|
+
# Root-anchored (#125): the project root's ``.wl``.
|
|
178
|
+
cache_dir = current_root(RootKind.PROJECT).wl_dir() / "cache"
|
|
179
|
+
legacy_format_cache = cache_dir / "format_cache.json"
|
|
180
|
+
if legacy_format_cache.exists():
|
|
181
|
+
legacy_format_cache.unlink(missing_ok=True)
|
|
182
|
+
|
|
183
|
+
format_entries = cache_dir / "format"
|
|
184
|
+
if format_entries.exists():
|
|
185
|
+
shutil.rmtree(format_entries, ignore_errors=True)
|
|
186
|
+
except Exception:
|
|
187
|
+
pass
|
|
188
|
+
|
|
189
|
+
def _run_wl_test(self) -> int:
|
|
190
|
+
"""Run the test suite."""
|
|
191
|
+
self.formatter.info("Running tests...")
|
|
192
|
+
return self._run_command_with_retry(["wl", "test", "-s"], "Tests")
|
|
193
|
+
|
|
194
|
+
def _run_wl_format(self) -> int:
|
|
195
|
+
"""Check code formatting."""
|
|
196
|
+
self.formatter.info("Checking formatting...")
|
|
197
|
+
return self._run_command_with_retry(["wl", "format", "-s"], "Formatting")
|
|
198
|
+
|
|
199
|
+
def execute(self) -> int:
|
|
200
|
+
"""Execute the debug validation flow.
|
|
201
|
+
|
|
202
|
+
Returns:
|
|
203
|
+
Exit code (0 on success, non-zero on failure)
|
|
204
|
+
"""
|
|
205
|
+
if not self.formatter.json_mode:
|
|
206
|
+
print("========================================")
|
|
207
|
+
print(" 🧪 wl-debug Code Quality Check ")
|
|
208
|
+
print("========================================")
|
|
209
|
+
|
|
210
|
+
exit_code = 0
|
|
211
|
+
|
|
212
|
+
try:
|
|
213
|
+
source_hash = self.source_hasher.compute_hash()
|
|
214
|
+
except Exception:
|
|
215
|
+
source_hash = None
|
|
216
|
+
self.formatter.info(
|
|
217
|
+
"Failed to compute source hash, running full validation..."
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
cache_needs_cleaning = True
|
|
221
|
+
|
|
222
|
+
if source_hash is not None:
|
|
223
|
+
cache_data = self.debug_cache.load()
|
|
224
|
+
if cache_data is not None and cache_data.get("source_hash") == source_hash:
|
|
225
|
+
self.formatter.success("Code unchanged, using cached results")
|
|
226
|
+
cache_needs_cleaning = False
|
|
227
|
+
|
|
228
|
+
if cache_needs_cleaning:
|
|
229
|
+
self.formatter.info("Cleaning caches...")
|
|
230
|
+
self._clean_test_cache()
|
|
231
|
+
self._clean_format_cache()
|
|
232
|
+
|
|
233
|
+
exit_code = self._run_wl_test()
|
|
234
|
+
if exit_code != 0:
|
|
235
|
+
self.formatter.error("Tests failed - stopping validation")
|
|
236
|
+
self.formatter.finalize(success=False, exit_code=exit_code)
|
|
237
|
+
return exit_code
|
|
238
|
+
|
|
239
|
+
exit_code = self._run_wl_format()
|
|
240
|
+
if exit_code != 0:
|
|
241
|
+
self.formatter.error("Formatting issues found")
|
|
242
|
+
self.formatter.finalize(success=False, exit_code=exit_code)
|
|
243
|
+
return exit_code
|
|
244
|
+
|
|
245
|
+
if source_hash is not None:
|
|
246
|
+
self.debug_cache.save(
|
|
247
|
+
{
|
|
248
|
+
"source_hash": source_hash,
|
|
249
|
+
"last_validated_at": time.time(),
|
|
250
|
+
"success": True,
|
|
251
|
+
}
|
|
252
|
+
)
|
|
253
|
+
|
|
254
|
+
if not self.formatter.json_mode:
|
|
255
|
+
print("========================================")
|
|
256
|
+
print("✅ All checks passed!")
|
|
257
|
+
print("========================================")
|
|
258
|
+
|
|
259
|
+
self.formatter.finalize(success=True, exit_code=exit_code)
|
|
260
|
+
|
|
261
|
+
return exit_code
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
@click.command()
|
|
265
|
+
@click.option(
|
|
266
|
+
"--json",
|
|
267
|
+
is_flag=True,
|
|
268
|
+
help="Enable JSON output mode",
|
|
269
|
+
)
|
|
270
|
+
@click.option(
|
|
271
|
+
"--subagent",
|
|
272
|
+
is_flag=True,
|
|
273
|
+
help="EXPERIMENTAL simulated auto-fix (records errors only; no real fixes yet)",
|
|
274
|
+
)
|
|
275
|
+
@click.option(
|
|
276
|
+
"-v",
|
|
277
|
+
"--verbose",
|
|
278
|
+
count=True,
|
|
279
|
+
help="Increase verbosity (can be used multiple times)",
|
|
280
|
+
)
|
|
281
|
+
@click.option(
|
|
282
|
+
"-q",
|
|
283
|
+
"--quiet",
|
|
284
|
+
count=True,
|
|
285
|
+
help="Decrease verbosity",
|
|
286
|
+
)
|
|
287
|
+
@click.pass_context
|
|
288
|
+
def debug_command(
|
|
289
|
+
ctx: click.Context,
|
|
290
|
+
json: bool,
|
|
291
|
+
subagent: bool,
|
|
292
|
+
verbose: int,
|
|
293
|
+
quiet: int,
|
|
294
|
+
) -> None:
|
|
295
|
+
"""DEPRECATED: scheduled for removal.
|
|
296
|
+
|
|
297
|
+
Prefer the supported quality gates `wl test -s` and `wl format -s`
|
|
298
|
+
(the flow used by the wlcc ``wl-debug`` skill). ``--subagent`` is
|
|
299
|
+
experimental and currently only records errors - it does not fix them.
|
|
300
|
+
"""
|
|
301
|
+
verbosity = max(0, min(2, verbose - quiet))
|
|
302
|
+
cmd = DebugCommand(
|
|
303
|
+
json_mode=json,
|
|
304
|
+
verbosity=verbosity,
|
|
305
|
+
subagent_mode=subagent,
|
|
306
|
+
)
|
|
307
|
+
exit_code = cmd.execute()
|
|
308
|
+
if exit_code != 0:
|
|
309
|
+
sys.exit(exit_code)
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
"""Debug cache module."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
from wl_cli.utils.cache import DiskCache
|
|
7
|
+
|
|
8
|
+
from ...utils.root import Root, RootKind
|
|
9
|
+
from ...utils.root_fallback import current_root
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class DebugCache:
|
|
13
|
+
"""Cache manager for debug operations."""
|
|
14
|
+
|
|
15
|
+
CACHE_KEY = "debug_cache"
|
|
16
|
+
#: Cache file name, inside the ``.wl/cache`` directory at the cache's root.
|
|
17
|
+
CACHE_FILE = "debug_cache.json"
|
|
18
|
+
|
|
19
|
+
def __init__(self, project_root: Path | None = None) -> None:
|
|
20
|
+
# Root-anchored (#125): the *project* root the CWD belongs to, not the
|
|
21
|
+
# CWD itself (see ``_wl_dir``). Explicit anchors are untouched -- a
|
|
22
|
+
# relative ``project_root`` still yields a relative ``.wl`` anchor.
|
|
23
|
+
self.project_root = project_root or current_root(RootKind.PROJECT).path
|
|
24
|
+
self._disk_cache = DiskCache(cache_dir=str(self._wl_dir() / "cache" / "debug"))
|
|
25
|
+
# Cache file path for backward compatibility
|
|
26
|
+
self.cache_path = self._wl_dir() / "cache" / self.CACHE_FILE
|
|
27
|
+
|
|
28
|
+
def _wl_dir(self) -> Path:
|
|
29
|
+
"""The ``.wl`` control directory at this cache's root.
|
|
30
|
+
|
|
31
|
+
Built from :class:`~wl_cli.utils.root.Root` so the control-directory
|
|
32
|
+
name has a single definition (#114/#122). The anchor is this cache's
|
|
33
|
+
``project_root`` -- exactly the directory the pre-migration code joined
|
|
34
|
+
``".wl/cache/..."`` onto, so behaviour is unchanged.
|
|
35
|
+
"""
|
|
36
|
+
return Root(self.project_root, RootKind.PROJECT).wl_dir()
|
|
37
|
+
|
|
38
|
+
def load(self) -> dict | None:
|
|
39
|
+
"""Load cache from file."""
|
|
40
|
+
try:
|
|
41
|
+
# First try to load from old JSON for backward compatibility
|
|
42
|
+
if self.cache_path.exists():
|
|
43
|
+
with self.cache_path.open("r", encoding="utf-8") as f:
|
|
44
|
+
data: dict = json.load(f)
|
|
45
|
+
# Also save to new format for next time
|
|
46
|
+
self._disk_cache.set(self.CACHE_KEY, data)
|
|
47
|
+
return data
|
|
48
|
+
|
|
49
|
+
# Fallback to new DiskCache
|
|
50
|
+
cached_data: dict | None = self._disk_cache.get(self.CACHE_KEY)
|
|
51
|
+
if cached_data is not None:
|
|
52
|
+
return cached_data
|
|
53
|
+
|
|
54
|
+
return None
|
|
55
|
+
except Exception:
|
|
56
|
+
return None
|
|
57
|
+
|
|
58
|
+
def save(self, data: dict) -> None:
|
|
59
|
+
"""Save cache to file."""
|
|
60
|
+
try:
|
|
61
|
+
# Save in both formats for backward compatibility
|
|
62
|
+
self._disk_cache.set(self.CACHE_KEY, data)
|
|
63
|
+
# Also save old JSON format
|
|
64
|
+
self.cache_path.parent.mkdir(parents=True, exist_ok=True)
|
|
65
|
+
with self.cache_path.open("w", encoding="utf-8") as f:
|
|
66
|
+
json.dump(data, f, indent=2)
|
|
67
|
+
except Exception:
|
|
68
|
+
pass
|
|
69
|
+
|
|
70
|
+
def is_valid(self, current_hash: str) -> bool:
|
|
71
|
+
"""Check if cache is valid for current source hash."""
|
|
72
|
+
try:
|
|
73
|
+
cached_data = self.load()
|
|
74
|
+
if cached_data is None:
|
|
75
|
+
return False
|
|
76
|
+
return cached_data.get("source_hash") == current_hash
|
|
77
|
+
except Exception:
|
|
78
|
+
return False
|
|
79
|
+
|
|
80
|
+
def invalidate(self) -> None:
|
|
81
|
+
"""Invalidate cache by removing the cache entry."""
|
|
82
|
+
try:
|
|
83
|
+
self._disk_cache.delete(self.CACHE_KEY)
|
|
84
|
+
self.cache_path.unlink(missing_ok=True)
|
|
85
|
+
except Exception:
|
|
86
|
+
pass
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
"""DebugSubagent - 自动修复子代理实现。
|
|
2
|
+
|
|
3
|
+
收集和结构化错误信息,提供修复接口。
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from dataclasses import dataclass, field
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@dataclass
|
|
11
|
+
class ErrorInfo:
|
|
12
|
+
"""结构化错误信息。"""
|
|
13
|
+
|
|
14
|
+
command: str
|
|
15
|
+
exit_code: int
|
|
16
|
+
stdout: str | None = None
|
|
17
|
+
stderr: str | None = None
|
|
18
|
+
context: dict[str, Any] = field(default_factory=dict)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@dataclass
|
|
22
|
+
class FixResult:
|
|
23
|
+
"""修复尝试结果。"""
|
|
24
|
+
|
|
25
|
+
attempt: int
|
|
26
|
+
success: bool
|
|
27
|
+
message: str
|
|
28
|
+
error_info: ErrorInfo | None = None
|
|
29
|
+
context: dict[str, Any] = field(default_factory=dict)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class DebugSubagent:
|
|
33
|
+
"""调试子代理 - 收集错误信息并提供修复接口。
|
|
34
|
+
|
|
35
|
+
注意:这是一个简单实现,不实际调用 investigate 技能,
|
|
36
|
+
只用于收集和结构化错误信息,提供接口供外部使用。
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
MAX_ATTEMPTS = 3
|
|
40
|
+
|
|
41
|
+
def __init__(self) -> None:
|
|
42
|
+
"""初始化 DebugSubagent。"""
|
|
43
|
+
self.errors: list[ErrorInfo] = []
|
|
44
|
+
self.fix_attempts: list[FixResult] = []
|
|
45
|
+
self.current_attempt = 0
|
|
46
|
+
self._enabled = False
|
|
47
|
+
|
|
48
|
+
@property
|
|
49
|
+
def enabled(self) -> bool:
|
|
50
|
+
"""检查子代理是否启用。"""
|
|
51
|
+
return self._enabled
|
|
52
|
+
|
|
53
|
+
@enabled.setter
|
|
54
|
+
def enabled(self, value: bool) -> None:
|
|
55
|
+
"""设置子代理启用状态。"""
|
|
56
|
+
self._enabled = value
|
|
57
|
+
|
|
58
|
+
def record_error(
|
|
59
|
+
self,
|
|
60
|
+
command: str,
|
|
61
|
+
exit_code: int,
|
|
62
|
+
stdout: str | None = None,
|
|
63
|
+
stderr: str | None = None,
|
|
64
|
+
context: dict[str, Any] | None = None,
|
|
65
|
+
) -> ErrorInfo:
|
|
66
|
+
"""记录一个错误。
|
|
67
|
+
|
|
68
|
+
Args:
|
|
69
|
+
command: 失败的命令
|
|
70
|
+
exit_code: 退出码
|
|
71
|
+
stdout: 标准输出
|
|
72
|
+
stderr: 标准错误
|
|
73
|
+
context: 附加上下文信息
|
|
74
|
+
|
|
75
|
+
Returns:
|
|
76
|
+
结构化的错误信息
|
|
77
|
+
"""
|
|
78
|
+
error_info = ErrorInfo(
|
|
79
|
+
command=command,
|
|
80
|
+
exit_code=exit_code,
|
|
81
|
+
stdout=stdout,
|
|
82
|
+
stderr=stderr,
|
|
83
|
+
context=context or {},
|
|
84
|
+
)
|
|
85
|
+
self.errors.append(error_info)
|
|
86
|
+
return error_info
|
|
87
|
+
|
|
88
|
+
def can_attempt_fix(self) -> bool:
|
|
89
|
+
"""检查是否可以尝试修复。
|
|
90
|
+
|
|
91
|
+
Returns:
|
|
92
|
+
如果可以尝试修复返回 True,否则返回 False
|
|
93
|
+
"""
|
|
94
|
+
return self._enabled and self.current_attempt < self.MAX_ATTEMPTS
|
|
95
|
+
|
|
96
|
+
def attempt_fix(
|
|
97
|
+
self,
|
|
98
|
+
error_info: ErrorInfo,
|
|
99
|
+
simulate_success: bool = False,
|
|
100
|
+
) -> FixResult:
|
|
101
|
+
"""尝试修复错误。
|
|
102
|
+
|
|
103
|
+
注意:这是一个模拟实现,不实际执行修复操作。
|
|
104
|
+
在完整实现中,这里会调用 investigate 技能。
|
|
105
|
+
|
|
106
|
+
Args:
|
|
107
|
+
error_info: 要修复的错误信息
|
|
108
|
+
simulate_success: 是否模拟修复成功(用于测试)
|
|
109
|
+
|
|
110
|
+
Returns:
|
|
111
|
+
修复尝试结果
|
|
112
|
+
"""
|
|
113
|
+
self.current_attempt += 1
|
|
114
|
+
|
|
115
|
+
success = simulate_success
|
|
116
|
+
message = (
|
|
117
|
+
"Simulated fix successful (for testing)"
|
|
118
|
+
if success
|
|
119
|
+
else "Fix attempt failed - max attempts reached or no fix available"
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
result = FixResult(
|
|
123
|
+
attempt=self.current_attempt,
|
|
124
|
+
success=success,
|
|
125
|
+
message=message,
|
|
126
|
+
error_info=error_info,
|
|
127
|
+
context={
|
|
128
|
+
"simulated": True,
|
|
129
|
+
"max_attempts": self.MAX_ATTEMPTS,
|
|
130
|
+
},
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
self.fix_attempts.append(result)
|
|
134
|
+
return result
|
|
135
|
+
|
|
136
|
+
def reset(self) -> None:
|
|
137
|
+
"""重置子代理状态。"""
|
|
138
|
+
self.errors.clear()
|
|
139
|
+
self.fix_attempts.clear()
|
|
140
|
+
self.current_attempt = 0
|
|
141
|
+
|
|
142
|
+
def get_summary(self) -> dict[str, Any]:
|
|
143
|
+
"""获取子代理活动摘要。
|
|
144
|
+
|
|
145
|
+
Returns:
|
|
146
|
+
包含活动摘要的字典
|
|
147
|
+
"""
|
|
148
|
+
return {
|
|
149
|
+
"enabled": self._enabled,
|
|
150
|
+
"errors_recorded": len(self.errors),
|
|
151
|
+
"fix_attempts_made": len(self.fix_attempts),
|
|
152
|
+
"successful_fixes": sum(1 for r in self.fix_attempts if r.success),
|
|
153
|
+
"failed_fixes": sum(1 for r in self.fix_attempts if not r.success),
|
|
154
|
+
"current_attempt": self.current_attempt,
|
|
155
|
+
"max_attempts": self.MAX_ATTEMPTS,
|
|
156
|
+
}
|