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,294 @@
|
|
|
1
|
+
"""Autoconfigure command implementation.
|
|
2
|
+
|
|
3
|
+
Opinionated project autoconfiguration that:
|
|
4
|
+
1. Detects project characteristics
|
|
5
|
+
2. Auto-selects recommended tools when none are detected
|
|
6
|
+
3. Installs tools to package manager files
|
|
7
|
+
4. Generates lucidscan.yml configuration
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
from argparse import Namespace
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
from typing import TYPE_CHECKING, List
|
|
15
|
+
|
|
16
|
+
if TYPE_CHECKING:
|
|
17
|
+
from lucidscan.config.models import LucidScanConfig
|
|
18
|
+
|
|
19
|
+
import questionary
|
|
20
|
+
from questionary import Style
|
|
21
|
+
|
|
22
|
+
from lucidscan.cli.commands import Command
|
|
23
|
+
from lucidscan.cli.exit_codes import EXIT_SUCCESS, EXIT_INVALID_USAGE
|
|
24
|
+
from lucidscan.core.logging import get_logger
|
|
25
|
+
from lucidscan.detection import CodebaseDetector, ProjectContext
|
|
26
|
+
from lucidscan.generation import ConfigGenerator, InitChoices, PackageInstaller
|
|
27
|
+
|
|
28
|
+
LOGGER = get_logger(__name__)
|
|
29
|
+
|
|
30
|
+
# Custom questionary style
|
|
31
|
+
STYLE = Style([
|
|
32
|
+
("qmark", "fg:cyan bold"),
|
|
33
|
+
("question", "bold"),
|
|
34
|
+
("answer", "fg:cyan"),
|
|
35
|
+
("pointer", "fg:cyan bold"),
|
|
36
|
+
("highlighted", "fg:cyan bold"),
|
|
37
|
+
("selected", "fg:green"),
|
|
38
|
+
("separator", "fg:gray"),
|
|
39
|
+
("instruction", "fg:gray"),
|
|
40
|
+
])
|
|
41
|
+
|
|
42
|
+
# Opinionated defaults - tools to use when none are detected
|
|
43
|
+
PYTHON_DEFAULT_LINTER = "ruff"
|
|
44
|
+
PYTHON_DEFAULT_TYPE_CHECKER = "mypy"
|
|
45
|
+
PYTHON_DEFAULT_TEST_RUNNER = "pytest"
|
|
46
|
+
JS_DEFAULT_LINTER = "eslint"
|
|
47
|
+
JS_DEFAULT_TYPE_CHECKER = "typescript"
|
|
48
|
+
JS_DEFAULT_TEST_RUNNER = "jest"
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class AutoconfigureCommand(Command):
|
|
52
|
+
"""Opinionated project autoconfiguration command."""
|
|
53
|
+
|
|
54
|
+
@property
|
|
55
|
+
def name(self) -> str:
|
|
56
|
+
"""Command identifier."""
|
|
57
|
+
return "autoconfigure"
|
|
58
|
+
|
|
59
|
+
def execute(self, args: Namespace, config: "LucidScanConfig | None" = None) -> int:
|
|
60
|
+
"""Execute the autoconfigure command.
|
|
61
|
+
|
|
62
|
+
Args:
|
|
63
|
+
args: Parsed command-line arguments.
|
|
64
|
+
config: Optional LucidScan configuration (unused).
|
|
65
|
+
|
|
66
|
+
Returns:
|
|
67
|
+
Exit code.
|
|
68
|
+
"""
|
|
69
|
+
project_root = Path(args.path).resolve()
|
|
70
|
+
|
|
71
|
+
if not project_root.is_dir():
|
|
72
|
+
print(f"Error: {project_root} is not a directory")
|
|
73
|
+
return EXIT_INVALID_USAGE
|
|
74
|
+
|
|
75
|
+
# Check for existing config
|
|
76
|
+
config_path = project_root / "lucidscan.yml"
|
|
77
|
+
if config_path.exists() and not args.force:
|
|
78
|
+
if args.non_interactive:
|
|
79
|
+
print(f"Error: {config_path} already exists. Use --force to overwrite.")
|
|
80
|
+
return EXIT_INVALID_USAGE
|
|
81
|
+
|
|
82
|
+
overwrite = questionary.confirm(
|
|
83
|
+
"lucidscan.yml already exists. Overwrite?",
|
|
84
|
+
default=False,
|
|
85
|
+
style=STYLE,
|
|
86
|
+
).ask()
|
|
87
|
+
|
|
88
|
+
if not overwrite:
|
|
89
|
+
print("Aborted.")
|
|
90
|
+
return EXIT_SUCCESS
|
|
91
|
+
|
|
92
|
+
# Detect project
|
|
93
|
+
print("\nAnalyzing project...\n")
|
|
94
|
+
detector = CodebaseDetector()
|
|
95
|
+
context = detector.detect(project_root)
|
|
96
|
+
|
|
97
|
+
# Display detection results
|
|
98
|
+
self._display_detection(context)
|
|
99
|
+
|
|
100
|
+
# Get opinionated choices (auto-select tools)
|
|
101
|
+
choices = self._get_opinionated_choices(context, args)
|
|
102
|
+
|
|
103
|
+
# Display what will be configured
|
|
104
|
+
print("\nConfiguration:")
|
|
105
|
+
self._display_choices(choices, context)
|
|
106
|
+
|
|
107
|
+
# In interactive mode, confirm before proceeding
|
|
108
|
+
if not args.non_interactive:
|
|
109
|
+
proceed = questionary.confirm(
|
|
110
|
+
"Proceed with this configuration?",
|
|
111
|
+
default=True,
|
|
112
|
+
style=STYLE,
|
|
113
|
+
).ask()
|
|
114
|
+
|
|
115
|
+
if not proceed:
|
|
116
|
+
print("\nAborted.")
|
|
117
|
+
return EXIT_SUCCESS
|
|
118
|
+
|
|
119
|
+
# Install tools to package files
|
|
120
|
+
tools_to_install = self._get_tools_to_install(choices, context)
|
|
121
|
+
if tools_to_install:
|
|
122
|
+
print("\nInstalling tools...")
|
|
123
|
+
installer = PackageInstaller()
|
|
124
|
+
installed = installer.install_tools(context, tools_to_install)
|
|
125
|
+
|
|
126
|
+
for tool, path in installed.items():
|
|
127
|
+
rel_path = path.relative_to(project_root)
|
|
128
|
+
print(f" Added {tool} to {rel_path}")
|
|
129
|
+
|
|
130
|
+
if installed:
|
|
131
|
+
# Show install command hint
|
|
132
|
+
if context.has_python:
|
|
133
|
+
print("\n Run: pip install -e '.[dev]' to install tools")
|
|
134
|
+
if context.has_javascript:
|
|
135
|
+
print("\n Run: npm install to install tools")
|
|
136
|
+
|
|
137
|
+
# Generate configuration
|
|
138
|
+
print("\nGenerating configuration...")
|
|
139
|
+
|
|
140
|
+
config_gen = ConfigGenerator()
|
|
141
|
+
config_path = config_gen.write(context, choices)
|
|
142
|
+
print(f" Created {config_path.relative_to(project_root)}")
|
|
143
|
+
|
|
144
|
+
# Summary
|
|
145
|
+
print("\nDone! Next steps:")
|
|
146
|
+
print(" 1. Review the generated lucidscan.yml")
|
|
147
|
+
print(" 2. Run 'lucidscan scan --all' to test the configuration")
|
|
148
|
+
|
|
149
|
+
return EXIT_SUCCESS
|
|
150
|
+
|
|
151
|
+
def _display_detection(self, context: ProjectContext) -> None:
|
|
152
|
+
"""Display detected project characteristics."""
|
|
153
|
+
print("Detected:")
|
|
154
|
+
|
|
155
|
+
# Languages
|
|
156
|
+
if context.languages:
|
|
157
|
+
langs = []
|
|
158
|
+
for lang in context.languages[:3]: # Show top 3
|
|
159
|
+
version = f" {lang.version}" if lang.version else ""
|
|
160
|
+
langs.append(f"{lang.name.title()}{version}")
|
|
161
|
+
print(f" Languages: {', '.join(langs)}")
|
|
162
|
+
else:
|
|
163
|
+
print(" Languages: (none detected)")
|
|
164
|
+
|
|
165
|
+
# Frameworks
|
|
166
|
+
if context.frameworks:
|
|
167
|
+
print(f" Frameworks: {', '.join(context.frameworks[:3])}")
|
|
168
|
+
|
|
169
|
+
# Test frameworks
|
|
170
|
+
if context.test_frameworks:
|
|
171
|
+
print(f" Testing: {', '.join(context.test_frameworks)}")
|
|
172
|
+
|
|
173
|
+
# Existing tools
|
|
174
|
+
if context.existing_tools:
|
|
175
|
+
tools = list(context.existing_tools.keys())[:5]
|
|
176
|
+
print(f" Tools: {', '.join(tools)}")
|
|
177
|
+
|
|
178
|
+
print()
|
|
179
|
+
|
|
180
|
+
def _get_opinionated_choices(
|
|
181
|
+
self,
|
|
182
|
+
context: ProjectContext,
|
|
183
|
+
args: Namespace,
|
|
184
|
+
) -> InitChoices:
|
|
185
|
+
"""Get opinionated default choices.
|
|
186
|
+
|
|
187
|
+
This method auto-selects tools based on the detected project.
|
|
188
|
+
If tools are already detected, use them. Otherwise, pick our
|
|
189
|
+
recommended defaults.
|
|
190
|
+
|
|
191
|
+
Args:
|
|
192
|
+
context: Detected project context.
|
|
193
|
+
args: Parsed command-line arguments.
|
|
194
|
+
|
|
195
|
+
Returns:
|
|
196
|
+
InitChoices with opinionated defaults.
|
|
197
|
+
"""
|
|
198
|
+
choices = InitChoices()
|
|
199
|
+
|
|
200
|
+
# Linter: use detected or default
|
|
201
|
+
if context.has_python:
|
|
202
|
+
if "ruff" in context.existing_tools:
|
|
203
|
+
choices.linter = "ruff"
|
|
204
|
+
else:
|
|
205
|
+
choices.linter = PYTHON_DEFAULT_LINTER
|
|
206
|
+
elif context.has_javascript:
|
|
207
|
+
if "eslint" in context.existing_tools:
|
|
208
|
+
choices.linter = "eslint"
|
|
209
|
+
elif "biome" in context.existing_tools:
|
|
210
|
+
choices.linter = "biome"
|
|
211
|
+
else:
|
|
212
|
+
choices.linter = JS_DEFAULT_LINTER
|
|
213
|
+
|
|
214
|
+
# Type checker: use detected or default
|
|
215
|
+
if context.has_python:
|
|
216
|
+
if "mypy" in context.existing_tools:
|
|
217
|
+
choices.type_checker = "mypy"
|
|
218
|
+
elif "pyright" in context.existing_tools:
|
|
219
|
+
choices.type_checker = "pyright"
|
|
220
|
+
else:
|
|
221
|
+
choices.type_checker = PYTHON_DEFAULT_TYPE_CHECKER
|
|
222
|
+
elif context.has_javascript:
|
|
223
|
+
if "typescript" in context.existing_tools:
|
|
224
|
+
choices.type_checker = "typescript"
|
|
225
|
+
else:
|
|
226
|
+
choices.type_checker = JS_DEFAULT_TYPE_CHECKER
|
|
227
|
+
|
|
228
|
+
# Security always enabled
|
|
229
|
+
choices.security_enabled = True
|
|
230
|
+
choices.security_tools = ["trivy", "opengrep"]
|
|
231
|
+
|
|
232
|
+
# Test runner: use detected or default
|
|
233
|
+
if context.test_frameworks:
|
|
234
|
+
choices.test_runner = context.test_frameworks[0]
|
|
235
|
+
elif context.has_python:
|
|
236
|
+
choices.test_runner = PYTHON_DEFAULT_TEST_RUNNER
|
|
237
|
+
elif context.has_javascript:
|
|
238
|
+
choices.test_runner = JS_DEFAULT_TEST_RUNNER
|
|
239
|
+
|
|
240
|
+
return choices
|
|
241
|
+
|
|
242
|
+
def _display_choices(self, choices: InitChoices, context: ProjectContext) -> None:
|
|
243
|
+
"""Display the tools that will be configured."""
|
|
244
|
+
items = []
|
|
245
|
+
|
|
246
|
+
if choices.linter:
|
|
247
|
+
status = "(detected)" if choices.linter in context.existing_tools else "(will install)"
|
|
248
|
+
items.append(f" Linter: {choices.linter} {status}")
|
|
249
|
+
|
|
250
|
+
if choices.type_checker:
|
|
251
|
+
status = "(detected)" if choices.type_checker in context.existing_tools else "(will install)"
|
|
252
|
+
items.append(f" Type checker: {choices.type_checker} {status}")
|
|
253
|
+
|
|
254
|
+
if choices.security_enabled:
|
|
255
|
+
items.append(f" Security: {', '.join(choices.security_tools)}")
|
|
256
|
+
|
|
257
|
+
if choices.test_runner:
|
|
258
|
+
status = "(detected)" if choices.test_runner in context.test_frameworks else "(will install)"
|
|
259
|
+
items.append(f" Test runner: {choices.test_runner} {status}")
|
|
260
|
+
|
|
261
|
+
for item in items:
|
|
262
|
+
print(item)
|
|
263
|
+
|
|
264
|
+
def _get_tools_to_install(
|
|
265
|
+
self,
|
|
266
|
+
choices: InitChoices,
|
|
267
|
+
context: ProjectContext,
|
|
268
|
+
) -> List[str]:
|
|
269
|
+
"""Get list of tools that need to be installed.
|
|
270
|
+
|
|
271
|
+
Only returns tools that are not already detected in the project.
|
|
272
|
+
|
|
273
|
+
Args:
|
|
274
|
+
choices: Selected tool choices.
|
|
275
|
+
context: Detected project context.
|
|
276
|
+
|
|
277
|
+
Returns:
|
|
278
|
+
List of tool names to install.
|
|
279
|
+
"""
|
|
280
|
+
tools = []
|
|
281
|
+
|
|
282
|
+
# Linter
|
|
283
|
+
if choices.linter and choices.linter not in context.existing_tools:
|
|
284
|
+
tools.append(choices.linter)
|
|
285
|
+
|
|
286
|
+
# Type checker
|
|
287
|
+
if choices.type_checker and choices.type_checker not in context.existing_tools:
|
|
288
|
+
tools.append(choices.type_checker)
|
|
289
|
+
|
|
290
|
+
# Test runner
|
|
291
|
+
if choices.test_runner and choices.test_runner not in context.test_frameworks:
|
|
292
|
+
tools.append(choices.test_runner)
|
|
293
|
+
|
|
294
|
+
return tools
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"""Help command implementation."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from argparse import Namespace
|
|
6
|
+
from importlib.resources import files # nosemgrep: python37-compatibility-importlib2
|
|
7
|
+
from typing import TYPE_CHECKING
|
|
8
|
+
|
|
9
|
+
if TYPE_CHECKING:
|
|
10
|
+
from lucidscan.config.models import LucidScanConfig
|
|
11
|
+
|
|
12
|
+
from lucidscan.cli.commands import Command
|
|
13
|
+
from lucidscan.cli.exit_codes import EXIT_SUCCESS
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def get_help_content() -> str:
|
|
17
|
+
"""Load help documentation from package resources.
|
|
18
|
+
|
|
19
|
+
Returns:
|
|
20
|
+
Help documentation as markdown string.
|
|
21
|
+
"""
|
|
22
|
+
# Try to load from package resources first (installed package)
|
|
23
|
+
try:
|
|
24
|
+
docs = files("lucidscan").joinpath("../../../docs/help.md")
|
|
25
|
+
return docs.read_text(encoding="utf-8")
|
|
26
|
+
except (FileNotFoundError, TypeError):
|
|
27
|
+
pass
|
|
28
|
+
|
|
29
|
+
# Fall back to relative path (development)
|
|
30
|
+
from pathlib import Path
|
|
31
|
+
|
|
32
|
+
docs_path = Path(__file__).parent.parent.parent.parent.parent / "docs" / "help.md"
|
|
33
|
+
if docs_path.exists():
|
|
34
|
+
return docs_path.read_text(encoding="utf-8")
|
|
35
|
+
|
|
36
|
+
return "Help documentation not found. Visit https://github.com/voldeq/lucidscan"
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class HelpCommand(Command):
|
|
40
|
+
"""Shows LucidScan documentation."""
|
|
41
|
+
|
|
42
|
+
def __init__(self, version: str):
|
|
43
|
+
"""Initialize HelpCommand.
|
|
44
|
+
|
|
45
|
+
Args:
|
|
46
|
+
version: Current lucidscan version string.
|
|
47
|
+
"""
|
|
48
|
+
self._version = version
|
|
49
|
+
|
|
50
|
+
@property
|
|
51
|
+
def name(self) -> str:
|
|
52
|
+
"""Command identifier."""
|
|
53
|
+
return "help"
|
|
54
|
+
|
|
55
|
+
def execute(self, args: Namespace, config: "LucidScanConfig | None" = None) -> int:
|
|
56
|
+
"""Execute the help command.
|
|
57
|
+
|
|
58
|
+
Displays LucidScan documentation.
|
|
59
|
+
|
|
60
|
+
Args:
|
|
61
|
+
args: Parsed command-line arguments.
|
|
62
|
+
config: Optional LucidScan configuration (unused).
|
|
63
|
+
|
|
64
|
+
Returns:
|
|
65
|
+
Exit code (always 0 for help).
|
|
66
|
+
"""
|
|
67
|
+
content = get_help_content()
|
|
68
|
+
print(content)
|
|
69
|
+
return EXIT_SUCCESS
|