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,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