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.
Files changed (91) hide show
  1. lucidscan/__init__.py +12 -0
  2. lucidscan/bootstrap/__init__.py +26 -0
  3. lucidscan/bootstrap/paths.py +160 -0
  4. lucidscan/bootstrap/platform.py +111 -0
  5. lucidscan/bootstrap/validation.py +76 -0
  6. lucidscan/bootstrap/versions.py +119 -0
  7. lucidscan/cli/__init__.py +50 -0
  8. lucidscan/cli/__main__.py +8 -0
  9. lucidscan/cli/arguments.py +405 -0
  10. lucidscan/cli/commands/__init__.py +64 -0
  11. lucidscan/cli/commands/autoconfigure.py +294 -0
  12. lucidscan/cli/commands/help.py +69 -0
  13. lucidscan/cli/commands/init.py +656 -0
  14. lucidscan/cli/commands/list_scanners.py +59 -0
  15. lucidscan/cli/commands/scan.py +307 -0
  16. lucidscan/cli/commands/serve.py +142 -0
  17. lucidscan/cli/commands/status.py +84 -0
  18. lucidscan/cli/commands/validate.py +105 -0
  19. lucidscan/cli/config_bridge.py +152 -0
  20. lucidscan/cli/exit_codes.py +17 -0
  21. lucidscan/cli/runner.py +284 -0
  22. lucidscan/config/__init__.py +29 -0
  23. lucidscan/config/ignore.py +178 -0
  24. lucidscan/config/loader.py +431 -0
  25. lucidscan/config/models.py +316 -0
  26. lucidscan/config/validation.py +645 -0
  27. lucidscan/core/__init__.py +3 -0
  28. lucidscan/core/domain_runner.py +463 -0
  29. lucidscan/core/git.py +174 -0
  30. lucidscan/core/logging.py +34 -0
  31. lucidscan/core/models.py +207 -0
  32. lucidscan/core/streaming.py +340 -0
  33. lucidscan/core/subprocess_runner.py +164 -0
  34. lucidscan/detection/__init__.py +21 -0
  35. lucidscan/detection/detector.py +154 -0
  36. lucidscan/detection/frameworks.py +270 -0
  37. lucidscan/detection/languages.py +328 -0
  38. lucidscan/detection/tools.py +229 -0
  39. lucidscan/generation/__init__.py +15 -0
  40. lucidscan/generation/config_generator.py +275 -0
  41. lucidscan/generation/package_installer.py +330 -0
  42. lucidscan/mcp/__init__.py +20 -0
  43. lucidscan/mcp/formatter.py +510 -0
  44. lucidscan/mcp/server.py +297 -0
  45. lucidscan/mcp/tools.py +1049 -0
  46. lucidscan/mcp/watcher.py +237 -0
  47. lucidscan/pipeline/__init__.py +17 -0
  48. lucidscan/pipeline/executor.py +187 -0
  49. lucidscan/pipeline/parallel.py +181 -0
  50. lucidscan/plugins/__init__.py +40 -0
  51. lucidscan/plugins/coverage/__init__.py +28 -0
  52. lucidscan/plugins/coverage/base.py +160 -0
  53. lucidscan/plugins/coverage/coverage_py.py +454 -0
  54. lucidscan/plugins/coverage/istanbul.py +411 -0
  55. lucidscan/plugins/discovery.py +107 -0
  56. lucidscan/plugins/enrichers/__init__.py +61 -0
  57. lucidscan/plugins/enrichers/base.py +63 -0
  58. lucidscan/plugins/linters/__init__.py +26 -0
  59. lucidscan/plugins/linters/base.py +125 -0
  60. lucidscan/plugins/linters/biome.py +448 -0
  61. lucidscan/plugins/linters/checkstyle.py +393 -0
  62. lucidscan/plugins/linters/eslint.py +368 -0
  63. lucidscan/plugins/linters/ruff.py +498 -0
  64. lucidscan/plugins/reporters/__init__.py +45 -0
  65. lucidscan/plugins/reporters/base.py +30 -0
  66. lucidscan/plugins/reporters/json_reporter.py +79 -0
  67. lucidscan/plugins/reporters/sarif_reporter.py +303 -0
  68. lucidscan/plugins/reporters/summary_reporter.py +61 -0
  69. lucidscan/plugins/reporters/table_reporter.py +81 -0
  70. lucidscan/plugins/scanners/__init__.py +57 -0
  71. lucidscan/plugins/scanners/base.py +60 -0
  72. lucidscan/plugins/scanners/checkov.py +484 -0
  73. lucidscan/plugins/scanners/opengrep.py +464 -0
  74. lucidscan/plugins/scanners/trivy.py +492 -0
  75. lucidscan/plugins/test_runners/__init__.py +27 -0
  76. lucidscan/plugins/test_runners/base.py +111 -0
  77. lucidscan/plugins/test_runners/jest.py +381 -0
  78. lucidscan/plugins/test_runners/karma.py +481 -0
  79. lucidscan/plugins/test_runners/playwright.py +434 -0
  80. lucidscan/plugins/test_runners/pytest.py +598 -0
  81. lucidscan/plugins/type_checkers/__init__.py +27 -0
  82. lucidscan/plugins/type_checkers/base.py +106 -0
  83. lucidscan/plugins/type_checkers/mypy.py +355 -0
  84. lucidscan/plugins/type_checkers/pyright.py +313 -0
  85. lucidscan/plugins/type_checkers/typescript.py +280 -0
  86. lucidscan-0.5.12.dist-info/METADATA +242 -0
  87. lucidscan-0.5.12.dist-info/RECORD +91 -0
  88. lucidscan-0.5.12.dist-info/WHEEL +5 -0
  89. lucidscan-0.5.12.dist-info/entry_points.txt +34 -0
  90. lucidscan-0.5.12.dist-info/licenses/LICENSE +201 -0
  91. lucidscan-0.5.12.dist-info/top_level.txt +1 -0
