xenfra 0.4.2__py3-none-any.whl → 0.4.4__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.
xenfra/utils/codebase.py CHANGED
@@ -1,169 +1,169 @@
1
- """
2
- Codebase scanning utilities for AI-powered project initialization.
3
- """
4
-
5
- import os
6
- from pathlib import Path
7
-
8
-
9
- def scan_codebase(max_files: int = 10, max_size: int = 50000) -> dict[str, str]:
10
- """
11
- Scan current directory for important code files.
12
-
13
- Args:
14
- max_files: Maximum number of files to include (validated: 1-100)
15
- max_size: Maximum file size in bytes (validated: 1KB-10MB)
16
-
17
- Returns:
18
- Dictionary of filename -> content for AI analysis
19
- """
20
- # Validate limits
21
- from .validation import validate_codebase_scan_limits
22
-
23
- is_valid, error_msg = validate_codebase_scan_limits(max_files, max_size)
24
- if not is_valid:
25
- raise ValueError(f"Invalid scan limits: {error_msg}")
26
-
27
- # Ensure limits are within bounds
28
- max_files = max(1, min(100, max_files))
29
- max_size = max(1024, min(10 * 1024 * 1024, max_size))
30
- """
31
- Scan current directory for important code files.
32
-
33
- Args:
34
- max_files: Maximum number of files to include
35
- max_size: Maximum file size in bytes (default 50KB)
36
-
37
- Returns:
38
- Dictionary of filename -> content for AI analysis
39
- """
40
- code_snippets = {}
41
-
42
- # Priority files to scan (in order)
43
- important_files = [
44
- # Python entry points
45
- "main.py",
46
- "app.py",
47
- "wsgi.py",
48
- "asgi.py",
49
- "manage.py",
50
- # Configuration files
51
- "requirements.txt",
52
- "pyproject.toml",
53
- "Pipfile",
54
- "setup.py",
55
- # Django/Flask specific
56
- "settings.py",
57
- "config.py",
58
- # Docker
59
- "Dockerfile",
60
- "docker-compose.yml",
61
- # Xenfra config
62
- "xenfra.yaml",
63
- "xenfra.yml",
64
- ]
65
-
66
- # Scan for important files in current directory
67
- for filename in important_files:
68
- if len(code_snippets) >= max_files:
69
- break
70
-
71
- if os.path.exists(filename) and os.path.isfile(filename):
72
- try:
73
- file_size = os.path.getsize(filename)
74
- if file_size > max_size:
75
- continue
76
-
77
- with open(filename, "r", encoding="utf-8") as f:
78
- content = f.read(max_size)
79
- code_snippets[filename] = content
80
- except (IOError, OSError, PermissionError, UnicodeDecodeError) as e:
81
- # Skip files that can't be read (log but don't crash)
82
- import logging
83
-
84
- logger = logging.getLogger(__name__)
85
- logger.debug(f"Skipping file {filename}: {type(e).__name__}")
86
- continue
87
-
88
- # If we haven't found enough files, look for Python files in common locations
89
- if len(code_snippets) < 3:
90
- search_patterns = [
91
- "src/**/*.py",
92
- "app/**/*.py",
93
- "*.py",
94
- ]
95
-
96
- for pattern in search_patterns:
97
- if len(code_snippets) >= max_files:
98
- break
99
-
100
- for filepath in Path(".").glob(pattern):
101
- if len(code_snippets) >= max_files:
102
- break
103
-
104
- if filepath.is_file() and filepath.name not in code_snippets:
105
- try:
106
- file_size = filepath.stat().st_size
107
- if file_size > max_size:
108
- continue
109
-
110
- with open(filepath, "r", encoding="utf-8") as f:
111
- content = f.read(max_size)
112
- code_snippets[str(filepath)] = content
113
- except (IOError, OSError, PermissionError, UnicodeDecodeError) as e:
114
- # Skip files that can't be read
115
- import logging
116
-
117
- logger = logging.getLogger(__name__)
118
- logger.debug(f"Skipping file {filepath}: {type(e).__name__}")
119
- continue
120
-
121
- return code_snippets
122
-
123
- def detect_package_manager_conflicts(code_snippets: dict[str, str]) -> tuple[bool, list[dict]]:
124
- """
125
- Deterministically detect package manager conflicts from scanned files.
126
-
127
- This ensures Zen Nod (conflict resolution) always triggers when multiple
128
- package managers are present, regardless of AI detection.
129
-
130
- Args:
131
- code_snippets: Dictionary of filename -> content from scan_codebase()
132
-
133
- Returns:
134
- (has_conflict, detected_managers) where detected_managers is a list of
135
- {"manager": str, "file": str} dictionaries
136
- """
137
- detected = []
138
-
139
- # Check for Python package managers
140
- if "pyproject.toml" in code_snippets:
141
- content = code_snippets["pyproject.toml"]
142
- if "[tool.poetry]" in content or "poetry.lock" in code_snippets:
143
- detected.append({"manager": "poetry", "file": "pyproject.toml"})
144
- elif "[tool.uv]" in content or "uv.lock" in code_snippets or "[project]" in content:
145
- detected.append({"manager": "uv", "file": "pyproject.toml"})
146
-
147
- if "Pipfile" in code_snippets:
148
- detected.append({"manager": "pipenv", "file": "Pipfile"})
149
-
150
- if "requirements.txt" in code_snippets:
151
- detected.append({"manager": "pip", "file": "requirements.txt"})
152
-
153
- # Check for Node.js package managers (all independent checks)
154
- if "pnpm-lock.yaml" in code_snippets:
155
- detected.append({"manager": "pnpm", "file": "pnpm-lock.yaml"})
156
-
157
- if "yarn.lock" in code_snippets:
158
- detected.append({"manager": "yarn", "file": "yarn.lock"})
159
-
160
- if "package-lock.json" in code_snippets:
161
- detected.append({"manager": "npm", "file": "package-lock.json"})
162
-
163
- has_conflict = len(detected) > 1
164
- return has_conflict, detected
165
-
166
-
167
- def has_xenfra_config() -> bool:
168
- """Check if xenfra.yaml already exists."""
169
- return os.path.exists("xenfra.yaml") or os.path.exists("xenfra.yml")
1
+ """
2
+ Codebase scanning utilities for AI-powered project initialization.
3
+ """
4
+
5
+ import os
6
+ from pathlib import Path
7
+
8
+
9
+ def scan_codebase(max_files: int = 10, max_size: int = 50000) -> dict[str, str]:
10
+ """
11
+ Scan current directory for important code files.
12
+
13
+ Args:
14
+ max_files: Maximum number of files to include (validated: 1-100)
15
+ max_size: Maximum file size in bytes (validated: 1KB-10MB)
16
+
17
+ Returns:
18
+ Dictionary of filename -> content for AI analysis
19
+ """
20
+ # Validate limits
21
+ from .validation import validate_codebase_scan_limits
22
+
23
+ is_valid, error_msg = validate_codebase_scan_limits(max_files, max_size)
24
+ if not is_valid:
25
+ raise ValueError(f"Invalid scan limits: {error_msg}")
26
+
27
+ # Ensure limits are within bounds
28
+ max_files = max(1, min(100, max_files))
29
+ max_size = max(1024, min(10 * 1024 * 1024, max_size))
30
+ """
31
+ Scan current directory for important code files.
32
+
33
+ Args:
34
+ max_files: Maximum number of files to include
35
+ max_size: Maximum file size in bytes (default 50KB)
36
+
37
+ Returns:
38
+ Dictionary of filename -> content for AI analysis
39
+ """
40
+ code_snippets = {}
41
+
42
+ # Priority files to scan (in order)
43
+ important_files = [
44
+ # Python entry points
45
+ "main.py",
46
+ "app.py",
47
+ "wsgi.py",
48
+ "asgi.py",
49
+ "manage.py",
50
+ # Configuration files
51
+ "requirements.txt",
52
+ "pyproject.toml",
53
+ "Pipfile",
54
+ "setup.py",
55
+ # Django/Flask specific
56
+ "settings.py",
57
+ "config.py",
58
+ # Docker
59
+ "Dockerfile",
60
+ "docker-compose.yml",
61
+ # Xenfra config
62
+ "xenfra.yaml",
63
+ "xenfra.yml",
64
+ ]
65
+
66
+ # Scan for important files in current directory
67
+ for filename in important_files:
68
+ if len(code_snippets) >= max_files:
69
+ break
70
+
71
+ if os.path.exists(filename) and os.path.isfile(filename):
72
+ try:
73
+ file_size = os.path.getsize(filename)
74
+ if file_size > max_size:
75
+ continue
76
+
77
+ with open(filename, "r", encoding="utf-8") as f:
78
+ content = f.read(max_size)
79
+ code_snippets[filename] = content
80
+ except (IOError, OSError, PermissionError, UnicodeDecodeError) as e:
81
+ # Skip files that can't be read (log but don't crash)
82
+ import logging
83
+
84
+ logger = logging.getLogger(__name__)
85
+ logger.debug(f"Skipping file {filename}: {type(e).__name__}")
86
+ continue
87
+
88
+ # If we haven't found enough files, look for Python files in common locations
89
+ if len(code_snippets) < 3:
90
+ search_patterns = [
91
+ "src/**/*.py",
92
+ "app/**/*.py",
93
+ "*.py",
94
+ ]
95
+
96
+ for pattern in search_patterns:
97
+ if len(code_snippets) >= max_files:
98
+ break
99
+
100
+ for filepath in Path(".").glob(pattern):
101
+ if len(code_snippets) >= max_files:
102
+ break
103
+
104
+ if filepath.is_file() and filepath.name not in code_snippets:
105
+ try:
106
+ file_size = filepath.stat().st_size
107
+ if file_size > max_size:
108
+ continue
109
+
110
+ with open(filepath, "r", encoding="utf-8") as f:
111
+ content = f.read(max_size)
112
+ code_snippets[str(filepath)] = content
113
+ except (IOError, OSError, PermissionError, UnicodeDecodeError) as e:
114
+ # Skip files that can't be read
115
+ import logging
116
+
117
+ logger = logging.getLogger(__name__)
118
+ logger.debug(f"Skipping file {filepath}: {type(e).__name__}")
119
+ continue
120
+
121
+ return code_snippets
122
+
123
+ def detect_package_manager_conflicts(code_snippets: dict[str, str]) -> tuple[bool, list[dict]]:
124
+ """
125
+ Deterministically detect package manager conflicts from scanned files.
126
+
127
+ This ensures Zen Nod (conflict resolution) always triggers when multiple
128
+ package managers are present, regardless of AI detection.
129
+
130
+ Args:
131
+ code_snippets: Dictionary of filename -> content from scan_codebase()
132
+
133
+ Returns:
134
+ (has_conflict, detected_managers) where detected_managers is a list of
135
+ {"manager": str, "file": str} dictionaries
136
+ """
137
+ detected = []
138
+
139
+ # Check for Python package managers
140
+ if "pyproject.toml" in code_snippets:
141
+ content = code_snippets["pyproject.toml"]
142
+ if "[tool.poetry]" in content or "poetry.lock" in code_snippets:
143
+ detected.append({"manager": "poetry", "file": "pyproject.toml"})
144
+ elif "[tool.uv]" in content or "uv.lock" in code_snippets or "[project]" in content:
145
+ detected.append({"manager": "uv", "file": "pyproject.toml"})
146
+
147
+ if "Pipfile" in code_snippets:
148
+ detected.append({"manager": "pipenv", "file": "Pipfile"})
149
+
150
+ if "requirements.txt" in code_snippets:
151
+ detected.append({"manager": "pip", "file": "requirements.txt"})
152
+
153
+ # Check for Node.js package managers (all independent checks)
154
+ if "pnpm-lock.yaml" in code_snippets:
155
+ detected.append({"manager": "pnpm", "file": "pnpm-lock.yaml"})
156
+
157
+ if "yarn.lock" in code_snippets:
158
+ detected.append({"manager": "yarn", "file": "yarn.lock"})
159
+
160
+ if "package-lock.json" in code_snippets:
161
+ detected.append({"manager": "npm", "file": "package-lock.json"})
162
+
163
+ has_conflict = len(detected) > 1
164
+ return has_conflict, detected
165
+
166
+
167
+ def has_xenfra_config() -> bool:
168
+ """Check if xenfra.yaml already exists."""
169
+ return os.path.exists("xenfra.yaml") or os.path.exists("xenfra.yml")