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
wl_cli/env_status.py ADDED
@@ -0,0 +1,157 @@
1
+ """Shared payload builder for the ``wl env check`` probe (issue #221).
2
+
3
+ ``wl env check`` (the ``wl_cli.env`` domain) and the deprecated
4
+ ``wl self capabilities`` alias (``wl_cli.commands``) must answer with the same
5
+ five-state envelope. The quality domain may not import the env domain (#220),
6
+ so the builder lives here in the shared layer and both commands call it.
7
+
8
+ The probe reports **the wl that is running**: its version, its interpreter and
9
+ the shared platform matrix from :func:`wl_cli.platform.platform_payload`
10
+ (wlcc #321). It does not look up another ``wl`` on ``PATH`` and it writes
11
+ nothing. The probe runs with a throwaway temporary directory as its CWD, so a
12
+ future probe cannot create or rewrite ``.wl/`` in the caller's directory; the
13
+ startup self-sync is independently skipped by the dispatcher for read-only
14
+ commands (#220).
15
+
16
+ Envelope contract: ``status`` is ``ok`` for a supported interpreter,
17
+ ``unverifiable`` when the platform matrix rejects it (unreachable for an
18
+ installed wl -- the wheel requires Python 3.11+), and ``error`` only when the
19
+ payload itself cannot be built. The envelope's ``reason`` is the platform
20
+ reason when unsupported; the matrix's own ``reason`` is always carried as
21
+ ``platform_reason``, so the two keys never collide.
22
+ """
23
+
24
+ from __future__ import annotations
25
+
26
+ import os
27
+ import shlex
28
+ import sys
29
+ import tempfile
30
+ from contextlib import contextmanager
31
+ from typing import TYPE_CHECKING, Any
32
+
33
+ from . import __version__
34
+ from .capabilities import CAPABILITY_SINCE, capability_names, capability_since
35
+ from .envelope import OK_STATUSES, dump, exit_code
36
+ from .platform import platform_payload
37
+
38
+ if TYPE_CHECKING:
39
+ from collections.abc import Generator
40
+
41
+ #: Stable tool name in the envelope's ``tool`` key (the alias reuses it: both
42
+ #: commands answer the same question, so consumers key off one name).
43
+ TOOL = "env check"
44
+
45
+ #: Minimum release the repair hint and ``wl env repair`` install. Derived from
46
+ #: the shared capability registry -- ``env.check`` first ships in the release
47
+ #: the repair should install -- so the floor is not a second version literal
48
+ #: that can drift from the registry (#227). ``VERSION`` itself is bumped by the
49
+ #: release process, not by this change.
50
+ INSTALL_FLOOR = CAPABILITY_SINCE["env.check"]
51
+
52
+ #: The repair command as argv -- executed literally, with no shell (so the
53
+ #: ``>=`` spec can never be reinterpreted as a redirection) and no ``--force``
54
+ #: (uv treats an identical install as a no-op, which is what makes repair
55
+ #: idempotent).
56
+ INSTALL_ARGV: tuple[str, ...] = (
57
+ "uv",
58
+ "tool",
59
+ "install",
60
+ "--python",
61
+ "3.12",
62
+ f"py-wlcli>={INSTALL_FLOOR}",
63
+ )
64
+
65
+ #: Human-readable form derived from the argv, so the printed command and the
66
+ #: executed argv cannot drift apart (wlcc #266: printed == executed).
67
+ INSTALL_COMMAND = shlex.join(INSTALL_ARGV)
68
+
69
+
70
+ @contextmanager
71
+ def _isolated_cwd() -> Generator[None, None, None]:
72
+ """Run the read-only probe with a throwaway directory as the CWD.
73
+
74
+ ``wl env check`` must not create or rewrite ``.wl/`` in the caller's
75
+ directory. Running the probe in a temporary CWD keeps that guarantee even
76
+ if a future probe grows a relative write; the previous CWD is restored on
77
+ every exit path.
78
+ """
79
+ previous = os.getcwd()
80
+ with tempfile.TemporaryDirectory(prefix="wl-env-check-") as tmp:
81
+ os.chdir(tmp)
82
+ try:
83
+ yield
84
+ finally:
85
+ os.chdir(previous)
86
+
87
+
88
+ def executable_path() -> str:
89
+ """Return the interpreter running this wl (diagnostic; #221 ruling 2).
90
+
91
+ ``sys.executable`` is empty for some embedded interpreters, so fall back to
92
+ the invocation path. Resolve before any CWD change so a relative
93
+ ``sys.argv[0]`` keeps its original meaning.
94
+ """
95
+ candidate = sys.executable or (sys.argv[0] if sys.argv else "")
96
+ return os.path.abspath(candidate) if candidate else "unknown"
97
+
98
+
99
+ def install_fields() -> dict[str, str]:
100
+ """Return the ``install`` payload: the actual command and its floor."""
101
+ return {"command": INSTALL_COMMAND, "floor": INSTALL_FLOOR}
102
+
103
+
104
+ def build_payload() -> tuple[str, str | None, dict[str, Any]]:
105
+ """Build the ``wl env check`` fields.
106
+
107
+ Returns:
108
+ ``(status, reason, fields)``. ``status`` is ``ok`` when the running
109
+ interpreter is supported, ``unverifiable`` when the shared platform
110
+ matrix says otherwise, and ``error`` only when the payload cannot be
111
+ built at all. ``reason`` carries the readable platform reason when the
112
+ status is not ``ok``; ``fields`` is the envelope payload.
113
+ """
114
+ executable = executable_path()
115
+ try:
116
+ with _isolated_cwd():
117
+ platform_payload_fields = platform_payload()
118
+ # Consume the shared payload read-only: pop ``reason`` from a copy, so a
119
+ # future platform_payload that caches and returns one dict cannot lose
120
+ # the key on the next build (#227).
121
+ platform_fields = dict(platform_payload_fields)
122
+ raw_reason = platform_fields.pop("reason")
123
+ platform_reason = raw_reason if isinstance(raw_reason, str) else None
124
+ fields: dict[str, Any] = {
125
+ "version": __version__,
126
+ "executable": executable,
127
+ **platform_fields,
128
+ "platform_reason": platform_reason,
129
+ "capabilities": capability_names(),
130
+ "capability_since": capability_since(),
131
+ "install": install_fields(),
132
+ }
133
+ except Exception as exc: # noqa: BLE001 - the envelope is the error report
134
+ return (
135
+ "error",
136
+ f"could not probe the environment: {exc}",
137
+ {
138
+ "version": __version__,
139
+ "executable": executable,
140
+ "install": install_fields(),
141
+ },
142
+ )
143
+
144
+ if fields["supported"] is True:
145
+ return "ok", None, fields
146
+ return "unverifiable", platform_reason or "unsupported platform", fields
147
+
148
+
149
+ def emit_check() -> int:
150
+ """Write the ``env check`` envelope to stdout and return its exit code.
151
+
152
+ Both ``wl env check --json`` and the deprecated ``wl self capabilities
153
+ --json`` alias call this, so the two commands cannot answer differently.
154
+ """
155
+ status, reason, fields = build_payload()
156
+ dump(status, status in OK_STATUSES, tool=TOOL, reason=reason, **fields)
157
+ return exit_code(status)
wl_cli/envelope.py ADDED
@@ -0,0 +1,91 @@
1
+ """Shared five-state JSON envelope for the domain commands (issue #220).
2
+
3
+ Every ``wl`` domain command that answers a machine-readable question
4
+ (``wl env check``, ``wl cc repair``, ...) reports through this one envelope,
5
+ so consumers learn the contract once instead of per tool. The line on stdout
6
+ is exactly:
7
+
8
+ {"tool": "<tool>", "ok": <bool>, "status": "<one of five>", "reason": null, ...payload}
9
+
10
+ The five states and their exit codes are defined **only** here:
11
+
12
+ ok probe holds, nothing pending -> rc 0
13
+ dangling probe holds, dangling entries remain (not fixed) -> rc 0
14
+ repaired probe holds, changes were written -> rc 0
15
+ unverifiable probe does not hold, but reported explicitly -> rc 1
16
+ error the tool itself failed (bad JSON, write, args) -> rc 1
17
+
18
+ ``ok`` is the boolean reduction of ``status`` (``ok == status in OK_STATUSES``);
19
+ the conclusion lives in the JSON, never in the exit code alone. stdout is
20
+ always exactly one JSON line; diagnostics go to stderr.
21
+
22
+ Mirrors ``scripts/probe_json.py`` in the wlcc repo (the contract oracle)
23
+ exactly: ``exit_code`` maps every status outside :data:`OK_STATUSES` — known
24
+ or unknown — to rc 1, while :func:`dump` rejects unknown statuses so an
25
+ unknown state can never be emitted.
26
+ """
27
+
28
+ import json
29
+ import sys
30
+ from typing import Any
31
+
32
+ STATUSES = ("ok", "dangling", "repaired", "unverifiable", "error")
33
+ OK_STATUSES = ("ok", "dangling", "repaired")
34
+
35
+ EXIT_ESTABLISHED = 0
36
+ EXIT_NOT_ESTABLISHED = 1
37
+
38
+
39
+ def exit_code(status: str) -> int:
40
+ """Map a five-state status to an exit code.
41
+
42
+ Args:
43
+ status: One of :data:`STATUSES`.
44
+
45
+ Returns:
46
+ ``0`` when the probe is established (``ok``/``dangling``/``repaired``),
47
+ ``1`` otherwise — including unknown statuses, matching the wlcc oracle.
48
+ Emitting an unknown status is still impossible because :func:`dump`
49
+ rejects it.
50
+ """
51
+ return EXIT_ESTABLISHED if status in OK_STATUSES else EXIT_NOT_ESTABLISHED
52
+
53
+
54
+ def dump(
55
+ status: str,
56
+ ok: bool,
57
+ *,
58
+ tool: str,
59
+ reason: str | None = None,
60
+ **fields: Any,
61
+ ) -> None:
62
+ """Write one envelope line to stdout as machine-readable JSON.
63
+
64
+ ``tool``, ``ok``, ``status`` and ``reason`` are the four fixed top-level
65
+ keys (in that order); ``fields`` carries the tool-specific payload after
66
+ them. ``ok`` must agree with ``status`` — disagreement raises immediately
67
+ rather than emitting two contradictory conclusions.
68
+
69
+ Args:
70
+ status: One of :data:`STATUSES`.
71
+ ok: Boolean reduction of ``status`` (``status in OK_STATUSES``).
72
+ tool: Stable tool name reported in the envelope.
73
+ reason: Human-readable explanation, or ``None``.
74
+ **fields: Tool-specific payload appended after the fixed keys.
75
+
76
+ Raises:
77
+ ValueError: If ``status`` is unknown or ``ok`` contradicts it.
78
+ """
79
+ if status not in STATUSES:
80
+ raise ValueError(f"未知 status: {status!r}(应 ∈ {', '.join(STATUSES)})")
81
+ if ok != (status in OK_STATUSES):
82
+ raise ValueError(f"ok={ok!r} 与 status={status!r} 不自洽")
83
+
84
+ payload: dict[str, Any] = {
85
+ "tool": tool,
86
+ "ok": ok,
87
+ "status": status,
88
+ "reason": reason,
89
+ }
90
+ payload.update(fields)
91
+ sys.stdout.write(json.dumps(payload, ensure_ascii=False) + "\n")
wl_cli/exceptions.py ADDED
@@ -0,0 +1,85 @@
1
+ """
2
+ Custom exceptions for WL Commands.
3
+ """
4
+
5
+
6
+ class ErrorCode:
7
+ """Standard error codes."""
8
+
9
+ SUCCESS = 0
10
+ COMMAND_NOT_FOUND = 1
11
+ COMMAND_EXECUTION_FAILED = 2
12
+ INVALID_ARGUMENT = 3
13
+ MISSING_DEPENDENCY = 4
14
+ MAKE_NOT_FOUND = 5
15
+ UV_NOT_FOUND = 6
16
+
17
+
18
+ class CommandError(Exception):
19
+ """Base exception for command failures."""
20
+
21
+ def __init__(
22
+ self, message: str, error_code: int = ErrorCode.COMMAND_EXECUTION_FAILED
23
+ ) -> None:
24
+ """
25
+ Initialize command error.
26
+
27
+ Args:
28
+ message (str): Error message.
29
+ error_code (int): Error code.
30
+ """
31
+ super().__init__(message)
32
+ self.error_code = error_code
33
+
34
+
35
+ class MakeNotFoundError(CommandError):
36
+ """Raised when 'make' command is not found."""
37
+
38
+ def __init__(self, message: str = "Make command not found") -> None:
39
+ super().__init__(message, ErrorCode.MAKE_NOT_FOUND)
40
+
41
+
42
+ class UVNotFoundError(CommandError):
43
+ """Raised when 'uv' command is not found."""
44
+
45
+ def __init__(self, message: str = "UV command not found") -> None:
46
+ super().__init__(message, ErrorCode.UV_NOT_FOUND)
47
+
48
+
49
+ class ConfigError(CommandError):
50
+ """Raised when configuration errors occur."""
51
+
52
+ def __init__(self, message: str) -> None:
53
+ super().__init__(message, ErrorCode.INVALID_ARGUMENT)
54
+
55
+
56
+ class FileOperationError(CommandError):
57
+ """Raised when file operation errors occur."""
58
+
59
+ def __init__(self, message: str, path: str | None = None) -> None:
60
+ super().__init__(message, ErrorCode.COMMAND_EXECUTION_FAILED)
61
+ self.path = path
62
+
63
+
64
+ class GitError(CommandError):
65
+ """Raised when Git operation errors occur."""
66
+
67
+ def __init__(self, message: str, command: str | None = None) -> None:
68
+ super().__init__(message, ErrorCode.COMMAND_EXECUTION_FAILED)
69
+ self.command = command
70
+
71
+
72
+ class NetworkError(CommandError):
73
+ """Raised when network operation errors occur."""
74
+
75
+ def __init__(self, message: str, url: str | None = None) -> None:
76
+ super().__init__(message, ErrorCode.COMMAND_EXECUTION_FAILED)
77
+ self.url = url
78
+
79
+
80
+ class ValidationError(CommandError):
81
+ """Raised when validation errors occur."""
82
+
83
+ def __init__(self, message: str, field: str | None = None) -> None:
84
+ super().__init__(message, ErrorCode.INVALID_ARGUMENT)
85
+ self.field = field
wl_cli/lib/__init__.py ADDED
@@ -0,0 +1,3 @@
1
+ from .rust_utils import HAS_RUST_EXTENSION, sum_as_string
2
+
3
+ __all__ = ["sum_as_string", "HAS_RUST_EXTENSION"]
@@ -0,0 +1,35 @@
1
+ # 尝试导入 Rust 扩展,如果失败则使用 Python 实现
2
+ import os
3
+ import sys
4
+ from typing import cast
5
+
6
+ try:
7
+ # 使用当前文件所在目录动态构建 Rust 扩展路径
8
+ current_dir = os.path.dirname(os.path.abspath(__file__))
9
+ sys.path.insert(0, current_dir)
10
+
11
+ # 导入 Rust 扩展
12
+ from wl_cli_native import sum_as_string as rust_sum_as_string
13
+
14
+ HAS_RUST_EXTENSION = True
15
+ except ImportError:
16
+ HAS_RUST_EXTENSION = False
17
+
18
+
19
+ def sum_as_string(a: int, b: int) -> str:
20
+ """
21
+ 计算两个整数的和并返回字符串表示。
22
+ 优先使用 Rust 扩展实现,不可用时回落至 Python 实现。
23
+
24
+ Args:
25
+ a: 第一个整数
26
+ b: 第二个整数
27
+
28
+ Returns:
29
+ 两个整数和的字符串表示
30
+ """
31
+ if HAS_RUST_EXTENSION:
32
+ return cast("str", rust_sum_as_string(a, b))
33
+ else:
34
+ # Python 实现作为回落
35
+ return str(a + b)
wl_cli/mcp/__init__.py ADDED
@@ -0,0 +1,6 @@
1
+ """Model Context Protocol server domain."""
2
+
3
+ from .capabilities import CAPABILITIES
4
+ from .cli import mcp_group
5
+
6
+ __all__ = ["CAPABILITIES", "mcp_group"]
@@ -0,0 +1,19 @@
1
+ """MCP capabilities owned by this domain (issue #224).
2
+
3
+ Since #221 the shared registry :mod:`wl_cli.capabilities` is the single source
4
+ of truth for every capability name. This module keeps the domain-local view
5
+ that #224 introduced -- ``mcp.*`` entries only, derived from the shared
6
+ mapping -- so domain code and tests keep importing the same ``CAPABILITIES``
7
+ dict without keeping a second copy of the data.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ from ..capabilities import CAPABILITY_SINCE
13
+
14
+ #: Capability name -> the first wl release that provides it (domain subset).
15
+ CAPABILITIES: dict[str, str] = {
16
+ name: since for name, since in CAPABILITY_SINCE.items() if name.startswith("mcp.")
17
+ }
18
+
19
+ __all__ = ["CAPABILITIES"]
wl_cli/mcp/cli.py ADDED
@@ -0,0 +1,22 @@
1
+ """Click commands for the ``mcp`` domain."""
2
+
3
+ import click
4
+
5
+
6
+ @click.group(
7
+ name="mcp",
8
+ help="Model Context Protocol server commands.",
9
+ )
10
+ def mcp_group() -> None:
11
+ """MCP domain entry point."""
12
+
13
+
14
+ @mcp_group.command("serve")
15
+ @click.argument("server", type=click.Choice(("gitlab", "k8s")))
16
+ def serve_command(server: str) -> None:
17
+ """Serve a registered MCP server over stdin/stdout."""
18
+ if server == "gitlab":
19
+ from .gitlab import serve
20
+ else:
21
+ from .k8s import serve
22
+ serve()