@@ -0,0 +1,21 @@
1
+ """Codebase detection module.
2
+
3
+ This module provides automatic detection of:
4
+ - Programming languages and their versions
5
+ - Frameworks and libraries
6
+ - Existing tool configurations (linters, type checkers, etc.)
7
+
8
+ Usage:
9
+ from lucidscan.detection import CodebaseDetector, ProjectContext
10
+
11
+ detector = CodebaseDetector()
12
+ context = detector.detect(Path("."))
13
+ """
14
+
15
+ from lucidscan.detection.detector import CodebaseDetector, ProjectContext, LanguageInfo
16
+
17
+ __all__ = [
18
+ "CodebaseDetector",
19
+ "ProjectContext",
20
+ "LanguageInfo",
21
+ ]
@@ -0,0 +1,154 @@
1
+ """Main codebase detector orchestrating all detection modules."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass, field
6
+ from pathlib import Path
7
+ from typing import Optional
8
+
9
+ from lucidscan.detection.languages import detect_languages, LanguageInfo
10
+ from lucidscan.detection.frameworks import detect_frameworks
11
+ from lucidscan.detection.tools import detect_tools, ToolConfig
12
+
13
+
14
+ @dataclass
15
+ class ProjectContext:
16
+ """Detected project characteristics.
17
+
18
+ This dataclass holds all information detected about a project,
19
+ including languages, frameworks, existing tools, and CI systems.
20
+ """
21
+
22
+ root: Path
23
+ """Project root directory."""
24
+
25
+ languages: list[LanguageInfo] = field(default_factory=list)
26
+ """Detected programming languages with metadata."""
27
+
28
+ package_managers: list[str] = field(default_factory=list)
29
+ """Detected package managers (pip, npm, cargo, etc.)."""
30
+
31
+ frameworks: list[str] = field(default_factory=list)
32
+ """Detected frameworks (fastapi, react, django, etc.)."""
33
+
34
+ existing_tools: dict[str, ToolConfig] = field(default_factory=dict)
35
+ """Existing tool configurations found in the project."""
36
+
37
+ test_frameworks: list[str] = field(default_factory=list)
38
+ """Detected test frameworks (pytest, jest, etc.)."""
39
+
40
+ @property
41
+ def primary_language(self) -> Optional[str]:
42
+ """Get the primary language (by file count).
43
+
44
+ Returns:
45
+ Primary language name or None if no languages detected.
46
+ """
47
+ if not self.languages:
48
+ return None
49
+ return max(self.languages, key=lambda lang: lang.file_count).name
50
+
51
+ @property
52
+ def has_python(self) -> bool:
53
+ """Check if project has Python code."""
54
+ return any(lang.name == "python" for lang in self.languages)
55
+
56
+ @property
57
+ def has_javascript(self) -> bool:
58
+ """Check if project has JavaScript/TypeScript code."""
59
+ return any(lang.name in ("javascript", "typescript") for lang in self.languages)
60
+
61
+ @property
62
+ def has_go(self) -> bool:
63
+ """Check if project has Go code."""
64
+ return any(lang.name == "go" for lang in self.languages)
65
+
66
+
67
+ class CodebaseDetector:
68
+ """Orchestrates codebase detection.
69
+
70
+ This class coordinates all detection modules to build a complete
71
+ ProjectContext for a given project directory.
72
+ """
73
+
74
+ def detect(self, project_root: Path) -> ProjectContext:
75
+ """Detect project characteristics.
76
+
77
+ Args:
78
+ project_root: Path to the project root directory.
79
+
80
+ Returns:
81
+ ProjectContext with detected information.
82
+ """
83
+ project_root = project_root.resolve()
84
+
85
+ # Detect languages first as other detectors may use this info
86
+ languages = detect_languages(project_root)
87
+
88
+ # Extract package managers from language detection
89
+ package_managers = self._extract_package_managers(languages, project_root)
90
+
91
+ # Detect frameworks based on dependencies
92
+ frameworks, test_frameworks = detect_frameworks(project_root)
93
+
94
+ # Detect existing tool configurations
95
+ existing_tools = detect_tools(project_root)
96
+
97
+ return ProjectContext(
98
+ root=project_root,
99
+ languages=languages,
100
+ package_managers=package_managers,
101
+ frameworks=frameworks,
102
+ existing_tools=existing_tools,
103
+ test_frameworks=test_frameworks,
104
+ )
105
+
106
+ def _extract_package_managers(
107
+ self,
108
+ languages: list[LanguageInfo],
109
+ project_root: Path,
110
+ ) -> list[str]:
111
+ """Extract package managers from detected languages and files.
112
+
113
+ Args:
114
+ languages: Detected languages.
115
+ project_root: Project root directory.
116
+
117
+ Returns:
118
+ List of detected package manager names.
119
+ """
120
+ managers = []
121
+
122
+ # Python package managers
123
+ if any(lang.name == "python" for lang in languages):
124
+ if (project_root / "pyproject.toml").exists():
125
+ managers.append("pip")
126
+ elif (project_root / "requirements.txt").exists():
127
+ managers.append("pip")
128
+ elif (project_root / "Pipfile").exists():
129
+ managers.append("pipenv")
130
+ elif (project_root / "poetry.lock").exists():
131
+ managers.append("poetry")
132
+
133
+ # JavaScript/TypeScript package managers
134
+ if any(lang.name in ("javascript", "typescript") for lang in languages):
135
+ if (project_root / "package-lock.json").exists():
136
+ managers.append("npm")
137
+ elif (project_root / "yarn.lock").exists():
138
+ managers.append("yarn")
139
+ elif (project_root / "pnpm-lock.yaml").exists():
140
+ managers.append("pnpm")
141
+ elif (project_root / "package.json").exists():
142
+ managers.append("npm") # Default
143
+
144
+ # Go
145
+ if any(lang.name == "go" for lang in languages):
146
+ if (project_root / "go.mod").exists():
147
+ managers.append("go")
148
+
149
+ # Rust
150
+ if any(lang.name == "rust" for lang in languages):
151
+ if (project_root / "Cargo.toml").exists():
152
+ managers.append("cargo")
153
+
154
+ return managers
@@ -0,0 +1,270 @@
1
+ """Framework detection module.
2
+
3
+ Detects web frameworks, testing frameworks, and libraries by analyzing:
4
+ - Dependencies in package manifests (pyproject.toml, package.json)
5
+ - Import statements (optional, for deeper analysis)
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import json
11
+ import re
12
+ from pathlib import Path
13
+ from typing import Dict, Optional
14
+
15
+ # Python frameworks and their package names
16
+ PYTHON_FRAMEWORKS: Dict[str, str] = {
17
+ "fastapi": "fastapi",
18
+ "django": "django",
19
+ "flask": "flask",
20
+ "starlette": "starlette",
21
+ "tornado": "tornado",
22
+ "aiohttp": "aiohttp",
23
+ "sanic": "sanic",
24
+ "quart": "quart",
25
+ "falcon": "falcon",
26
+ "bottle": "bottle",
27
+ "pyramid": "pyramid",
28
+ "cherrypy": "cherrypy",
29
+ }
30
+
31
+ # Python test frameworks
32
+ PYTHON_TEST_FRAMEWORKS: Dict[str, Optional[str]] = {
33
+ "pytest": "pytest",
34
+ "unittest": None, # Built-in, detected by imports
35
+ "nose": "nose",
36
+ "nose2": "nose2",
37
+ "hypothesis": "hypothesis",
38
+ }
39
+
40
+ # JavaScript/TypeScript frameworks
41
+ JS_FRAMEWORKS = {
42
+ "react": "react",
43
+ "vue": "vue",
44
+ "angular": "@angular/core",
45
+ "svelte": "svelte",
46
+ "next": "next",
47
+ "nuxt": "nuxt",
48
+ "express": "express",
49
+ "fastify": "fastify",
50
+ "nest": "@nestjs/core",
51
+ "koa": "koa",
52
+ "hapi": "@hapi/hapi",
53
+ }
54
+
55
+ # JavaScript test frameworks
56
+ JS_TEST_FRAMEWORKS = {
57
+ "jest": "jest",
58
+ "mocha": "mocha",
59
+ "vitest": "vitest",
60
+ "jasmine": "jasmine",
61
+ "cypress": "cypress",
62
+ "playwright": "@playwright/test",
63
+ }
64
+
65
+
66
+ def detect_frameworks(project_root: Path) -> tuple[list[str], list[str]]:
67
+ """Detect frameworks and test frameworks in a project.
68
+
69
+ Args:
70
+ project_root: Path to the project root directory.
71
+
72
+ Returns:
73
+ Tuple of (frameworks, test_frameworks).
74
+ """
75
+ frameworks = []
76
+ test_frameworks = []
77
+
78
+ # Check Python dependencies
79
+ python_deps = _get_python_dependencies(project_root)
80
+ for framework, package in PYTHON_FRAMEWORKS.items():
81
+ if package in python_deps:
82
+ frameworks.append(framework)
83
+
84
+ for framework, pkg in PYTHON_TEST_FRAMEWORKS.items():
85
+ if pkg and pkg in python_deps:
86
+ test_frameworks.append(framework)
87
+
88
+ # Check JavaScript/TypeScript dependencies
89
+ js_deps = _get_js_dependencies(project_root)
90
+ for framework, package in JS_FRAMEWORKS.items():
91
+ if package in js_deps:
92
+ frameworks.append(framework)
93
+
94
+ for framework, package in JS_TEST_FRAMEWORKS.items():
95
+ if package in js_deps:
96
+ test_frameworks.append(framework)
97
+
98
+ # Check for pytest.ini or conftest.py as indicators
99
+ if (project_root / "pytest.ini").exists() or (project_root / "conftest.py").exists():
100
+ if "pytest" not in test_frameworks:
101
+ test_frameworks.append("pytest")
102
+
103
+ # Check for jest.config.js
104
+ jest_configs = ["jest.config.js", "jest.config.ts", "jest.config.mjs"]
105
+ if any((project_root / cfg).exists() for cfg in jest_configs):
106
+ if "jest" not in test_frameworks:
107
+ test_frameworks.append("jest")
108
+
109
+ return frameworks, test_frameworks
110
+
111
+
112
+ def _get_python_dependencies(project_root: Path) -> set[str]:
113
+ """Extract Python dependencies from pyproject.toml or requirements.txt.
114
+
115
+ Args:
116
+ project_root: Project root directory.
117
+
118
+ Returns:
119
+ Set of package names (lowercase).
120
+ """
121
+ deps = set()
122
+
123
+ # Check pyproject.toml
124
+ pyproject = project_root / "pyproject.toml"
125
+ if pyproject.exists():
126
+ try:
127
+ content = pyproject.read_text()
128
+ deps.update(_parse_pyproject_deps(content))
129
+ except Exception:
130
+ pass
131
+
132
+ # Check requirements.txt
133
+ requirements = project_root / "requirements.txt"
134
+ if requirements.exists():
135
+ try:
136
+ deps.update(_parse_requirements_txt(requirements.read_text()))
137
+ except Exception:
138
+ pass
139
+
140
+ # Check requirements-dev.txt or requirements_dev.txt
141
+ for dev_req in ["requirements-dev.txt", "requirements_dev.txt", "dev-requirements.txt"]:
142
+ dev_requirements = project_root / dev_req
143
+ if dev_requirements.exists():
144
+ try:
145
+ deps.update(_parse_requirements_txt(dev_requirements.read_text()))
146
+ except Exception:
147
+ pass
148
+
149
+ return deps
150
+
151
+
152
+ def _parse_pyproject_deps(content: str) -> set[str]:
153
+ """Parse dependencies from pyproject.toml content.
154
+
155
+ Args:
156
+ content: pyproject.toml file content.
157
+
158
+ Returns:
159
+ Set of package names.
160
+ """
161
+ deps = set()
162
+
163
+ # Simple regex-based parsing for dependencies
164
+ # Matches: "package-name>=1.0" or "package-name[extra]>=1.0" etc.
165
+ # In dependencies array or optional-dependencies
166
+
167
+ # Find dependencies section
168
+ dep_section = re.search(
169
+ r'dependencies\s*=\s*\[(.*?)\]',
170
+ content,
171
+ re.DOTALL
172
+ )
173
+ if dep_section:
174
+ deps.update(_extract_package_names(dep_section.group(1)))
175
+
176
+ # Find optional-dependencies (all groups)
177
+ opt_deps = re.findall(
178
+ r'\[project\.optional-dependencies\.[^\]]+\]\s*\n([^\[]+)',
179
+ content
180
+ )
181
+ for section in opt_deps:
182
+ deps.update(_extract_package_names(section))
183
+
184
+ # Also check [tool.poetry.dependencies] for Poetry projects
185
+ poetry_deps = re.search(
186
+ r'\[tool\.poetry\.dependencies\](.*?)(?=\[|$)',
187
+ content,
188
+ re.DOTALL
189
+ )
190
+ if poetry_deps:
191
+ # Poetry uses package = "version" format
192
+ package_matches = re.findall(r'^(\w[\w-]*)\s*=', poetry_deps.group(1), re.MULTILINE)
193
+ deps.update(p.lower().replace("-", "_").replace("_", "-") for p in package_matches)
194
+
195
+ return deps
196
+
197
+
198
+ def _extract_package_names(text: str) -> set[str]:
199
+ """Extract package names from a dependencies list.
200
+
201
+ Args:
202
+ text: Text containing package specifications.
203
+
204
+ Returns:
205
+ Set of normalized package names.
206
+ """
207
+ deps = set()
208
+
209
+ # Match quoted strings like "fastapi>=0.100" or 'django[async]'
210
+ matches = re.findall(r'["\']([a-zA-Z][\w.-]*)', text)
211
+ for match in matches:
212
+ # Normalize: remove extras, convert to lowercase
213
+ package = re.sub(r'\[.*?\]', '', match).lower()
214
+ # Normalize underscores and hyphens
215
+ package = package.replace("_", "-")
216
+ deps.add(package)
217
+
218
+ return deps
219
+
220
+
221
+ def _parse_requirements_txt(content: str) -> set[str]:
222
+ """Parse package names from requirements.txt content.
223
+
224
+ Args:
225
+ content: requirements.txt file content.
226
+
227
+ Returns:
228
+ Set of package names.
229
+ """
230
+ deps = set()
231
+
232
+ for line in content.splitlines():
233
+ line = line.strip()
234
+ # Skip comments and empty lines
235
+ if not line or line.startswith("#") or line.startswith("-"):
236
+ continue
237
+
238
+ # Extract package name (before any version specifier)
239
+ match = re.match(r'^([a-zA-Z][\w.-]*)', line)
240
+ if match:
241
+ package = match.group(1).lower().replace("_", "-")
242
+ deps.add(package)
243
+
244
+ return deps
245
+
246
+
247
+ def _get_js_dependencies(project_root: Path) -> set[str]:
248
+ """Extract JavaScript/TypeScript dependencies from package.json.
249
+
250
+ Args:
251
+ project_root: Project root directory.
252
+
253
+ Returns:
254
+ Set of package names.
255
+ """
256
+ deps = set()
257
+
258
+ package_json = project_root / "package.json"
259
+ if package_json.exists():
260
+ try:
261
+ data = json.loads(package_json.read_text())
262
+
263
+ # Collect from all dependency types
264
+ for dep_type in ["dependencies", "devDependencies", "peerDependencies"]:
265
+ if dep_type in data:
266
+ deps.update(data[dep_type].keys())
267
+ except Exception:
268
+ pass
269
+
270
+ return deps