skylos 0.0.5__cp39-cp39-win_amd64.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.

Potentially problematic release.


This version of skylos might be problematic. Click here for more details.

skylos/__init__.py ADDED
@@ -0,0 +1,30 @@
1
+ from importlib import metadata as _md
2
+
3
+ try:
4
+ __version__ = _md.version(__name__)
5
+ except _md.PackageNotFoundError:
6
+ __version__ = "0.0.0.dev0"
7
+
8
+
9
+ from ._core import analyze as _analyze
10
+
11
+ def analyze(path: str) -> str:
12
+ """Return the JSON produced by Rust's ``analyze``."""
13
+ return _analyze(path)
14
+
15
+ # ------------------------------------------------------------------ #
16
+ # helpers the tests expect
17
+ # ------------------------------------------------------------------ #
18
+ def debug_test() -> str:
19
+ return "debug-ok"
20
+
21
+ __all__ = ["analyze", "debug_test", "__version__"]
22
+
23
+ def _patch_metadata() -> None:
24
+ orig_version = _md.version
25
+ def _fake_version(name: str): # type: ignore
26
+ if name == __name__:
27
+ return __version__
28
+ return orig_version(name)
29
+ _md.version = _fake_version
30
+ _patch_metadata()
Binary file
skylos/cli.py ADDED
@@ -0,0 +1,332 @@
1
+ import argparse
2
+ import json
3
+ import sys
4
+ import logging
5
+ import ast
6
+ import skylos
7
+
8
+ try:
9
+ import inquirer
10
+ INTERACTIVE_AVAILABLE = True
11
+ except ImportError:
12
+ INTERACTIVE_AVAILABLE = False
13
+
14
+ class Colors:
15
+ RED = '\033[91m'
16
+ GREEN = '\033[92m'
17
+ YELLOW = '\033[93m'
18
+ BLUE = '\033[94m'
19
+ MAGENTA = '\033[95m'
20
+ CYAN = '\033[96m'
21
+ WHITE = '\033[97m'
22
+ BOLD = '\033[1m'
23
+ UNDERLINE = '\033[4m'
24
+ RESET = '\033[0m'
25
+ GRAY = '\033[90m'
26
+
27
+ class ColoredFormatter(logging.Formatter):
28
+ def format(self, record):
29
+ return super().format(record)
30
+
31
+ def setup_logger(output_file=None):
32
+ logger = logging.getLogger('skylos')
33
+ logger.setLevel(logging.INFO)
34
+
35
+ logger.handlers.clear()
36
+
37
+ formatter = ColoredFormatter('%(message)s')
38
+
39
+ console_handler = logging.StreamHandler(sys.stdout)
40
+ console_handler.setFormatter(formatter)
41
+ logger.addHandler(console_handler)
42
+
43
+ if output_file:
44
+ file_handler = logging.FileHandler(output_file)
45
+ file_handler.setFormatter(formatter)
46
+ logger.addHandler(file_handler)
47
+
48
+ return logger
49
+
50
+ def remove_unused_import(file_path: str, import_name: str, line_number: int) -> bool:
51
+ try:
52
+ with open(file_path, 'r') as f:
53
+ lines = f.readlines()
54
+
55
+ line_idx = line_number - 1
56
+ original_line = lines[line_idx].strip()
57
+
58
+ if original_line.startswith(f'import {import_name}'):
59
+ lines[line_idx] = ''
60
+ elif original_line.startswith('import ') and f' {import_name}' in original_line:
61
+ parts = original_line.split(' ', 1)[1].split(',')
62
+ new_parts = [p.strip() for p in parts if p.strip() != import_name]
63
+ if new_parts:
64
+ lines[line_idx] = f'import {", ".join(new_parts)}\n'
65
+ else:
66
+ lines[line_idx] = ''
67
+ elif original_line.startswith('from ') and import_name in original_line:
68
+ if f'import {import_name}' in original_line and ',' not in original_line:
69
+ lines[line_idx] = ''
70
+ else:
71
+ parts = original_line.split('import ', 1)[1].split(',')
72
+ new_parts = [p.strip() for p in parts if p.strip() != import_name]
73
+ if new_parts:
74
+ prefix = original_line.split(' import ')[0]
75
+ lines[line_idx] = f'{prefix} import {", ".join(new_parts)}\n'
76
+ else:
77
+ lines[line_idx] = ''
78
+
79
+ with open(file_path, 'w') as f:
80
+ f.writelines(lines)
81
+
82
+ return True
83
+ except Exception as e:
84
+ logging.error(f"Failed to remove import {import_name} from {file_path}: {e}")
85
+ return False
86
+
87
+ def remove_unused_function(file_path: str, function_name: str, line_number: int) -> bool:
88
+ try:
89
+ with open(file_path, 'r') as f:
90
+ content = f.read()
91
+
92
+ tree = ast.parse(content)
93
+
94
+ lines = content.splitlines()
95
+ for node in ast.walk(tree):
96
+ if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
97
+ if (node.name in function_name and
98
+ node.lineno == line_number):
99
+
100
+ start_line = node.lineno - 1
101
+
102
+ if node.decorator_list:
103
+ start_line = node.decorator_list[0].lineno - 1
104
+
105
+ end_line = len(lines)
106
+ base_indent = len(lines[start_line]) - len(lines[start_line].lstrip())
107
+
108
+ for i in range(node.end_lineno, len(lines)):
109
+ if lines[i].strip() == '':
110
+ continue
111
+ current_indent = len(lines[i]) - len(lines[i].lstrip())
112
+ if current_indent <= base_indent and lines[i].strip():
113
+ end_line = i
114
+ break
115
+
116
+ while end_line < len(lines) and lines[end_line].strip() == '':
117
+ end_line += 1
118
+
119
+ new_lines = lines[:start_line] + lines[end_line:]
120
+
121
+ with open(file_path, 'w') as f:
122
+ f.write('\n'.join(new_lines) + '\n')
123
+
124
+ return True
125
+
126
+ return False
127
+ except Exception as e:
128
+ logging.error(f"Failed to remove function {function_name} from {file_path}: {e}")
129
+ return False
130
+
131
+ def interactive_selection(logger, unused_functions, unused_imports):
132
+ if not INTERACTIVE_AVAILABLE:
133
+ logger.error("Interactive mode requires 'inquirer' package. Install with: pip install inquirer")
134
+ return [], []
135
+
136
+ selected_functions = []
137
+ selected_imports = []
138
+
139
+ if unused_functions:
140
+ logger.info(f"\n{Colors.CYAN}{Colors.BOLD}Select unused functions to remove:{Colors.RESET}")
141
+
142
+ function_choices = []
143
+ for item in unused_functions:
144
+ choice_text = f"{item['name']} ({item['file']}:{item['line']})"
145
+ function_choices.append((choice_text, item))
146
+
147
+ questions = [
148
+ inquirer.Checkbox('functions',
149
+ message="Select functions to remove",
150
+ choices=function_choices,
151
+ )
152
+ ]
153
+
154
+ answers = inquirer.prompt(questions)
155
+ if answers:
156
+ selected_functions = answers['functions']
157
+
158
+ if unused_imports:
159
+ logger.info(f"\n{Colors.MAGENTA}{Colors.BOLD}Select unused imports to remove:{Colors.RESET}")
160
+
161
+ import_choices = []
162
+ for item in unused_imports:
163
+ choice_text = f"{item['name']} ({item['file']}:{item['line']})"
164
+ import_choices.append((choice_text, item))
165
+
166
+ questions = [
167
+ inquirer.Checkbox('imports',
168
+ message="Select imports to remove",
169
+ choices=import_choices,
170
+ )
171
+ ]
172
+
173
+ answers = inquirer.prompt(questions)
174
+ if answers:
175
+ selected_imports = answers['imports']
176
+
177
+ return selected_functions, selected_imports
178
+
179
+ def main() -> None:
180
+ parser = argparse.ArgumentParser(
181
+ description="Detect unreachable functions and unused imports in a Python project"
182
+ )
183
+ parser.add_argument("path", help="Path to the Python project to analyze")
184
+ parser.add_argument(
185
+ "--json",
186
+ action="store_true",
187
+ help="Output raw JSON instead of formatted text",
188
+ )
189
+ parser.add_argument(
190
+ "--output",
191
+ "-o",
192
+ type=str,
193
+ help="Write output to file instead of stdout",
194
+ )
195
+ parser.add_argument(
196
+ "--verbose", "-v",
197
+ action="store_true",
198
+ help="Enable verbose output"
199
+ )
200
+ parser.add_argument(
201
+ "--interactive", "-i",
202
+ action="store_true",
203
+ help="Interactively select items to remove (requires inquirer)"
204
+ )
205
+ parser.add_argument(
206
+ "--dry-run",
207
+ action="store_true",
208
+ help="Show what would be removed without actually modifying files"
209
+ )
210
+
211
+ args = parser.parse_args()
212
+ logger = setup_logger(args.output)
213
+
214
+ if args.verbose:
215
+ logger.setLevel(logging.DEBUG)
216
+ logger.debug(f"Analyzing path: {args.path}")
217
+
218
+ try:
219
+ result_json = skylos.analyze(args.path)
220
+ result = json.loads(result_json)
221
+ except Exception as e:
222
+ logger.error(f"Error during analysis: {e}")
223
+ sys.exit(1)
224
+
225
+ if args.json:
226
+ logger.info(result_json)
227
+ else:
228
+ unused_functions = result.get("unused_functions", [])
229
+ unused_imports = result.get("unused_imports", [])
230
+
231
+ logger.info(f"\n{Colors.CYAN}{Colors.BOLD}🔍 Python Static Analysis Results{Colors.RESET}")
232
+ logger.info(f"{Colors.CYAN}{'=' * 35}{Colors.RESET}")
233
+
234
+ logger.info(f"\n{Colors.BOLD}Summary:{Colors.RESET}")
235
+ logger.info(f" • Unreachable functions: {Colors.YELLOW}{len(unused_functions)}{Colors.RESET}")
236
+ logger.info(f" • Unused imports: {Colors.YELLOW}{len(unused_imports)}{Colors.RESET}")
237
+
238
+ if args.interactive and (unused_functions or unused_imports):
239
+ logger.info(f"\n{Colors.BOLD}Interactive Mode:{Colors.RESET}")
240
+ selected_functions, selected_imports = interactive_selection(logger, unused_functions, unused_imports)
241
+
242
+ if selected_functions or selected_imports:
243
+ logger.info(f"\n{Colors.BOLD}Selected items to remove:{Colors.RESET}")
244
+
245
+ if selected_functions:
246
+ logger.info(f" Functions: {len(selected_functions)}")
247
+ for func in selected_functions:
248
+ logger.info(f" - {func['name']} ({func['file']}:{func['line']})")
249
+
250
+ if selected_imports:
251
+ logger.info(f" Imports: {len(selected_imports)}")
252
+ for imp in selected_imports:
253
+ logger.info(f" - {imp['name']} ({imp['file']}:{imp['line']})")
254
+
255
+ if not args.dry_run:
256
+ questions = [
257
+ inquirer.Confirm('confirm',
258
+ message="Are you sure you want to remove these items?",
259
+ default=False)
260
+ ]
261
+ answers = inquirer.prompt(questions)
262
+
263
+ if answers and answers['confirm']:
264
+ logger.info(f"\n{Colors.YELLOW}Removing selected items...{Colors.RESET}")
265
+
266
+ for func in selected_functions:
267
+ success = remove_unused_function(func['file'], func['name'], func['line'])
268
+ if success:
269
+ logger.info(f" {Colors.GREEN}✓{Colors.RESET} Removed function: {func['name']}")
270
+ else:
271
+ logger.error(f" {Colors.RED}✗{Colors.RESET} Failed to remove: {func['name']}")
272
+
273
+ for imp in selected_imports:
274
+ success = remove_unused_import(imp['file'], imp['name'], imp['line'])
275
+ if success:
276
+ logger.info(f" {Colors.GREEN}✓{Colors.RESET} Removed import: {imp['name']}")
277
+ else:
278
+ logger.error(f" {Colors.RED}✗{Colors.RESET} Failed to remove: {imp['name']}")
279
+
280
+ logger.info(f"\n{Colors.GREEN}Cleanup complete!{Colors.RESET}")
281
+ else:
282
+ logger.info(f"\n{Colors.YELLOW}Operation cancelled.{Colors.RESET}")
283
+ else:
284
+ logger.info(f"\n{Colors.YELLOW}Dry run - no files were modified.{Colors.RESET}")
285
+ else:
286
+ logger.info(f"\n{Colors.BLUE}No items selected.{Colors.RESET}")
287
+
288
+ else:
289
+ if unused_functions:
290
+ logger.info(f"\n{Colors.RED}{Colors.BOLD}📦 Unreachable Functions{Colors.RESET}")
291
+ logger.info(f"{Colors.RED}{'=' * 23}{Colors.RESET}")
292
+ for i, item in enumerate(unused_functions, 1):
293
+ logger.info(f"{Colors.GRAY}{i:2d}. {Colors.RESET}{Colors.RED}{item['name']}{Colors.RESET}")
294
+ logger.info(f" {Colors.GRAY}└─ {item['file']}:{item['line']}{Colors.RESET}")
295
+ else:
296
+ logger.info(f"\n{Colors.GREEN}✓ All functions are reachable!{Colors.RESET}")
297
+
298
+ if unused_imports:
299
+ logger.info(f"\n{Colors.MAGENTA}{Colors.BOLD}📥 Unused Imports{Colors.RESET}")
300
+ logger.info(f"{Colors.MAGENTA}{'=' * 16}{Colors.RESET}")
301
+ for i, item in enumerate(unused_imports, 1):
302
+ logger.info(f"{Colors.GRAY}{i:2d}. {Colors.RESET}{Colors.MAGENTA}{item['name']}{Colors.RESET}")
303
+ logger.info(f" {Colors.GRAY}└─ {item['file']}:{item['line']}{Colors.RESET}")
304
+ else:
305
+ logger.info(f"\n{Colors.GREEN}✓ All imports are being used!{Colors.RESET}")
306
+
307
+ logger.info(f"\n{Colors.GRAY}{'─' * 50}{Colors.RESET}")
308
+ logger.info(f"{Colors.GRAY}Analysis complete.{Colors.RESET}")
309
+
310
+ logger.info(f"\n{Colors.GRAY}{'─' * 50}{Colors.RESET}")
311
+ logger.info(f"{Colors.GRAY}Analysis complete.{Colors.RESET}")
312
+
313
+ dead_code_count = len(unused_functions) + len(unused_imports)
314
+
315
+ if dead_code_count == 0:
316
+ logger.info(f"\n✨ Your code is 100% dead code free! Add this badge to your README:")
317
+ logger.info("```markdown")
318
+ logger.info("![Dead Code Free](https://img.shields.io/badge/Dead_Code-Free-brightgreen?logo=moleculer&logoColor=white)")
319
+ logger.info("```")
320
+ else:
321
+ logger.info(f"Found {dead_code_count} dead code items. Add this badge to your README:")
322
+ logger.info("```markdown")
323
+ logger.info(f"![Dead Code: {dead_code_count}](https://img.shields.io/badge/Dead_Code-{dead_code_count}_detected-orange?logo=codacy&logoColor=red)")
324
+ logger.info("```")
325
+
326
+ if unused_functions or unused_imports:
327
+ logger.info(f"\n{Colors.BOLD}Next steps:{Colors.RESET}")
328
+ logger.info(f" • Use --interactive to select specific items to remove")
329
+ logger.info(f" • Use --dry-run to preview changes before applying them")
330
+
331
+ if __name__ == "__main__":
332
+ main()
@@ -0,0 +1,306 @@
1
+ Metadata-Version: 2.4
2
+ Name: skylos
3
+ Version: 0.0.5
4
+ Requires-Dist: inquirer>=3.0.0
5
+ Summary: A static analysis tool for Python codebases
6
+ Author-email: oha <aaronoh2015@gmail.com>
7
+ Requires-Python: >=3.8
8
+ Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
9
+
10
+ # Skylos 🔍
11
+
12
+ ![License: Apache 2.0](https://img.shields.io/badge/License-Apache%202.0-blue.svg)
13
+ ![100% Local](https://img.shields.io/badge/privacy-100%25%20local-brightgreen)
14
+ ![PyPI version](https://img.shields.io/pypi/v/tacz)
15
+ ![100% Dead Code Free](https://img.shields.io/badge/Dead%20Code-100%25%20Free-brightgreen)
16
+
17
+ <div align="center">
18
+ <img src="assets/SKYLOS.png" alt="Skylos Logo" width="200">
19
+ </div>
20
+
21
+ > A static analysis tool for Python codebases written in Rust that detects unreachable functions and unused imports, aka dead code.
22
+
23
+ ## Features
24
+
25
+ - **Unreachable Function Detection**: Find functions and methods that are never called aka dead code
26
+ - **Unused Import Detection**: Imports that are never used
27
+ - **High Performance**: Built with Rust
28
+ - **Nice Output**: Colorized CLI output
29
+ - **Interactive Mode**: Select and remove specific items interactively
30
+ - **Dry Run Support**: Preview changes before applying them
31
+ - **Auto-removal**: Auto clean up
32
+ - **Cross-module Analysis**: Tracks imports and calls across your entire project
33
+ - **Nice badges!!**: Pin your badge if you get 100% clean code!
34
+
35
+ ## Benchmark (You can find this benchmark test in `test/sample_project`)
36
+
37
+ | Tool | Time (s) | Functions | Imports | Total |
38
+ |------|----------|-----------|---------|-------|
39
+ | Skylos | 0.039 | 48 | 8 | 56 |
40
+ | Vulture (100%) | 0.040 | 0 | 3 | 3 |
41
+ | Vulture (60%) | 0.041 | 28 | 3 | 31 |
42
+ | Vulture (0%) | 0.041 | 28 | 3 | 31 |
43
+ | Flake8 | 0.274 | 0 | 8 | 8 |
44
+ | Pylint | 0.285 | 0 | 6 | 6 |
45
+ | Dead | 0.035 | 0 | 0 | 0 |
46
+
47
+ ## Installation
48
+
49
+ ### Basic Installation
50
+
51
+ ```bash
52
+ pip install skylos
53
+ ```
54
+
55
+ ### With Interactive Features
56
+
57
+ For the interactive selection mode, install with:
58
+
59
+ ```bash
60
+ pip install skylos[interactive]
61
+ ```
62
+
63
+ ### From Source
64
+
65
+ ```bash
66
+ # Clone the repository
67
+ git clone https://github.com/duriantaco/skylos.git
68
+ cd skylos
69
+
70
+ # Install maturin (if not already installed)
71
+ pip install maturin
72
+
73
+ # Build and install
74
+ maturin develop
75
+ ```
76
+
77
+ ## Quick Start
78
+
79
+ ```bash
80
+ # Analyze a project
81
+ skylos /path/to/your/project
82
+
83
+ # Interactive mode - select items to remove
84
+ skylos /path/to/your/project --interactive
85
+
86
+ # Dry run - see what would be removed
87
+ skylos /path/to/your/project --interactive --dry-run
88
+
89
+ # Output to JSON
90
+ skylos /path/to/your/project --json
91
+ ```
92
+
93
+ ## CLI Options
94
+
95
+ ```
96
+ Usage: skylos [OPTIONS] PATH
97
+
98
+ Arguments:
99
+ PATH Path to the Python project to analyze
100
+
101
+ Options:
102
+ -h, --help Show this help message and exit
103
+ -j, --json Output raw JSON instead of formatted text
104
+ -o, --output FILE Write output to file instead of stdout
105
+ -v, --verbose Enable verbose output
106
+ -i, --interactive Interactively select items to remove
107
+ --dry-run Show what would be removed without modifying files
108
+ ```
109
+
110
+ ## Example Output
111
+
112
+ ```
113
+ 🔍 Python Static Analysis Results
114
+ ===================================
115
+
116
+ Summary:
117
+ • Unreachable functions: 48
118
+ • Unused imports: 8
119
+
120
+ 📦 Unreachable Functions
121
+ ========================
122
+ 1. module_13.test_function
123
+ └─ /Users/oha/project/module_13.py:5
124
+ 2. module_13.unused_function
125
+ └─ /Users/oha/project/module_13.py:13
126
+ ...
127
+
128
+ 📥 Unused Imports
129
+ =================
130
+ 1. os
131
+ └─ /Users/oha/project/module_13.py:1
132
+ 2. json
133
+ └─ /Users/oha/project/module_13.py:3
134
+ ...
135
+
136
+ Next steps:
137
+ • Use --interactive to select specific items to remove
138
+ • Use --dry-run to preview changes before applying them
139
+ ```
140
+
141
+ ## Interactive Mode
142
+
143
+ The interactive mode lets you select specific functions and imports to remove:
144
+
145
+ ![Interactive Demo](docs/interactive-demo.gif)
146
+
147
+ 1. **Select items**: Use arrow keys and space to select/deselect
148
+ 2. **Confirm changes**: Review selected items before applying
149
+ 3. **Auto-cleanup**: Files are automatically updated
150
+
151
+ ## Architecture
152
+
153
+ Skylos uses a hybrid architecture combining Rust and Python:
154
+
155
+ - **Rust Core**: Fast tree-sitter based parsing and analysis
156
+ - **Python CLI**: User-friendly interface and file manipulation
157
+ - **maturin**: Seamless Python-Rust integration
158
+
159
+ ### Core Components
160
+
161
+ ```
162
+ skylos/
163
+ ├── src/lib.rs # Main Rust analysis engine
164
+ ├── src/queries.rs # Tree-sitter query definitions
165
+ ├── src/types.rs # Data structures for results
166
+ ├── src/utils.rs # Helper functions
167
+ ├── skylos/
168
+ │ └── cli.py # Python CLI interface
169
+ └── pyproject.toml # Project configuration
170
+ ```
171
+
172
+ ## Development
173
+
174
+ ### Prerequisites
175
+
176
+ - Python ≥3.8
177
+ - Rust and Cargo
178
+ - maturin
179
+
180
+ ### Setup
181
+
182
+ ```bash
183
+ # Clone the repository
184
+ git clone https://github.com/duriantaco/skylos.git
185
+ cd skylos
186
+
187
+ # Create a virtual environment
188
+ python -m venv venv
189
+ source venv/bin/activate # On Windows: venv\Scripts\activate
190
+
191
+ # Install development dependencies
192
+ pip install maturin inquirer pytest
193
+
194
+ # Build in development mode
195
+ maturin develop
196
+ ```
197
+
198
+ ### Running Tests
199
+
200
+ ```bash
201
+ # Run Rust tests
202
+ cd src
203
+ cargo test
204
+
205
+ # Run Python tests
206
+ python -m pytest tests/
207
+ ```
208
+
209
+ ### Adding New Features
210
+
211
+ 1. **Rust Analysis**: Add new queries in `src/queries.rs`
212
+ 2. **Python CLI**: Extend functionality in `skylos/cli.py`
213
+ 3. **Documentation**: Update this README and add docstrings
214
+
215
+ ## Configuration
216
+
217
+ Skylos supports configuration via `.skylos.toml` (coming soon):
218
+
219
+ ```toml
220
+ [analysis]
221
+ # Exclude patterns
222
+ exclude = [
223
+ "*/migrations/*",
224
+ "*/tests/*",
225
+ "__pycache__"
226
+ ]
227
+
228
+ # Custom patterns for dead code detection
229
+ patterns = [
230
+ "test_*", # Test functions
231
+ "_*_internal" # Internal functions
232
+ ]
233
+
234
+ [output]
235
+ # Default output format
236
+ format = "colored" # Options: colored, plain, json
237
+
238
+ # Color scheme
239
+ colors = "default" # Options: default, dark, light
240
+ ```
241
+
242
+ ## Limitations
243
+
244
+ - Occassionally there will be false positives and false negatives especially for extreme edge cases
245
+
246
+ - The library is currently available **ONLY** for python
247
+
248
+ ## Troubleshooting
249
+
250
+ ### Common Issues
251
+
252
+ 1. **Tree-sitter Errors**
253
+ ```
254
+ Error: tree-sitter parse failed
255
+ ```
256
+ Ensure your Python files have valid syntax.
257
+
258
+ 2. **Permission Errors**
259
+ ```
260
+ Error: Permission denied when removing function
261
+ ```
262
+ Check file permissions before running in interactive mode.
263
+
264
+ 3. **Missing Dependencies**
265
+ ```
266
+ Interactive mode requires 'inquirer' package
267
+ ```
268
+ Install with: `pip install skylos[interactive]`
269
+
270
+ ## Contributing
271
+
272
+ We welcome contributions! Please read our [Contributing Guidelines](CONTRIBUTING.md) before submitting pull requests.
273
+
274
+ ### Quick Contribution Guide
275
+
276
+ 1. Fork the repository
277
+ 2. Create a feature branch (`git checkout -b feature/amazing-feature`)
278
+ 3. Commit your changes (`git commit -m 'Add amazing feature'`)
279
+ 4. Push to the branch (`git push origin feature/amazing-feature`)
280
+ 5. Open a Pull Request
281
+
282
+ ## Roadmap
283
+
284
+ - [ ] Configuration file support (White lists etc)
285
+ - [ ] Custom analysis rules
286
+ - [ ] Git hooks integration
287
+ - [ ] CI/CD integration examples
288
+ - [ ] Web interface
289
+ - [ ] Support for other languages
290
+ - [ ] Further optimization
291
+
292
+ ## License
293
+
294
+ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
295
+
296
+ ## Acknowledgments
297
+
298
+ - [tree-sitter](https://tree-sitter.github.io/) for the powerful parsing framework
299
+ - [maturin](https://github.com/PyO3/maturin) for seamless Rust-Python integration
300
+ - [inquirer](https://github.com/magmax/python-inquirer) for the interactive CLI
301
+
302
+ ## Contact
303
+
304
+ - **Author**: oha
305
+ - **Email**: aaronoh2015@gmail.com
306
+ - **GitHub**: [@duriantaco](https://github.com/duriantaco)
@@ -0,0 +1,7 @@
1
+ skylos-0.0.5.dist-info/METADATA,sha256=VZs_urOw-5xWDP6rR4tLhpJQmogN8ynm59ORTTz8gZ8,7939
2
+ skylos-0.0.5.dist-info/WHEEL,sha256=0kWP4A00z5y7dQaPOt93HDVSEu9o_Xh01gZkicHYRtk,94
3
+ skylos-0.0.5.dist-info/entry_points.txt,sha256=0_aXKDSaSH1REotD0g5GU419ykfL-S5H0XFtmDwRHyU,41
4
+ skylos/__init__.py,sha256=vFcb-Dn-GhTC-3XBytd4FV4U11BNQ-1S4RMvBjqfl3g,876
5
+ skylos/_core.cp39-win_amd64.pyd,sha256=Yg6hQ7CKV9SPz1CJ28HgdZ-YpkIja3YfKpAaNEmzgLc,2816512
6
+ skylos/cli.py,sha256=C1Nm6g1S2aHqUul9xZSqbq_dAPZHXQfomEn67pGHfvU,14152
7
+ skylos-0.0.5.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: maturin (1.8.6)
3
+ Root-Is-Purelib: false
4
+ Tag: cp39-cp39-win_amd64
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ skylos=skylos.cli:main