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,28 @@
1
+ from .core import WorkspaceDetector
2
+ from .detection_strategies import (
3
+ check_init_workspace,
4
+ check_pyproject_workspace,
5
+ check_tree_workspace,
6
+ check_uv_lock_workspace,
7
+ is_valid_version,
8
+ parse_root_packages,
9
+ )
10
+ from .exceptions import WorkspaceDetectionError
11
+ from .types import ValidationResult, WorkspaceDetectionRules
12
+ from .venv_resolver import get_active_venv_path, get_active_venv_path_str, get_venv_path
13
+
14
+ __all__ = [
15
+ "WorkspaceDetectionError",
16
+ "WorkspaceDetectionRules",
17
+ "ValidationResult",
18
+ "WorkspaceDetector",
19
+ "check_pyproject_workspace",
20
+ "check_uv_lock_workspace",
21
+ "check_init_workspace",
22
+ "check_tree_workspace",
23
+ "parse_root_packages",
24
+ "is_valid_version",
25
+ "get_venv_path",
26
+ "get_active_venv_path_str",
27
+ "get_active_venv_path",
28
+ ]
@@ -0,0 +1,161 @@
1
+ from pathlib import Path
2
+
3
+ from ..subprocess_utils import SubprocessExecutor
4
+ from .detection_strategies import (
5
+ check_init_workspace,
6
+ check_pyproject_workspace,
7
+ check_tree_workspace,
8
+ check_uv_lock_workspace,
9
+ )
10
+ from .types import ValidationResult, WorkspaceDetectionRules
11
+ from .venv_resolver import get_active_venv_path_str, get_venv_path
12
+
13
+
14
+ class WorkspaceDetector:
15
+ def __init__(self, rules: WorkspaceDetectionRules | None = None) -> None:
16
+ self._executor = SubprocessExecutor()
17
+ self._cache: dict[str, bool] = {}
18
+ self._rules: WorkspaceDetectionRules = {
19
+ "use_pyproject": True,
20
+ "use_uv_lock": True,
21
+ "use_uv_init": True,
22
+ "use_uv_tree": True,
23
+ "uv_tree_min_roots": 2,
24
+ }
25
+ if rules:
26
+ self._rules.update(rules)
27
+
28
+ def detect(self, project_root: Path | None = None) -> bool:
29
+ """Whether ``project_root`` looks like a uv workspace.
30
+
31
+ Worst case is bounded: the file checks are local I/O, and the two
32
+ ``uv`` probes each carry ``UV_PROBE_TIMEOUT_SECONDS``, so a wedged
33
+ ``uv`` costs at most ``2 x`` that value (30 s) before detection
34
+ answers ``False`` -- never an unbounded wait (#184). The strategies
35
+ keep their order; the bound is the probes', not a skipped probe's.
36
+ """
37
+ start = (project_root or Path(self._rules.get("cwd", "."))).resolve()
38
+ key = str(start)
39
+ if key in self._cache:
40
+ return self._cache[key]
41
+
42
+ try:
43
+ root = self._find_workspace_root(start)
44
+
45
+ if self._rules.get("use_pyproject", True) and check_pyproject_workspace(
46
+ root
47
+ ):
48
+ self._cache[key] = True
49
+ return True
50
+
51
+ if self._rules.get("use_uv_lock", True) and check_uv_lock_workspace(root):
52
+ self._cache[key] = True
53
+ return True
54
+
55
+ if self._rules.get("use_uv_init", True) and check_init_workspace(
56
+ root, self._executor
57
+ ):
58
+ self._cache[key] = True
59
+ return True
60
+
61
+ min_roots = int(self._rules.get("uv_tree_min_roots", 2))
62
+ if self._rules.get("use_uv_tree", True) and check_tree_workspace(
63
+ root, self._executor, min_roots
64
+ ):
65
+ self._cache[key] = True
66
+ return True
67
+
68
+ self._cache[key] = False
69
+ return False
70
+ except Exception:
71
+ self._cache[key] = False
72
+ return False
73
+
74
+ def validate(self, project_root: Path | None = None) -> ValidationResult:
75
+ root = self._find_workspace_root(
76
+ (project_root or Path(self._rules.get("cwd", "."))).resolve()
77
+ )
78
+ detected = self.detect(root)
79
+ details: dict[str, str] = {}
80
+ pyproject_path = root / "pyproject.toml"
81
+ uv_lock_path = root / "uv.lock"
82
+ details["pyproject.exists"] = str(pyproject_path.exists())
83
+ details["uv.lock.exists"] = str(uv_lock_path.exists())
84
+ details["apps.exists"] = str((root / "apps").exists())
85
+ details["packages.exists"] = str((root / "packages").exists())
86
+ return ValidationResult(valid=detected, details=details)
87
+
88
+ def get_config(self) -> WorkspaceDetectionRules:
89
+ return self._rules.copy()
90
+
91
+ def resolve_path(self, path: str | Path, project_root: Path | None = None) -> Path:
92
+ root = self._find_workspace_root(
93
+ (project_root or Path(self._rules.get("cwd", "."))).resolve()
94
+ )
95
+ p = Path(path)
96
+ if p.is_absolute():
97
+ return p
98
+ resolved = (root / p).resolve()
99
+ return resolved
100
+
101
+ def get_venv_path(self, project_root: Path | None = None) -> Path | None:
102
+ root = self._find_workspace_root(
103
+ (project_root or Path(self._rules.get("cwd", "."))).resolve()
104
+ )
105
+ return get_venv_path(root, self._executor)
106
+
107
+ def get_active_venv_path_str(self, project_root: Path | None = None) -> str:
108
+ root = self._find_workspace_root(
109
+ (project_root or Path(self._rules.get("cwd", "."))).resolve()
110
+ )
111
+ return get_active_venv_path_str(root)
112
+
113
+ def getActiveVenvPath(self, project_root: Path | None = None) -> str:
114
+ """
115
+ Alias for get_active_venv_path_str to support backward compatibility.
116
+
117
+ This method uses camelCase naming for backward compatibility.
118
+ """
119
+ return self.get_active_venv_path_str(project_root)
120
+
121
+ def _find_workspace_root(self, start: Path) -> Path:
122
+ cur = start
123
+ try:
124
+ while True:
125
+ if self._rules.get("use_pyproject", True) and check_pyproject_workspace(
126
+ cur
127
+ ):
128
+ return cur
129
+ if self._rules.get("use_uv_lock", True) and check_uv_lock_workspace(
130
+ cur
131
+ ):
132
+ return cur
133
+ if cur.parent == cur:
134
+ return start
135
+ cur = cur.parent
136
+ except Exception:
137
+ return start
138
+
139
+ def getConfig(self) -> WorkspaceDetectionRules:
140
+ """
141
+ Alias for get_config to support backward compatibility.
142
+
143
+ This method uses camelCase naming for backward compatibility.
144
+ """
145
+ return self.get_config()
146
+
147
+ def resolvePath(self, path: str | Path, project_root: Path | None = None) -> Path:
148
+ """
149
+ Alias for resolve_path to support backward compatibility.
150
+
151
+ This method uses camelCase naming for backward compatibility.
152
+ """
153
+ return self.resolve_path(path, project_root)
154
+
155
+ def getVenvPath(self, project_root: Path | None = None) -> Path | None:
156
+ """
157
+ Alias for get_venv_path to support backward compatibility.
158
+
159
+ This method uses camelCase naming for backward compatibility.
160
+ """
161
+ return self.get_venv_path(project_root)
@@ -0,0 +1,107 @@
1
+ from pathlib import Path
2
+
3
+ from ..logging import log_warning_pair
4
+ from ..subprocess_utils import SubprocessExecutor, SubprocessResult
5
+ from ..uv_tree import is_valid_version as _is_valid_version
6
+ from ..uv_tree import parse_uv_tree_roots
7
+
8
+ #: Upper bound, in seconds, for the two ``uv`` *probes* behind this detector
9
+ #: (``uv init --dry-run`` and ``uv tree``). Both are heuristics that normally
10
+ #: answer in milliseconds, but on a runner whose ``uv`` blocks on a network
11
+ #: fetch or on its cache lock they can hang forever -- three MRs silently hung
12
+ #: >10 minutes on exactly this path (#184). Expiry is treated as "not
13
+ #: detected", which costs a caller nothing it could have relied on, while an
14
+ #: unbounded probe costs the whole ``wl test`` / ``wl init`` / ``wl build`` run.
15
+ #: Only quick probes get a deadline: production commands stay unbounded.
16
+ #: ``detect()`` can run both probes, so its worst case is ``2 x`` this
17
+ #: value (30 s). The strategies keep their order -- the bound belongs to the
18
+ #: probes, not to skipping one.
19
+ UV_PROBE_TIMEOUT_SECONDS: float = 15.0
20
+
21
+
22
+ def check_pyproject_workspace(root: Path) -> bool:
23
+ """检查pyproject.toml中是否包含workspace配置。"""
24
+ py = root / "pyproject.toml"
25
+ if not py.exists():
26
+ return False
27
+ try:
28
+ content = py.read_text(encoding="utf-8")
29
+ return "[tool.uv.workspace]" in content or "tool.uv.workspace" in content
30
+ except Exception:
31
+ return False
32
+
33
+
34
+ def check_uv_lock_workspace(root: Path) -> bool:
35
+ """检查uv.lock文件是否包含足够多的包。"""
36
+ lock = root / "uv.lock"
37
+ if not lock.exists():
38
+ return False
39
+ try:
40
+ content = lock.read_text(encoding="utf-8")
41
+ count = content.count("[package]")
42
+ return count >= 5
43
+ except Exception:
44
+ return False
45
+
46
+
47
+ def check_init_workspace(root: Path, executor: SubprocessExecutor) -> bool:
48
+ """使用uv init --dry-run检查是否已在工作区中。"""
49
+ try:
50
+ result = executor.run(
51
+ ["uv", "init", "--dry-run"],
52
+ cwd=root,
53
+ quiet=True,
54
+ cache_result=True,
55
+ timeout=UV_PROBE_TIMEOUT_SECONDS,
56
+ )
57
+ _warn_if_probe_timed_out("uv init --dry-run", root, result)
58
+ output = (result.stdout or "") + (result.stderr or "")
59
+ return result.success and ("is already a member of workspace" in output)
60
+ except Exception:
61
+ return False
62
+
63
+
64
+ def check_tree_workspace(
65
+ root: Path, executor: SubprocessExecutor, min_roots: int = 2
66
+ ) -> bool:
67
+ """使用uv tree检查根包数量是否达到最小值。"""
68
+ try:
69
+ result = executor.run(
70
+ ["uv", "tree"],
71
+ cwd=root,
72
+ quiet=True,
73
+ cache_result=True,
74
+ timeout=UV_PROBE_TIMEOUT_SECONDS,
75
+ )
76
+ _warn_if_probe_timed_out("uv tree", root, result)
77
+ if not result.success or not result.stdout:
78
+ return False
79
+ lines = result.stdout.split("\n")
80
+ roots = parse_root_packages(lines)
81
+ return len(roots) >= min_roots
82
+ except Exception:
83
+ return False
84
+
85
+
86
+ def _warn_if_probe_timed_out(probe: str, root: Path, result: SubprocessResult) -> None:
87
+ """让「探测超时」可见:否则它和「确实不是工作区」在调用方眼里一模一样。"""
88
+ if not result.timed_out:
89
+ return
90
+ # Language-neutral payload (a command and a path): both variants are
91
+ # identical by design.
92
+ log_warning_pair(
93
+ f"{probe} did not answer within {UV_PROBE_TIMEOUT_SECONDS:g}s in {root}; "
94
+ "treating the workspace probe as not detected",
95
+ f"{probe} 在 {root} 未于 {UV_PROBE_TIMEOUT_SECONDS:g} 秒内返回;"
96
+ "按未探测到工作区处理",
97
+ )
98
+
99
+
100
+ def parse_root_packages(lines: list[str]) -> list[str]:
101
+ """从uv tree输出中解析根包(委派到共享解析器,见 #178)。"""
102
+ return parse_uv_tree_roots(lines)
103
+
104
+
105
+ def is_valid_version(version: str) -> bool:
106
+ """检查版本字符串是否有效(委派到共享解析器,字符集不变)。"""
107
+ return _is_valid_version(version)
@@ -0,0 +1,5 @@
1
+ class WorkspaceDetectionError(Exception):
2
+ """工作区检测错误异常。"""
3
+
4
+ def __init__(self, message: str) -> None:
5
+ super().__init__(message)
@@ -0,0 +1,17 @@
1
+ from dataclasses import dataclass
2
+ from typing import TypedDict
3
+
4
+
5
+ class WorkspaceDetectionRules(TypedDict, total=False):
6
+ use_pyproject: bool
7
+ use_uv_lock: bool
8
+ use_uv_init: bool
9
+ use_uv_tree: bool
10
+ uv_tree_min_roots: int
11
+ cwd: str
12
+
13
+
14
+ @dataclass
15
+ class ValidationResult:
16
+ valid: bool
17
+ details: dict[str, str]
@@ -0,0 +1,84 @@
1
+ import os
2
+ from pathlib import Path
3
+
4
+ from ..logging import log_error, log_info
5
+ from ..subprocess_utils import SubprocessExecutor
6
+ from .exceptions import WorkspaceDetectionError
7
+
8
+
9
+ def get_venv_path(root: Path, executor: SubprocessExecutor) -> Path | None:
10
+ """获取虚拟环境路径。"""
11
+ env_path = os.environ.get("VENV_PATH")
12
+ if env_path:
13
+ p = Path(env_path)
14
+ if p.exists():
15
+ return p.resolve()
16
+ local = root / ".venv"
17
+ if local.exists():
18
+ return local.resolve()
19
+ try:
20
+ res = executor.run(
21
+ [
22
+ "uv",
23
+ "run",
24
+ "python",
25
+ "-c",
26
+ "import sys;print(sys.executable)",
27
+ ],
28
+ cwd=root,
29
+ quiet=True,
30
+ )
31
+ if res.success and res.stdout:
32
+ exe = Path(res.stdout.strip())
33
+ parent = exe.parent
34
+ venv_dir = parent.parent
35
+ if venv_dir.exists():
36
+ return venv_dir.resolve()
37
+ except Exception:
38
+ return None
39
+ return None
40
+
41
+
42
+ def get_active_venv_path_str(root: Path) -> str:
43
+ """获取当前激活的虚拟环境路径。"""
44
+ log_info(f"Workspace venv path resolution at: {root}")
45
+
46
+ # 检查工作区配置
47
+ py = root / "pyproject.toml"
48
+ uv_toml = root / "uv.toml"
49
+ ws_json = root / "workspace.json"
50
+ has_ws_conf = False
51
+ try:
52
+ if py.exists() and "tool.uv.workspace" in py.read_text(encoding="utf-8"):
53
+ has_ws_conf = True
54
+ if uv_toml.exists() or ws_json.exists():
55
+ has_ws_conf = True
56
+ except Exception:
57
+ pass
58
+ if not has_ws_conf:
59
+ log_error("Workspace configuration file not found or invalid")
60
+ raise WorkspaceDetectionError("Workspace configuration not found or invalid")
61
+
62
+ # 检查虚拟环境是否激活
63
+ venv_env = os.environ.get("VIRTUAL_ENV", "").strip()
64
+ if not venv_env:
65
+ log_error("Virtual environment is not activated (VIRTUAL_ENV not set)")
66
+ raise WorkspaceDetectionError("Virtual environment not activated")
67
+
68
+ venv_path = Path(venv_env)
69
+ if not venv_path.exists():
70
+ log_error(f"Activated virtual environment path does not exist: {venv_env}")
71
+ raise WorkspaceDetectionError(
72
+ "Activated virtual environment path does not exist"
73
+ )
74
+
75
+ resolved = venv_path.resolve()
76
+ # Use platform-appropriate path separators instead of forcing POSIX format
77
+ normalized = str(resolved)
78
+ log_info(f"Resolved virtual environment path: {normalized}")
79
+ return normalized
80
+
81
+
82
+ def get_active_venv_path(root: Path) -> str:
83
+ """get_active_venv_path_str的别名,用于向后兼容。"""
84
+ return get_active_venv_path_str(root)
@@ -0,0 +1,2 @@
1
+ [codespell]
2
+ ignore-words-list = crate,assertIn
@@ -0,0 +1,10 @@
1
+ [dependencies]
2
+ default = [
3
+ "numpy>=1.20.0",
4
+ "scipy>=1.7.0",
5
+ "matplotlib>=3.5.0",
6
+ "cffi>=1.15.0",
7
+ "pyyaml>=6.0.0",
8
+ "toml>=0.10.2",
9
+ "data-science-types>=0.2.23",
10
+ ]
@@ -0,0 +1,62 @@
1
+ # MyPy configuration file for WL Commands
2
+ # WL Commands 的 mypy 配置文件
3
+
4
+ [mypy]
5
+ # Python version to target (目标 Python 版本)
6
+ python_version = 3.12
7
+
8
+ # Baseline: ignore missing imports from untyped third-party libraries.
9
+ # 基线:忽略未类型化第三方库的导入告警(import-untyped)。
10
+ ignore_missing_imports = true
11
+
12
+ # Error reporting settings (错误输出设置)
13
+ show_error_codes = true
14
+ show_error_context = true
15
+ show_column_numbers = true
16
+ pretty = true
17
+
18
+ # Incremental mode for faster checks (增量检查,加快速度)
19
+ incremental = true
20
+
21
+ # Cache directory (缓存目录)
22
+ cache_dir = .mypy_cache
23
+
24
+ # Core warning flags — always ON (核心告警项,始终开启)
25
+ warn_return_any = true
26
+ warn_unused_configs = true
27
+ warn_unused_ignores = true
28
+
29
+ # ---------------------------------------------------------------------------
30
+ # Tightening ladder (渐进收紧阶梯)
31
+ # The stricter flags below are intentionally DISABLED (commented out) in the
32
+ # baseline. Teams can progressively enable them as the codebase becomes fully
33
+ # typed. 以下为渐进式收紧项:基线默认关闭,待代码完全类型化后逐项开启。
34
+ # ---------------------------------------------------------------------------
35
+ # allow_untyped_defs = false
36
+ # 禁止未类型标注的函数定义 (Disallow untyped function definitions)
37
+ # allow_untyped_calls = false
38
+ # 禁止调用未类型标注的函数 (Disallow calls to untyped functions)
39
+ # allow_untyped_decorators = false
40
+ # 禁止未类型标注的装饰器 (Disallow untyped decorators)
41
+ # allow_incomplete_defs = false
42
+ # 禁止不完整的类型标注 (Disallow partially annotated definitions)
43
+ # 注(2026-09-16):此处曾把 strict_optional 列为 “future rung”,那是错的。
44
+ # mypy 早已**默认开启** strict optional(CLI 只提供 `--no-strict-optional`),
45
+ # 所以 `strict_optional = true` 是 **no-op**、不构成一档阶梯(只有显式 `= false` 才有意义)。
46
+ # 日后若要加下一档,应挑一个真正有效的开关(如 disallow_untyped_defs),而不是这一条。
47
+ # Rung 1 (enabled 2026-09): no_implicit_optional + warn_redundant_casts
48
+ no_implicit_optional = true
49
+ # 禁止隐式 Optional (Disallow implicit Optional)
50
+ warn_redundant_casts = true
51
+ # 警告冗余的强制类型转换 (Warn about redundant casts)
52
+
53
+ # Exclude specific directories (排除目录)
54
+ exclude =
55
+ __pycache__,
56
+ build,
57
+ dist,
58
+ *.egg-info,
59
+ .ruff_cache,
60
+ .venv,
61
+ .git,
62
+ rust/
@@ -0,0 +1,28 @@
1
+ [project]
2
+ name = "{{project_name}}"
3
+ dynamic = ["version"]
4
+ description = "{{project_name}} - Python项目"
5
+ requires-python = ">=3.10"
6
+ license = "MIT"
7
+ readme = "README.md"
8
+ authors = [{name = "Admin", email = "admin@example.com"}]
9
+
10
+ [project.urls]
11
+ Homepage = "https://github.com/example/{{project_name}}"
12
+ Issues = "https://github.com/example/{{project_name}}/issues"
13
+
14
+ [build-system]
15
+ requires = ["maturin>=1.0,<2.0"]
16
+ build-backend = "maturin"
17
+
18
+ [maturin]
19
+ manifest-path = "rust/Cargo.toml"
20
+ module-name = "{{python_name}}.lib.{{rust_name}}"
21
+ python-source = "src"
22
+ include = ["src/{{python_name}}/**/*"]
23
+ bindings = "pyo3"
24
+ features = ["pyo3/extension-module"]
25
+
26
+ [build-system.setuptools]
27
+ requires = ["setuptools>=61.0"]
28
+ build-backend = "setuptools.build_meta"
@@ -0,0 +1,24 @@
1
+ [core_templates]
2
+ python_version = { src = "project/.python-version", dest = ".python-version" }
3
+ license = { src = "project/LICENSE", dest = "LICENSE" }
4
+ gitignore = { src = "project/.gitignore", dest = ".gitignore" }
5
+
6
+ [pyproject_templates]
7
+ workspace = { src = "project/pyproject.toml", dest = "pyproject.toml" }
8
+ no_workspace = { src = "project/pyproject_no_workspace.toml", dest = "pyproject.toml" }
9
+ workspace_root = { src = "project/pyproject_workspace.toml", dest = "pyproject.toml" }
10
+
11
+ [rust_templates]
12
+ init_py = { src = "rust/lib/__init__.py", dest = "src/{project_name}/lib/__init__.py" }
13
+ rust_utils = { src = "rust/lib/rust_utils.py", dest = "src/{project_name}/lib/rust_utils.py", template = true }
14
+ test_fallback = { src = "rust/tests/test_rust_fallback.py", dest = "tests/test_rust_fallback.py" }
15
+
16
+ [sop_templates]
17
+ rust_readme = { src = "sop_doc/rust-readme.md", dest = "rust/README.md" }
18
+ python_sop = { src = "sop_doc/python-sop.md", dest = "src/{project_name}/lib/sop.md" }
19
+ git_dev_sop = { src = "sop_doc/git-dev-sop.md", dest = "docs/git-dev-sop.md" }
20
+
21
+ [config_templates]
22
+ codespellrc = { src = "config/.codespellrc", dest = ".wl/.codespellrc" }
23
+ mypy = { src = "config/mypy.ini", dest = ".wl/mypy.ini" }
24
+ precommit = { src = "git/.pre-commit-config.yaml", dest = ".wl/.pre-commit-config.yaml" }
@@ -0,0 +1,76 @@
1
+ [tool.ruff]
2
+ line-length = 88
3
+ target-version = "py312"
4
+
5
+ [tool.ruff.lint]
6
+ select = [
7
+ "E",
8
+ "W",
9
+ "F",
10
+ "I",
11
+ "N",
12
+ "UP",
13
+ "B",
14
+ "C4",
15
+ "SIM",
16
+ "TCH",
17
+ "PTH",
18
+ "PGH",
19
+ "PL",
20
+ "RUF",
21
+ ]
22
+ ignore = [
23
+ "E501",
24
+ "PLR0913",
25
+ "PLR0915",
26
+ ]
27
+ per-file-ignores = { "tests/*" = ["S101", "ARG"] }
28
+
29
+ [tool.mypy]
30
+ # Python version to target (PEP 8 compatible)
31
+ python_version = "3.12"
32
+
33
+ # Enable strict type checking following PEP 8
34
+ type_checking_mode = "strict"
35
+
36
+ # Error reporting settings
37
+ show_error_codes = true
38
+ show_error_context = true
39
+ show_column_numbers = true
40
+ pretty = true
41
+
42
+ # Incremental mode for faster checks
43
+ incremental = true
44
+
45
+ # Cache directory
46
+ cache_dir = ".mypy_cache"
47
+
48
+ # Basic strictness settings
49
+ warn_return_any = true
50
+ warn_unused_configs = true
51
+ warn_unused_ignores = true
52
+
53
+ # Typing discipline settings
54
+ allow_untyped_defs = false
55
+ allow_untyped_calls = false
56
+ allow_untyped_decorators = false
57
+ allow_incomplete_defs = false
58
+ strict_optional = true
59
+
60
+ # Import settings
61
+ no_implicit_optional = true
62
+
63
+ # Error handling
64
+ warn_redundant_casts = true
65
+
66
+ # Exclude specific directories
67
+ exclude = [
68
+ "__pycache__",
69
+ "build",
70
+ "dist",
71
+ "*.egg-info",
72
+ ".ruff_cache",
73
+ ".venv",
74
+ ".git",
75
+ "rust/"
76
+ ]