yuho 5.0.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 (91) hide show
  1. yuho/__init__.py +16 -0
  2. yuho/ast/__init__.py +196 -0
  3. yuho/ast/builder.py +926 -0
  4. yuho/ast/constant_folder.py +280 -0
  5. yuho/ast/dead_code.py +199 -0
  6. yuho/ast/exhaustiveness.py +503 -0
  7. yuho/ast/nodes.py +907 -0
  8. yuho/ast/overlap.py +291 -0
  9. yuho/ast/reachability.py +293 -0
  10. yuho/ast/scope_analysis.py +490 -0
  11. yuho/ast/transformer.py +490 -0
  12. yuho/ast/type_check.py +471 -0
  13. yuho/ast/type_inference.py +425 -0
  14. yuho/ast/visitor.py +239 -0
  15. yuho/cli/__init__.py +14 -0
  16. yuho/cli/commands/__init__.py +1 -0
  17. yuho/cli/commands/api.py +431 -0
  18. yuho/cli/commands/ast_viz.py +334 -0
  19. yuho/cli/commands/check.py +218 -0
  20. yuho/cli/commands/config.py +311 -0
  21. yuho/cli/commands/contribute.py +122 -0
  22. yuho/cli/commands/diff.py +487 -0
  23. yuho/cli/commands/explain.py +240 -0
  24. yuho/cli/commands/fmt.py +253 -0
  25. yuho/cli/commands/generate.py +316 -0
  26. yuho/cli/commands/graph.py +410 -0
  27. yuho/cli/commands/init.py +120 -0
  28. yuho/cli/commands/library.py +656 -0
  29. yuho/cli/commands/lint.py +503 -0
  30. yuho/cli/commands/lsp.py +36 -0
  31. yuho/cli/commands/preview.py +377 -0
  32. yuho/cli/commands/repl.py +444 -0
  33. yuho/cli/commands/serve.py +44 -0
  34. yuho/cli/commands/test.py +528 -0
  35. yuho/cli/commands/transpile.py +121 -0
  36. yuho/cli/commands/wizard.py +370 -0
  37. yuho/cli/completions.py +182 -0
  38. yuho/cli/error_formatter.py +193 -0
  39. yuho/cli/main.py +1064 -0
  40. yuho/config/__init__.py +46 -0
  41. yuho/config/loader.py +235 -0
  42. yuho/config/mask.py +194 -0
  43. yuho/config/schema.py +147 -0
  44. yuho/library/__init__.py +84 -0
  45. yuho/library/index.py +328 -0
  46. yuho/library/install.py +699 -0
  47. yuho/library/lockfile.py +330 -0
  48. yuho/library/package.py +421 -0
  49. yuho/library/resolver.py +791 -0
  50. yuho/library/signature.py +335 -0
  51. yuho/llm/__init__.py +45 -0
  52. yuho/llm/config.py +75 -0
  53. yuho/llm/factory.py +123 -0
  54. yuho/llm/prompts.py +146 -0
  55. yuho/llm/providers.py +383 -0
  56. yuho/llm/utils.py +470 -0
  57. yuho/lsp/__init__.py +14 -0
  58. yuho/lsp/code_action_handler.py +518 -0
  59. yuho/lsp/completion_handler.py +85 -0
  60. yuho/lsp/diagnostics.py +100 -0
  61. yuho/lsp/hover_handler.py +130 -0
  62. yuho/lsp/server.py +1425 -0
  63. yuho/mcp/__init__.py +10 -0
  64. yuho/mcp/server.py +1452 -0
  65. yuho/parser/__init__.py +8 -0
  66. yuho/parser/source_location.py +108 -0
  67. yuho/parser/wrapper.py +311 -0
  68. yuho/testing/__init__.py +48 -0
  69. yuho/testing/coverage.py +274 -0
  70. yuho/testing/fixtures.py +263 -0
  71. yuho/transpile/__init__.py +52 -0
  72. yuho/transpile/alloy_transpiler.py +546 -0
  73. yuho/transpile/base.py +100 -0
  74. yuho/transpile/blocks_transpiler.py +338 -0
  75. yuho/transpile/english_transpiler.py +470 -0
  76. yuho/transpile/graphql_transpiler.py +404 -0
  77. yuho/transpile/json_transpiler.py +217 -0
  78. yuho/transpile/jsonld_transpiler.py +250 -0
  79. yuho/transpile/latex_preamble.py +161 -0
  80. yuho/transpile/latex_transpiler.py +406 -0
  81. yuho/transpile/latex_utils.py +206 -0
  82. yuho/transpile/mermaid_transpiler.py +357 -0
  83. yuho/transpile/registry.py +275 -0
  84. yuho/verify/__init__.py +43 -0
  85. yuho/verify/alloy.py +352 -0
  86. yuho/verify/combined.py +218 -0
  87. yuho/verify/z3_solver.py +1155 -0
  88. yuho-5.0.0.dist-info/METADATA +186 -0
  89. yuho-5.0.0.dist-info/RECORD +91 -0
  90. yuho-5.0.0.dist-info/WHEEL +4 -0
  91. yuho-5.0.0.dist-info/entry_points.txt +2 -0
