diagram-to-iac 1.12.0__py3-none-any.whl → 1.13.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.
- diagram_to_iac/config.yaml +236 -0
- diagram_to_iac/core/issue_tracker.py +97 -6
- {diagram_to_iac-1.12.0.dist-info → diagram_to_iac-1.13.0.dist-info}/METADATA +1 -1
- {diagram_to_iac-1.12.0.dist-info → diagram_to_iac-1.13.0.dist-info}/RECORD +7 -6
- {diagram_to_iac-1.12.0.dist-info → diagram_to_iac-1.13.0.dist-info}/WHEEL +0 -0
- {diagram_to_iac-1.12.0.dist-info → diagram_to_iac-1.13.0.dist-info}/entry_points.txt +0 -0
- {diagram_to_iac-1.12.0.dist-info → diagram_to_iac-1.13.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,236 @@
|
|
1
|
+
# src/diagram_to_iac/config.yaml
|
2
|
+
# Central application configuration for diagram-to-iac
|
3
|
+
# This file contains common settings shared across agents and tools
|
4
|
+
# Individual agent/tool configs can override these values
|
5
|
+
|
6
|
+
# System settings
|
7
|
+
system:
|
8
|
+
workspace_base: "/workspace"
|
9
|
+
log_level: "INFO"
|
10
|
+
|
11
|
+
# Network timeouts
|
12
|
+
network:
|
13
|
+
api_timeout: 10 # Used in api_utils.py for API calls
|
14
|
+
shell_timeout: 30 # Used in shell tools
|
15
|
+
terraform_timeout: 300 # Used in terraform tools
|
16
|
+
github_timeout: 15 # Used in GitHub API calls
|
17
|
+
git_timeout: 300 # Used in git operations
|
18
|
+
|
19
|
+
# AI/LLM settings (common defaults)
|
20
|
+
ai:
|
21
|
+
default_model: "gpt-4o-mini"
|
22
|
+
default_temperature: 0.1 # Most agents use 0.1
|
23
|
+
max_tokens: 1000 # Common across agents
|
24
|
+
|
25
|
+
# Provider selection strategy for intelligent fallback
|
26
|
+
provider_selection:
|
27
|
+
strategy: "auto" # Options: "auto", "prefer_cost", "prefer_performance", "manual"
|
28
|
+
preferred_order: # Provider preference order when strategy is "auto"
|
29
|
+
- "openai" # Primary choice - good balance of cost/performance
|
30
|
+
- "anthropic" # Secondary - good for reasoning tasks
|
31
|
+
- "google" # Tertiary - good for multimodal tasks
|
32
|
+
- "grok" # Experimental - not implemented yet
|
33
|
+
|
34
|
+
# Strategy-specific configurations
|
35
|
+
cost_optimization:
|
36
|
+
prefer_models:
|
37
|
+
- "gpt-4o-mini" # Most cost-effective
|
38
|
+
- "gpt-3.5-turbo" # Good performance/cost ratio
|
39
|
+
- "claude-3-haiku" # Fast and cheap
|
40
|
+
- "gemini-pro" # Google's cost-effective option
|
41
|
+
|
42
|
+
performance_optimization:
|
43
|
+
prefer_models:
|
44
|
+
- "gpt-4o" # Best OpenAI performance
|
45
|
+
- "claude-3-sonnet" # Best Anthropic reasoning
|
46
|
+
- "gpt-4-turbo" # High performance OpenAI
|
47
|
+
- "gemini-1.5-pro" # Best Google model
|
48
|
+
|
49
|
+
# Fallback behavior when all providers fail
|
50
|
+
fallback:
|
51
|
+
enabled: true
|
52
|
+
retry_attempts: 2
|
53
|
+
retry_delay_seconds: 1
|
54
|
+
default_to_offline: false # If true, gracefully degrade to offline mode
|
55
|
+
|
56
|
+
# Common memory settings
|
57
|
+
memory:
|
58
|
+
type: "persistent"
|
59
|
+
max_history: 100
|
60
|
+
|
61
|
+
# Common error handling
|
62
|
+
error_handling:
|
63
|
+
max_retries: 3
|
64
|
+
retry_delay_seconds: 5
|
65
|
+
auto_fix_enabled: true
|
66
|
+
create_github_issues: true
|
67
|
+
|
68
|
+
# Common logging
|
69
|
+
logging:
|
70
|
+
level: "INFO"
|
71
|
+
format: "%(asctime)s - %(name)s - %(levelname)s - %(threadName)s - %(message)s"
|
72
|
+
|
73
|
+
# Route tokens (commonly used across agents)
|
74
|
+
routing:
|
75
|
+
tokens:
|
76
|
+
# Hello agent operations
|
77
|
+
addition: "ROUTE_TO_ADDITION"
|
78
|
+
multiplication: "ROUTE_TO_MULTIPLICATION"
|
79
|
+
|
80
|
+
# Git operations
|
81
|
+
git_clone: "ROUTE_TO_GIT_CLONE"
|
82
|
+
github_cli: "ROUTE_TO_GITHUB_CLI"
|
83
|
+
github_issue: "ROUTE_TO_GITHUB_ISSUE"
|
84
|
+
shell_exec: "ROUTE_TO_SHELL_EXEC"
|
85
|
+
shell: "ROUTE_TO_SHELL"
|
86
|
+
create_pr: "ROUTE_TO_CREATE_PR"
|
87
|
+
|
88
|
+
# Terraform operations
|
89
|
+
terraform_init: "ROUTE_TO_TF_INIT"
|
90
|
+
terraform_plan: "ROUTE_TO_TF_PLAN"
|
91
|
+
terraform_apply: "ROUTE_TO_TF_APPLY"
|
92
|
+
|
93
|
+
# Supervisor operations
|
94
|
+
clone: "ROUTE_TO_CLONE"
|
95
|
+
stack_detect: "ROUTE_TO_STACK_DETECT"
|
96
|
+
terraform: "ROUTE_TO_TERRAFORM"
|
97
|
+
issue: "ROUTE_TO_ISSUE"
|
98
|
+
|
99
|
+
# Demonstrator operations
|
100
|
+
analyze: "ROUTE_TO_ANALYZE"
|
101
|
+
demonstrate: "ROUTE_TO_DEMONSTRATE"
|
102
|
+
collect_inputs: "ROUTE_TO_COLLECT_INPUTS"
|
103
|
+
retry: "ROUTE_TO_RETRY"
|
104
|
+
|
105
|
+
# Policy operations
|
106
|
+
policy_scan: "ROUTE_TO_POLICY_SCAN"
|
107
|
+
policy_evaluate: "ROUTE_TO_POLICY_EVALUATE"
|
108
|
+
policy_block: "ROUTE_TO_POLICY_BLOCK"
|
109
|
+
policy_report: "ROUTE_TO_POLICY_REPORT"
|
110
|
+
|
111
|
+
# End states
|
112
|
+
create_issue: "ROUTE_TO_CREATE_ISSUE"
|
113
|
+
open_issue: "ROUTE_TO_OPEN_ISSUE"
|
114
|
+
end: "ROUTE_TO_END"
|
115
|
+
|
116
|
+
# Tool settings (common defaults)
|
117
|
+
tools:
|
118
|
+
shell:
|
119
|
+
allowed_binaries:
|
120
|
+
- "terraform"
|
121
|
+
- "git"
|
122
|
+
- "bash"
|
123
|
+
- "sh"
|
124
|
+
- "curl"
|
125
|
+
- "wget"
|
126
|
+
- "jq"
|
127
|
+
- "gh"
|
128
|
+
- "tfsec"
|
129
|
+
- "ls"
|
130
|
+
- "find"
|
131
|
+
- "wc"
|
132
|
+
max_output_size: 8192
|
133
|
+
allow_relative_paths: true
|
134
|
+
restrict_to_workspace: true
|
135
|
+
|
136
|
+
git:
|
137
|
+
default_clone_depth: 1
|
138
|
+
sanitize_repo_names: true
|
139
|
+
store_operations_in_memory: true
|
140
|
+
remote_name: "origin"
|
141
|
+
|
142
|
+
terraform:
|
143
|
+
allowed_binaries:
|
144
|
+
- "terraform"
|
145
|
+
- "git"
|
146
|
+
- "bash"
|
147
|
+
- "sh"
|
148
|
+
default_plan_file: "plan.tfplan"
|
149
|
+
default_auto_approve: true
|
150
|
+
restrict_to_workspace: true
|
151
|
+
store_operations_in_memory: true
|
152
|
+
|
153
|
+
policy:
|
154
|
+
tfsec_enabled: true
|
155
|
+
block_on_severity: ["CRITICAL", "HIGH"]
|
156
|
+
artifact_on_severity: ["CRITICAL", "HIGH", "MEDIUM"]
|
157
|
+
|
158
|
+
# Agent-specific configurations
|
159
|
+
agents:
|
160
|
+
policy_agent:
|
161
|
+
# Policy enforcement settings
|
162
|
+
policy:
|
163
|
+
tfsec:
|
164
|
+
enabled: true
|
165
|
+
timeout_seconds: 120
|
166
|
+
output_format: "json"
|
167
|
+
block_on_severity: ["CRITICAL", "HIGH"]
|
168
|
+
artifact_on_severity: ["CRITICAL", "HIGH", "MEDIUM"]
|
169
|
+
working_directory: "/workspace"
|
170
|
+
artifacts:
|
171
|
+
output_dir: "/workspace/.policy_findings"
|
172
|
+
json_filename: "policy_findings_{timestamp}.json"
|
173
|
+
summary_filename: "policy_summary_{timestamp}.txt"
|
174
|
+
|
175
|
+
# Agent-specific routing tokens
|
176
|
+
routing_keys:
|
177
|
+
policy_scan: "ROUTE_TO_POLICY_SCAN"
|
178
|
+
policy_evaluate: "ROUTE_TO_POLICY_EVALUATE"
|
179
|
+
policy_block: "ROUTE_TO_POLICY_BLOCK"
|
180
|
+
policy_report: "ROUTE_TO_POLICY_REPORT"
|
181
|
+
end: "ROUTE_TO_END"
|
182
|
+
|
183
|
+
# Memory settings
|
184
|
+
memory:
|
185
|
+
max_history: 50
|
186
|
+
|
187
|
+
policy:
|
188
|
+
tfsec_enabled: true
|
189
|
+
block_on_severity:
|
190
|
+
- "CRITICAL"
|
191
|
+
- "HIGH"
|
192
|
+
artifact_on_severity:
|
193
|
+
- "CRITICAL"
|
194
|
+
- "HIGH"
|
195
|
+
- "MEDIUM"
|
196
|
+
|
197
|
+
# GitHub settings
|
198
|
+
github:
|
199
|
+
default_assignees: ["team-infra"]
|
200
|
+
copilot_assignee: "CopilotUser"
|
201
|
+
|
202
|
+
# GitHub credentials and repository settings (for tests and development)
|
203
|
+
username: "amartyamandal" # GitHub username used in tests and development
|
204
|
+
default_repo: "amartyamandal/diagram-to-iac" # Default repository for development
|
205
|
+
|
206
|
+
# Secret management
|
207
|
+
security:
|
208
|
+
required_secrets:
|
209
|
+
- "REPO_API_KEY"
|
210
|
+
optional_secrets:
|
211
|
+
- "TF_API_KEY"
|
212
|
+
- "OPENAI_API_KEY"
|
213
|
+
- "GOOGLE_API_KEY"
|
214
|
+
- "ANTHROPIC_API_KEY"
|
215
|
+
- "GROK_API_KEY"
|
216
|
+
- "DOCKERHUB_API_KEY"
|
217
|
+
- "DOCKERHUB_USERNAME"
|
218
|
+
- "PYPI_API_KEY"
|
219
|
+
secret_mappings:
|
220
|
+
REPO_API_KEY: "GITHUB_TOKEN"
|
221
|
+
TF_API_KEY: "TFE_TOKEN"
|
222
|
+
|
223
|
+
# Environment variable overrides configuration
|
224
|
+
environment_overrides:
|
225
|
+
allowed_overrides:
|
226
|
+
- "network.api_timeout"
|
227
|
+
- "network.shell_timeout"
|
228
|
+
- "network.terraform_timeout"
|
229
|
+
- "network.github_timeout"
|
230
|
+
- "network.git_timeout"
|
231
|
+
- "ai.default_model"
|
232
|
+
- "ai.default_temperature"
|
233
|
+
- "ai.max_tokens"
|
234
|
+
- "system.workspace_base"
|
235
|
+
- "system.log_level"
|
236
|
+
- "logging.level"
|
@@ -1,35 +1,126 @@
|
|
1
1
|
import json
|
2
|
+
import os
|
2
3
|
from pathlib import Path
|
3
4
|
from typing import Dict, Optional
|
5
|
+
import logging
|
6
|
+
|
7
|
+
logger = logging.getLogger(__name__)
|
8
|
+
|
9
|
+
try:
|
10
|
+
from .config_loader import get_config_value
|
11
|
+
except ImportError:
|
12
|
+
# Fallback for tests or standalone usage
|
13
|
+
def get_config_value(path: str, default: any = None) -> any:
|
14
|
+
return default
|
4
15
|
|
5
16
|
class IssueTracker:
|
6
17
|
"""Simple persistent tracker for GitHub issues keyed by repo and error type."""
|
7
18
|
|
8
19
|
def __init__(self, file_path: Optional[str] = None):
|
9
20
|
if file_path is None:
|
10
|
-
|
11
|
-
|
21
|
+
# Debug: Check current working directory
|
22
|
+
cwd = os.getcwd()
|
23
|
+
logger.debug(f"IssueTracker: Current working directory = {cwd}")
|
24
|
+
|
25
|
+
# Get workspace base from environment variable first, then config with fallback
|
26
|
+
workspace_base = os.environ.get("WORKSPACE_BASE")
|
27
|
+
logger.debug(f"IssueTracker: WORKSPACE_BASE env var = {workspace_base}")
|
28
|
+
|
29
|
+
if not workspace_base:
|
30
|
+
# Try to get from config
|
31
|
+
try:
|
32
|
+
workspace_base = get_config_value("system.workspace_base", "/workspace")
|
33
|
+
logger.debug(f"IssueTracker: workspace_base from config = {workspace_base}")
|
34
|
+
except Exception as e:
|
35
|
+
logger.debug(f"IssueTracker: Failed to get workspace_base from config: {e}")
|
36
|
+
workspace_base = "/workspace"
|
37
|
+
|
38
|
+
# Safeguard: Never use Python installation paths or current working directory if it's a Python path
|
39
|
+
if (workspace_base and
|
40
|
+
"/usr/local/lib/python" not in workspace_base and
|
41
|
+
"/site-packages" not in workspace_base and
|
42
|
+
not workspace_base.startswith(cwd) if "/usr/local/lib/python" in cwd else True):
|
43
|
+
|
44
|
+
try:
|
45
|
+
base_dir = Path(workspace_base)
|
46
|
+
logger.debug(f"IssueTracker: Using workspace base_dir = {base_dir}")
|
47
|
+
|
48
|
+
# For container environments, always try /workspace first
|
49
|
+
if base_dir == Path("/workspace"):
|
50
|
+
data_dir = base_dir / "data" / "db"
|
51
|
+
data_dir.mkdir(parents=True, exist_ok=True)
|
52
|
+
file_path = str(data_dir / "issue_tracker.json")
|
53
|
+
logger.debug(f"IssueTracker: Using workspace path = {file_path}")
|
54
|
+
# For other paths, check if they exist and are writable
|
55
|
+
elif base_dir.exists() and base_dir.is_dir():
|
56
|
+
# Test if we can write to this directory
|
57
|
+
test_file = base_dir / "test_write_access"
|
58
|
+
try:
|
59
|
+
test_file.touch()
|
60
|
+
test_file.unlink()
|
61
|
+
data_dir = base_dir / "data" / "db"
|
62
|
+
data_dir.mkdir(parents=True, exist_ok=True)
|
63
|
+
file_path = str(data_dir / "issue_tracker.json")
|
64
|
+
logger.debug(f"IssueTracker: Using accessible workspace path = {file_path}")
|
65
|
+
except (PermissionError, OSError):
|
66
|
+
raise PermissionError(f"Workspace directory {base_dir} not writable")
|
67
|
+
else:
|
68
|
+
raise PermissionError(f"Workspace directory {base_dir} not accessible")
|
69
|
+
|
70
|
+
except (PermissionError, OSError) as e:
|
71
|
+
logger.debug(f"IssueTracker: Workspace not accessible ({e}), falling back to /tmp")
|
72
|
+
# Fallback to /tmp for container environments
|
73
|
+
data_dir = Path("/tmp") / "diagram_to_iac" / "data" / "db"
|
74
|
+
data_dir.mkdir(parents=True, exist_ok=True)
|
75
|
+
file_path = str(data_dir / "issue_tracker.json")
|
76
|
+
logger.debug(f"IssueTracker: Using tmp path = {file_path}")
|
77
|
+
else:
|
78
|
+
logger.debug(f"IssueTracker: Invalid workspace_base ({workspace_base}) or unsafe cwd ({cwd}), using /tmp")
|
79
|
+
# Always fallback to /tmp if workspace_base is invalid or points to Python paths
|
80
|
+
data_dir = Path("/tmp") / "diagram_to_iac" / "data" / "db"
|
81
|
+
data_dir.mkdir(parents=True, exist_ok=True)
|
82
|
+
file_path = str(data_dir / "issue_tracker.json")
|
83
|
+
logger.debug(f"IssueTracker: Using tmp fallback path = {file_path}")
|
84
|
+
|
12
85
|
self.file_path = Path(file_path)
|
86
|
+
logger.debug(f"IssueTracker: Final file_path = {self.file_path}")
|
87
|
+
|
88
|
+
# Final safety check: ensure the path is never in Python installation
|
89
|
+
if "/usr/local/lib/python" in str(self.file_path) or "/site-packages" in str(self.file_path):
|
90
|
+
logger.warning(f"IssueTracker: Detected Python installation path {self.file_path}, forcing /tmp fallback")
|
91
|
+
data_dir = Path("/tmp") / "diagram_to_iac" / "data" / "db"
|
92
|
+
data_dir.mkdir(parents=True, exist_ok=True)
|
93
|
+
self.file_path = data_dir / "issue_tracker.json"
|
94
|
+
logger.debug(f"IssueTracker: Corrected file_path = {self.file_path}")
|
95
|
+
|
13
96
|
self._table: Dict[str, Dict[str, int]] = {}
|
14
97
|
self._load()
|
15
98
|
|
16
99
|
def _load(self) -> None:
|
100
|
+
"""Load the issue tracker data from file."""
|
17
101
|
if self.file_path.exists():
|
18
102
|
try:
|
19
103
|
with open(self.file_path, "r") as f:
|
20
104
|
self._table = json.load(f)
|
21
|
-
|
105
|
+
logger.debug(f"IssueTracker: Loaded {len(self._table)} repos from {self.file_path}")
|
106
|
+
except Exception as e:
|
107
|
+
logger.warning(f"IssueTracker: Failed to load from {self.file_path}: {e}")
|
22
108
|
self._table = {}
|
23
109
|
else:
|
24
|
-
|
110
|
+
# File doesn't exist, start with empty table
|
111
|
+
# Directory was already created in __init__, so no need to create again
|
25
112
|
self._table = {}
|
113
|
+
logger.debug(f"IssueTracker: Starting with empty table at {self.file_path}")
|
26
114
|
|
27
115
|
def _save(self) -> None:
|
116
|
+
"""Save the issue tracker data to file."""
|
28
117
|
try:
|
29
|
-
|
118
|
+
# Directory was already created in __init__, no need to create again
|
30
119
|
with open(self.file_path, "w") as f:
|
31
120
|
json.dump(self._table, f, indent=2)
|
32
|
-
|
121
|
+
logger.debug(f"IssueTracker: Saved {len(self._table)} repos to {self.file_path}")
|
122
|
+
except Exception as e:
|
123
|
+
logger.error(f"IssueTracker: Failed to save to {self.file_path}: {e}")
|
33
124
|
pass
|
34
125
|
|
35
126
|
def get_issue(self, repo_url: str, error_type: str) -> Optional[int]:
|
@@ -1,5 +1,6 @@
|
|
1
1
|
diagram_to_iac/__init__.py,sha256=gQanRC5O_7AMB-NQFEEd-MU0GICa-dBsgvcJgquKBfs,1427
|
2
2
|
diagram_to_iac/cli.py,sha256=uumG1frF42eCkdLIZxyxQB1x6lDwtG-qKL4vcHnLLXY,400
|
3
|
+
diagram_to_iac/config.yaml,sha256=M2INu-Vfsbb-oWQgv_puoUljRj1GhcnwuWAbDsE7bMc,6739
|
3
4
|
diagram_to_iac/r2d.py,sha256=I7XSuUtu8TdvAhK4tCMLc3U_3ZtP7DJGfq168aeI3Mk,13208
|
4
5
|
diagram_to_iac/actions/__init__.py,sha256=P1CjjY4FYUA0Tcx8FQNLYYSI9fhv8yKd_TmRGtmhW50,229
|
5
6
|
diagram_to_iac/actions/git_entry.py,sha256=mhY6gYquUPVvyvnTC2S90z_uXEe1asqWLoi1989aB_Q,5403
|
@@ -36,7 +37,7 @@ diagram_to_iac/core/agent_base.py,sha256=DjZjcfzDpEhfIOki00XwQ-4lPli3OBcQ_7RNKsT
|
|
36
37
|
diagram_to_iac/core/config_loader.py,sha256=6WWOp6G7_xYUhm1x62sVa-7kFlCthcthbppmeGz1YsM,9276
|
37
38
|
diagram_to_iac/core/enhanced_memory.py,sha256=PI7GPpgo4APeRZ7b9t0MqWKqthRI4_iAg-GLCykDhtU,11845
|
38
39
|
diagram_to_iac/core/errors.py,sha256=gZwZocnIcBlS4YccIBdjG8XztRCtMe4Cu6KWxLzebDM,115
|
39
|
-
diagram_to_iac/core/issue_tracker.py,sha256=
|
40
|
+
diagram_to_iac/core/issue_tracker.py,sha256=_Hl1fG6jUczqzB-Cmwx8qzYqB10ijyRKFBGrsy4Hfp4,7097
|
40
41
|
diagram_to_iac/core/memory.py,sha256=P9URX8m2nab65ZPF36uf6Z9hEXQGXrjrXa8dPXG7pm8,4444
|
41
42
|
diagram_to_iac/core/registry.py,sha256=ibdMz68W7qkwF0dprt4ni5pekgJfAPuRgL85uRU7wHY,26632
|
42
43
|
diagram_to_iac/core/test_config.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
@@ -63,8 +64,8 @@ diagram_to_iac/tools/llm_utils/router.py,sha256=ga8xfmPMl_SGINDwazeAAFYTAx9L_IQc
|
|
63
64
|
diagram_to_iac/tools/shell/__init__.py,sha256=6UZjBcnbPabA6Qy7t4j-dCi3S2sE6sB2bTE9PIL98bA,292
|
64
65
|
diagram_to_iac/tools/shell/shell.py,sha256=ZOJ7Vo3l_R2Gm6Ml2FL0RX__-C_JOsUrLJVvBMwAy9E,21122
|
65
66
|
diagram_to_iac/tools/tf/terraform.py,sha256=j1boWRo6JKpNGf1OwnWoWboO0gMYTizCOHDSxozoFZw,37343
|
66
|
-
diagram_to_iac-1.
|
67
|
-
diagram_to_iac-1.
|
68
|
-
diagram_to_iac-1.
|
69
|
-
diagram_to_iac-1.
|
70
|
-
diagram_to_iac-1.
|
67
|
+
diagram_to_iac-1.13.0.dist-info/METADATA,sha256=N4FyUgEIYvHVHN1dzQ2v8R6AZD-hvDHOYRfA1bRMUOo,10690
|
68
|
+
diagram_to_iac-1.13.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
69
|
+
diagram_to_iac-1.13.0.dist-info/entry_points.txt,sha256=DfGCnmgWWGHtQpqU8VqcUWs5k_be-bfO67z1vOuTitA,277
|
70
|
+
diagram_to_iac-1.13.0.dist-info/top_level.txt,sha256=k1cV0YODiCUU46qlmbQaquMcbMXhNm05NZLxsinDUBA,15
|
71
|
+
diagram_to_iac-1.13.0.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|