lucidscan 0.5.12__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.
- lucidscan/__init__.py +12 -0
- lucidscan/bootstrap/__init__.py +26 -0
- lucidscan/bootstrap/paths.py +160 -0
- lucidscan/bootstrap/platform.py +111 -0
- lucidscan/bootstrap/validation.py +76 -0
- lucidscan/bootstrap/versions.py +119 -0
- lucidscan/cli/__init__.py +50 -0
- lucidscan/cli/__main__.py +8 -0
- lucidscan/cli/arguments.py +405 -0
- lucidscan/cli/commands/__init__.py +64 -0
- lucidscan/cli/commands/autoconfigure.py +294 -0
- lucidscan/cli/commands/help.py +69 -0
- lucidscan/cli/commands/init.py +656 -0
- lucidscan/cli/commands/list_scanners.py +59 -0
- lucidscan/cli/commands/scan.py +307 -0
- lucidscan/cli/commands/serve.py +142 -0
- lucidscan/cli/commands/status.py +84 -0
- lucidscan/cli/commands/validate.py +105 -0
- lucidscan/cli/config_bridge.py +152 -0
- lucidscan/cli/exit_codes.py +17 -0
- lucidscan/cli/runner.py +284 -0
- lucidscan/config/__init__.py +29 -0
- lucidscan/config/ignore.py +178 -0
- lucidscan/config/loader.py +431 -0
- lucidscan/config/models.py +316 -0
- lucidscan/config/validation.py +645 -0
- lucidscan/core/__init__.py +3 -0
- lucidscan/core/domain_runner.py +463 -0
- lucidscan/core/git.py +174 -0
- lucidscan/core/logging.py +34 -0
- lucidscan/core/models.py +207 -0
- lucidscan/core/streaming.py +340 -0
- lucidscan/core/subprocess_runner.py +164 -0
- lucidscan/detection/__init__.py +21 -0
- lucidscan/detection/detector.py +154 -0
- lucidscan/detection/frameworks.py +270 -0
- lucidscan/detection/languages.py +328 -0
- lucidscan/detection/tools.py +229 -0
- lucidscan/generation/__init__.py +15 -0
- lucidscan/generation/config_generator.py +275 -0
- lucidscan/generation/package_installer.py +330 -0
- lucidscan/mcp/__init__.py +20 -0
- lucidscan/mcp/formatter.py +510 -0
- lucidscan/mcp/server.py +297 -0
- lucidscan/mcp/tools.py +1049 -0
- lucidscan/mcp/watcher.py +237 -0
- lucidscan/pipeline/__init__.py +17 -0
- lucidscan/pipeline/executor.py +187 -0
- lucidscan/pipeline/parallel.py +181 -0
- lucidscan/plugins/__init__.py +40 -0
- lucidscan/plugins/coverage/__init__.py +28 -0
- lucidscan/plugins/coverage/base.py +160 -0
- lucidscan/plugins/coverage/coverage_py.py +454 -0
- lucidscan/plugins/coverage/istanbul.py +411 -0
- lucidscan/plugins/discovery.py +107 -0
- lucidscan/plugins/enrichers/__init__.py +61 -0
- lucidscan/plugins/enrichers/base.py +63 -0
- lucidscan/plugins/linters/__init__.py +26 -0
- lucidscan/plugins/linters/base.py +125 -0
- lucidscan/plugins/linters/biome.py +448 -0
- lucidscan/plugins/linters/checkstyle.py +393 -0
- lucidscan/plugins/linters/eslint.py +368 -0
- lucidscan/plugins/linters/ruff.py +498 -0
- lucidscan/plugins/reporters/__init__.py +45 -0
- lucidscan/plugins/reporters/base.py +30 -0
- lucidscan/plugins/reporters/json_reporter.py +79 -0
- lucidscan/plugins/reporters/sarif_reporter.py +303 -0
- lucidscan/plugins/reporters/summary_reporter.py +61 -0
- lucidscan/plugins/reporters/table_reporter.py +81 -0
- lucidscan/plugins/scanners/__init__.py +57 -0
- lucidscan/plugins/scanners/base.py +60 -0
- lucidscan/plugins/scanners/checkov.py +484 -0
- lucidscan/plugins/scanners/opengrep.py +464 -0
- lucidscan/plugins/scanners/trivy.py +492 -0
- lucidscan/plugins/test_runners/__init__.py +27 -0
- lucidscan/plugins/test_runners/base.py +111 -0
- lucidscan/plugins/test_runners/jest.py +381 -0
- lucidscan/plugins/test_runners/karma.py +481 -0
- lucidscan/plugins/test_runners/playwright.py +434 -0
- lucidscan/plugins/test_runners/pytest.py +598 -0
- lucidscan/plugins/type_checkers/__init__.py +27 -0
- lucidscan/plugins/type_checkers/base.py +106 -0
- lucidscan/plugins/type_checkers/mypy.py +355 -0
- lucidscan/plugins/type_checkers/pyright.py +313 -0
- lucidscan/plugins/type_checkers/typescript.py +280 -0
- lucidscan-0.5.12.dist-info/METADATA +242 -0
- lucidscan-0.5.12.dist-info/RECORD +91 -0
- lucidscan-0.5.12.dist-info/WHEEL +5 -0
- lucidscan-0.5.12.dist-info/entry_points.txt +34 -0
- lucidscan-0.5.12.dist-info/licenses/LICENSE +201 -0
- lucidscan-0.5.12.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
"""Language detection module.
|
|
2
|
+
|
|
3
|
+
Detects programming languages in a project by analyzing:
|
|
4
|
+
- File extensions
|
|
5
|
+
- Marker files (package.json, pyproject.toml, go.mod, etc.)
|
|
6
|
+
- Configuration files
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import re
|
|
12
|
+
from dataclasses import dataclass
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
from typing import Optional
|
|
15
|
+
|
|
16
|
+
# Directories to skip during detection
|
|
17
|
+
SKIP_DIRS = {
|
|
18
|
+
".git",
|
|
19
|
+
".svn",
|
|
20
|
+
".hg",
|
|
21
|
+
"node_modules",
|
|
22
|
+
"__pycache__",
|
|
23
|
+
".venv",
|
|
24
|
+
"venv",
|
|
25
|
+
".env",
|
|
26
|
+
"env",
|
|
27
|
+
".tox",
|
|
28
|
+
".pytest_cache",
|
|
29
|
+
".mypy_cache",
|
|
30
|
+
".ruff_cache",
|
|
31
|
+
"dist",
|
|
32
|
+
"build",
|
|
33
|
+
"target",
|
|
34
|
+
"vendor",
|
|
35
|
+
".next",
|
|
36
|
+
".nuxt",
|
|
37
|
+
"coverage",
|
|
38
|
+
".coverage",
|
|
39
|
+
"htmlcov",
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
# File extension to language mapping
|
|
43
|
+
EXTENSION_MAP = {
|
|
44
|
+
".py": "python",
|
|
45
|
+
".pyw": "python",
|
|
46
|
+
".pyi": "python",
|
|
47
|
+
".js": "javascript",
|
|
48
|
+
".mjs": "javascript",
|
|
49
|
+
".cjs": "javascript",
|
|
50
|
+
".jsx": "javascript",
|
|
51
|
+
".ts": "typescript",
|
|
52
|
+
".tsx": "typescript",
|
|
53
|
+
".mts": "typescript",
|
|
54
|
+
".cts": "typescript",
|
|
55
|
+
".go": "go",
|
|
56
|
+
".rs": "rust",
|
|
57
|
+
".java": "java",
|
|
58
|
+
".kt": "kotlin",
|
|
59
|
+
".kts": "kotlin",
|
|
60
|
+
".scala": "scala",
|
|
61
|
+
".rb": "ruby",
|
|
62
|
+
".php": "php",
|
|
63
|
+
".cs": "csharp",
|
|
64
|
+
".swift": "swift",
|
|
65
|
+
".c": "c",
|
|
66
|
+
".h": "c",
|
|
67
|
+
".cpp": "cpp",
|
|
68
|
+
".cc": "cpp",
|
|
69
|
+
".cxx": "cpp",
|
|
70
|
+
".hpp": "cpp",
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
# Marker files that indicate a language
|
|
74
|
+
MARKER_FILES = {
|
|
75
|
+
"python": ["pyproject.toml", "setup.py", "setup.cfg", "requirements.txt", "Pipfile"],
|
|
76
|
+
"javascript": ["package.json"],
|
|
77
|
+
"typescript": ["tsconfig.json"],
|
|
78
|
+
"go": ["go.mod"],
|
|
79
|
+
"rust": ["Cargo.toml"],
|
|
80
|
+
"java": ["pom.xml", "build.gradle", "build.gradle.kts"],
|
|
81
|
+
"ruby": ["Gemfile"],
|
|
82
|
+
"php": ["composer.json"],
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
@dataclass
|
|
87
|
+
class LanguageInfo:
|
|
88
|
+
"""Information about a detected language."""
|
|
89
|
+
|
|
90
|
+
name: str
|
|
91
|
+
"""Language name (lowercase)."""
|
|
92
|
+
|
|
93
|
+
version: Optional[str] = None
|
|
94
|
+
"""Detected version (if available)."""
|
|
95
|
+
|
|
96
|
+
file_count: int = 0
|
|
97
|
+
"""Number of files with this language."""
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def detect_languages(project_root: Path) -> list[LanguageInfo]:
|
|
101
|
+
"""Detect programming languages in a project.
|
|
102
|
+
|
|
103
|
+
Args:
|
|
104
|
+
project_root: Path to the project root directory.
|
|
105
|
+
|
|
106
|
+
Returns:
|
|
107
|
+
List of detected languages with metadata.
|
|
108
|
+
"""
|
|
109
|
+
# Count files by extension
|
|
110
|
+
extension_counts: dict[str, int] = {}
|
|
111
|
+
|
|
112
|
+
for file_path in _walk_files(project_root):
|
|
113
|
+
ext = file_path.suffix.lower()
|
|
114
|
+
if ext in EXTENSION_MAP:
|
|
115
|
+
lang = EXTENSION_MAP[ext]
|
|
116
|
+
extension_counts[lang] = extension_counts.get(lang, 0) + 1
|
|
117
|
+
|
|
118
|
+
# Check for marker files
|
|
119
|
+
marker_languages = set()
|
|
120
|
+
for lang, markers in MARKER_FILES.items():
|
|
121
|
+
for marker in markers:
|
|
122
|
+
if (project_root / marker).exists():
|
|
123
|
+
marker_languages.add(lang)
|
|
124
|
+
break
|
|
125
|
+
|
|
126
|
+
# Combine results
|
|
127
|
+
all_languages = set(extension_counts.keys()) | marker_languages
|
|
128
|
+
|
|
129
|
+
# Build LanguageInfo objects
|
|
130
|
+
results = []
|
|
131
|
+
for lang in all_languages:
|
|
132
|
+
info = LanguageInfo(
|
|
133
|
+
name=lang,
|
|
134
|
+
file_count=extension_counts.get(lang, 0),
|
|
135
|
+
version=_detect_version(lang, project_root),
|
|
136
|
+
)
|
|
137
|
+
results.append(info)
|
|
138
|
+
|
|
139
|
+
# Sort by file count (descending)
|
|
140
|
+
results.sort(key=lambda x: x.file_count, reverse=True)
|
|
141
|
+
|
|
142
|
+
return results
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def _walk_files(root: Path, max_depth: int = 10) -> list[Path]:
|
|
146
|
+
"""Walk directory tree yielding files.
|
|
147
|
+
|
|
148
|
+
Args:
|
|
149
|
+
root: Root directory to walk.
|
|
150
|
+
max_depth: Maximum recursion depth.
|
|
151
|
+
|
|
152
|
+
Returns:
|
|
153
|
+
List of file paths.
|
|
154
|
+
"""
|
|
155
|
+
files = []
|
|
156
|
+
|
|
157
|
+
def _walk(path: Path, depth: int) -> None:
|
|
158
|
+
if depth > max_depth:
|
|
159
|
+
return
|
|
160
|
+
|
|
161
|
+
try:
|
|
162
|
+
for item in path.iterdir():
|
|
163
|
+
if item.is_dir():
|
|
164
|
+
if item.name not in SKIP_DIRS and not item.name.startswith("."):
|
|
165
|
+
_walk(item, depth + 1)
|
|
166
|
+
elif item.is_file():
|
|
167
|
+
files.append(item)
|
|
168
|
+
except PermissionError:
|
|
169
|
+
pass
|
|
170
|
+
|
|
171
|
+
_walk(root, 0)
|
|
172
|
+
return files
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def _detect_version(language: str, project_root: Path) -> Optional[str]:
|
|
176
|
+
"""Detect the version of a language from config files.
|
|
177
|
+
|
|
178
|
+
Args:
|
|
179
|
+
language: Language name.
|
|
180
|
+
project_root: Project root directory.
|
|
181
|
+
|
|
182
|
+
Returns:
|
|
183
|
+
Version string or None.
|
|
184
|
+
"""
|
|
185
|
+
if language == "python":
|
|
186
|
+
return _detect_python_version(project_root)
|
|
187
|
+
elif language == "typescript":
|
|
188
|
+
return _detect_typescript_version(project_root)
|
|
189
|
+
elif language == "go":
|
|
190
|
+
return _detect_go_version(project_root)
|
|
191
|
+
elif language == "rust":
|
|
192
|
+
return _detect_rust_version(project_root)
|
|
193
|
+
elif language == "java":
|
|
194
|
+
return _detect_java_version(project_root)
|
|
195
|
+
return None
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
def _detect_python_version(project_root: Path) -> Optional[str]:
|
|
199
|
+
"""Detect Python version from pyproject.toml or other files."""
|
|
200
|
+
pyproject = project_root / "pyproject.toml"
|
|
201
|
+
if pyproject.exists():
|
|
202
|
+
try:
|
|
203
|
+
content = pyproject.read_text()
|
|
204
|
+
# Look for requires-python
|
|
205
|
+
match = re.search(r'requires-python\s*=\s*["\']([^"\']+)["\']', content)
|
|
206
|
+
if match:
|
|
207
|
+
version_spec = match.group(1)
|
|
208
|
+
# Extract version number (e.g., ">=3.10" -> "3.10")
|
|
209
|
+
version_match = re.search(r"(\d+\.\d+)", version_spec)
|
|
210
|
+
if version_match:
|
|
211
|
+
return version_match.group(1)
|
|
212
|
+
except Exception:
|
|
213
|
+
pass
|
|
214
|
+
|
|
215
|
+
# Check .python-version file
|
|
216
|
+
python_version_file = project_root / ".python-version"
|
|
217
|
+
if python_version_file.exists():
|
|
218
|
+
try:
|
|
219
|
+
version = python_version_file.read_text().strip()
|
|
220
|
+
return version.split(".")[0] + "." + version.split(".")[1] if "." in version else version
|
|
221
|
+
except Exception:
|
|
222
|
+
pass
|
|
223
|
+
|
|
224
|
+
return None
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
def _detect_typescript_version(project_root: Path) -> Optional[str]:
|
|
228
|
+
"""Detect TypeScript version from package.json."""
|
|
229
|
+
package_json = project_root / "package.json"
|
|
230
|
+
if package_json.exists():
|
|
231
|
+
try:
|
|
232
|
+
import json
|
|
233
|
+
data = json.loads(package_json.read_text())
|
|
234
|
+
deps = {**data.get("dependencies", {}), **data.get("devDependencies", {})}
|
|
235
|
+
if "typescript" in deps:
|
|
236
|
+
version = deps["typescript"]
|
|
237
|
+
# Strip version prefix (^, ~, etc.)
|
|
238
|
+
return re.sub(r"^[\^~>=<]+", "", version)
|
|
239
|
+
except Exception:
|
|
240
|
+
pass
|
|
241
|
+
return None
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
def _detect_go_version(project_root: Path) -> Optional[str]:
|
|
245
|
+
"""Detect Go version from go.mod."""
|
|
246
|
+
go_mod = project_root / "go.mod"
|
|
247
|
+
if go_mod.exists():
|
|
248
|
+
try:
|
|
249
|
+
content = go_mod.read_text()
|
|
250
|
+
match = re.search(r"^go\s+(\d+\.\d+)", content, re.MULTILINE)
|
|
251
|
+
if match:
|
|
252
|
+
return match.group(1)
|
|
253
|
+
except Exception:
|
|
254
|
+
pass
|
|
255
|
+
return None
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
def _detect_rust_version(project_root: Path) -> Optional[str]:
|
|
259
|
+
"""Detect Rust edition from Cargo.toml."""
|
|
260
|
+
cargo_toml = project_root / "Cargo.toml"
|
|
261
|
+
if cargo_toml.exists():
|
|
262
|
+
try:
|
|
263
|
+
content = cargo_toml.read_text()
|
|
264
|
+
match = re.search(r'edition\s*=\s*["\'](\d+)["\']', content)
|
|
265
|
+
if match:
|
|
266
|
+
return match.group(1)
|
|
267
|
+
except Exception:
|
|
268
|
+
pass
|
|
269
|
+
return None
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
def _detect_java_version(project_root: Path) -> Optional[str]:
|
|
273
|
+
"""Detect Java version from pom.xml or build.gradle."""
|
|
274
|
+
# Check pom.xml (Maven)
|
|
275
|
+
pom_xml = project_root / "pom.xml"
|
|
276
|
+
if pom_xml.exists():
|
|
277
|
+
try:
|
|
278
|
+
content = pom_xml.read_text()
|
|
279
|
+
# Look for maven.compiler.source or java.version property
|
|
280
|
+
match = re.search(
|
|
281
|
+
r"<(?:maven\.compiler\.source|java\.version)>(\d+)</",
|
|
282
|
+
content,
|
|
283
|
+
)
|
|
284
|
+
if match:
|
|
285
|
+
return match.group(1)
|
|
286
|
+
# Look for release property
|
|
287
|
+
match = re.search(r"<release>(\d+)</release>", content)
|
|
288
|
+
if match:
|
|
289
|
+
return match.group(1)
|
|
290
|
+
except Exception:
|
|
291
|
+
pass
|
|
292
|
+
|
|
293
|
+
# Check build.gradle (Gradle)
|
|
294
|
+
for gradle_file in ["build.gradle", "build.gradle.kts"]:
|
|
295
|
+
gradle_path = project_root / gradle_file
|
|
296
|
+
if gradle_path.exists():
|
|
297
|
+
try:
|
|
298
|
+
content = gradle_path.read_text()
|
|
299
|
+
# Look for sourceCompatibility or targetCompatibility
|
|
300
|
+
match = re.search(
|
|
301
|
+
r"(?:source|target)Compatibility\s*=\s*['\"]?(?:JavaVersion\.VERSION_)?(\d+)",
|
|
302
|
+
content,
|
|
303
|
+
)
|
|
304
|
+
if match:
|
|
305
|
+
return match.group(1)
|
|
306
|
+
# Look for toolchain languageVersion
|
|
307
|
+
match = re.search(
|
|
308
|
+
r"languageVersion\.set\s*\(\s*JavaLanguageVersion\.of\s*\(\s*(\d+)\s*\)",
|
|
309
|
+
content,
|
|
310
|
+
)
|
|
311
|
+
if match:
|
|
312
|
+
return match.group(1)
|
|
313
|
+
except Exception:
|
|
314
|
+
pass
|
|
315
|
+
|
|
316
|
+
# Check .java-version file
|
|
317
|
+
java_version_file = project_root / ".java-version"
|
|
318
|
+
if java_version_file.exists():
|
|
319
|
+
try:
|
|
320
|
+
version = java_version_file.read_text().strip()
|
|
321
|
+
# Extract major version (e.g., "17.0.2" -> "17")
|
|
322
|
+
match = re.match(r"(\d+)", version)
|
|
323
|
+
if match:
|
|
324
|
+
return match.group(1)
|
|
325
|
+
except Exception:
|
|
326
|
+
pass
|
|
327
|
+
|
|
328
|
+
return None
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
"""Tool configuration detection module.
|
|
2
|
+
|
|
3
|
+
Detects existing tool configurations for:
|
|
4
|
+
- Linters (Ruff, ESLint, Biome, etc.)
|
|
5
|
+
- Type checkers (mypy, TypeScript, Pyright, etc.)
|
|
6
|
+
- Formatters (Black, Prettier, etc.)
|
|
7
|
+
- Security scanners (Trivy, etc.)
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import re
|
|
13
|
+
from dataclasses import dataclass
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
from typing import Any, Dict, Optional
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@dataclass
|
|
19
|
+
class ToolConfig:
|
|
20
|
+
"""Detected tool configuration."""
|
|
21
|
+
|
|
22
|
+
tool: str
|
|
23
|
+
"""Tool name (e.g., 'ruff', 'eslint')."""
|
|
24
|
+
|
|
25
|
+
config_file: Optional[Path] = None
|
|
26
|
+
"""Path to the configuration file."""
|
|
27
|
+
|
|
28
|
+
config_location: Optional[str] = None
|
|
29
|
+
"""Location type: 'file', 'pyproject.toml', 'package.json'."""
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
# Tool detection definitions
|
|
33
|
+
# Format: tool_name -> (config_files, pyproject_section, package_json_key)
|
|
34
|
+
TOOL_CONFIGS: Dict[str, Dict[str, Any]] = {
|
|
35
|
+
# Python linters
|
|
36
|
+
"ruff": {
|
|
37
|
+
"files": ["ruff.toml", ".ruff.toml"],
|
|
38
|
+
"pyproject_section": "tool.ruff",
|
|
39
|
+
},
|
|
40
|
+
"flake8": {
|
|
41
|
+
"files": [".flake8", "setup.cfg", "tox.ini"],
|
|
42
|
+
"pyproject_section": None, # flake8 doesn't use pyproject.toml natively
|
|
43
|
+
},
|
|
44
|
+
"pylint": {
|
|
45
|
+
"files": [".pylintrc", "pylintrc"],
|
|
46
|
+
"pyproject_section": "tool.pylint",
|
|
47
|
+
},
|
|
48
|
+
"black": {
|
|
49
|
+
"files": [".black.toml"],
|
|
50
|
+
"pyproject_section": "tool.black",
|
|
51
|
+
},
|
|
52
|
+
"isort": {
|
|
53
|
+
"files": [".isort.cfg"],
|
|
54
|
+
"pyproject_section": "tool.isort",
|
|
55
|
+
},
|
|
56
|
+
|
|
57
|
+
# Python type checkers
|
|
58
|
+
"mypy": {
|
|
59
|
+
"files": ["mypy.ini", ".mypy.ini"],
|
|
60
|
+
"pyproject_section": "tool.mypy",
|
|
61
|
+
},
|
|
62
|
+
"pyright": {
|
|
63
|
+
"files": ["pyrightconfig.json"],
|
|
64
|
+
"pyproject_section": "tool.pyright",
|
|
65
|
+
},
|
|
66
|
+
|
|
67
|
+
# Python test tools
|
|
68
|
+
"pytest": {
|
|
69
|
+
"files": ["pytest.ini", "setup.cfg"],
|
|
70
|
+
"pyproject_section": "tool.pytest",
|
|
71
|
+
},
|
|
72
|
+
"coverage": {
|
|
73
|
+
"files": [".coveragerc"],
|
|
74
|
+
"pyproject_section": "tool.coverage",
|
|
75
|
+
},
|
|
76
|
+
|
|
77
|
+
# JavaScript/TypeScript linters
|
|
78
|
+
"eslint": {
|
|
79
|
+
"files": [
|
|
80
|
+
".eslintrc", ".eslintrc.js", ".eslintrc.cjs", ".eslintrc.mjs",
|
|
81
|
+
".eslintrc.json", ".eslintrc.yaml", ".eslintrc.yml",
|
|
82
|
+
"eslint.config.js", "eslint.config.mjs", "eslint.config.cjs",
|
|
83
|
+
],
|
|
84
|
+
"package_json_key": "eslintConfig",
|
|
85
|
+
},
|
|
86
|
+
"biome": {
|
|
87
|
+
"files": ["biome.json", "biome.jsonc"],
|
|
88
|
+
},
|
|
89
|
+
"prettier": {
|
|
90
|
+
"files": [
|
|
91
|
+
".prettierrc", ".prettierrc.json", ".prettierrc.yaml",
|
|
92
|
+
".prettierrc.yml", ".prettierrc.js", ".prettierrc.cjs",
|
|
93
|
+
"prettier.config.js", "prettier.config.cjs",
|
|
94
|
+
],
|
|
95
|
+
"package_json_key": "prettier",
|
|
96
|
+
},
|
|
97
|
+
|
|
98
|
+
# TypeScript
|
|
99
|
+
"typescript": {
|
|
100
|
+
"files": ["tsconfig.json"],
|
|
101
|
+
},
|
|
102
|
+
|
|
103
|
+
# Security scanners
|
|
104
|
+
"trivy": {
|
|
105
|
+
"files": [".trivy.yaml", "trivy.yaml"],
|
|
106
|
+
},
|
|
107
|
+
"semgrep": {
|
|
108
|
+
"files": [".semgrep.yaml", ".semgrep.yml", "semgrep.yaml"],
|
|
109
|
+
},
|
|
110
|
+
"checkov": {
|
|
111
|
+
"files": [".checkov.yaml", ".checkov.yml"],
|
|
112
|
+
},
|
|
113
|
+
|
|
114
|
+
# JavaScript/TypeScript test runners
|
|
115
|
+
"jest": {
|
|
116
|
+
"files": ["jest.config.js", "jest.config.ts", "jest.config.mjs", "jest.config.cjs"],
|
|
117
|
+
"package_json_key": "jest",
|
|
118
|
+
},
|
|
119
|
+
"karma": {
|
|
120
|
+
"files": ["karma.conf.js", "karma.conf.ts", "karma.config.js", "karma.config.ts"],
|
|
121
|
+
},
|
|
122
|
+
"playwright": {
|
|
123
|
+
"files": ["playwright.config.js", "playwright.config.ts", "playwright.config.mjs"],
|
|
124
|
+
},
|
|
125
|
+
|
|
126
|
+
# Other
|
|
127
|
+
"pre-commit": {
|
|
128
|
+
"files": [".pre-commit-config.yaml"],
|
|
129
|
+
},
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def detect_tools(project_root: Path) -> dict[str, ToolConfig]:
|
|
134
|
+
"""Detect existing tool configurations in a project.
|
|
135
|
+
|
|
136
|
+
Args:
|
|
137
|
+
project_root: Path to the project root directory.
|
|
138
|
+
|
|
139
|
+
Returns:
|
|
140
|
+
Dictionary mapping tool names to their configurations.
|
|
141
|
+
"""
|
|
142
|
+
detected = {}
|
|
143
|
+
|
|
144
|
+
# Load pyproject.toml if it exists
|
|
145
|
+
pyproject_content = None
|
|
146
|
+
pyproject_path = project_root / "pyproject.toml"
|
|
147
|
+
if pyproject_path.exists():
|
|
148
|
+
try:
|
|
149
|
+
pyproject_content = pyproject_path.read_text()
|
|
150
|
+
except Exception:
|
|
151
|
+
pass
|
|
152
|
+
|
|
153
|
+
# Load package.json if it exists
|
|
154
|
+
package_json_content = None
|
|
155
|
+
package_json_path = project_root / "package.json"
|
|
156
|
+
if package_json_path.exists():
|
|
157
|
+
try:
|
|
158
|
+
import json
|
|
159
|
+
package_json_content = json.loads(package_json_path.read_text())
|
|
160
|
+
except Exception:
|
|
161
|
+
pass
|
|
162
|
+
|
|
163
|
+
# Check each tool
|
|
164
|
+
for tool_name, config in TOOL_CONFIGS.items():
|
|
165
|
+
result = _check_tool(
|
|
166
|
+
tool_name,
|
|
167
|
+
config,
|
|
168
|
+
project_root,
|
|
169
|
+
pyproject_content,
|
|
170
|
+
package_json_content,
|
|
171
|
+
)
|
|
172
|
+
if result:
|
|
173
|
+
detected[tool_name] = result
|
|
174
|
+
|
|
175
|
+
return detected
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def _check_tool(
|
|
179
|
+
tool_name: str,
|
|
180
|
+
config: dict,
|
|
181
|
+
project_root: Path,
|
|
182
|
+
pyproject_content: Optional[str],
|
|
183
|
+
package_json_content: Optional[dict],
|
|
184
|
+
) -> Optional[ToolConfig]:
|
|
185
|
+
"""Check if a specific tool is configured.
|
|
186
|
+
|
|
187
|
+
Args:
|
|
188
|
+
tool_name: Name of the tool.
|
|
189
|
+
config: Tool configuration definition.
|
|
190
|
+
project_root: Project root directory.
|
|
191
|
+
pyproject_content: Content of pyproject.toml (if exists).
|
|
192
|
+
package_json_content: Parsed package.json (if exists).
|
|
193
|
+
|
|
194
|
+
Returns:
|
|
195
|
+
ToolConfig if found, None otherwise.
|
|
196
|
+
"""
|
|
197
|
+
# Check for standalone config files
|
|
198
|
+
for config_file in config.get("files", []):
|
|
199
|
+
file_path = project_root / config_file
|
|
200
|
+
if file_path.exists():
|
|
201
|
+
return ToolConfig(
|
|
202
|
+
tool=tool_name,
|
|
203
|
+
config_file=file_path,
|
|
204
|
+
config_location="file",
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
# Check pyproject.toml section
|
|
208
|
+
pyproject_section = config.get("pyproject_section")
|
|
209
|
+
if pyproject_section and pyproject_content:
|
|
210
|
+
# Convert section to regex pattern (e.g., "tool.ruff" -> [tool.ruff])
|
|
211
|
+
section_pattern = pyproject_section.replace(".", r"\.")
|
|
212
|
+
if re.search(rf"\[{section_pattern}(\.[^\]]+)?\]", pyproject_content):
|
|
213
|
+
return ToolConfig(
|
|
214
|
+
tool=tool_name,
|
|
215
|
+
config_file=project_root / "pyproject.toml",
|
|
216
|
+
config_location="pyproject.toml",
|
|
217
|
+
)
|
|
218
|
+
|
|
219
|
+
# Check package.json key
|
|
220
|
+
package_key = config.get("package_json_key")
|
|
221
|
+
if package_key and package_json_content:
|
|
222
|
+
if package_key in package_json_content:
|
|
223
|
+
return ToolConfig(
|
|
224
|
+
tool=tool_name,
|
|
225
|
+
config_file=project_root / "package.json",
|
|
226
|
+
config_location="package.json",
|
|
227
|
+
)
|
|
228
|
+
|
|
229
|
+
return None
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"""Configuration generation module.
|
|
2
|
+
|
|
3
|
+
This module provides generators for:
|
|
4
|
+
- lucidscan.yml configuration files
|
|
5
|
+
- Package manager tool installation
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from lucidscan.generation.config_generator import ConfigGenerator, InitChoices
|
|
9
|
+
from lucidscan.generation.package_installer import PackageInstaller
|
|
10
|
+
|
|
11
|
+
__all__ = [
|
|
12
|
+
"ConfigGenerator",
|
|
13
|
+
"InitChoices",
|
|
14
|
+
"PackageInstaller",
|
|
15
|
+
]
|