vibesop 4.2.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 (170) hide show
  1. vibesop/__init__.py +33 -0
  2. vibesop/_version.py +29 -0
  3. vibesop/adapters/__init__.py +51 -0
  4. vibesop/adapters/base.py +327 -0
  5. vibesop/adapters/claude_code.py +526 -0
  6. vibesop/adapters/kimi_cli.py +588 -0
  7. vibesop/adapters/models.py +354 -0
  8. vibesop/adapters/opencode.py +569 -0
  9. vibesop/adapters/protocol.py +158 -0
  10. vibesop/adapters/templates/claude-code/CLAUDE.md.j2 +164 -0
  11. vibesop/adapters/templates/claude-code/docs/safety.md.j2 +177 -0
  12. vibesop/adapters/templates/claude-code/docs/skills.md.j2 +151 -0
  13. vibesop/adapters/templates/claude-code/docs/task-routing.md.j2 +247 -0
  14. vibesop/adapters/templates/claude-code/rules/behaviors.md.j2 +112 -0
  15. vibesop/adapters/templates/claude-code/rules/memory-flush.md.j2 +55 -0
  16. vibesop/adapters/templates/claude-code/rules/routing.md.j2 +136 -0
  17. vibesop/adapters/templates/claude-code/rules/skill-triggers.md.j2 +133 -0
  18. vibesop/adapters/templates/claude-code/skills/SKILL.md.j2 +76 -0
  19. vibesop/builder/__init__.py +51 -0
  20. vibesop/builder/doc_generators.py +194 -0
  21. vibesop/builder/doc_models.py +53 -0
  22. vibesop/builder/doc_renderer.py +349 -0
  23. vibesop/builder/doc_templates.py +250 -0
  24. vibesop/builder/dynamic_renderer.py +366 -0
  25. vibesop/builder/manifest.py +493 -0
  26. vibesop/builder/overlay.py +285 -0
  27. vibesop/builder/renderer.py +354 -0
  28. vibesop/cli/__init__.py +19 -0
  29. vibesop/cli/commands/__init__.py +100 -0
  30. vibesop/cli/commands/algorithms.py +33 -0
  31. vibesop/cli/commands/analyze.py +432 -0
  32. vibesop/cli/commands/build.py +250 -0
  33. vibesop/cli/commands/config.py +57 -0
  34. vibesop/cli/commands/execute_cmd.py +195 -0
  35. vibesop/cli/commands/feedback_cmd.py +187 -0
  36. vibesop/cli/commands/import_rules.py +162 -0
  37. vibesop/cli/commands/init.py +330 -0
  38. vibesop/cli/commands/inspect.py +173 -0
  39. vibesop/cli/commands/install.py +245 -0
  40. vibesop/cli/commands/onboard.py +125 -0
  41. vibesop/cli/commands/quickstart.py +109 -0
  42. vibesop/cli/commands/session_cmd.py +247 -0
  43. vibesop/cli/commands/skill_add.py +670 -0
  44. vibesop/cli/commands/skill_config.py +414 -0
  45. vibesop/cli/commands/skill_craft.py +325 -0
  46. vibesop/cli/commands/skills.py +64 -0
  47. vibesop/cli/commands/skills_cmd.py +590 -0
  48. vibesop/cli/commands/switch.py +185 -0
  49. vibesop/cli/commands/targets.py +212 -0
  50. vibesop/cli/commands/tools_cmd.py +222 -0
  51. vibesop/cli/interactive.py +330 -0
  52. vibesop/cli/main.py +375 -0
  53. vibesop/cli/subcommands/__init__.py +133 -0
  54. vibesop/constants.py +165 -0
  55. vibesop/core/ai_enhancer.py +327 -0
  56. vibesop/core/algorithms/__init__.py +26 -0
  57. vibesop/core/algorithms/interview/__init__.py +15 -0
  58. vibesop/core/algorithms/interview/ambiguity.py +103 -0
  59. vibesop/core/algorithms/ralph/__init__.py +17 -0
  60. vibesop/core/algorithms/ralph/deslop.py +96 -0
  61. vibesop/core/algorithms/registry.py +63 -0
  62. vibesop/core/checkpoint/__init__.py +26 -0
  63. vibesop/core/checkpoint/base.py +137 -0
  64. vibesop/core/checkpoint/manager.py +346 -0
  65. vibesop/core/checkpoint/storage.py +280 -0
  66. vibesop/core/config/__init__.py +47 -0
  67. vibesop/core/config/manager.py +714 -0
  68. vibesop/core/config/optimization_config.py +92 -0
  69. vibesop/core/exceptions.py +190 -0
  70. vibesop/core/feedback.py +369 -0
  71. vibesop/core/instinct/__init__.py +15 -0
  72. vibesop/core/instinct/learner.py +488 -0
  73. vibesop/core/llm_config.py +435 -0
  74. vibesop/core/matching/__init__.py +64 -0
  75. vibesop/core/matching/base.py +226 -0
  76. vibesop/core/matching/similarity.py +342 -0
  77. vibesop/core/matching/strategies.py +700 -0
  78. vibesop/core/matching/tfidf.py +351 -0
  79. vibesop/core/matching/tokenizers.py +236 -0
  80. vibesop/core/memory/__init__.py +28 -0
  81. vibesop/core/memory/base.py +184 -0
  82. vibesop/core/memory/manager.py +425 -0
  83. vibesop/core/memory/storage.py +126 -0
  84. vibesop/core/models.py +221 -0
  85. vibesop/core/optimization/__init__.py +19 -0
  86. vibesop/core/optimization/clustering.py +206 -0
  87. vibesop/core/optimization/cold_start.py +259 -0
  88. vibesop/core/optimization/preference_boost.py +118 -0
  89. vibesop/core/optimization/prefilter.py +312 -0
  90. vibesop/core/preference.py +354 -0
  91. vibesop/core/routing/__init__.py +50 -0
  92. vibesop/core/routing/cache.py +205 -0
  93. vibesop/core/routing/circuit_breaker.py +149 -0
  94. vibesop/core/routing/conflict.py +447 -0
  95. vibesop/core/routing/explicit_layer.py +60 -0
  96. vibesop/core/routing/layers.py +63 -0
  97. vibesop/core/routing/matcher_pipeline.py +132 -0
  98. vibesop/core/routing/optimization_service.py +156 -0
  99. vibesop/core/routing/project_config.py +270 -0
  100. vibesop/core/routing/scenario_layer.py +109 -0
  101. vibesop/core/routing/stats_mixin.py +64 -0
  102. vibesop/core/routing/triage_service.py +302 -0
  103. vibesop/core/routing/unified.py +694 -0
  104. vibesop/core/session_analyzer.py +525 -0
  105. vibesop/core/sessions/__init__.py +55 -0
  106. vibesop/core/sessions/context.py +426 -0
  107. vibesop/core/sessions/tracker.py +390 -0
  108. vibesop/core/skills/__init__.py +92 -0
  109. vibesop/core/skills/base.py +299 -0
  110. vibesop/core/skills/config_manager.py +360 -0
  111. vibesop/core/skills/executor.py +439 -0
  112. vibesop/core/skills/external_loader.py +427 -0
  113. vibesop/core/skills/format_converter.py +300 -0
  114. vibesop/core/skills/loader.py +477 -0
  115. vibesop/core/skills/manager.py +390 -0
  116. vibesop/core/skills/parser.py +270 -0
  117. vibesop/core/skills/registry_sync.py +127 -0
  118. vibesop/core/skills/storage.py +594 -0
  119. vibesop/core/skills/understander.py +732 -0
  120. vibesop/core/skills/workflow.py +1141 -0
  121. vibesop/core/state/__init__.py +23 -0
  122. vibesop/core/state/manager.py +99 -0
  123. vibesop/core/state/migration.py +85 -0
  124. vibesop/core/state/schema.py +69 -0
  125. vibesop/core/types.py +74 -0
  126. vibesop/hooks/__init__.py +46 -0
  127. vibesop/hooks/base.py +265 -0
  128. vibesop/hooks/installer.py +395 -0
  129. vibesop/hooks/points.py +122 -0
  130. vibesop/hooks/templates/post-session-start.sh.j2 +30 -0
  131. vibesop/hooks/templates/pre-session-end.sh.j2 +49 -0
  132. vibesop/hooks/templates/pre-tool-use.sh.j2 +75 -0
  133. vibesop/installer/__init__.py +22 -0
  134. vibesop/installer/analyzer.py +164 -0
  135. vibesop/installer/init_support.py +252 -0
  136. vibesop/installer/installer.py +368 -0
  137. vibesop/installer/pack_installer.py +138 -0
  138. vibesop/installer/planner.py +95 -0
  139. vibesop/installer/quickstart_runner.py +408 -0
  140. vibesop/installer/skill_installer.py +407 -0
  141. vibesop/installer/transactional.py +411 -0
  142. vibesop/integrations/__init__.py +43 -0
  143. vibesop/integrations/detector.py +287 -0
  144. vibesop/integrations/health_monitor.py +274 -0
  145. vibesop/integrations/manager.py +257 -0
  146. vibesop/integrations/recommender.py +421 -0
  147. vibesop/integrations/verifier.py +468 -0
  148. vibesop/llm/__init__.py +26 -0
  149. vibesop/llm/anthropic.py +183 -0
  150. vibesop/llm/base.py +186 -0
  151. vibesop/llm/cost_tracker.py +201 -0
  152. vibesop/llm/factory.py +113 -0
  153. vibesop/llm/openai.py +195 -0
  154. vibesop/llm/triage_prompts.py +104 -0
  155. vibesop/security/__init__.py +72 -0
  156. vibesop/security/exceptions.py +104 -0
  157. vibesop/security/path_safety.py +278 -0
  158. vibesop/security/rules.py +273 -0
  159. vibesop/security/scanner.py +369 -0
  160. vibesop/security/skill_auditor.py +517 -0
  161. vibesop/utils/__init__.py +70 -0
  162. vibesop/utils/atomic_writer.py +236 -0
  163. vibesop/utils/external_tools.py +422 -0
  164. vibesop/utils/helpers.py +224 -0
  165. vibesop/utils/marker_files.py +512 -0
  166. vibesop-4.2.0.dist-info/METADATA +907 -0
  167. vibesop-4.2.0.dist-info/RECORD +170 -0
  168. vibesop-4.2.0.dist-info/WHEEL +4 -0
  169. vibesop-4.2.0.dist-info/entry_points.txt +2 -0
  170. vibesop-4.2.0.dist-info/licenses/LICENSE +21 -0