@@ -0,0 +1,46 @@
1
+ """
2
+ Yuho configuration module.
3
+
4
+ Handles configuration loading from:
5
+ 1. Default values
6
+ 2. Config file (~/.config/yuho/config.toml)
7
+ 3. Environment variables (YUHO_*)
8
+ 4. CLI flags
9
+
10
+ Later sources override earlier ones.
11
+ """
12
+
13
+ from yuho.config.loader import Config, load_config, get_config
14
+ from yuho.config.schema import (
15
+ LLMSection,
16
+ TranspileSection,
17
+ LSPSection,
18
+ MCPSection,
19
+ )
20
+ from yuho.config.mask import (
21
+ mask_value,
22
+ mask_dict,
23
+ mask_string,
24
+ mask_error,
25
+ mask_url,
26
+ safe_repr,
27
+ is_sensitive_key,
28
+ )
29
+
30
+ __all__ = [
31
+ "Config",
32
+ "load_config",
33
+ "get_config",
34
+ "LLMSection",
35
+ "TranspileSection",
36
+ "LSPSection",
37
+ "MCPSection",
38
+ # Masking utilities
39
+ "mask_value",
40
+ "mask_dict",
41
+ "mask_string",
42
+ "mask_error",
43
+ "mask_url",
44
+ "safe_repr",
45
+ "is_sensitive_key",
46
+ ]
yuho/config/loader.py ADDED
@@ -0,0 +1,235 @@
1
+ """
2
+ Configuration loader with multi-source override support.
3
+
4
+ Load order (later overrides earlier):
5
+ 1. Built-in defaults
6
+ 2. Config file (~/.config/yuho/config.toml)
7
+ 3. Environment variables (YUHO_*)
8
+ 4. CLI flags
9
+ """
10
+
11
+ import os
12
+ from pathlib import Path
13
+ from typing import Optional, Dict, Any
14
+ import logging
15
+
16
+ from yuho.config.schema import ConfigSchema
17
+
18
+ logger = logging.getLogger(__name__)
19
+
20
+ # Default config file location
21
+ DEFAULT_CONFIG_PATH = Path.home() / ".config" / "yuho" / "config.toml"
22
+
23
+ # Singleton config instance
24
+ _config: Optional["Config"] = None
25
+
26
+
27
+ class Config:
28
+ """
29
+ Yuho configuration manager.
30
+
31
+ Handles loading and accessing configuration from multiple sources.
32
+ """
33
+
34
+ def __init__(self, schema: ConfigSchema):
35
+ self._schema = schema
36
+
37
+ @property
38
+ def llm(self):
39
+ """Get LLM configuration section."""
40
+ return self._schema.llm
41
+
42
+ @property
43
+ def transpile(self):
44
+ """Get transpile configuration section."""
45
+ return self._schema.transpile
46
+
47
+ @property
48
+ def lsp(self):
49
+ """Get LSP configuration section."""
50
+ return self._schema.lsp
51
+
52
+ @property
53
+ def mcp(self):
54
+ """Get MCP configuration section."""
55
+ return self._schema.mcp
56
+
57
+ def to_dict(self) -> Dict[str, Any]:
58
+ """Convert to dictionary."""
59
+ return self._schema.to_dict()
60
+
61
+
62
+ def load_config(
63
+ config_path: Optional[Path] = None,
64
+ env_prefix: str = "YUHO_",
65
+ cli_overrides: Optional[Dict[str, Any]] = None,
66
+ ) -> Config:
67
+ """
68
+ Load configuration from all sources.
69
+
70
+ Args:
71
+ config_path: Path to config file (default: ~/.config/yuho/config.toml)
72
+ env_prefix: Prefix for environment variables
73
+ cli_overrides: Dictionary of CLI flag overrides
74
+
75
+ Returns:
76
+ Loaded Config instance
77
+ """
78
+ # Start with defaults
79
+ config_data: Dict[str, Any] = {}
80
+
81
+ # Load from file
82
+ file_path = config_path or DEFAULT_CONFIG_PATH
83
+ if file_path.exists():
84
+ config_data = _load_from_file(file_path)
85
+ logger.debug(f"Loaded config from {file_path}")
86
+
87
+ # Override from environment
88
+ env_data = _load_from_env(env_prefix)
89
+ config_data = _merge_dicts(config_data, env_data)
90
+
91
+ # Override from CLI
92
+ if cli_overrides:
93
+ config_data = _merge_dicts(config_data, cli_overrides)
94
+
95
+ # Create schema
96
+ schema = ConfigSchema.from_dict(config_data)
97
+
98
+ return Config(schema)
99
+
100
+
101
+ def get_config() -> Config:
102
+ """
103
+ Get the global config instance.
104
+
105
+ Loads config on first call, then returns cached instance.
106
+ """
107
+ global _config
108
+ if _config is None:
109
+ _config = load_config()
110
+ return _config
111
+
112
+
113
+ def _load_from_file(path: Path) -> Dict[str, Any]:
114
+ """Load config from TOML file."""
115
+ try:
116
+ import tomllib
117
+ except ImportError:
118
+ try:
119
+ import tomli as tomllib
120
+ except ImportError:
121
+ logger.warning("tomllib/tomli not available, skipping config file")
122
+ return {}
123
+
124
+ try:
125
+ with open(path, "rb") as f:
126
+ return tomllib.load(f)
127
+ except Exception as e:
128
+ logger.warning(f"Failed to load config file: {e}")
129
+ return {}
130
+
131
+
132
+ def _load_from_env(prefix: str) -> Dict[str, Any]:
133
+ """Load config from environment variables."""
134
+ config: Dict[str, Any] = {}
135
+
136
+ # Map environment variables to config paths
137
+ env_mapping = {
138
+ f"{prefix}LLM_PROVIDER": ("llm", "provider"),
139
+ f"{prefix}LLM_MODEL": ("llm", "model"),
140
+ f"{prefix}LLM_OLLAMA_HOST": ("llm", "ollama_host"),
141
+ f"{prefix}LLM_OLLAMA_PORT": ("llm", "ollama_port"),
142
+ f"{prefix}LLM_HUGGINGFACE_CACHE": ("llm", "huggingface_cache"),
143
+ f"{prefix}LLM_OPENAI_API_KEY": ("llm", "openai_api_key"),
144
+ f"{prefix}LLM_ANTHROPIC_API_KEY": ("llm", "anthropic_api_key"),
145
+ f"{prefix}LLM_MAX_TOKENS": ("llm", "max_tokens"),
146
+ f"{prefix}LLM_TEMPERATURE": ("llm", "temperature"),
147
+ f"{prefix}TRANSPILE_DEFAULT_TARGET": ("transpile", "default_target"),
148
+ f"{prefix}TRANSPILE_LATEX_COMPILER": ("transpile", "latex_compiler"),
149
+ f"{prefix}TRANSPILE_OUTPUT_DIR": ("transpile", "output_dir"),
150
+ f"{prefix}MCP_HOST": ("mcp", "host"),
151
+ f"{prefix}MCP_PORT": ("mcp", "port"),
152
+ f"{prefix}MCP_AUTH_TOKEN": ("mcp", "auth_token"),
153
+ }
154
+
155
+ for env_var, (section, key) in env_mapping.items():
156
+ value = os.environ.get(env_var)
157
+ if value is not None:
158
+ if section not in config:
159
+ config[section] = {}
160
+
161
+ # Type conversion
162
+ if key in ("ollama_port", "max_tokens", "port"):
163
+ value = int(value)
164
+ elif key == "temperature":
165
+ value = float(value)
166
+
167
+ config[section][key] = value
168
+
169
+ return config
170
+
171
+
172
+ def _merge_dicts(base: Dict[str, Any], override: Dict[str, Any]) -> Dict[str, Any]:
173
+ """Deep merge two dictionaries."""
174
+ result = base.copy()
175
+
176
+ for key, value in override.items():
177
+ if key in result and isinstance(result[key], dict) and isinstance(value, dict):
178
+ result[key] = _merge_dicts(result[key], value)
179
+ else:
180
+ result[key] = value
181
+
182
+ return result
183
+
184
+
185
+ def create_default_config(path: Optional[Path] = None) -> Path:
186
+ """
187
+ Create a default config file.
188
+
189
+ Args:
190
+ path: Path to create file at (default: ~/.config/yuho/config.toml)
191
+
192
+ Returns:
193
+ Path to created file
194
+ """
195
+ file_path = path or DEFAULT_CONFIG_PATH
196
+ file_path.parent.mkdir(parents=True, exist_ok=True)
197
+
198
+ default_config = """# Yuho Configuration
199
+ # https://yuho.dev/docs/configuration
200
+
201
+ [llm]
202
+ # LLM provider: ollama, huggingface, openai, anthropic
203
+ provider = "ollama"
204
+ model = "llama3"
205
+ ollama_host = "localhost"
206
+ ollama_port = 11434
207
+ # huggingface_cache = "~/.cache/huggingface"
208
+ # openai_api_key = ""
209
+ # anthropic_api_key = ""
210
+ max_tokens = 2048
211
+ temperature = 0.7
212
+ fallback_providers = ["huggingface"]
213
+
214
+ [transpile]
215
+ default_target = "json"
216
+ latex_compiler = "pdflatex"
217
+ # output_dir = "./output"
218
+ include_source_locations = true
219
+
220
+ [lsp]
221
+ diagnostic_severity_error = true
222
+ diagnostic_severity_warning = true
223
+ diagnostic_severity_info = true
224
+ diagnostic_severity_hint = true
225
+ completion_trigger_chars = [".", ":"]
226
+
227
+ [mcp]
228
+ host = "127.0.0.1"
229
+ port = 8080
230
+ allowed_origins = ["*"]
231
+ # auth_token = ""
232
+ """
233
+
234
+ file_path.write_text(default_config)
235
+ return file_path
yuho/config/mask.py ADDED
@@ -0,0 +1,194 @@
1
+ """
2
+ Utilities for masking sensitive data in logs, errors, and output.
3
+
4
+ This module provides functions to redact sensitive information like
5
+ API keys, tokens, and passwords before they appear in logs or error messages.
6
+ """
7
+
8
+ import re
9
+ from typing import Any, Dict, List, Optional, Set
10
+
11
+ # Sensitive field names that should always be masked
12
+ SENSITIVE_FIELDS: Set[str] = {
13
+ "api_key",
14
+ "apikey",
15
+ "api-key",
16
+ "auth_token",
17
+ "authtoken",
18
+ "auth-token",
19
+ "token",
20
+ "password",
21
+ "secret",
22
+ "credential",
23
+ "openai_api_key",
24
+ "anthropic_api_key",
25
+ "authorization",
26
+ "bearer",
27
+ }
28
+
29
+ # Patterns that look like secrets (for string scanning)
30
+ SECRET_PATTERNS = [
31
+ # API keys (various formats)
32
+ re.compile(r"sk-[a-zA-Z0-9]{20,}"), # OpenAI-style
33
+ re.compile(r"sk-ant-[a-zA-Z0-9-]{20,}"), # Anthropic-style
34
+ re.compile(r"hf_[a-zA-Z0-9]{20,}"), # HuggingFace-style
35
+ # Bearer tokens in headers
36
+ re.compile(r"Bearer\s+[a-zA-Z0-9._-]{20,}", re.IGNORECASE),
37
+ # Generic long alphanumeric strings that look like tokens
38
+ re.compile(r"[a-zA-Z0-9]{32,}"),
39
+ ]
40
+
41
+
42
+ def mask_value(value: str, visible_chars: int = 4) -> str:
43
+ """
44
+ Mask a sensitive value, showing only the first few characters.
45
+
46
+ Args:
47
+ value: The sensitive value to mask
48
+ visible_chars: Number of characters to show at the start
49
+
50
+ Returns:
51
+ Masked value like "sk-a***" or "***" if too short
52
+ """
53
+ if not value or len(value) <= visible_chars:
54
+ return "***"
55
+ return value[:visible_chars] + "***"
56
+
57
+
58
+ def is_sensitive_key(key: str) -> bool:
59
+ """
60
+ Check if a key name indicates sensitive data.
61
+
62
+ Args:
63
+ key: The key/field name to check
64
+
65
+ Returns:
66
+ True if the key appears to be sensitive
67
+ """
68
+ key_lower = key.lower().replace("_", "").replace("-", "")
69
+ for sensitive in SENSITIVE_FIELDS:
70
+ if sensitive.replace("_", "").replace("-", "") in key_lower:
71
+ return True
72
+ return False
73
+
74
+
75
+ def mask_dict(data: Dict[str, Any], deep: bool = True) -> Dict[str, Any]:
76
+ """
77
+ Mask sensitive values in a dictionary.
78
+
79
+ Args:
80
+ data: Dictionary potentially containing sensitive values
81
+ deep: Whether to recursively mask nested dictionaries
82
+
83
+ Returns:
84
+ New dictionary with sensitive values masked
85
+ """
86
+ result = {}
87
+ for key, value in data.items():
88
+ if is_sensitive_key(key):
89
+ if isinstance(value, str) and value:
90
+ result[key] = mask_value(value)
91
+ elif value is not None:
92
+ result[key] = "***"
93
+ else:
94
+ result[key] = None
95
+ elif deep and isinstance(value, dict):
96
+ result[key] = mask_dict(value, deep=True)
97
+ elif deep and isinstance(value, list):
98
+ result[key] = [
99
+ mask_dict(v, deep=True) if isinstance(v, dict) else v
100
+ for v in value
101
+ ]
102
+ else:
103
+ result[key] = value
104
+ return result
105
+
106
+
107
+ def mask_string(text: str) -> str:
108
+ """
109
+ Scan a string and mask anything that looks like a secret.
110
+
111
+ Args:
112
+ text: Text that might contain secrets
113
+
114
+ Returns:
115
+ Text with secret-like patterns masked
116
+ """
117
+ result = text
118
+ for pattern in SECRET_PATTERNS:
119
+ result = pattern.sub(lambda m: mask_value(m.group(0)), result)
120
+ return result
121
+
122
+
123
+ def mask_error(error: Exception) -> str:
124
+ """
125
+ Get a safe string representation of an error, masking any secrets.
126
+
127
+ Args:
128
+ error: The exception to format
129
+
130
+ Returns:
131
+ Error message with potential secrets masked
132
+ """
133
+ return mask_string(str(error))
134
+
135
+
136
+ def mask_url(url: str) -> str:
137
+ """
138
+ Mask sensitive parts of a URL (query params with sensitive names).
139
+
140
+ Args:
141
+ url: URL that might contain sensitive query parameters
142
+
143
+ Returns:
144
+ URL with sensitive query parameters masked
145
+ """
146
+ from urllib.parse import urlparse, parse_qs, urlencode, urlunparse
147
+
148
+ try:
149
+ parsed = urlparse(url)
150
+ if not parsed.query:
151
+ return url
152
+
153
+ params = parse_qs(parsed.query, keep_blank_values=True)
154
+ masked_params = {}
155
+
156
+ for key, values in params.items():
157
+ if is_sensitive_key(key):
158
+ masked_params[key] = ["***" for _ in values]
159
+ else:
160
+ masked_params[key] = values
161
+
162
+ # Rebuild URL with masked params
163
+ masked_query = urlencode(masked_params, doseq=True)
164
+ return urlunparse((
165
+ parsed.scheme,
166
+ parsed.netloc,
167
+ parsed.path,
168
+ parsed.params,
169
+ masked_query,
170
+ parsed.fragment,
171
+ ))
172
+ except Exception:
173
+ # If URL parsing fails, just return original
174
+ return url
175
+
176
+
177
+ def safe_repr(obj: Any) -> str:
178
+ """
179
+ Get a safe string representation of any object, masking secrets.
180
+
181
+ Args:
182
+ obj: Object to represent
183
+
184
+ Returns:
185
+ Safe string representation
186
+ """
187
+ if isinstance(obj, dict):
188
+ return str(mask_dict(obj))
189
+ elif isinstance(obj, str):
190
+ return mask_string(obj)
191
+ elif isinstance(obj, Exception):
192
+ return mask_error(obj)
193
+ else:
194
+ return mask_string(repr(obj))
yuho/config/schema.py ADDED
@@ -0,0 +1,147 @@
1
+ """
2
+ Configuration schema definitions.
3
+ """
4
+
5
+ from dataclasses import dataclass, field
6
+ from typing import Optional, List, Dict, Any
7
+ from pathlib import Path
8
+
9
+
10
+ @dataclass
11
+ class LLMSection:
12
+ """[llm] configuration section."""
13
+
14
+ provider: str = "ollama"
15
+ model: str = "llama3"
16
+ ollama_host: str = "localhost"
17
+ ollama_port: int = 11434
18
+ huggingface_cache: Optional[str] = None
19
+ openai_api_key: Optional[str] = None
20
+ anthropic_api_key: Optional[str] = None
21
+ max_tokens: int = 2048
22
+ temperature: float = 0.7
23
+ fallback_providers: List[str] = field(default_factory=lambda: ["huggingface"])
24
+
25
+ def to_llm_config(self):
26
+ """Convert to LLMConfig."""
27
+ from yuho.llm import LLMConfig
28
+ return LLMConfig(
29
+ provider=self.provider,
30
+ model_name=self.model,
31
+ ollama_host=self.ollama_host,
32
+ ollama_port=self.ollama_port,
33
+ huggingface_cache=self.huggingface_cache,
34
+ api_key=self.openai_api_key or self.anthropic_api_key,
35
+ max_tokens=self.max_tokens,
36
+ temperature=self.temperature,
37
+ fallback_providers=self.fallback_providers,
38
+ )
39
+
40
+
41
+ @dataclass
42
+ class TranspileSection:
43
+ """[transpile] configuration section."""
44
+
45
+ default_target: str = "json"
46
+ latex_compiler: str = "pdflatex"
47
+ output_dir: Optional[str] = None
48
+ include_source_locations: bool = True
49
+
50
+
51
+ @dataclass
52
+ class LSPSection:
53
+ """[lsp] configuration section."""
54
+
55
+ diagnostic_severity_error: bool = True
56
+ diagnostic_severity_warning: bool = True
57
+ diagnostic_severity_info: bool = True
58
+ diagnostic_severity_hint: bool = True
59
+ completion_trigger_chars: List[str] = field(default_factory=lambda: [".", ":"])
60
+
61
+
62
+ @dataclass
63
+ class MCPSection:
64
+ """[mcp] configuration section."""
65
+
66
+ host: str = "127.0.0.1"
67
+ port: int = 8080
68
+ allowed_origins: List[str] = field(default_factory=lambda: ["*"])
69
+ auth_token: Optional[str] = None
70
+
71
+
72
+ @dataclass
73
+ class LibrarySection:
74
+ """[library] configuration section."""
75
+
76
+ registry_url: str = "https://registry.yuho.dev"
77
+ registry_api_version: str = "v1"
78
+ auth_token: Optional[str] = None
79
+ timeout: int = 30
80
+ verify_ssl: bool = True
81
+
82
+
83
+ @dataclass
84
+ class ConfigSchema:
85
+ """Complete configuration schema."""
86
+
87
+ llm: LLMSection = field(default_factory=LLMSection)
88
+ transpile: TranspileSection = field(default_factory=TranspileSection)
89
+ lsp: LSPSection = field(default_factory=LSPSection)
90
+ mcp: MCPSection = field(default_factory=MCPSection)
91
+ library: LibrarySection = field(default_factory=LibrarySection)
92
+
93
+ @classmethod
94
+ def from_dict(cls, data: Dict[str, Any]) -> "ConfigSchema":
95
+ """Create config from dictionary."""
96
+ llm_data = data.get("llm", {})
97
+ transpile_data = data.get("transpile", {})
98
+ lsp_data = data.get("lsp", {})
99
+ mcp_data = data.get("mcp", {})
100
+ library_data = data.get("library", {})
101
+
102
+ return cls(
103
+ llm=LLMSection(**{k: v for k, v in llm_data.items() if k in LLMSection.__dataclass_fields__}),
104
+ transpile=TranspileSection(**{k: v for k, v in transpile_data.items() if k in TranspileSection.__dataclass_fields__}),
105
+ lsp=LSPSection(**{k: v for k, v in lsp_data.items() if k in LSPSection.__dataclass_fields__}),
106
+ mcp=MCPSection(**{k: v for k, v in mcp_data.items() if k in MCPSection.__dataclass_fields__}),
107
+ library=LibrarySection(**{k: v for k, v in library_data.items() if k in LibrarySection.__dataclass_fields__}),
108
+ )
109
+
110
+ def to_dict(self) -> Dict[str, Any]:
111
+ """Convert to dictionary."""
112
+ return {
113
+ "llm": {
114
+ "provider": self.llm.provider,
115
+ "model": self.llm.model,
116
+ "ollama_host": self.llm.ollama_host,
117
+ "ollama_port": self.llm.ollama_port,
118
+ "huggingface_cache": self.llm.huggingface_cache,
119
+ "max_tokens": self.llm.max_tokens,
120
+ "temperature": self.llm.temperature,
121
+ "fallback_providers": self.llm.fallback_providers,
122
+ },
123
+ "transpile": {
124
+ "default_target": self.transpile.default_target,
125
+ "latex_compiler": self.transpile.latex_compiler,
126
+ "output_dir": self.transpile.output_dir,
127
+ "include_source_locations": self.transpile.include_source_locations,
128
+ },
129
+ "lsp": {
130
+ "diagnostic_severity_error": self.lsp.diagnostic_severity_error,
131
+ "diagnostic_severity_warning": self.lsp.diagnostic_severity_warning,
132
+ "diagnostic_severity_info": self.lsp.diagnostic_severity_info,
133
+ "diagnostic_severity_hint": self.lsp.diagnostic_severity_hint,
134
+ "completion_trigger_chars": self.lsp.completion_trigger_chars,
135
+ },
136
+ "mcp": {
137
+ "host": self.mcp.host,
138
+ "port": self.mcp.port,
139
+ "allowed_origins": self.mcp.allowed_origins,
140
+ },
141
+ "library": {
142
+ "registry_url": self.library.registry_url,
143
+ "registry_api_version": self.library.registry_api_version,
144
+ "timeout": self.library.timeout,
145
+ "verify_ssl": self.library.verify_ssl,
146
+ },
147
+ }
@@ -0,0 +1,84 @@
1
+ """
2
+ Yuho library module - user-contributed statute repository.
3
+
4
+ Provides:
5
+ - Package format definitions (.yhpkg)
6
+ - Contribution validation
7
+ - Library indexing and search
8
+ - Package installation and management
9
+ - Dependency resolution with version constraints
10
+ """
11
+
12
+ from yuho.library.package import (
13
+ Package,
14
+ PackageMetadata,
15
+ PackageValidator,
16
+ DeprecationInfo,
17
+ )
18
+ from yuho.library.index import (
19
+ LibraryIndex,
20
+ search_library,
21
+ )
22
+ from yuho.library.install import (
23
+ install_package,
24
+ uninstall_package,
25
+ list_installed,
26
+ update_package,
27
+ check_updates,
28
+ download_package,
29
+ update_all_packages,
30
+ publish_package,
31
+ )
32
+ from yuho.library.resolver import (
33
+ DependencyResolver,
34
+ Resolution,
35
+ Dependency,
36
+ Version,
37
+ VersionConstraint,
38
+ resolve_dependencies,
39
+ validate_semver,
40
+ SemverValidation,
41
+ SemverValidationError,
42
+ CompatibilityChecker,
43
+ CompatibilityResult,
44
+ )
45
+ from yuho.library.lockfile import (
46
+ LockFile,
47
+ LockFileManager,
48
+ LockedPackage,
49
+ load_lock_file,
50
+ create_lock_file,
51
+ )
52
+
53
+ __all__ = [
54
+ "Package",
55
+ "PackageMetadata",
56
+ "PackageValidator",
57
+ "DeprecationInfo",
58
+ "LibraryIndex",
59
+ "search_library",
60
+ "install_package",
61
+ "uninstall_package",
62
+ "list_installed",
63
+ "update_package",
64
+ "check_updates",
65
+ "download_package",
66
+ "update_all_packages",
67
+ "publish_package",
68
+ "DependencyResolver",
69
+ "Resolution",
70
+ "Dependency",
71
+ "Version",
72
+ "VersionConstraint",
73
+ "resolve_dependencies",
74
+ "validate_semver",
75
+ "SemverValidation",
76
+ "SemverValidationError",
77
+ "CompatibilityChecker",
78
+ "CompatibilityResult",
79
+ "LockFile",
80
+ "LockFileManager",
81
+ "LockedPackage",
82
+ "load_lock_file",
83
+ "create_lock_file",
84
+ ]