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/cc/cli.py ADDED
@@ -0,0 +1,370 @@
1
+ """Click namespace for the ``cc`` domain (issues #220, #222, #223).
2
+
3
+ ``wl cc check`` reports the Claude Code host state as the shared five-state
4
+ envelope :mod:`wl_cli.envelope`. The six host sections are read-only (see
5
+ :mod:`wl_cli.cc.checks`); the nested ``env`` field reuses
6
+ :func:`wl_cli.env_status.build_payload` in this process, so ``wl cc check``
7
+ and ``wl env check`` cannot answer the environment question differently.
8
+
9
+ ``wl cc init`` / ``wl cc repair`` / ``wl cc upgrade`` and the granular
10
+ ``plugins|mcp|settings|statusline|hooks|stamps`` commands are the write side
11
+ (:mod:`wl_cli.cc.mutate`). They reuse the very same probes: probe first, then
12
+ act only on the difference -- so a second ``init`` is a no-op and ``check``
13
+ can never disagree with ``repair``.
14
+
15
+ ``cc`` is allowed to import ``wl_cli.env`` (one-way), but ``env``/``mcp``/
16
+ ``quality`` may not import ``wl_cli.cc`` -- enforced by
17
+ ``tests/test_domain_import_direction.py``.
18
+ """
19
+
20
+ from __future__ import annotations
21
+
22
+ from typing import TYPE_CHECKING, Any
23
+
24
+ import click
25
+
26
+ from ..envelope import OK_STATUSES, dump, exit_code
27
+ from . import mutate
28
+ from .checks import TOOL, build_report
29
+
30
+ if TYPE_CHECKING:
31
+ from collections.abc import Callable
32
+
33
+ #: Host sections rendered by the human-readable summary, in payload order.
34
+ _HOST_SECTIONS = ("plugins", "mcp", "settings", "stamps", "hooks", "statusline")
35
+
36
+ _JSON_HELP = "Emit one five-state JSON envelope on stdout."
37
+
38
+
39
+ def _section_line(name: str, section: dict[str, Any]) -> str:
40
+ """Render one ``name: status -- reason`` line."""
41
+ reason = section.get("reason")
42
+ suffix = f" -- {reason}" if reason else ""
43
+ return f"{name}: {section.get('status')}{suffix}"
44
+
45
+
46
+ def _summary_lines(
47
+ status: str, reason: str | None, sections: dict[str, Any]
48
+ ) -> list[str]:
49
+ """Render the human-readable ``check`` summary (no ``--json``)."""
50
+ lines = [f"wl cc check: {status}"]
51
+ env = sections.get("env", {})
52
+ if env.get("status") == "ok":
53
+ lines.append(f"env: ok (wl {env.get('version')}, Python {env.get('python')})")
54
+ else:
55
+ lines.append(_section_line("env", env))
56
+ lines.extend(_section_line(name, sections.get(name, {})) for name in _HOST_SECTIONS)
57
+ if reason:
58
+ lines.append(f"reason: {reason}")
59
+ return lines
60
+
61
+
62
+ def _split(result: dict[str, Any]) -> tuple[str, str | None, dict[str, Any]]:
63
+ """Split a mutation result into ``(status, reason, payload)``."""
64
+ reason = result.get("reason")
65
+ fields = {
66
+ key: value for key, value in result.items() if key not in ("status", "reason")
67
+ }
68
+ return str(result["status"]), reason if isinstance(reason, str) else None, fields
69
+
70
+
71
+ def _changed_lines(changed: Any) -> list[str]:
72
+ """Render the ``changed`` action list; an empty list says so explicitly."""
73
+ items = [str(item) for item in changed] if isinstance(changed, list) else []
74
+ if not items:
75
+ return ["changed: none"]
76
+ return ["changed:", *(f" - {item}" for item in items)]
77
+
78
+
79
+ def _summary(
80
+ tool: str, status: str, reason: str | None, fields: dict[str, Any]
81
+ ) -> list[str]:
82
+ """Render one section command's human-readable summary."""
83
+ lines = [f"{tool}: {status}" + (f" -- {reason}" if reason else "")]
84
+ lines.extend(_changed_lines(fields.get("changed")))
85
+ return lines
86
+
87
+
88
+ def _report_summary(
89
+ tool: str, status: str, reason: str | None, fields: dict[str, Any]
90
+ ) -> list[str]:
91
+ """Render ``init`` / ``repair``: the aggregate plus every section."""
92
+ lines = [f"{tool}: {status}" + (f" -- {reason}" if reason else "")]
93
+ if fields.get("dry_run"):
94
+ lines.append("dry run: nothing was written")
95
+ for name in mutate.SECTION_ORDER:
96
+ section = fields.get(name)
97
+ if isinstance(section, dict):
98
+ lines.append(_section_line(name, section))
99
+ lines.extend(_changed_lines(fields.get("changed")))
100
+ return lines
101
+
102
+
103
+ def _emit(
104
+ tool: str,
105
+ status: str,
106
+ reason: str | None,
107
+ fields: dict[str, Any],
108
+ *,
109
+ as_json: bool,
110
+ summary: list[str],
111
+ ) -> None:
112
+ """Write the envelope or the human summary, then exit with the status code."""
113
+ if as_json:
114
+ dump(status, status in OK_STATUSES, tool=tool, reason=reason, **fields)
115
+ else:
116
+ for line in summary:
117
+ click.echo(line)
118
+ raise SystemExit(exit_code(status))
119
+
120
+
121
+ def _run_single(tool: str, action: Callable[[], dict[str, Any]], as_json: bool) -> None:
122
+ """Run one section mutation and report it."""
123
+ status, reason, fields = _split(action())
124
+ _emit(
125
+ tool,
126
+ status,
127
+ reason,
128
+ fields,
129
+ as_json=as_json,
130
+ summary=_summary(tool, status, reason, fields),
131
+ )
132
+
133
+
134
+ @click.group(
135
+ name="cc",
136
+ help="Claude Code integration helpers (machine-readable five-state JSON).",
137
+ )
138
+ def cc_group() -> None:
139
+ """Claude Code domain entry point (``check`` plus the write commands)."""
140
+
141
+
142
+ @cc_group.command(name="check")
143
+ @click.option(
144
+ "--json",
145
+ "as_json",
146
+ is_flag=True,
147
+ help=_JSON_HELP,
148
+ )
149
+ def check(as_json: bool) -> None:
150
+ """Report the Claude Code host state without writing anything.
151
+
152
+ The report nests the same-process ``wl env check`` payload under ``env``
153
+ and adds the read-only host sections ``plugins`` / ``mcp`` / ``settings``
154
+ / ``stamps`` / ``hooks`` / ``statusline``. Every section carries its own
155
+ five-state ``status`` and a readable ``reason``; one failing section never
156
+ hides the others. In ``--json`` mode stdout is exactly one envelope line
157
+ and diagnostics go to stderr; the probe is read-only, and the dispatcher
158
+ skips the startup ``.wl`` self-sync (#220).
159
+ """
160
+ status, reason, sections = build_report()
161
+ if as_json:
162
+ dump(status, status in OK_STATUSES, tool=TOOL, reason=reason, **sections)
163
+ raise SystemExit(exit_code(status))
164
+
165
+ for line in _summary_lines(status, reason, sections):
166
+ click.echo(line)
167
+ raise SystemExit(exit_code(status))
168
+
169
+
170
+ @cc_group.command(name="init")
171
+ @click.option("--json", "as_json", is_flag=True, help=_JSON_HELP)
172
+ @click.option(
173
+ "--dry-run",
174
+ is_flag=True,
175
+ help="Run every probe and report the plan without writing anything.",
176
+ )
177
+ def init_command(as_json: bool, dry_run: bool) -> None:
178
+ """Provision the host and write the ``init-done`` stamp (idempotent).
179
+
180
+ Every section is probed first and only its difference is acted on, so a
181
+ second run is a no-op. ``env`` is reported but never mutated: installing
182
+ ``wl`` itself is ``wl env repair``'s single literal. The ``init-done``
183
+ stamp is skipped when a section is ``error`` or ``unverifiable``.
184
+ """
185
+ status, reason, fields = mutate.run_init(dry_run=dry_run)
186
+ _emit(
187
+ "cc init",
188
+ status,
189
+ reason,
190
+ fields,
191
+ as_json=as_json,
192
+ summary=_report_summary("cc init", status, reason, fields),
193
+ )
194
+
195
+
196
+ @cc_group.command(name="repair")
197
+ @click.option("--json", "as_json", is_flag=True, help=_JSON_HELP)
198
+ @click.option(
199
+ "--dry-run",
200
+ is_flag=True,
201
+ help="Run every probe and report the plan without writing anything.",
202
+ )
203
+ @click.option(
204
+ "--section",
205
+ "sections",
206
+ multiple=True,
207
+ type=click.Choice(mutate.MUTABLE_SECTIONS),
208
+ help="Repair only this section (repeatable; default: all of them).",
209
+ )
210
+ def repair_command(as_json: bool, dry_run: bool, sections: tuple[str, ...]) -> None:
211
+ """Repair the selected sections; only the difference is written.
212
+
213
+ ``disabled`` plugins are never re-enabled, an existing claude-mem
214
+ ``settings.json`` and a conflicting statusline are never overwritten, and
215
+ the MCP stamp is written only when both servers registered. ``repaired``
216
+ is reported only for a section that actually wrote.
217
+ """
218
+ status, reason, fields = mutate.run_repair(sections or None, dry_run=dry_run)
219
+ _emit(
220
+ "cc repair",
221
+ status,
222
+ reason,
223
+ fields,
224
+ as_json=as_json,
225
+ summary=_report_summary("cc repair", status, reason, fields),
226
+ )
227
+
228
+
229
+ @cc_group.command(name="upgrade")
230
+ @click.option("--json", "as_json", is_flag=True, help=_JSON_HELP)
231
+ def upgrade_command(as_json: bool) -> None:
232
+ """Upgrade the wlcc Claude Code plugin (not the ``wl`` binary).
233
+
234
+ Refreshes the ``wlcc`` marketplace (adding it when unregistered) and runs
235
+ ``claude plugin update wlcc@wlcc -s user``. The installed version comes
236
+ from ``installed_plugins.json``'s ``scope: user`` entry; a changed version
237
+ sets ``requires_restart``. ``wl self update`` / ``wl env repair`` own the
238
+ CLI itself and are deliberately a different command.
239
+ """
240
+ _run_single("cc upgrade", mutate.upgrade_wlcc, as_json)
241
+
242
+
243
+ @cc_group.group(name="plugins")
244
+ def plugins_group() -> None:
245
+ """Required-plugin provisioning (never re-enables a disabled plugin)."""
246
+
247
+
248
+ @plugins_group.command(name="ensure")
249
+ @click.option("--json", "as_json", is_flag=True, help=_JSON_HELP)
250
+ def plugins_ensure(as_json: bool) -> None:
251
+ """Install only the missing required plugins.
252
+
253
+ Disabled plugins are already installed and are never re-enabled; only an
254
+ entry absent from the registry is installed.
255
+ """
256
+ _run_single("cc plugins ensure", mutate.ensure_plugins, as_json)
257
+
258
+
259
+ @plugins_group.command(name="list")
260
+ @click.option("--json", "as_json", is_flag=True, help=_JSON_HELP)
261
+ def plugins_list(as_json: bool) -> None:
262
+ """Report plugin presence and enable state without writing."""
263
+ _run_single("cc plugins list", lambda: mutate.probe_section("plugins"), as_json)
264
+
265
+
266
+ @cc_group.group(name="mcp")
267
+ def mcp_group() -> None:
268
+ """MCP server registration and the version stamp."""
269
+
270
+
271
+ @mcp_group.command(name="ensure")
272
+ @click.option("--json", "as_json", is_flag=True, help=_JSON_HELP)
273
+ def mcp_ensure(as_json: bool) -> None:
274
+ """Re-register both MCP servers; the stamp is written only on full success."""
275
+ _run_single("cc mcp ensure", mutate.ensure_mcp, as_json)
276
+
277
+
278
+ @mcp_group.command(name="list")
279
+ @click.option("--json", "as_json", is_flag=True, help=_JSON_HELP)
280
+ def mcp_list(as_json: bool) -> None:
281
+ """Report the plugin root, interpreter, stamp and servers without writing."""
282
+ _run_single("cc mcp list", lambda: mutate.probe_section("mcp"), as_json)
283
+
284
+
285
+ @cc_group.group(name="settings")
286
+ def settings_group() -> None:
287
+ """Host-side settings: gstack skill and claude-mem configuration."""
288
+
289
+
290
+ @settings_group.command(name="apply")
291
+ @click.option("--json", "as_json", is_flag=True, help=_JSON_HELP)
292
+ def settings_apply(as_json: bool) -> None:
293
+ """Provision gstack and write claude-mem's settings only when they are absent.
294
+
295
+ An existing claude-mem ``settings.json`` is never overwritten; a differing
296
+ model stays a merge-it-yourself state.
297
+ """
298
+ _run_single("cc settings apply", mutate.ensure_settings, as_json)
299
+
300
+
301
+ @settings_group.command(name="check")
302
+ @click.option("--json", "as_json", is_flag=True, help=_JSON_HELP)
303
+ def settings_check(as_json: bool) -> None:
304
+ """Report gstack and claude-mem without writing."""
305
+ _run_single("cc settings check", lambda: mutate.probe_section("settings"), as_json)
306
+
307
+
308
+ @cc_group.group(name="statusline")
309
+ def statusline_group() -> None:
310
+ """The claude-hud statusLine (merge-write, never overwrite a conflict)."""
311
+
312
+
313
+ @statusline_group.command(name="write")
314
+ @click.option("--json", "as_json", is_flag=True, help=_JSON_HELP)
315
+ def statusline_write(as_json: bool) -> None:
316
+ """Write the claude-hud command; a conflicting statusline is never overwritten.
317
+
318
+ The write is backed up, atomically replaced and self-checked: every key
319
+ other than ``statusLine`` must stay byte-identical.
320
+ """
321
+ _run_single("cc statusline write", mutate.write_statusline, as_json)
322
+
323
+
324
+ @statusline_group.command(name="check")
325
+ @click.option("--json", "as_json", is_flag=True, help=_JSON_HELP)
326
+ def statusline_check(as_json: bool) -> None:
327
+ """Report the configured statusline without writing."""
328
+ _run_single(
329
+ "cc statusline check", lambda: mutate.probe_section("statusline"), as_json
330
+ )
331
+
332
+
333
+ @cc_group.group(name="hooks")
334
+ def hooks_group() -> None:
335
+ """Dangling hook commands (rewrite preferred over prune)."""
336
+
337
+
338
+ @hooks_group.command(name="check")
339
+ @click.option("--json", "as_json", is_flag=True, help=_JSON_HELP)
340
+ def hooks_check(as_json: bool) -> None:
341
+ """Report dangling hook commands without writing."""
342
+ _run_single("cc hooks check", lambda: mutate.probe_section("hooks"), as_json)
343
+
344
+
345
+ @hooks_group.command(name="repair")
346
+ @click.option("--json", "as_json", is_flag=True, help=_JSON_HELP)
347
+ def hooks_repair(as_json: bool) -> None:
348
+ """Rewrite or prune dangling hook commands with a backup and self-check.
349
+
350
+ Rewrite wins over prune; only a command whose every dangling path has no
351
+ local counterpart is pruned. Nothing is written when the plan is identical
352
+ to the file.
353
+ """
354
+ _run_single("cc hooks repair", mutate.repair_hooks, as_json)
355
+
356
+
357
+ @cc_group.group(name="stamps")
358
+ def stamps_group() -> None:
359
+ """The wlcc stamp registry under ``~/.config/wlcc``."""
360
+
361
+
362
+ @stamps_group.command(name="check")
363
+ @click.option("--json", "as_json", is_flag=True, help=_JSON_HELP)
364
+ def stamps_check(as_json: bool) -> None:
365
+ """Report retired and unregistered stamps without deleting anything.
366
+
367
+ Deleting the retired list is part of ``wl cc repair``; unregistered entries
368
+ are reported and kept here and there.
369
+ """
370
+ _run_single("cc stamps check", lambda: mutate.probe_section("stamps"), as_json)