vibesop/__init__.py ADDED
@@ -0,0 +1,33 @@
1
+ """VibeSOP - Modern Python Edition.
2
+
3
+ A battle-tested, multi-platform workflow SOP for AI-assisted development.
4
+
5
+ This project is a complete rewrite of the Ruby version, leveraging:
6
+ - Python 3.12+ type system
7
+ - Pydantic v2 for runtime validation
8
+ - Modern async/await patterns
9
+ - Type-safe LLM clients
10
+ """
11
+
12
+ from vibesop._version import __version__
13
+
14
+ __author__ = "nehcuh"
15
+ __license__ = "MIT"
16
+
17
+ # Core public API
18
+ from vibesop.core.models import (
19
+ RoutingLayer,
20
+ RoutingRequest,
21
+ RoutingResult,
22
+ SkillRegistry,
23
+ SkillRoute,
24
+ )
25
+
26
+ __all__ = [
27
+ "RoutingLayer",
28
+ "RoutingRequest",
29
+ "RoutingResult",
30
+ "SkillRegistry",
31
+ "SkillRoute",
32
+ "__version__",
33
+ ]
vibesop/_version.py ADDED
@@ -0,0 +1,29 @@
1
+ """VibeSOP version information.
2
+
3
+ This file contains the version information for VibeSOP.
4
+ It is the single source of truth for the version number.
5
+
6
+ Version Format: MAJOR.MINOR.PATCH
7
+ - MAJOR: Incompatible API changes
8
+ - MINOR: Backwards-compatible functionality additions
9
+ - PATCH: Backwards-compatible bug fixes
10
+ """
11
+
12
+ __version__ = "4.2.0"
13
+
14
+ # Version components
15
+ MAJOR = 4
16
+ MINOR = 2
17
+ PATCH = 0
18
+
19
+ # Version metadata
20
+ VERSION_SUFFIX = "" # e.g., "a1", "b1", "rc1", "" for stable
21
+ DEV_VERSION = False # Set to True for development versions
22
+
23
+ # Full version string with optional suffix
24
+ if VERSION_SUFFIX:
25
+ __version__ = f"{MAJOR}.{MINOR}.{PATCH}{VERSION_SUFFIX}"
26
+ elif DEV_VERSION:
27
+ __version__ = f"{MAJOR}.{MINOR}.{PATCH}.dev"
28
+ else:
29
+ __version__ = f"{MAJOR}.{MINOR}.{PATCH}"
@@ -0,0 +1,51 @@
1
+ """Platform adapters for generating configuration files.
2
+
3
+ This module provides adapters for different AI coding assistant platforms,
4
+ enabling generation of platform-specific configuration from a unified manifest.
5
+
6
+ Available Adapters:
7
+ - Claude Code
8
+ - Kimi Code CLI
9
+ - OpenCode
10
+
11
+ Example:
12
+ >>> from vibesop.adapters import ClaudeCodeAdapter, Manifest
13
+ >>>
14
+ >>> manifest = Manifest(...)
15
+ >>> adapter = ClaudeCodeAdapter()
16
+ >>> result = adapter.render_config(manifest, Path("~/.claude"))
17
+ >>> print(f"Created {result.file_count} files")
18
+ """
19
+
20
+ from vibesop.adapters.base import PlatformAdapter
21
+ from vibesop.adapters.claude_code import ClaudeCodeAdapter
22
+ from vibesop.adapters.kimi_cli import KimiCliAdapter
23
+ from vibesop.adapters.models import (
24
+ Manifest,
25
+ ManifestMetadata,
26
+ PolicySet,
27
+ RenderResult,
28
+ RoutingConfig,
29
+ SecurityPolicy,
30
+ )
31
+ from vibesop.adapters.opencode import OpenCodeAdapter
32
+ from vibesop.adapters.protocol import AdapterProtocol
33
+
34
+ __all__ = [
35
+ "AdapterProtocol",
36
+ # Adapters
37
+ "ClaudeCodeAdapter",
38
+ "KimiCliAdapter",
39
+ # Models
40
+ "Manifest",
41
+ "ManifestMetadata",
42
+ "OpenCodeAdapter",
43
+ # Base classes
44
+ "PlatformAdapter",
45
+ "PolicySet",
46
+ "RenderResult",
47
+ "RoutingConfig",
48
+ "SecurityPolicy",
49
+ ]
50
+
51
+ from vibesop._version import __version__
@@ -0,0 +1,327 @@
1
+ """Base class for platform adapters.
2
+
3
+ This module provides the abstract base class that all platform
4
+ adapters must inherit from, along with shared utility methods.
5
+ """
6
+
7
+ from abc import ABC, abstractmethod
8
+ from pathlib import Path
9
+ from typing import Any
10
+
11
+ from vibesop.adapters.models import Manifest, RenderResult
12
+ from vibesop.security import PathSafety, SecurityScanner
13
+
14
+
15
+ class PlatformAdapter(ABC):
16
+ """Abstract base class for platform adapters.
17
+
18
+ Provides a common interface and shared utilities for all
19
+ platform-specific adapters.
20
+
21
+ Example:
22
+ class ClaudeCodeAdapter(PlatformAdapter):
23
+ @property
24
+ def platform_name(self) -> str:
25
+ return "claude-code"
26
+
27
+ @property
28
+ def config_dir(self) -> Path:
29
+ return Path("~/.claude").expanduser()
30
+
31
+ def render_config(self, manifest: Manifest, output_dir: Path) -> RenderResult:
32
+ # Implementation
33
+ ...
34
+
35
+ def get_settings_schema(self) -> dict:
36
+ # Implementation
37
+ ...
38
+ """
39
+
40
+ # Safety validators
41
+ _path_safety: PathSafety
42
+ _security_scanner: SecurityScanner
43
+
44
+ def __init__(self) -> None:
45
+ """Initialize the platform adapter."""
46
+ self._path_safety = PathSafety()
47
+ self._security_scanner = SecurityScanner()
48
+
49
+ @property
50
+ @abstractmethod
51
+ def platform_name(self) -> str:
52
+ """Platform identifier.
53
+
54
+ Returns:
55
+ Unique platform name (e.g., 'claude-code', 'kimi-cli', 'opencode')
56
+ """
57
+ ...
58
+
59
+ @property
60
+ @abstractmethod
61
+ def config_dir(self) -> Path:
62
+ """Default configuration directory for this platform.
63
+
64
+ Returns:
65
+ Path to the default config directory (e.g., ~/.claude)
66
+ """
67
+ ...
68
+
69
+ @abstractmethod
70
+ def render_config(self, manifest: Manifest, output_dir: Path) -> RenderResult:
71
+ """Render platform configuration from manifest.
72
+
73
+ This method generates all necessary configuration files
74
+ for the target platform based on the provided manifest.
75
+
76
+ Args:
77
+ manifest: Configuration manifest
78
+ output_dir: Directory to write configuration files
79
+
80
+ Returns:
81
+ RenderResult with list of created files and any warnings/errors
82
+ """
83
+ ...
84
+
85
+ @abstractmethod
86
+ def get_settings_schema(self) -> dict[str, Any]:
87
+ """Get the settings schema for this platform.
88
+
89
+ Returns a JSON schema describing the structure of the
90
+ platform's settings file (e.g., settings.json).
91
+
92
+ Returns:
93
+ JSON schema as a dictionary
94
+ """
95
+ ...
96
+
97
+ def install_hooks(self, _config_dir: Path) -> dict[str, bool]:
98
+ """Install platform-specific hooks.
99
+
100
+ Default implementation does nothing. Override this method
101
+ if your platform supports hooks.
102
+
103
+ Args:
104
+ config_dir: Configuration directory
105
+
106
+ Returns:
107
+ Dictionary mapping hook names to installation status
108
+ """
109
+ return {}
110
+
111
+ # Utility methods
112
+
113
+ def validate_manifest(self, manifest: Manifest) -> list[str]:
114
+ """Validate a manifest before rendering.
115
+
116
+ Performs basic validation checks on the manifest to ensure
117
+ it's ready for rendering.
118
+
119
+ Args:
120
+ manifest: Manifest to validate
121
+
122
+ Returns:
123
+ List of validation errors (empty if valid)
124
+ """
125
+ errors = []
126
+
127
+ # Check metadata
128
+ if not manifest.metadata:
129
+ errors.append("Manifest metadata is required")
130
+
131
+ # Check platform compatibility
132
+ if manifest.metadata.platform != self.platform_name:
133
+ errors.append(
134
+ f"Manifest platform '{manifest.metadata.platform}' "
135
+ f"does not match adapter platform '{self.platform_name}'"
136
+ )
137
+
138
+ # Check security policy
139
+ security_policy = manifest.get_effective_security_policy()
140
+ if security_policy.allow_path_traversal:
141
+ errors.append("Security policy must not allow path traversal")
142
+
143
+ return errors
144
+
145
+ def ensure_output_dir(self, output_dir: Path) -> Path:
146
+ """Ensure output directory exists and is safe.
147
+
148
+ Creates the output directory if it doesn't exist,
149
+ after validating it's safe to write to.
150
+
151
+ Args:
152
+ output_dir: Desired output directory
153
+
154
+ Returns:
155
+ Path to the validated output directory
156
+
157
+ Raises:
158
+ ValueError: If output directory is unsafe
159
+ """
160
+ output_dir = Path(output_dir).expanduser().resolve()
161
+
162
+ # Ensure it's safe
163
+ self._path_safety.ensure_safe_output_path(
164
+ output_dir / "dummy.txt",
165
+ output_dir.parent,
166
+ create_parents=True,
167
+ )
168
+
169
+ # Create if needed
170
+ output_dir.mkdir(parents=True, exist_ok=True)
171
+
172
+ return output_dir
173
+
174
+ def write_file_atomic(
175
+ self,
176
+ path: Path,
177
+ content: str,
178
+ validate_security: bool = True,
179
+ base_dir: Path | None = None,
180
+ ) -> None:
181
+ """Write content to file atomically.
182
+
183
+ Writes to a temporary file first, then renames to ensure
184
+ atomic operation and prevent corruption.
185
+
186
+ Args:
187
+ path: Path to write to
188
+ content: Content to write
189
+ validate_security: Whether to scan content for threats
190
+ base_dir: Base directory for path safety validation
191
+
192
+ Raises:
193
+ ValueError: If path is unsafe or content contains threats
194
+ IOError: If write operation fails
195
+ """
196
+ path = Path(path).expanduser().resolve()
197
+
198
+ # Determine base directory for safety check
199
+ base_dir = path.parent if base_dir is None else Path(base_dir).expanduser().resolve()
200
+
201
+ # Create parent directories if needed
202
+ path.parent.mkdir(parents=True, exist_ok=True)
203
+
204
+ # Validate path safety (now that parent exists)
205
+ self._path_safety.ensure_safe_output_path(
206
+ path,
207
+ base_dir,
208
+ )
209
+
210
+ # Validate content security if enabled
211
+ if validate_security and self._security_scanner:
212
+ scan_result = self._security_scanner.scan(content)
213
+ if not scan_result.safe:
214
+ msg = f"Content contains security threats: {scan_result.summary}"
215
+ raise ValueError(msg)
216
+
217
+ # Write to temporary file
218
+ tmp_path = path.with_suffix(".tmp")
219
+ try:
220
+ tmp_path.write_text(content, encoding="utf-8")
221
+ # Atomic rename
222
+ tmp_path.replace(path)
223
+ finally:
224
+ # Clean up temp file if it still exists
225
+ tmp_path.unlink(missing_ok=True)
226
+
227
+ def render_template_string(
228
+ self,
229
+ template_string: str,
230
+ context: dict[str, Any],
231
+ ) -> str:
232
+ """Render a template string with context.
233
+
234
+ Simple template rendering without external dependencies.
235
+ Supports {variable} substitution.
236
+
237
+ Args:
238
+ template_string: Template string
239
+ context: Template variables
240
+
241
+ Returns:
242
+ Rendered string
243
+ """
244
+ try:
245
+ return template_string.format(**context)
246
+ except KeyError as e:
247
+ msg = f"Missing template variable: {e}"
248
+ raise ValueError(msg) from e
249
+
250
+ def get_template_context(self, manifest: Manifest) -> dict[str, Any]:
251
+ """Get standard template context from manifest.
252
+
253
+ Extracts common variables that all templates might need.
254
+
255
+ Args:
256
+ manifest: Source manifest
257
+
258
+ Returns:
259
+ Template context dictionary
260
+ """
261
+ return {
262
+ "manifest": manifest,
263
+ "skills": manifest.skills,
264
+ "policies": manifest.policies,
265
+ "security": manifest.get_effective_security_policy(),
266
+ "routing": manifest.get_effective_routing_config(),
267
+ "metadata": manifest.metadata,
268
+ "platform": self.platform_name,
269
+ "version": manifest.metadata.version,
270
+ }
271
+
272
+ def create_render_result(
273
+ self,
274
+ success: bool,
275
+ files_created: list[Path] | None = None,
276
+ warnings: list[str] | None = None,
277
+ errors: list[str] | None = None,
278
+ ) -> RenderResult:
279
+ """Create a RenderResult object.
280
+
281
+ Helper method for creating standardized render results.
282
+
283
+ Args:
284
+ success: Whether rendering was successful
285
+ files_created: List of files created
286
+ warnings: List of warnings
287
+ errors: List of errors
288
+
289
+ Returns:
290
+ RenderResult object
291
+ """
292
+ return RenderResult(
293
+ success=success,
294
+ files_created=files_created or [],
295
+ warnings=warnings or [],
296
+ errors=errors or [],
297
+ )
298
+
299
+ def scan_for_threats(self, text: str) -> list[str]:
300
+ """Scan text for security threats.
301
+
302
+ Args:
303
+ text: Text to scan
304
+
305
+ Returns:
306
+ List of threat descriptions (empty if safe)
307
+ """
308
+ if not self._security_scanner:
309
+ return []
310
+
311
+ result = self._security_scanner.scan(text)
312
+ if result.safe:
313
+ return []
314
+
315
+ return [f"{t.type.value}: {t.description}" for t in result.threats]
316
+
317
+ def is_safe_path(self, path: Path, base_dir: Path) -> bool:
318
+ """Check if a path is safe (no traversal).
319
+
320
+ Args:
321
+ path: Path to check
322
+ base_dir: Base directory
323
+
324
+ Returns:
325
+ True if path is safe, False otherwise
326
+ """
327
+ return self._path_safety.check_traversal(path, base_dir)