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.
- vibesop/__init__.py +33 -0
- vibesop/_version.py +29 -0
- vibesop/adapters/__init__.py +51 -0
- vibesop/adapters/base.py +327 -0
- vibesop/adapters/claude_code.py +526 -0
- vibesop/adapters/kimi_cli.py +588 -0
- vibesop/adapters/models.py +354 -0
- vibesop/adapters/opencode.py +569 -0
- vibesop/adapters/protocol.py +158 -0
- vibesop/adapters/templates/claude-code/CLAUDE.md.j2 +164 -0
- vibesop/adapters/templates/claude-code/docs/safety.md.j2 +177 -0
- vibesop/adapters/templates/claude-code/docs/skills.md.j2 +151 -0
- vibesop/adapters/templates/claude-code/docs/task-routing.md.j2 +247 -0
- vibesop/adapters/templates/claude-code/rules/behaviors.md.j2 +112 -0
- vibesop/adapters/templates/claude-code/rules/memory-flush.md.j2 +55 -0
- vibesop/adapters/templates/claude-code/rules/routing.md.j2 +136 -0
- vibesop/adapters/templates/claude-code/rules/skill-triggers.md.j2 +133 -0
- vibesop/adapters/templates/claude-code/skills/SKILL.md.j2 +76 -0
- vibesop/builder/__init__.py +51 -0
- vibesop/builder/doc_generators.py +194 -0
- vibesop/builder/doc_models.py +53 -0
- vibesop/builder/doc_renderer.py +349 -0
- vibesop/builder/doc_templates.py +250 -0
- vibesop/builder/dynamic_renderer.py +366 -0
- vibesop/builder/manifest.py +493 -0
- vibesop/builder/overlay.py +285 -0
- vibesop/builder/renderer.py +354 -0
- vibesop/cli/__init__.py +19 -0
- vibesop/cli/commands/__init__.py +100 -0
- vibesop/cli/commands/algorithms.py +33 -0
- vibesop/cli/commands/analyze.py +432 -0
- vibesop/cli/commands/build.py +250 -0
- vibesop/cli/commands/config.py +57 -0
- vibesop/cli/commands/execute_cmd.py +195 -0
- vibesop/cli/commands/feedback_cmd.py +187 -0
- vibesop/cli/commands/import_rules.py +162 -0
- vibesop/cli/commands/init.py +330 -0
- vibesop/cli/commands/inspect.py +173 -0
- vibesop/cli/commands/install.py +245 -0
- vibesop/cli/commands/onboard.py +125 -0
- vibesop/cli/commands/quickstart.py +109 -0
- vibesop/cli/commands/session_cmd.py +247 -0
- vibesop/cli/commands/skill_add.py +670 -0
- vibesop/cli/commands/skill_config.py +414 -0
- vibesop/cli/commands/skill_craft.py +325 -0
- vibesop/cli/commands/skills.py +64 -0
- vibesop/cli/commands/skills_cmd.py +590 -0
- vibesop/cli/commands/switch.py +185 -0
- vibesop/cli/commands/targets.py +212 -0
- vibesop/cli/commands/tools_cmd.py +222 -0
- vibesop/cli/interactive.py +330 -0
- vibesop/cli/main.py +375 -0
- vibesop/cli/subcommands/__init__.py +133 -0
- vibesop/constants.py +165 -0
- vibesop/core/ai_enhancer.py +327 -0
- vibesop/core/algorithms/__init__.py +26 -0
- vibesop/core/algorithms/interview/__init__.py +15 -0
- vibesop/core/algorithms/interview/ambiguity.py +103 -0
- vibesop/core/algorithms/ralph/__init__.py +17 -0
- vibesop/core/algorithms/ralph/deslop.py +96 -0
- vibesop/core/algorithms/registry.py +63 -0
- vibesop/core/checkpoint/__init__.py +26 -0
- vibesop/core/checkpoint/base.py +137 -0
- vibesop/core/checkpoint/manager.py +346 -0
- vibesop/core/checkpoint/storage.py +280 -0
- vibesop/core/config/__init__.py +47 -0
- vibesop/core/config/manager.py +714 -0
- vibesop/core/config/optimization_config.py +92 -0
- vibesop/core/exceptions.py +190 -0
- vibesop/core/feedback.py +369 -0
- vibesop/core/instinct/__init__.py +15 -0
- vibesop/core/instinct/learner.py +488 -0
- vibesop/core/llm_config.py +435 -0
- vibesop/core/matching/__init__.py +64 -0
- vibesop/core/matching/base.py +226 -0
- vibesop/core/matching/similarity.py +342 -0
- vibesop/core/matching/strategies.py +700 -0
- vibesop/core/matching/tfidf.py +351 -0
- vibesop/core/matching/tokenizers.py +236 -0
- vibesop/core/memory/__init__.py +28 -0
- vibesop/core/memory/base.py +184 -0
- vibesop/core/memory/manager.py +425 -0
- vibesop/core/memory/storage.py +126 -0
- vibesop/core/models.py +221 -0
- vibesop/core/optimization/__init__.py +19 -0
- vibesop/core/optimization/clustering.py +206 -0
- vibesop/core/optimization/cold_start.py +259 -0
- vibesop/core/optimization/preference_boost.py +118 -0
- vibesop/core/optimization/prefilter.py +312 -0
- vibesop/core/preference.py +354 -0
- vibesop/core/routing/__init__.py +50 -0
- vibesop/core/routing/cache.py +205 -0
- vibesop/core/routing/circuit_breaker.py +149 -0
- vibesop/core/routing/conflict.py +447 -0
- vibesop/core/routing/explicit_layer.py +60 -0
- vibesop/core/routing/layers.py +63 -0
- vibesop/core/routing/matcher_pipeline.py +132 -0
- vibesop/core/routing/optimization_service.py +156 -0
- vibesop/core/routing/project_config.py +270 -0
- vibesop/core/routing/scenario_layer.py +109 -0
- vibesop/core/routing/stats_mixin.py +64 -0
- vibesop/core/routing/triage_service.py +302 -0
- vibesop/core/routing/unified.py +694 -0
- vibesop/core/session_analyzer.py +525 -0
- vibesop/core/sessions/__init__.py +55 -0
- vibesop/core/sessions/context.py +426 -0
- vibesop/core/sessions/tracker.py +390 -0
- vibesop/core/skills/__init__.py +92 -0
- vibesop/core/skills/base.py +299 -0
- vibesop/core/skills/config_manager.py +360 -0
- vibesop/core/skills/executor.py +439 -0
- vibesop/core/skills/external_loader.py +427 -0
- vibesop/core/skills/format_converter.py +300 -0
- vibesop/core/skills/loader.py +477 -0
- vibesop/core/skills/manager.py +390 -0
- vibesop/core/skills/parser.py +270 -0
- vibesop/core/skills/registry_sync.py +127 -0
- vibesop/core/skills/storage.py +594 -0
- vibesop/core/skills/understander.py +732 -0
- vibesop/core/skills/workflow.py +1141 -0
- vibesop/core/state/__init__.py +23 -0
- vibesop/core/state/manager.py +99 -0
- vibesop/core/state/migration.py +85 -0
- vibesop/core/state/schema.py +69 -0
- vibesop/core/types.py +74 -0
- vibesop/hooks/__init__.py +46 -0
- vibesop/hooks/base.py +265 -0
- vibesop/hooks/installer.py +395 -0
- vibesop/hooks/points.py +122 -0
- vibesop/hooks/templates/post-session-start.sh.j2 +30 -0
- vibesop/hooks/templates/pre-session-end.sh.j2 +49 -0
- vibesop/hooks/templates/pre-tool-use.sh.j2 +75 -0
- vibesop/installer/__init__.py +22 -0
- vibesop/installer/analyzer.py +164 -0
- vibesop/installer/init_support.py +252 -0
- vibesop/installer/installer.py +368 -0
- vibesop/installer/pack_installer.py +138 -0
- vibesop/installer/planner.py +95 -0
- vibesop/installer/quickstart_runner.py +408 -0
- vibesop/installer/skill_installer.py +407 -0
- vibesop/installer/transactional.py +411 -0
- vibesop/integrations/__init__.py +43 -0
- vibesop/integrations/detector.py +287 -0
- vibesop/integrations/health_monitor.py +274 -0
- vibesop/integrations/manager.py +257 -0
- vibesop/integrations/recommender.py +421 -0
- vibesop/integrations/verifier.py +468 -0
- vibesop/llm/__init__.py +26 -0
- vibesop/llm/anthropic.py +183 -0
- vibesop/llm/base.py +186 -0
- vibesop/llm/cost_tracker.py +201 -0
- vibesop/llm/factory.py +113 -0
- vibesop/llm/openai.py +195 -0
- vibesop/llm/triage_prompts.py +104 -0
- vibesop/security/__init__.py +72 -0
- vibesop/security/exceptions.py +104 -0
- vibesop/security/path_safety.py +278 -0
- vibesop/security/rules.py +273 -0
- vibesop/security/scanner.py +369 -0
- vibesop/security/skill_auditor.py +517 -0
- vibesop/utils/__init__.py +70 -0
- vibesop/utils/atomic_writer.py +236 -0
- vibesop/utils/external_tools.py +422 -0
- vibesop/utils/helpers.py +224 -0
- vibesop/utils/marker_files.py +512 -0
- vibesop-4.2.0.dist-info/METADATA +907 -0
- vibesop-4.2.0.dist-info/RECORD +170 -0
- vibesop-4.2.0.dist-info/WHEEL +4 -0
- vibesop-4.2.0.dist-info/entry_points.txt +2 -0
- 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__
|
vibesop/adapters/base.py
ADDED
|
@@ -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)
|