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.
Files changed (320) hide show
  1. py_wlcli-0.8.0.dist-info/METADATA +139 -0
  2. py_wlcli-0.8.0.dist-info/RECORD +320 -0
  3. py_wlcli-0.8.0.dist-info/WHEEL +4 -0
  4. py_wlcli-0.8.0.dist-info/entry_points.txt +2 -0
  5. py_wlcli-0.8.0.dist-info/licenses/LICENSE +21 -0
  6. wl_cli/__init__.py +27 -0
  7. wl_cli/__main__.py +130 -0
  8. wl_cli/capabilities.py +79 -0
  9. wl_cli/cc/__init__.py +5 -0
  10. wl_cli/cc/checks.py +1019 -0
  11. wl_cli/cc/cli.py +370 -0
  12. wl_cli/cc/mutate.py +1350 -0
  13. wl_cli/cli_click.py +156 -0
  14. wl_cli/commands/__init__.py +84 -0
  15. wl_cli/commands/base.py +80 -0
  16. wl_cli/commands/buildcommands/__init__.py +1 -0
  17. wl_cli/commands/buildcommands/build.py +71 -0
  18. wl_cli/commands/buildcommands/build_utils.py +47 -0
  19. wl_cli/commands/buildcommands/builders/__init__.py +18 -0
  20. wl_cli/commands/buildcommands/builders/cleanup.py +35 -0
  21. wl_cli/commands/buildcommands/builders/coordinator.py +122 -0
  22. wl_cli/commands/buildcommands/builders/environment.py +81 -0
  23. wl_cli/commands/buildcommands/builders/rust_builder.py +39 -0
  24. wl_cli/commands/buildcommands/builders/stubs.py +58 -0
  25. wl_cli/commands/buildcommands/dist.py +326 -0
  26. wl_cli/commands/buildcommands/test.py +89 -0
  27. wl_cli/commands/clean/__init__.py +1 -0
  28. wl_cli/commands/clean/build.py +151 -0
  29. wl_cli/commands/clean/clean.py +294 -0
  30. wl_cli/commands/clean/clean_utils.py +77 -0
  31. wl_cli/commands/clean/core.py +236 -0
  32. wl_cli/commands/clean/dead_code/__init__.py +0 -0
  33. wl_cli/commands/clean/dead_code/dead_code_command.py +70 -0
  34. wl_cli/commands/clean/dead_code/detector.py +161 -0
  35. wl_cli/commands/clean/dead_code/fixer.py +176 -0
  36. wl_cli/commands/clean/dead_code/reporter.py +295 -0
  37. wl_cli/commands/clean/dead_code/utils.py +0 -0
  38. wl_cli/commands/clean/lfs.py +215 -0
  39. wl_cli/commands/clean/python.py +81 -0
  40. wl_cli/commands/clean/rust.py +91 -0
  41. wl_cli/commands/clean/utils/__init__.py +34 -0
  42. wl_cli/commands/clean/venv.py +91 -0
  43. wl_cli/commands/cleanup.py +73 -0
  44. wl_cli/commands/click_base.py +79 -0
  45. wl_cli/commands/config/__init__.py +1 -0
  46. wl_cli/commands/config/config.py +291 -0
  47. wl_cli/commands/debug/__init__.py +6 -0
  48. wl_cli/commands/debug/debug.py +309 -0
  49. wl_cli/commands/debug/debug_cache.py +86 -0
  50. wl_cli/commands/debug/debug_subagent.py +156 -0
  51. wl_cli/commands/debug/output_formatter.py +165 -0
  52. wl_cli/commands/debug/source_hasher.py +92 -0
  53. wl_cli/commands/format/__init__.py +13 -0
  54. wl_cli/commands/format/format_cache_adapter.py +125 -0
  55. wl_cli/commands/format/format_command.py +197 -0
  56. wl_cli/commands/format/format_coordinator.py +191 -0
  57. wl_cli/commands/format/format_executor.py +470 -0
  58. wl_cli/commands/format/format_outcome.py +44 -0
  59. wl_cli/commands/format/format_path_handler.py +1016 -0
  60. wl_cli/commands/format/format_progress.py +62 -0
  61. wl_cli/commands/format/format_reporter.py +31 -0
  62. wl_cli/commands/format/python_formatter.py +506 -0
  63. wl_cli/commands/format/rust_formatter.py +106 -0
  64. wl_cli/commands/gate/__init__.py +1 -0
  65. wl_cli/commands/gate/gate_command.py +862 -0
  66. wl_cli/commands/gate/preflight.py +445 -0
  67. wl_cli/commands/git/__init__.py +1 -0
  68. wl_cli/commands/git/config.py +169 -0
  69. wl_cli/commands/git/git_facts.py +60 -0
  70. wl_cli/commands/git/git_flow.py +86 -0
  71. wl_cli/commands/git/hook.py +112 -0
  72. wl_cli/commands/git/policy.py +216 -0
  73. wl_cli/commands/git/push_update.py +82 -0
  74. wl_cli/commands/initenv/__init__.py +1 -0
  75. wl_cli/commands/initenv/common/__init__.py +23 -0
  76. wl_cli/commands/initenv/common/check_git_bash.py +78 -0
  77. wl_cli/commands/initenv/common/error_handler.py +68 -0
  78. wl_cli/commands/initenv/common/exceptions.py +52 -0
  79. wl_cli/commands/initenv/components/__init__.py +4 -0
  80. wl_cli/commands/initenv/components/cargo_sync.py +240 -0
  81. wl_cli/commands/initenv/components/config_manager.py +106 -0
  82. wl_cli/commands/initenv/components/directory_utils.py +71 -0
  83. wl_cli/commands/initenv/components/git/__init__.py +9 -0
  84. wl_cli/commands/initenv/components/git/branch_creator.py +143 -0
  85. wl_cli/commands/initenv/components/git/git_flow_interface.py +24 -0
  86. wl_cli/commands/initenv/components/git/git_flow_manager.py +159 -0
  87. wl_cli/commands/initenv/components/git/git_lfs_manager.py +272 -0
  88. wl_cli/commands/initenv/components/git/git_utility.py +254 -0
  89. wl_cli/commands/initenv/components/git/gitignore_manager.py +53 -0
  90. wl_cli/commands/initenv/components/git_initializer.py +212 -0
  91. wl_cli/commands/initenv/components/i18n_manager.py +104 -0
  92. wl_cli/commands/initenv/components/log_manager.py +241 -0
  93. wl_cli/commands/initenv/components/project_structure/__init__.py +23 -0
  94. wl_cli/commands/initenv/components/project_structure/config_generator.py +360 -0
  95. wl_cli/commands/initenv/components/project_structure/directory_creator.py +184 -0
  96. wl_cli/commands/initenv/components/project_structure/hook_source.py +110 -0
  97. wl_cli/commands/initenv/components/project_structure/hooks_manager.py +995 -0
  98. wl_cli/commands/initenv/components/project_structure/readme_handler.py +56 -0
  99. wl_cli/commands/initenv/components/project_structure/rust_files_handler.py +30 -0
  100. wl_cli/commands/initenv/components/project_structure/setup_handler.py +103 -0
  101. wl_cli/commands/initenv/components/project_structure/sop_generator.py +73 -0
  102. wl_cli/commands/initenv/components/project_structure/template_copier.py +104 -0
  103. wl_cli/commands/initenv/components/project_structure/template_manager.py +195 -0
  104. wl_cli/commands/initenv/components/pyproject_generator.py +194 -0
  105. wl_cli/commands/initenv/components/rust_initializer.py +145 -0
  106. wl_cli/commands/initenv/components/symlink_cleaner.py +99 -0
  107. wl_cli/commands/initenv/components/symlink_manager.py +52 -0
  108. wl_cli/commands/initenv/components/venv_manager.py +118 -0
  109. wl_cli/commands/initenv/initenv.py +169 -0
  110. wl_cli/commands/initenv/services/__init__.py +11 -0
  111. wl_cli/commands/initenv/services/environment_initializer.py +288 -0
  112. wl_cli/commands/initenv/services/git_lfs_initializer.py +26 -0
  113. wl_cli/commands/initenv/services/project_structure_initializer.py +52 -0
  114. wl_cli/commands/lfs/README.md +221 -0
  115. wl_cli/commands/lfs/__init__.py +5 -0
  116. wl_cli/commands/lfs/cli.py +106 -0
  117. wl_cli/commands/lfs/config.py +164 -0
  118. wl_cli/commands/lfs/hook.py +61 -0
  119. wl_cli/commands/lfs/initializer.py +531 -0
  120. wl_cli/commands/lfs/manager.py +95 -0
  121. wl_cli/commands/lfs/policy.py +165 -0
  122. wl_cli/commands/lfs/watcher.py +294 -0
  123. wl_cli/commands/lint/__init__.py +1 -0
  124. wl_cli/commands/lint/lint_command.py +297 -0
  125. wl_cli/commands/lint/lint_executor.py +409 -0
  126. wl_cli/commands/lint/lint_reporter.py +579 -0
  127. wl_cli/commands/lint/lint_run.py +258 -0
  128. wl_cli/commands/lint/mypy_config.py +90 -0
  129. wl_cli/commands/registry.py +103 -0
  130. wl_cli/commands/self/__init__.py +7 -0
  131. wl_cli/commands/self/common/__init__.py +8 -0
  132. wl_cli/commands/self/common/capabilities.py +30 -0
  133. wl_cli/commands/self/common/config.py +27 -0
  134. wl_cli/commands/self/common/version_cache.py +85 -0
  135. wl_cli/commands/self/handlers/__init__.py +19 -0
  136. wl_cli/commands/self/handlers/base_handler.py +28 -0
  137. wl_cli/commands/self/handlers/install_handler.py +85 -0
  138. wl_cli/commands/self/handlers/status_handler.py +66 -0
  139. wl_cli/commands/self/handlers/uninstall_handler.py +58 -0
  140. wl_cli/commands/self/handlers/update_handler.py +102 -0
  141. wl_cli/commands/self/handlers/version_handler.py +16 -0
  142. wl_cli/commands/self/self_command.py +237 -0
  143. wl_cli/commands/self/services/__init__.py +15 -0
  144. wl_cli/commands/self/services/installation_manager.py +187 -0
  145. wl_cli/commands/self/services/local_installer.py +165 -0
  146. wl_cli/commands/self/services/pypi_updater.py +278 -0
  147. wl_cli/commands/self/services/windows_update.py +174 -0
  148. wl_cli/commands/testcommands/__init__.py +8 -0
  149. wl_cli/commands/testcommands/base_dependency_analyzer.py +198 -0
  150. wl_cli/commands/testcommands/batch_processor.py +278 -0
  151. wl_cli/commands/testcommands/cache_manager.py +1156 -0
  152. wl_cli/commands/testcommands/coverage_analyzer.py +955 -0
  153. wl_cli/commands/testcommands/coverage_cache_manager.py +183 -0
  154. wl_cli/commands/testcommands/progress_manager.py +79 -0
  155. wl_cli/commands/testcommands/python_env_detector.py +207 -0
  156. wl_cli/commands/testcommands/report_generator.py +101 -0
  157. wl_cli/commands/testcommands/single_test_executor.py +250 -0
  158. wl_cli/commands/testcommands/speed_checker.py +118 -0
  159. wl_cli/commands/testcommands/test_command_builder.py +165 -0
  160. wl_cli/commands/testcommands/test_commands.py +786 -0
  161. wl_cli/commands/testcommands/test_reporter.py +181 -0
  162. wl_cli/commands/testcommands/test_result_handler.py +96 -0
  163. wl_cli/commands/testcommands/test_runner.py +1343 -0
  164. wl_cli/commands/testcommands/test_utils.py +197 -0
  165. wl_cli/commands/testcommands/utils/__init__.py +17 -0
  166. wl_cli/commands/testcommands/utils/coverage_parser.py +98 -0
  167. wl_cli/commands/testcommands/utils/env_info.py +36 -0
  168. wl_cli/commands/testcommands/utils/junit_parser.py +75 -0
  169. wl_cli/commands/testcommands/utils/markdown_utils.py +20 -0
  170. wl_cli/commands/testcommands/utils/project_root.py +61 -0
  171. wl_cli/commands/update/__init__.py +9 -0
  172. wl_cli/commands/update/services/__init__.py +21 -0
  173. wl_cli/commands/update/services/backup_manager.py +207 -0
  174. wl_cli/commands/update/services/component_manager.py +83 -0
  175. wl_cli/commands/update/services/conflict_resolver.py +189 -0
  176. wl_cli/commands/update/services/vendors_updater.py +85 -0
  177. wl_cli/commands/update/services/version_tracker.py +127 -0
  178. wl_cli/commands/update/update_command.py +270 -0
  179. wl_cli/commands/update/utils/__init__.py +3 -0
  180. wl_cli/commands/version/__init__.py +9 -0
  181. wl_cli/commands/version/version_command.py +155 -0
  182. wl_cli/commands/workflow.py +477 -0
  183. wl_cli/env/__init__.py +5 -0
  184. wl_cli/env/cli.py +167 -0
  185. wl_cli/env_status.py +157 -0
  186. wl_cli/envelope.py +91 -0
  187. wl_cli/exceptions.py +85 -0
  188. wl_cli/lib/__init__.py +3 -0
  189. wl_cli/lib/rust_utils.py +35 -0
  190. wl_cli/mcp/__init__.py +6 -0
  191. wl_cli/mcp/capabilities.py +19 -0
  192. wl_cli/mcp/cli.py +22 -0
  193. wl_cli/mcp/gitlab.py +553 -0
  194. wl_cli/mcp/k8s.py +344 -0
  195. wl_cli/mcp/protocol.py +159 -0
  196. wl_cli/platform.py +139 -0
  197. wl_cli/quality/__init__.py +18 -0
  198. wl_cli/utils/__init__.py +7 -0
  199. wl_cli/utils/cache.py +491 -0
  200. wl_cli/utils/cache_ledger.py +355 -0
  201. wl_cli/utils/cache_store.py +546 -0
  202. wl_cli/utils/config/__init__.py +27 -0
  203. wl_cli/utils/config/config_loader.py +163 -0
  204. wl_cli/utils/config/core.py +862 -0
  205. wl_cli/utils/config/defaults.py +142 -0
  206. wl_cli/utils/config/env_exporter.py +86 -0
  207. wl_cli/utils/config/env_parser.py +108 -0
  208. wl_cli/utils/config/validation.py +280 -0
  209. wl_cli/utils/error_handler.py +165 -0
  210. wl_cli/utils/file_copier.py +56 -0
  211. wl_cli/utils/file_operations.py +506 -0
  212. wl_cli/utils/language_utils.py +122 -0
  213. wl_cli/utils/lazy_import.py +240 -0
  214. wl_cli/utils/log_levels.py +35 -0
  215. wl_cli/utils/log_rotators.py +74 -0
  216. wl_cli/utils/logging.py +458 -0
  217. wl_cli/utils/output_context.py +142 -0
  218. wl_cli/utils/performance/__init__.py +1 -0
  219. wl_cli/utils/performance/decorators.py +111 -0
  220. wl_cli/utils/performance/exporter.py +38 -0
  221. wl_cli/utils/performance/models.py +16 -0
  222. wl_cli/utils/performance/monitor.py +161 -0
  223. wl_cli/utils/performance_monitor.py +32 -0
  224. wl_cli/utils/platform_adapter.py +131 -0
  225. wl_cli/utils/pre_commit.py +266 -0
  226. wl_cli/utils/pre_commit_config.py +72 -0
  227. wl_cli/utils/root.py +521 -0
  228. wl_cli/utils/root_fallback.py +256 -0
  229. wl_cli/utils/run_evidence.py +236 -0
  230. wl_cli/utils/structured_logger.py +18 -0
  231. wl_cli/utils/structured_logging/__init__.py +19 -0
  232. wl_cli/utils/structured_logging/config.py +337 -0
  233. wl_cli/utils/structured_logging/core.py +177 -0
  234. wl_cli/utils/structured_logging/env.py +56 -0
  235. wl_cli/utils/structured_logging/filtering.py +75 -0
  236. wl_cli/utils/structured_logging/filters.py +55 -0
  237. wl_cli/utils/structured_logging/formatters.py +251 -0
  238. wl_cli/utils/structured_logging/handlers.py +308 -0
  239. wl_cli/utils/structured_logging/logger.py +354 -0
  240. wl_cli/utils/subprocess.py +71 -0
  241. wl_cli/utils/subprocess_utils.py +260 -0
  242. wl_cli/utils/test_utils/__init__.py +5 -0
  243. wl_cli/utils/test_utils/test_filter.py +36 -0
  244. wl_cli/utils/uv_run.py +79 -0
  245. wl_cli/utils/uv_tool.py +117 -0
  246. wl_cli/utils/uv_tree.py +71 -0
  247. wl_cli/utils/uv_workspace.py +174 -0
  248. wl_cli/utils/version/__init__.py +15 -0
  249. wl_cli/utils/version/pypi_version_checker.py +95 -0
  250. wl_cli/utils/version/version_comparator.py +133 -0
  251. wl_cli/utils/version/version_detectors.py +312 -0
  252. wl_cli/utils/version/version_service.py +131 -0
  253. wl_cli/utils/version/version_updater.py +198 -0
  254. wl_cli/utils/wl_dir_updater.py +1440 -0
  255. wl_cli/utils/workspace_detector/__init__.py +28 -0
  256. wl_cli/utils/workspace_detector/core.py +161 -0
  257. wl_cli/utils/workspace_detector/detection_strategies.py +107 -0
  258. wl_cli/utils/workspace_detector/exceptions.py +5 -0
  259. wl_cli/utils/workspace_detector/types.py +17 -0
  260. wl_cli/utils/workspace_detector/venv_resolver.py +84 -0
  261. wl_cli/vendors/config/.codespellrc +2 -0
  262. wl_cli/vendors/config/dependencies.toml +10 -0
  263. wl_cli/vendors/config/mypy.ini +62 -0
  264. wl_cli/vendors/config/pyproject_defaults.toml +28 -0
  265. wl_cli/vendors/config/template_config.toml +24 -0
  266. wl_cli/vendors/config/tool_configs.toml +76 -0
  267. wl_cli/vendors/git/.gitignore +122 -0
  268. wl_cli/vendors/git/.pre-commit-config.yaml +37 -0
  269. wl_cli/vendors/hooks/applypatch-msg.sample +15 -0
  270. wl_cli/vendors/hooks/commit-msg.sample +24 -0
  271. wl_cli/vendors/hooks/fsmonitor-watchman.sample +174 -0
  272. wl_cli/vendors/hooks/post-update.sample +8 -0
  273. wl_cli/vendors/hooks/pre-applypatch.sample +14 -0
  274. wl_cli/vendors/hooks/pre-commit +45 -0
  275. wl_cli/vendors/hooks/pre-commit.legacy +31 -0
  276. wl_cli/vendors/hooks/pre-commit.sample +49 -0
  277. wl_cli/vendors/hooks/pre-merge-commit.sample +13 -0
  278. wl_cli/vendors/hooks/pre-push.sample +60 -0
  279. wl_cli/vendors/hooks/pre-rebase.sample +169 -0
  280. wl_cli/vendors/hooks/pre-receive.sample +24 -0
  281. wl_cli/vendors/hooks/prepare-commit-msg.sample +42 -0
  282. wl_cli/vendors/hooks/push-to-checkout.sample +78 -0
  283. wl_cli/vendors/hooks/sendemail-validate.sample +77 -0
  284. wl_cli/vendors/hooks/unix/applypatch-msg.sample +15 -0
  285. wl_cli/vendors/hooks/unix/check_lockfile.py +58 -0
  286. wl_cli/vendors/hooks/unix/check_lockfile.sh +36 -0
  287. wl_cli/vendors/hooks/unix/commit-msg.sample +24 -0
  288. wl_cli/vendors/hooks/unix/fsmonitor-watchman.sample +174 -0
  289. wl_cli/vendors/hooks/unix/post-update.sample +8 -0
  290. wl_cli/vendors/hooks/unix/pre-applypatch.sample +14 -0
  291. wl_cli/vendors/hooks/unix/pre-commit +190 -0
  292. wl_cli/vendors/hooks/unix/pre-commit.legacy +31 -0
  293. wl_cli/vendors/hooks/unix/pre-commit.py +81 -0
  294. wl_cli/vendors/hooks/unix/pre-commit.sample +49 -0
  295. wl_cli/vendors/hooks/unix/pre-merge-commit.sample +13 -0
  296. wl_cli/vendors/hooks/unix/pre-push +417 -0
  297. wl_cli/vendors/hooks/unix/pre-push.sample +53 -0
  298. wl_cli/vendors/hooks/unix/pre-rebase.sample +169 -0
  299. wl_cli/vendors/hooks/unix/pre-receive.sample +24 -0
  300. wl_cli/vendors/hooks/unix/prepare-commit-msg.sample +42 -0
  301. wl_cli/vendors/hooks/update.sample +128 -0
  302. wl_cli/vendors/hooks/win/work.bat +63 -0
  303. wl_cli/vendors/project/.gitignore +90 -0
  304. wl_cli/vendors/project/.python-version +1 -0
  305. wl_cli/vendors/project/LICENSE +21 -0
  306. wl_cli/vendors/project/README.md +0 -0
  307. wl_cli/vendors/project/pyproject.toml +27 -0
  308. wl_cli/vendors/project/pyproject_no_workspace.toml +16 -0
  309. wl_cli/vendors/project/pyproject_workspace.toml +14 -0
  310. wl_cli/vendors/readme/README.md +120 -0
  311. wl_cli/vendors/rust/Cargo.toml +14 -0
  312. wl_cli/vendors/rust/lib/__init__.py +3 -0
  313. wl_cli/vendors/rust/lib/rust_utils.py +31 -0
  314. wl_cli/vendors/rust/src/lib.rs +13 -0
  315. wl_cli/vendors/rust/src/main.rs +3 -0
  316. wl_cli/vendors/rust/tests/test_rust_fallback.py +40 -0
  317. wl_cli/vendors/sop_doc/git-dev-sop.md +220 -0
  318. wl_cli/vendors/sop_doc/industrial-standard-sop.md +145 -0
  319. wl_cli/vendors/sop_doc/python-sop.md +342 -0
  320. 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
+ }