py-wlcli 0.8.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- py_wlcli-0.8.0.dist-info/METADATA +139 -0
- py_wlcli-0.8.0.dist-info/RECORD +320 -0
- py_wlcli-0.8.0.dist-info/WHEEL +4 -0
- py_wlcli-0.8.0.dist-info/entry_points.txt +2 -0
- py_wlcli-0.8.0.dist-info/licenses/LICENSE +21 -0
- wl_cli/__init__.py +27 -0
- wl_cli/__main__.py +130 -0
- wl_cli/capabilities.py +79 -0
- wl_cli/cc/__init__.py +5 -0
- wl_cli/cc/checks.py +1019 -0
- wl_cli/cc/cli.py +370 -0
- wl_cli/cc/mutate.py +1350 -0
- wl_cli/cli_click.py +156 -0
- wl_cli/commands/__init__.py +84 -0
- wl_cli/commands/base.py +80 -0
- wl_cli/commands/buildcommands/__init__.py +1 -0
- wl_cli/commands/buildcommands/build.py +71 -0
- wl_cli/commands/buildcommands/build_utils.py +47 -0
- wl_cli/commands/buildcommands/builders/__init__.py +18 -0
- wl_cli/commands/buildcommands/builders/cleanup.py +35 -0
- wl_cli/commands/buildcommands/builders/coordinator.py +122 -0
- wl_cli/commands/buildcommands/builders/environment.py +81 -0
- wl_cli/commands/buildcommands/builders/rust_builder.py +39 -0
- wl_cli/commands/buildcommands/builders/stubs.py +58 -0
- wl_cli/commands/buildcommands/dist.py +326 -0
- wl_cli/commands/buildcommands/test.py +89 -0
- wl_cli/commands/clean/__init__.py +1 -0
- wl_cli/commands/clean/build.py +151 -0
- wl_cli/commands/clean/clean.py +294 -0
- wl_cli/commands/clean/clean_utils.py +77 -0
- wl_cli/commands/clean/core.py +236 -0
- wl_cli/commands/clean/dead_code/__init__.py +0 -0
- wl_cli/commands/clean/dead_code/dead_code_command.py +70 -0
- wl_cli/commands/clean/dead_code/detector.py +161 -0
- wl_cli/commands/clean/dead_code/fixer.py +176 -0
- wl_cli/commands/clean/dead_code/reporter.py +295 -0
- wl_cli/commands/clean/dead_code/utils.py +0 -0
- wl_cli/commands/clean/lfs.py +215 -0
- wl_cli/commands/clean/python.py +81 -0
- wl_cli/commands/clean/rust.py +91 -0
- wl_cli/commands/clean/utils/__init__.py +34 -0
- wl_cli/commands/clean/venv.py +91 -0
- wl_cli/commands/cleanup.py +73 -0
- wl_cli/commands/click_base.py +79 -0
- wl_cli/commands/config/__init__.py +1 -0
- wl_cli/commands/config/config.py +291 -0
- wl_cli/commands/debug/__init__.py +6 -0
- wl_cli/commands/debug/debug.py +309 -0
- wl_cli/commands/debug/debug_cache.py +86 -0
- wl_cli/commands/debug/debug_subagent.py +156 -0
- wl_cli/commands/debug/output_formatter.py +165 -0
- wl_cli/commands/debug/source_hasher.py +92 -0
- wl_cli/commands/format/__init__.py +13 -0
- wl_cli/commands/format/format_cache_adapter.py +125 -0
- wl_cli/commands/format/format_command.py +197 -0
- wl_cli/commands/format/format_coordinator.py +191 -0
- wl_cli/commands/format/format_executor.py +470 -0
- wl_cli/commands/format/format_outcome.py +44 -0
- wl_cli/commands/format/format_path_handler.py +1016 -0
- wl_cli/commands/format/format_progress.py +62 -0
- wl_cli/commands/format/format_reporter.py +31 -0
- wl_cli/commands/format/python_formatter.py +506 -0
- wl_cli/commands/format/rust_formatter.py +106 -0
- wl_cli/commands/gate/__init__.py +1 -0
- wl_cli/commands/gate/gate_command.py +862 -0
- wl_cli/commands/gate/preflight.py +445 -0
- wl_cli/commands/git/__init__.py +1 -0
- wl_cli/commands/git/config.py +169 -0
- wl_cli/commands/git/git_facts.py +60 -0
- wl_cli/commands/git/git_flow.py +86 -0
- wl_cli/commands/git/hook.py +112 -0
- wl_cli/commands/git/policy.py +216 -0
- wl_cli/commands/git/push_update.py +82 -0
- wl_cli/commands/initenv/__init__.py +1 -0
- wl_cli/commands/initenv/common/__init__.py +23 -0
- wl_cli/commands/initenv/common/check_git_bash.py +78 -0
- wl_cli/commands/initenv/common/error_handler.py +68 -0
- wl_cli/commands/initenv/common/exceptions.py +52 -0
- wl_cli/commands/initenv/components/__init__.py +4 -0
- wl_cli/commands/initenv/components/cargo_sync.py +240 -0
- wl_cli/commands/initenv/components/config_manager.py +106 -0
- wl_cli/commands/initenv/components/directory_utils.py +71 -0
- wl_cli/commands/initenv/components/git/__init__.py +9 -0
- wl_cli/commands/initenv/components/git/branch_creator.py +143 -0
- wl_cli/commands/initenv/components/git/git_flow_interface.py +24 -0
- wl_cli/commands/initenv/components/git/git_flow_manager.py +159 -0
- wl_cli/commands/initenv/components/git/git_lfs_manager.py +272 -0
- wl_cli/commands/initenv/components/git/git_utility.py +254 -0
- wl_cli/commands/initenv/components/git/gitignore_manager.py +53 -0
- wl_cli/commands/initenv/components/git_initializer.py +212 -0
- wl_cli/commands/initenv/components/i18n_manager.py +104 -0
- wl_cli/commands/initenv/components/log_manager.py +241 -0
- wl_cli/commands/initenv/components/project_structure/__init__.py +23 -0
- wl_cli/commands/initenv/components/project_structure/config_generator.py +360 -0
- wl_cli/commands/initenv/components/project_structure/directory_creator.py +184 -0
- wl_cli/commands/initenv/components/project_structure/hook_source.py +110 -0
- wl_cli/commands/initenv/components/project_structure/hooks_manager.py +995 -0
- wl_cli/commands/initenv/components/project_structure/readme_handler.py +56 -0
- wl_cli/commands/initenv/components/project_structure/rust_files_handler.py +30 -0
- wl_cli/commands/initenv/components/project_structure/setup_handler.py +103 -0
- wl_cli/commands/initenv/components/project_structure/sop_generator.py +73 -0
- wl_cli/commands/initenv/components/project_structure/template_copier.py +104 -0
- wl_cli/commands/initenv/components/project_structure/template_manager.py +195 -0
- wl_cli/commands/initenv/components/pyproject_generator.py +194 -0
- wl_cli/commands/initenv/components/rust_initializer.py +145 -0
- wl_cli/commands/initenv/components/symlink_cleaner.py +99 -0
- wl_cli/commands/initenv/components/symlink_manager.py +52 -0
- wl_cli/commands/initenv/components/venv_manager.py +118 -0
- wl_cli/commands/initenv/initenv.py +169 -0
- wl_cli/commands/initenv/services/__init__.py +11 -0
- wl_cli/commands/initenv/services/environment_initializer.py +288 -0
- wl_cli/commands/initenv/services/git_lfs_initializer.py +26 -0
- wl_cli/commands/initenv/services/project_structure_initializer.py +52 -0
- wl_cli/commands/lfs/README.md +221 -0
- wl_cli/commands/lfs/__init__.py +5 -0
- wl_cli/commands/lfs/cli.py +106 -0
- wl_cli/commands/lfs/config.py +164 -0
- wl_cli/commands/lfs/hook.py +61 -0
- wl_cli/commands/lfs/initializer.py +531 -0
- wl_cli/commands/lfs/manager.py +95 -0
- wl_cli/commands/lfs/policy.py +165 -0
- wl_cli/commands/lfs/watcher.py +294 -0
- wl_cli/commands/lint/__init__.py +1 -0
- wl_cli/commands/lint/lint_command.py +297 -0
- wl_cli/commands/lint/lint_executor.py +409 -0
- wl_cli/commands/lint/lint_reporter.py +579 -0
- wl_cli/commands/lint/lint_run.py +258 -0
- wl_cli/commands/lint/mypy_config.py +90 -0
- wl_cli/commands/registry.py +103 -0
- wl_cli/commands/self/__init__.py +7 -0
- wl_cli/commands/self/common/__init__.py +8 -0
- wl_cli/commands/self/common/capabilities.py +30 -0
- wl_cli/commands/self/common/config.py +27 -0
- wl_cli/commands/self/common/version_cache.py +85 -0
- wl_cli/commands/self/handlers/__init__.py +19 -0
- wl_cli/commands/self/handlers/base_handler.py +28 -0
- wl_cli/commands/self/handlers/install_handler.py +85 -0
- wl_cli/commands/self/handlers/status_handler.py +66 -0
- wl_cli/commands/self/handlers/uninstall_handler.py +58 -0
- wl_cli/commands/self/handlers/update_handler.py +102 -0
- wl_cli/commands/self/handlers/version_handler.py +16 -0
- wl_cli/commands/self/self_command.py +237 -0
- wl_cli/commands/self/services/__init__.py +15 -0
- wl_cli/commands/self/services/installation_manager.py +187 -0
- wl_cli/commands/self/services/local_installer.py +165 -0
- wl_cli/commands/self/services/pypi_updater.py +278 -0
- wl_cli/commands/self/services/windows_update.py +174 -0
- wl_cli/commands/testcommands/__init__.py +8 -0
- wl_cli/commands/testcommands/base_dependency_analyzer.py +198 -0
- wl_cli/commands/testcommands/batch_processor.py +278 -0
- wl_cli/commands/testcommands/cache_manager.py +1156 -0
- wl_cli/commands/testcommands/coverage_analyzer.py +955 -0
- wl_cli/commands/testcommands/coverage_cache_manager.py +183 -0
- wl_cli/commands/testcommands/progress_manager.py +79 -0
- wl_cli/commands/testcommands/python_env_detector.py +207 -0
- wl_cli/commands/testcommands/report_generator.py +101 -0
- wl_cli/commands/testcommands/single_test_executor.py +250 -0
- wl_cli/commands/testcommands/speed_checker.py +118 -0
- wl_cli/commands/testcommands/test_command_builder.py +165 -0
- wl_cli/commands/testcommands/test_commands.py +786 -0
- wl_cli/commands/testcommands/test_reporter.py +181 -0
- wl_cli/commands/testcommands/test_result_handler.py +96 -0
- wl_cli/commands/testcommands/test_runner.py +1343 -0
- wl_cli/commands/testcommands/test_utils.py +197 -0
- wl_cli/commands/testcommands/utils/__init__.py +17 -0
- wl_cli/commands/testcommands/utils/coverage_parser.py +98 -0
- wl_cli/commands/testcommands/utils/env_info.py +36 -0
- wl_cli/commands/testcommands/utils/junit_parser.py +75 -0
- wl_cli/commands/testcommands/utils/markdown_utils.py +20 -0
- wl_cli/commands/testcommands/utils/project_root.py +61 -0
- wl_cli/commands/update/__init__.py +9 -0
- wl_cli/commands/update/services/__init__.py +21 -0
- wl_cli/commands/update/services/backup_manager.py +207 -0
- wl_cli/commands/update/services/component_manager.py +83 -0
- wl_cli/commands/update/services/conflict_resolver.py +189 -0
- wl_cli/commands/update/services/vendors_updater.py +85 -0
- wl_cli/commands/update/services/version_tracker.py +127 -0
- wl_cli/commands/update/update_command.py +270 -0
- wl_cli/commands/update/utils/__init__.py +3 -0
- wl_cli/commands/version/__init__.py +9 -0
- wl_cli/commands/version/version_command.py +155 -0
- wl_cli/commands/workflow.py +477 -0
- wl_cli/env/__init__.py +5 -0
- wl_cli/env/cli.py +167 -0
- wl_cli/env_status.py +157 -0
- wl_cli/envelope.py +91 -0
- wl_cli/exceptions.py +85 -0
- wl_cli/lib/__init__.py +3 -0
- wl_cli/lib/rust_utils.py +35 -0
- wl_cli/mcp/__init__.py +6 -0
- wl_cli/mcp/capabilities.py +19 -0
- wl_cli/mcp/cli.py +22 -0
- wl_cli/mcp/gitlab.py +553 -0
- wl_cli/mcp/k8s.py +344 -0
- wl_cli/mcp/protocol.py +159 -0
- wl_cli/platform.py +139 -0
- wl_cli/quality/__init__.py +18 -0
- wl_cli/utils/__init__.py +7 -0
- wl_cli/utils/cache.py +491 -0
- wl_cli/utils/cache_ledger.py +355 -0
- wl_cli/utils/cache_store.py +546 -0
- wl_cli/utils/config/__init__.py +27 -0
- wl_cli/utils/config/config_loader.py +163 -0
- wl_cli/utils/config/core.py +862 -0
- wl_cli/utils/config/defaults.py +142 -0
- wl_cli/utils/config/env_exporter.py +86 -0
- wl_cli/utils/config/env_parser.py +108 -0
- wl_cli/utils/config/validation.py +280 -0
- wl_cli/utils/error_handler.py +165 -0
- wl_cli/utils/file_copier.py +56 -0
- wl_cli/utils/file_operations.py +506 -0
- wl_cli/utils/language_utils.py +122 -0
- wl_cli/utils/lazy_import.py +240 -0
- wl_cli/utils/log_levels.py +35 -0
- wl_cli/utils/log_rotators.py +74 -0
- wl_cli/utils/logging.py +458 -0
- wl_cli/utils/output_context.py +142 -0
- wl_cli/utils/performance/__init__.py +1 -0
- wl_cli/utils/performance/decorators.py +111 -0
- wl_cli/utils/performance/exporter.py +38 -0
- wl_cli/utils/performance/models.py +16 -0
- wl_cli/utils/performance/monitor.py +161 -0
- wl_cli/utils/performance_monitor.py +32 -0
- wl_cli/utils/platform_adapter.py +131 -0
- wl_cli/utils/pre_commit.py +266 -0
- wl_cli/utils/pre_commit_config.py +72 -0
- wl_cli/utils/root.py +521 -0
- wl_cli/utils/root_fallback.py +256 -0
- wl_cli/utils/run_evidence.py +236 -0
- wl_cli/utils/structured_logger.py +18 -0
- wl_cli/utils/structured_logging/__init__.py +19 -0
- wl_cli/utils/structured_logging/config.py +337 -0
- wl_cli/utils/structured_logging/core.py +177 -0
- wl_cli/utils/structured_logging/env.py +56 -0
- wl_cli/utils/structured_logging/filtering.py +75 -0
- wl_cli/utils/structured_logging/filters.py +55 -0
- wl_cli/utils/structured_logging/formatters.py +251 -0
- wl_cli/utils/structured_logging/handlers.py +308 -0
- wl_cli/utils/structured_logging/logger.py +354 -0
- wl_cli/utils/subprocess.py +71 -0
- wl_cli/utils/subprocess_utils.py +260 -0
- wl_cli/utils/test_utils/__init__.py +5 -0
- wl_cli/utils/test_utils/test_filter.py +36 -0
- wl_cli/utils/uv_run.py +79 -0
- wl_cli/utils/uv_tool.py +117 -0
- wl_cli/utils/uv_tree.py +71 -0
- wl_cli/utils/uv_workspace.py +174 -0
- wl_cli/utils/version/__init__.py +15 -0
- wl_cli/utils/version/pypi_version_checker.py +95 -0
- wl_cli/utils/version/version_comparator.py +133 -0
- wl_cli/utils/version/version_detectors.py +312 -0
- wl_cli/utils/version/version_service.py +131 -0
- wl_cli/utils/version/version_updater.py +198 -0
- wl_cli/utils/wl_dir_updater.py +1440 -0
- wl_cli/utils/workspace_detector/__init__.py +28 -0
- wl_cli/utils/workspace_detector/core.py +161 -0
- wl_cli/utils/workspace_detector/detection_strategies.py +107 -0
- wl_cli/utils/workspace_detector/exceptions.py +5 -0
- wl_cli/utils/workspace_detector/types.py +17 -0
- wl_cli/utils/workspace_detector/venv_resolver.py +84 -0
- wl_cli/vendors/config/.codespellrc +2 -0
- wl_cli/vendors/config/dependencies.toml +10 -0
- wl_cli/vendors/config/mypy.ini +62 -0
- wl_cli/vendors/config/pyproject_defaults.toml +28 -0
- wl_cli/vendors/config/template_config.toml +24 -0
- wl_cli/vendors/config/tool_configs.toml +76 -0
- wl_cli/vendors/git/.gitignore +122 -0
- wl_cli/vendors/git/.pre-commit-config.yaml +37 -0
- wl_cli/vendors/hooks/applypatch-msg.sample +15 -0
- wl_cli/vendors/hooks/commit-msg.sample +24 -0
- wl_cli/vendors/hooks/fsmonitor-watchman.sample +174 -0
- wl_cli/vendors/hooks/post-update.sample +8 -0
- wl_cli/vendors/hooks/pre-applypatch.sample +14 -0
- wl_cli/vendors/hooks/pre-commit +45 -0
- wl_cli/vendors/hooks/pre-commit.legacy +31 -0
- wl_cli/vendors/hooks/pre-commit.sample +49 -0
- wl_cli/vendors/hooks/pre-merge-commit.sample +13 -0
- wl_cli/vendors/hooks/pre-push.sample +60 -0
- wl_cli/vendors/hooks/pre-rebase.sample +169 -0
- wl_cli/vendors/hooks/pre-receive.sample +24 -0
- wl_cli/vendors/hooks/prepare-commit-msg.sample +42 -0
- wl_cli/vendors/hooks/push-to-checkout.sample +78 -0
- wl_cli/vendors/hooks/sendemail-validate.sample +77 -0
- wl_cli/vendors/hooks/unix/applypatch-msg.sample +15 -0
- wl_cli/vendors/hooks/unix/check_lockfile.py +58 -0
- wl_cli/vendors/hooks/unix/check_lockfile.sh +36 -0
- wl_cli/vendors/hooks/unix/commit-msg.sample +24 -0
- wl_cli/vendors/hooks/unix/fsmonitor-watchman.sample +174 -0
- wl_cli/vendors/hooks/unix/post-update.sample +8 -0
- wl_cli/vendors/hooks/unix/pre-applypatch.sample +14 -0
- wl_cli/vendors/hooks/unix/pre-commit +190 -0
- wl_cli/vendors/hooks/unix/pre-commit.legacy +31 -0
- wl_cli/vendors/hooks/unix/pre-commit.py +81 -0
- wl_cli/vendors/hooks/unix/pre-commit.sample +49 -0
- wl_cli/vendors/hooks/unix/pre-merge-commit.sample +13 -0
- wl_cli/vendors/hooks/unix/pre-push +417 -0
- wl_cli/vendors/hooks/unix/pre-push.sample +53 -0
- wl_cli/vendors/hooks/unix/pre-rebase.sample +169 -0
- wl_cli/vendors/hooks/unix/pre-receive.sample +24 -0
- wl_cli/vendors/hooks/unix/prepare-commit-msg.sample +42 -0
- wl_cli/vendors/hooks/update.sample +128 -0
- wl_cli/vendors/hooks/win/work.bat +63 -0
- wl_cli/vendors/project/.gitignore +90 -0
- wl_cli/vendors/project/.python-version +1 -0
- wl_cli/vendors/project/LICENSE +21 -0
- wl_cli/vendors/project/README.md +0 -0
- wl_cli/vendors/project/pyproject.toml +27 -0
- wl_cli/vendors/project/pyproject_no_workspace.toml +16 -0
- wl_cli/vendors/project/pyproject_workspace.toml +14 -0
- wl_cli/vendors/readme/README.md +120 -0
- wl_cli/vendors/rust/Cargo.toml +14 -0
- wl_cli/vendors/rust/lib/__init__.py +3 -0
- wl_cli/vendors/rust/lib/rust_utils.py +31 -0
- wl_cli/vendors/rust/src/lib.rs +13 -0
- wl_cli/vendors/rust/src/main.rs +3 -0
- wl_cli/vendors/rust/tests/test_rust_fallback.py +40 -0
- wl_cli/vendors/sop_doc/git-dev-sop.md +220 -0
- wl_cli/vendors/sop_doc/industrial-standard-sop.md +145 -0
- wl_cli/vendors/sop_doc/python-sop.md +342 -0
- wl_cli/vendors/sop_doc/rust-readme.md +219 -0
wl_cli/utils/logging.py
ADDED
|
@@ -0,0 +1,458 @@
|
|
|
1
|
+
"""Logging utilities for WL Commands."""
|
|
2
|
+
|
|
3
|
+
import inspect
|
|
4
|
+
import os
|
|
5
|
+
import sys
|
|
6
|
+
import traceback
|
|
7
|
+
from dataclasses import dataclass
|
|
8
|
+
from typing import Any, Literal, cast
|
|
9
|
+
|
|
10
|
+
from .language_utils import _should_display_language
|
|
11
|
+
from .log_levels import LogLevel
|
|
12
|
+
from .structured_logging.core import StructuredLogger
|
|
13
|
+
from .structured_logging.env import is_performance_test as _is_performance_test
|
|
14
|
+
from .structured_logging.env import is_pytest
|
|
15
|
+
|
|
16
|
+
# default_logger 是**延迟**构造的。早先这里是模块级直接构造,于是 `import wl_cli`
|
|
17
|
+
# 这一个动作就会经由 StructuredLogger → LoggingConfig → get_config() → ConfigManager
|
|
18
|
+
# 去解析配置:读盘,并(在当时 _load_config 的写法下)把 .wl/config.json 写进 CWD。
|
|
19
|
+
# 导入一个库不该有文件系统副作用,故模块级只放一个惰性代理。
|
|
20
|
+
_logger_instance: StructuredLogger | None = None
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _logger() -> StructuredLogger:
|
|
24
|
+
"""Return the module-level logger, constructing it on first use."""
|
|
25
|
+
global _logger_instance
|
|
26
|
+
if _logger_instance is None:
|
|
27
|
+
_logger_instance = StructuredLogger("wl", min_level=LogLevel.INFO)
|
|
28
|
+
_logger_instance.enable_console = True
|
|
29
|
+
return _logger_instance
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class _LazyLogger:
|
|
33
|
+
"""Module-level proxy that builds the real logger on first attribute use.
|
|
34
|
+
|
|
35
|
+
Keeps the `default_logger` name — including unittest.mock's ability to
|
|
36
|
+
patch it — without paying StructuredLogger's filesystem work at import.
|
|
37
|
+
|
|
38
|
+
Attribute writes and deletes are forwarded to the real logger, so
|
|
39
|
+
``default_logger.min_level = X`` and ``mock.patch.object(default_logger,
|
|
40
|
+
"info")`` behave exactly as they did when the module held a concrete
|
|
41
|
+
logger: patch installs the mock on the real instance, and its ``delattr``
|
|
42
|
+
round-trip restores the bound method.
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
def __getattr__(self, item: str) -> Any:
|
|
46
|
+
return getattr(_logger(), item)
|
|
47
|
+
|
|
48
|
+
def __setattr__(self, key: str, value: Any) -> None:
|
|
49
|
+
setattr(_logger(), key, value)
|
|
50
|
+
|
|
51
|
+
def __delattr__(self, key: str) -> None:
|
|
52
|
+
delattr(_logger(), key)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
default_logger: StructuredLogger = cast("StructuredLogger", _LazyLogger())
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def _is_log_level_enabled(level: int) -> bool:
|
|
59
|
+
"""Check if the given log level is enabled based on the current minimum level."""
|
|
60
|
+
try:
|
|
61
|
+
min_level = default_logger.min_level
|
|
62
|
+
if isinstance(min_level, int | float):
|
|
63
|
+
return min_level <= level
|
|
64
|
+
except (TypeError, AttributeError):
|
|
65
|
+
pass
|
|
66
|
+
return True
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def _add_caller_info(kwargs: dict[str, Any], frame_offset: int = 1) -> None:
|
|
70
|
+
"""Add caller module/function/line information to kwargs."""
|
|
71
|
+
try:
|
|
72
|
+
frame = inspect.currentframe()
|
|
73
|
+
for _ in range(frame_offset):
|
|
74
|
+
if frame:
|
|
75
|
+
frame = frame.f_back
|
|
76
|
+
if frame:
|
|
77
|
+
caller_info = inspect.getframeinfo(frame)
|
|
78
|
+
kwargs.update(
|
|
79
|
+
{
|
|
80
|
+
"module": os.path.basename(caller_info.filename).split(".")[0],
|
|
81
|
+
"function": caller_info.function,
|
|
82
|
+
"line": caller_info.lineno,
|
|
83
|
+
}
|
|
84
|
+
)
|
|
85
|
+
except (TypeError, AttributeError):
|
|
86
|
+
pass
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
# ---------------------------------------------------------------------------
|
|
90
|
+
# The single emit funnel (#135)
|
|
91
|
+
#
|
|
92
|
+
# The six verbs below are thin one-line aliases over `_emit`; the four guards
|
|
93
|
+
# (level / lang / perf / caller) and the caller-frame choice are assembled in
|
|
94
|
+
# `_emit` alone, driven by the per-verb policy table `_POLICIES`.
|
|
95
|
+
#
|
|
96
|
+
# The guards were never uniform across the verbs, and that drift is *behaviour*
|
|
97
|
+
# kept verbatim here -- do not "fix" it in this change (explicitly out of scope,
|
|
98
|
+
# tracked separately):
|
|
99
|
+
# * perf guard: only info / error / critical bail out under a performance test;
|
|
100
|
+
# debug / warning / exception log straight through.
|
|
101
|
+
# * caller info: debug / exception always attach it, error / critical attach it
|
|
102
|
+
# only outside pytest, info / warning never include it.
|
|
103
|
+
# ---------------------------------------------------------------------------
|
|
104
|
+
|
|
105
|
+
_CallerMode = Literal["never", "always", "outside_pytest"]
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
@dataclass(frozen=True)
|
|
109
|
+
class _EmitPolicy:
|
|
110
|
+
"""Guard policy for one verb, consumed by :func:`_emit`.
|
|
111
|
+
|
|
112
|
+
``logger_method`` is the :class:`StructuredLogger` method to call; note the
|
|
113
|
+
exception verb logs through ``error``.
|
|
114
|
+
"""
|
|
115
|
+
|
|
116
|
+
logger_method: str
|
|
117
|
+
level: int
|
|
118
|
+
perf_guard: bool = False
|
|
119
|
+
caller: _CallerMode = "never"
|
|
120
|
+
formats_exception: bool = False
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
_POLICIES: dict[str, _EmitPolicy] = {
|
|
124
|
+
"debug": _EmitPolicy("debug", LogLevel.DEBUG, caller="always"),
|
|
125
|
+
"info": _EmitPolicy("info", LogLevel.INFO, perf_guard=True),
|
|
126
|
+
"warning": _EmitPolicy("warning", LogLevel.WARNING),
|
|
127
|
+
"error": _EmitPolicy(
|
|
128
|
+
"error", LogLevel.ERROR, perf_guard=True, caller="outside_pytest"
|
|
129
|
+
),
|
|
130
|
+
"critical": _EmitPolicy(
|
|
131
|
+
"critical", LogLevel.CRITICAL, perf_guard=True, caller="outside_pytest"
|
|
132
|
+
),
|
|
133
|
+
"exception": _EmitPolicy(
|
|
134
|
+
"error", LogLevel.ERROR, caller="always", formats_exception=True
|
|
135
|
+
),
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
# The funnel sits between the verb and `_add_caller_info`, so this offset is one
|
|
139
|
+
# deeper than the pre-#135 value of 2. The extra hop is exactly the bump, which
|
|
140
|
+
# keeps the resolved caller frame identical to before -- both for a direct verb
|
|
141
|
+
# call and for a `log(level, ...)` dispatch.
|
|
142
|
+
_CALLER_FRAME_OFFSET = 3
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def _attach_exception(kwargs: dict[str, Any], exc_info: Any) -> None:
|
|
146
|
+
"""Attach the active (or explicitly supplied) exception to ``kwargs``."""
|
|
147
|
+
if exc_info is None:
|
|
148
|
+
exc_type, exc_value, exc_tb = sys.exc_info()
|
|
149
|
+
if exc_type:
|
|
150
|
+
kwargs.update(
|
|
151
|
+
{
|
|
152
|
+
"error": str(exc_value),
|
|
153
|
+
"traceback": "".join(
|
|
154
|
+
traceback.format_exception(exc_type, exc_value, exc_tb)
|
|
155
|
+
),
|
|
156
|
+
}
|
|
157
|
+
)
|
|
158
|
+
else:
|
|
159
|
+
kwargs["error"] = str(exc_info)
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def _emit(
|
|
163
|
+
verb: str,
|
|
164
|
+
message: str,
|
|
165
|
+
*,
|
|
166
|
+
lang: str,
|
|
167
|
+
kwargs: dict[str, Any],
|
|
168
|
+
exc_info: Any = None,
|
|
169
|
+
) -> None:
|
|
170
|
+
"""The single funnel every verb logs through.
|
|
171
|
+
|
|
172
|
+
Guard order matches the pre-#135 verbs: level, then the perf gate, then the
|
|
173
|
+
language gate, then caller info / exception formatting.
|
|
174
|
+
"""
|
|
175
|
+
policy = _POLICIES[verb]
|
|
176
|
+
if not _is_log_level_enabled(policy.level):
|
|
177
|
+
return
|
|
178
|
+
if policy.perf_guard and _is_performance_test():
|
|
179
|
+
return
|
|
180
|
+
if not _should_display_language(lang):
|
|
181
|
+
return
|
|
182
|
+
if policy.caller == "always" or (
|
|
183
|
+
policy.caller == "outside_pytest" and not is_pytest()
|
|
184
|
+
):
|
|
185
|
+
_add_caller_info(kwargs, frame_offset=_CALLER_FRAME_OFFSET)
|
|
186
|
+
if policy.formats_exception:
|
|
187
|
+
_attach_exception(kwargs, exc_info)
|
|
188
|
+
getattr(default_logger, policy.logger_method)(message, lang=lang, **kwargs)
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
# ``lang`` is keyword-only (#167). As a second positional parameter it silently
|
|
192
|
+
# absorbed a Chinese message -- ``log_info(en_msg, zh_msg)`` emitted nothing
|
|
193
|
+
# under any language setting, the #152 shape -- so the mistake now raises
|
|
194
|
+
# TypeError instead. ``log_*_pair`` is the bilingual entry point and is
|
|
195
|
+
# unaffected.
|
|
196
|
+
def log_debug(message: str, *, lang: str = "en", **kwargs) -> None:
|
|
197
|
+
"""Log a debug message (caller info attached)."""
|
|
198
|
+
_emit("debug", message, lang=lang, kwargs=kwargs)
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
def log_info(message: str, *, lang: str = "en", **kwargs) -> None:
|
|
202
|
+
"""Log an informational message (skipped under a performance test)."""
|
|
203
|
+
_emit("info", message, lang=lang, kwargs=kwargs)
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
def log_warning(message: str, *, lang: str = "en", **kwargs) -> None:
|
|
207
|
+
"""Log a warning message."""
|
|
208
|
+
_emit("warning", message, lang=lang, kwargs=kwargs)
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
def log_error(message: str, *, lang: str = "en", **kwargs) -> None:
|
|
212
|
+
"""Log an error message (caller info only outside pytest)."""
|
|
213
|
+
_emit("error", message, lang=lang, kwargs=kwargs)
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
def log_critical(message: str, *, lang: str = "en", **kwargs) -> None:
|
|
217
|
+
"""Log a critical message (caller info only outside pytest)."""
|
|
218
|
+
_emit("critical", message, lang=lang, kwargs=kwargs)
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
# ``exc_info`` is keyword-only for the same reason ``lang`` is (#167): as a
|
|
222
|
+
# second positional parameter it silently absorbed the Chinese half of a
|
|
223
|
+
# translation pair -- ``log_exception(en_msg, zh_msg)`` bound ``zh_msg`` to
|
|
224
|
+
# ``exc_info``, attached it as the error text, and emitted nothing under the
|
|
225
|
+
# Chinese language setting (#172 wave 9). Keyword-only makes that shape a
|
|
226
|
+
# ``TypeError`` at the call instead of a silent misbinding.
|
|
227
|
+
def log_exception(
|
|
228
|
+
message: str, *, exc_info: Any = None, lang: str = "en", **kwargs
|
|
229
|
+
) -> None:
|
|
230
|
+
"""Log an exception, attaching the active/explicit exception details.
|
|
231
|
+
|
|
232
|
+
``message`` is positional; ``exc_info`` and ``lang`` are keyword-only, so a
|
|
233
|
+
second positional argument (the old ``log_exception(en, zh)`` shape) fails
|
|
234
|
+
at the call rather than binding the Chinese text to ``exc_info``.
|
|
235
|
+
"""
|
|
236
|
+
_emit("exception", message, lang=lang, kwargs=kwargs, exc_info=exc_info)
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
# ---------------------------------------------------------------------------
|
|
240
|
+
# The bilingual double-write entry point (#146)
|
|
241
|
+
#
|
|
242
|
+
# A *pair* is the two adjacent statements the codebase used to spell out by
|
|
243
|
+
# hand -- an English line and a Chinese line carrying the same message:
|
|
244
|
+
#
|
|
245
|
+
# log_info("✓ Git repository initialized")
|
|
246
|
+
# log_info("✓ Git仓库初始化完成", lang="zh")
|
|
247
|
+
#
|
|
248
|
+
# `log_pair` collapses them into one call whose observable output is identical:
|
|
249
|
+
# the two `_emit` calls below are exactly the two the hand-written form made,
|
|
250
|
+
# in the same order, each judged by the same level / perf / language guards.
|
|
251
|
+
# The language decision stays in `_should_display_language` (reached through
|
|
252
|
+
# `_emit`) -- this module never re-derives it.
|
|
253
|
+
#
|
|
254
|
+
# Every entry point below calls `_emit` *directly* rather than delegating to a
|
|
255
|
+
# shared helper, because `_emit` resolves caller info at a fixed frame depth
|
|
256
|
+
# (_CALLER_FRAME_OFFSET): one extra hop would move debug/error/critical caller
|
|
257
|
+
# info onto the wrapper. Each `_emit` call also gets its own copy of `kwargs`,
|
|
258
|
+
# matching the fresh dict that `**kwargs` gave each hand-written call.
|
|
259
|
+
# ---------------------------------------------------------------------------
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
def log_pair(en: str, zh: str, level: str = "info", **kwargs) -> None:
|
|
263
|
+
"""Log the English and Chinese variants of one message (#146).
|
|
264
|
+
|
|
265
|
+
Args:
|
|
266
|
+
en: The English variant, emitted with ``lang="en"``.
|
|
267
|
+
zh: The Chinese variant, emitted with ``lang="zh"``.
|
|
268
|
+
level: Verb to log through -- one of "debug", "info", "warning",
|
|
269
|
+
"error", "critical" or "exception". Defaults to "info".
|
|
270
|
+
**kwargs: Extra context, applied to both variants. ``exc_info`` is
|
|
271
|
+
forwarded to `_emit` for the "exception" verb.
|
|
272
|
+
"""
|
|
273
|
+
exc_info = kwargs.pop("exc_info", None)
|
|
274
|
+
_emit(level, en, lang="en", kwargs=dict(kwargs), exc_info=exc_info)
|
|
275
|
+
_emit(level, zh, lang="zh", kwargs=dict(kwargs), exc_info=exc_info)
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
def log_debug_pair(en: str, zh: str, **kwargs) -> None:
|
|
279
|
+
"""Debug-level `log_pair` (caller info attached to both variants)."""
|
|
280
|
+
_emit("debug", en, lang="en", kwargs=dict(kwargs))
|
|
281
|
+
_emit("debug", zh, lang="zh", kwargs=dict(kwargs))
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
def log_info_pair(en: str, zh: str, **kwargs) -> None:
|
|
285
|
+
"""Info-level `log_pair` (both variants skipped under a performance test)."""
|
|
286
|
+
_emit("info", en, lang="en", kwargs=dict(kwargs))
|
|
287
|
+
_emit("info", zh, lang="zh", kwargs=dict(kwargs))
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
def log_warning_pair(en: str, zh: str, **kwargs) -> None:
|
|
291
|
+
"""Warning-level `log_pair`."""
|
|
292
|
+
_emit("warning", en, lang="en", kwargs=dict(kwargs))
|
|
293
|
+
_emit("warning", zh, lang="zh", kwargs=dict(kwargs))
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
def log_error_pair(en: str, zh: str, **kwargs) -> None:
|
|
297
|
+
"""Error-level `log_pair` (caller info on both, only outside pytest)."""
|
|
298
|
+
_emit("error", en, lang="en", kwargs=dict(kwargs))
|
|
299
|
+
_emit("error", zh, lang="zh", kwargs=dict(kwargs))
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
def log(level: int, message: str, **kwargs) -> None:
|
|
303
|
+
"""
|
|
304
|
+
Log a message with specified level.
|
|
305
|
+
|
|
306
|
+
Dispatch stays by verb name (each verb funnels through `_emit`); the
|
|
307
|
+
`mock.patch("wl_cli.utils.logging.log_*")` contract depends on it.
|
|
308
|
+
|
|
309
|
+
Args:
|
|
310
|
+
level: Log level as integer (use LogLevel enum).
|
|
311
|
+
message (str): Message to log.
|
|
312
|
+
**kwargs: Additional context for the log message.
|
|
313
|
+
"""
|
|
314
|
+
# Backward compatibility: handle old keyword argument names
|
|
315
|
+
# Old signature: def log(message: int, message_str: str, **kwargs)
|
|
316
|
+
if "message_str" in kwargs:
|
|
317
|
+
message = kwargs.pop("message_str")
|
|
318
|
+
old_level = kwargs.pop("message", None)
|
|
319
|
+
if old_level is not None and isinstance(old_level, int):
|
|
320
|
+
level = old_level
|
|
321
|
+
|
|
322
|
+
if level == LogLevel.DEBUG:
|
|
323
|
+
log_debug(message, **kwargs)
|
|
324
|
+
elif level == LogLevel.INFO:
|
|
325
|
+
log_info(message, **kwargs)
|
|
326
|
+
elif level == LogLevel.WARNING:
|
|
327
|
+
log_warning(message, **kwargs)
|
|
328
|
+
elif level == LogLevel.ERROR:
|
|
329
|
+
log_error(message, **kwargs)
|
|
330
|
+
elif level == LogLevel.CRITICAL:
|
|
331
|
+
log_critical(message, **kwargs)
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
# Context management utilities
|
|
335
|
+
def push_log_context(**context) -> None:
|
|
336
|
+
"""
|
|
337
|
+
Push context to the default logger's context stack.
|
|
338
|
+
|
|
339
|
+
Args:
|
|
340
|
+
**context: Context key-value pairs.
|
|
341
|
+
"""
|
|
342
|
+
if hasattr(default_logger, "push_context"):
|
|
343
|
+
default_logger.push_context(**context)
|
|
344
|
+
|
|
345
|
+
|
|
346
|
+
def pop_log_context() -> dict[str, Any] | None:
|
|
347
|
+
"""
|
|
348
|
+
Pop context from the default logger's context stack.
|
|
349
|
+
|
|
350
|
+
Returns:
|
|
351
|
+
The popped context or None if stack is empty.
|
|
352
|
+
"""
|
|
353
|
+
if hasattr(default_logger, "pop_context"):
|
|
354
|
+
return cast("dict[str, Any] | None", default_logger.pop_context())
|
|
355
|
+
return None
|
|
356
|
+
|
|
357
|
+
|
|
358
|
+
def clear_log_context() -> None:
|
|
359
|
+
"""
|
|
360
|
+
Clear all context from the default logger's context stack.
|
|
361
|
+
"""
|
|
362
|
+
if hasattr(default_logger, "clear_context"):
|
|
363
|
+
default_logger.clear_context()
|
|
364
|
+
|
|
365
|
+
|
|
366
|
+
# Enhanced get_logger function
|
|
367
|
+
def get_logger(
|
|
368
|
+
name: str = "wl", config: dict[str, Any] | None = None
|
|
369
|
+
) -> StructuredLogger:
|
|
370
|
+
"""
|
|
371
|
+
Get a logger instance with the specified name and optional configuration.
|
|
372
|
+
|
|
373
|
+
Args:
|
|
374
|
+
name (str, optional): Logger name. Defaults to "wl".
|
|
375
|
+
config (Optional[Dict[str, Any]]): Optional configuration dictionary.
|
|
376
|
+
|
|
377
|
+
Returns:
|
|
378
|
+
StructuredLogger: Logger instance.
|
|
379
|
+
"""
|
|
380
|
+
if config:
|
|
381
|
+
# Create logger with custom configuration
|
|
382
|
+
logger = StructuredLogger(name)
|
|
383
|
+
# Apply configuration
|
|
384
|
+
if "log_level" in config:
|
|
385
|
+
if isinstance(config["log_level"], str):
|
|
386
|
+
logger.min_level = getattr(
|
|
387
|
+
LogLevel, config["log_level"].upper(), LogLevel.INFO
|
|
388
|
+
)
|
|
389
|
+
else:
|
|
390
|
+
logger.min_level = config["log_level"]
|
|
391
|
+
if "log_file" in config:
|
|
392
|
+
logger.log_file = config["log_file"]
|
|
393
|
+
return logger
|
|
394
|
+
else:
|
|
395
|
+
return StructuredLogger(name)
|
|
396
|
+
|
|
397
|
+
|
|
398
|
+
# Aliases for backward compatibility
|
|
399
|
+
info = log_info
|
|
400
|
+
debug = log_debug
|
|
401
|
+
warning = log_warning
|
|
402
|
+
error = log_error
|
|
403
|
+
critical = log_critical
|
|
404
|
+
exception = log_exception
|
|
405
|
+
|
|
406
|
+
|
|
407
|
+
# Module-level convenience functions
|
|
408
|
+
def configure_logging(config: dict[str, Any]) -> None:
|
|
409
|
+
"""
|
|
410
|
+
Configure the default logger with the specified configuration.
|
|
411
|
+
|
|
412
|
+
Args:
|
|
413
|
+
config (Dict[str, Any]): Configuration dictionary.
|
|
414
|
+
"""
|
|
415
|
+
global _logger_instance, default_logger
|
|
416
|
+
_logger_instance = get_logger("wl", config)
|
|
417
|
+
default_logger = _logger_instance
|
|
418
|
+
|
|
419
|
+
|
|
420
|
+
def get_log_level() -> int:
|
|
421
|
+
"""
|
|
422
|
+
Get the current log level of the default logger.
|
|
423
|
+
|
|
424
|
+
Returns:
|
|
425
|
+
int: Current log level.
|
|
426
|
+
"""
|
|
427
|
+
return cast("int", default_logger.min_level)
|
|
428
|
+
|
|
429
|
+
|
|
430
|
+
def set_log_level(level: int) -> None:
|
|
431
|
+
"""
|
|
432
|
+
Set the log level of the default logger.
|
|
433
|
+
|
|
434
|
+
Args:
|
|
435
|
+
level (int): New log level.
|
|
436
|
+
"""
|
|
437
|
+
default_logger.min_level = level
|
|
438
|
+
|
|
439
|
+
|
|
440
|
+
def enable_console_logging(enable: bool = True) -> None:
|
|
441
|
+
"""
|
|
442
|
+
Enable or disable console logging for the default logger.
|
|
443
|
+
|
|
444
|
+
Args:
|
|
445
|
+
enable (bool): Whether to enable console logging.
|
|
446
|
+
"""
|
|
447
|
+
if hasattr(default_logger, "enable_console"):
|
|
448
|
+
default_logger.enable_console = enable
|
|
449
|
+
|
|
450
|
+
|
|
451
|
+
def set_log_file(file_path: str | None) -> None:
|
|
452
|
+
"""
|
|
453
|
+
Set the log file path for the default logger.
|
|
454
|
+
|
|
455
|
+
Args:
|
|
456
|
+
file_path (Optional[str]): Log file path or None to disable file logging.
|
|
457
|
+
"""
|
|
458
|
+
default_logger.log_file = file_path
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
"""Output context detection utility.
|
|
2
|
+
|
|
3
|
+
Detects execution context (Claude skill vs manual terminal) and provides
|
|
4
|
+
output verbosity configuration to optimize token consumption.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import os
|
|
8
|
+
import sys
|
|
9
|
+
from dataclasses import dataclass
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@dataclass
|
|
13
|
+
class OutputConfig:
|
|
14
|
+
"""Configuration for output behavior based on execution context."""
|
|
15
|
+
|
|
16
|
+
verbose: bool
|
|
17
|
+
show_progress_bars: bool
|
|
18
|
+
concise_mode: bool
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def detect_output_context() -> OutputConfig:
|
|
22
|
+
"""Detect the execution context and determine output configuration.
|
|
23
|
+
|
|
24
|
+
Detection strategy:
|
|
25
|
+
1. Check environment variables for explicit override (WL_VERBOSE, WL_QUIET)
|
|
26
|
+
2. Check if running in Claude/Anthropic skill context (CLAUDE, ANTHROPIC)
|
|
27
|
+
3. Check if stdout is a terminal (tty)
|
|
28
|
+
4. Check if running in CI
|
|
29
|
+
5. Default to verbose output if no indicators found
|
|
30
|
+
|
|
31
|
+
Returns:
|
|
32
|
+
OutputConfig with appropriate verbosity settings.
|
|
33
|
+
"""
|
|
34
|
+
# Check for explicit environment variable overrides first
|
|
35
|
+
if "WL_VERBOSE" in os.environ:
|
|
36
|
+
return OutputConfig(verbose=True, show_progress_bars=True, concise_mode=False)
|
|
37
|
+
if "WL_QUIET" in os.environ:
|
|
38
|
+
return OutputConfig(verbose=False, show_progress_bars=False, concise_mode=True)
|
|
39
|
+
|
|
40
|
+
# Check if running in Claude skill context
|
|
41
|
+
in_claude = (
|
|
42
|
+
"CLAUDE" in os.environ or "ANTHROPIC" in os.environ or "WL_SKILL" in os.environ
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
# Check if stdout is a terminal
|
|
46
|
+
is_tty = sys.stdout.isatty()
|
|
47
|
+
|
|
48
|
+
# Check if running in CI
|
|
49
|
+
is_ci = (
|
|
50
|
+
"CI" in os.environ
|
|
51
|
+
or "GITHUB_ACTIONS" in os.environ
|
|
52
|
+
or "GITLAB_CI" in os.environ
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
# Decision logic:
|
|
56
|
+
# - In Claude context: use concise output, no progress bars
|
|
57
|
+
# - In CI/non-tty: use concise output
|
|
58
|
+
# - In terminal (tty): use full output with progress bars
|
|
59
|
+
if in_claude or is_ci or not is_tty:
|
|
60
|
+
return OutputConfig(verbose=False, show_progress_bars=False, concise_mode=True)
|
|
61
|
+
else:
|
|
62
|
+
return OutputConfig(verbose=True, show_progress_bars=True, concise_mode=False)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def get_output_config(
|
|
66
|
+
override_quiet: bool | None = None, override_verbose: bool | None = None
|
|
67
|
+
) -> OutputConfig:
|
|
68
|
+
"""Get output configuration with optional overrides.
|
|
69
|
+
|
|
70
|
+
Args:
|
|
71
|
+
override_quiet: If True, force concise output. If False, no override.
|
|
72
|
+
override_verbose: If True, force verbose output. If False, no override.
|
|
73
|
+
|
|
74
|
+
Returns:
|
|
75
|
+
OutputConfig with overrides applied if specified.
|
|
76
|
+
"""
|
|
77
|
+
config = detect_output_context()
|
|
78
|
+
|
|
79
|
+
# Command-line flags override automatic detection
|
|
80
|
+
if override_verbose:
|
|
81
|
+
config.verbose = True
|
|
82
|
+
config.show_progress_bars = True
|
|
83
|
+
config.concise_mode = False
|
|
84
|
+
elif override_quiet:
|
|
85
|
+
config.verbose = False
|
|
86
|
+
config.show_progress_bars = False
|
|
87
|
+
config.concise_mode = True
|
|
88
|
+
|
|
89
|
+
return config
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def should_show_progress_bars(
|
|
93
|
+
override_quiet: bool | None = None, override_verbose: bool | None = None
|
|
94
|
+
) -> bool:
|
|
95
|
+
"""Check if progress bars should be shown.
|
|
96
|
+
|
|
97
|
+
Convenience helper for places that only need this decision.
|
|
98
|
+
|
|
99
|
+
Args:
|
|
100
|
+
override_quiet: Force quiet from command-line flag.
|
|
101
|
+
override_verbose: Force verbose from command-line flag.
|
|
102
|
+
|
|
103
|
+
Returns:
|
|
104
|
+
True if progress bars should be shown, False otherwise.
|
|
105
|
+
"""
|
|
106
|
+
config = get_output_config(override_quiet, override_verbose)
|
|
107
|
+
return config.show_progress_bars
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def is_concise_mode(
|
|
111
|
+
override_quiet: bool | None = None, override_verbose: bool | None = None
|
|
112
|
+
) -> bool:
|
|
113
|
+
"""Check if we should run in concise mode.
|
|
114
|
+
|
|
115
|
+
Concise mode reduces verbose logging and unnecessary output
|
|
116
|
+
to save tokens when running in Claude skill context.
|
|
117
|
+
|
|
118
|
+
Args:
|
|
119
|
+
override_quiet: Force quiet from command-line flag.
|
|
120
|
+
override_verbose: Force verbose from command-line flag.
|
|
121
|
+
|
|
122
|
+
Returns:
|
|
123
|
+
True if concise mode should be used.
|
|
124
|
+
"""
|
|
125
|
+
config = get_output_config(override_quiet, override_verbose)
|
|
126
|
+
return config.concise_mode
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def is_verbose(
|
|
130
|
+
override_quiet: bool | None = None, override_verbose: bool | None = None
|
|
131
|
+
) -> bool:
|
|
132
|
+
"""Check if verbose output is enabled.
|
|
133
|
+
|
|
134
|
+
Args:
|
|
135
|
+
override_quiet: Force quiet from command-line flag.
|
|
136
|
+
override_verbose: Force verbose from command-line flag.
|
|
137
|
+
|
|
138
|
+
Returns:
|
|
139
|
+
True if verbose output should be shown.
|
|
140
|
+
"""
|
|
141
|
+
config = get_output_config(override_quiet, override_verbose)
|
|
142
|
+
return config.verbose
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Performance monitoring utilities package."""
|