repodoctor-cli 1.0.0__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.
- repodoctor/__init__.py +1 -0
- repodoctor/__main__.py +370 -0
- repodoctor/baseline.py +42 -0
- repodoctor/cli.py +47 -0
- repodoctor/duplicates.py +69 -0
- repodoctor/git.py +67 -0
- repodoctor/languages.py +33 -0
- repodoctor/linter.py +226 -0
- repodoctor/metrics.py +73 -0
- repodoctor/models.py +81 -0
- repodoctor/report.py +361 -0
- repodoctor/scanner.py +177 -0
- repodoctor/scoring.py +49 -0
- repodoctor/security.py +56 -0
- repodoctor/spinner.py +174 -0
- repodoctor/structure.py +40 -0
- repodoctor/todos.py +32 -0
- repodoctor_cli-1.0.0.dist-info/METADATA +137 -0
- repodoctor_cli-1.0.0.dist-info/RECORD +22 -0
- repodoctor_cli-1.0.0.dist-info/WHEEL +5 -0
- repodoctor_cli-1.0.0.dist-info/entry_points.txt +2 -0
- repodoctor_cli-1.0.0.dist-info/top_level.txt +1 -0
repodoctor/spinner.py
ADDED
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
"""
|
|
2
|
+
spinner.py — Zero-dependency terminal spinner / progress animation.
|
|
3
|
+
|
|
4
|
+
Uses only Python stdlib: sys, threading, time, itertools.
|
|
5
|
+
Automatically suppresses output when stdout is not a TTY
|
|
6
|
+
(e.g. file redirection, CI pipelines).
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import sys
|
|
10
|
+
import threading
|
|
11
|
+
import time
|
|
12
|
+
import itertools
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
# Braille spinner frames — visually smooth, widely supported
|
|
16
|
+
_SPINNER_FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"]
|
|
17
|
+
|
|
18
|
+
# ANSI colour codes (disabled when not TTY)
|
|
19
|
+
_CYAN = "\033[36m"
|
|
20
|
+
_GREEN = "\033[32m"
|
|
21
|
+
_YELLOW = "\033[33m"
|
|
22
|
+
_RESET = "\033[0m"
|
|
23
|
+
_BOLD = "\033[1m"
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _is_tty() -> bool:
|
|
27
|
+
"""Return True only when stdout is an interactive terminal."""
|
|
28
|
+
return hasattr(sys.stdout, "isatty") and sys.stdout.isatty()
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class Spinner:
|
|
32
|
+
"""
|
|
33
|
+
Context-manager / manual spinner that renders a live animation line.
|
|
34
|
+
|
|
35
|
+
Usage (context manager — recommended):
|
|
36
|
+
with Spinner("Scanning repository"):
|
|
37
|
+
do_work()
|
|
38
|
+
|
|
39
|
+
Usage (manual):
|
|
40
|
+
sp = Spinner("Cloning repo")
|
|
41
|
+
sp.start()
|
|
42
|
+
do_work()
|
|
43
|
+
sp.stop(success=True)
|
|
44
|
+
"""
|
|
45
|
+
|
|
46
|
+
def __init__(self, message: str = "Working", colour: bool = True, silent: bool = False) -> None:
|
|
47
|
+
self._message = message
|
|
48
|
+
self._use_colour = colour and _is_tty()
|
|
49
|
+
self._active = False
|
|
50
|
+
self._thread: threading.Thread | None = None
|
|
51
|
+
self.silent = silent
|
|
52
|
+
|
|
53
|
+
# ------------------------------------------------------------------ #
|
|
54
|
+
# public API
|
|
55
|
+
# ------------------------------------------------------------------ #
|
|
56
|
+
|
|
57
|
+
def start(self) -> "Spinner":
|
|
58
|
+
if self.silent:
|
|
59
|
+
return self
|
|
60
|
+
if not _is_tty():
|
|
61
|
+
# Non-interactive: just print the static message
|
|
62
|
+
print(f"{self._message}…", flush=True)
|
|
63
|
+
return self
|
|
64
|
+
self._active = True
|
|
65
|
+
self._thread = threading.Thread(target=self._spin, daemon=True)
|
|
66
|
+
self._thread.start()
|
|
67
|
+
return self
|
|
68
|
+
|
|
69
|
+
def stop(self, success: bool = True, final_message: str = "") -> None:
|
|
70
|
+
if self.silent:
|
|
71
|
+
return
|
|
72
|
+
self._active = False
|
|
73
|
+
if self._thread is not None:
|
|
74
|
+
self._thread.join()
|
|
75
|
+
self._thread = None
|
|
76
|
+
if _is_tty():
|
|
77
|
+
# Clear the spinner line
|
|
78
|
+
sys.stdout.write("\r\033[K")
|
|
79
|
+
sys.stdout.flush()
|
|
80
|
+
# Print final status line
|
|
81
|
+
if not final_message:
|
|
82
|
+
final_message = self._message
|
|
83
|
+
if success:
|
|
84
|
+
icon = f"{_GREEN}✔{_RESET}" if self._use_colour else "✔"
|
|
85
|
+
else:
|
|
86
|
+
icon = f"{_YELLOW}✘{_RESET}" if self._use_colour else "✘"
|
|
87
|
+
print(f"{icon} {final_message}", flush=True)
|
|
88
|
+
|
|
89
|
+
def update_message(self, message: str) -> None:
|
|
90
|
+
"""Change the message shown next to the spinner in real time."""
|
|
91
|
+
self._message = message
|
|
92
|
+
|
|
93
|
+
# ------------------------------------------------------------------ #
|
|
94
|
+
# context-manager support
|
|
95
|
+
# ------------------------------------------------------------------ #
|
|
96
|
+
|
|
97
|
+
def __enter__(self) -> "Spinner":
|
|
98
|
+
return self.start()
|
|
99
|
+
|
|
100
|
+
def __exit__(self, exc_type, exc_val, exc_tb) -> None:
|
|
101
|
+
self.stop(success=exc_type is None)
|
|
102
|
+
return False # do not suppress exceptions
|
|
103
|
+
|
|
104
|
+
# ------------------------------------------------------------------ #
|
|
105
|
+
# internal spin loop (runs in background thread)
|
|
106
|
+
# ------------------------------------------------------------------ #
|
|
107
|
+
|
|
108
|
+
def _spin(self) -> None:
|
|
109
|
+
spinner = itertools.cycle(_SPINNER_FRAMES)
|
|
110
|
+
while self._active:
|
|
111
|
+
frame = next(spinner)
|
|
112
|
+
if self._use_colour:
|
|
113
|
+
line = f"\r{_CYAN}{_BOLD}{frame}{_RESET} {self._message} "
|
|
114
|
+
else:
|
|
115
|
+
line = f"\r{frame} {self._message} "
|
|
116
|
+
sys.stdout.write(line)
|
|
117
|
+
sys.stdout.flush()
|
|
118
|
+
time.sleep(0.08)
|
|
119
|
+
# Final clear is done in stop()
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
class ProgressBar:
|
|
123
|
+
"""
|
|
124
|
+
Simple zero-dependency terminal progress bar.
|
|
125
|
+
|
|
126
|
+
Usage:
|
|
127
|
+
bar = ProgressBar(total=len(files), label="Scanning")
|
|
128
|
+
for f in files:
|
|
129
|
+
process(f)
|
|
130
|
+
bar.advance()
|
|
131
|
+
bar.done()
|
|
132
|
+
"""
|
|
133
|
+
|
|
134
|
+
def __init__(self, total: int, label: str = "Progress",
|
|
135
|
+
width: int = 30, colour: bool = True) -> None:
|
|
136
|
+
self._total = max(total, 1)
|
|
137
|
+
self._current = 0
|
|
138
|
+
self._label = label
|
|
139
|
+
self._width = width
|
|
140
|
+
self._use_colour = colour and _is_tty()
|
|
141
|
+
self._tty = _is_tty()
|
|
142
|
+
|
|
143
|
+
def advance(self, n: int = 1) -> None:
|
|
144
|
+
self._current = min(self._current + n, self._total)
|
|
145
|
+
if self._tty:
|
|
146
|
+
self._render()
|
|
147
|
+
|
|
148
|
+
def done(self) -> None:
|
|
149
|
+
self._current = self._total
|
|
150
|
+
if self._tty:
|
|
151
|
+
self._render()
|
|
152
|
+
sys.stdout.write("\n")
|
|
153
|
+
sys.stdout.flush()
|
|
154
|
+
|
|
155
|
+
def _render(self) -> None:
|
|
156
|
+
pct = self._current / self._total
|
|
157
|
+
filled = int(self._width * pct)
|
|
158
|
+
bar_body = "█" * filled + "░" * (self._width - filled)
|
|
159
|
+
|
|
160
|
+
if self._use_colour:
|
|
161
|
+
bar_str = (
|
|
162
|
+
f"\r{_CYAN}{self._label}{_RESET} "
|
|
163
|
+
f"[{_GREEN}{bar_body}{_RESET}] "
|
|
164
|
+
f"{_BOLD}{int(pct * 100):3d}%{_RESET} "
|
|
165
|
+
f"({self._current}/{self._total})"
|
|
166
|
+
)
|
|
167
|
+
else:
|
|
168
|
+
bar_str = (
|
|
169
|
+
f"\r{self._label} [{bar_body}] "
|
|
170
|
+
f"{int(pct * 100):3d}% ({self._current}/{self._total})"
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
sys.stdout.write(bar_str)
|
|
174
|
+
sys.stdout.flush()
|
repodoctor/structure.py
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from typing import Dict, Any
|
|
3
|
+
|
|
4
|
+
def check_project_structure(root_path: str) -> Dict[str, str]:
|
|
5
|
+
"""
|
|
6
|
+
Returns PASS, WARN, FAIL, or NOT APPLICABLE
|
|
7
|
+
"""
|
|
8
|
+
results = {
|
|
9
|
+
"README": "WARN",
|
|
10
|
+
".gitignore": "WARN",
|
|
11
|
+
"Tests": "WARN",
|
|
12
|
+
"LICENSE": "WARN",
|
|
13
|
+
"CI config": "WARN"
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
root = os.path.abspath(root_path)
|
|
17
|
+
|
|
18
|
+
# Check README
|
|
19
|
+
if any(os.path.exists(os.path.join(root, f)) for f in ["README.md", "README.txt", "README"]):
|
|
20
|
+
results["README"] = "PASS"
|
|
21
|
+
|
|
22
|
+
# Check .gitignore
|
|
23
|
+
if os.path.exists(os.path.join(root, ".gitignore")):
|
|
24
|
+
results[".gitignore"] = "PASS"
|
|
25
|
+
elif not os.path.exists(os.path.join(root, ".git")):
|
|
26
|
+
results[".gitignore"] = "NOT APPLICABLE"
|
|
27
|
+
|
|
28
|
+
# Check tests
|
|
29
|
+
if os.path.exists(os.path.join(root, "tests")) or os.path.exists(os.path.join(root, "test")):
|
|
30
|
+
results["Tests"] = "PASS"
|
|
31
|
+
|
|
32
|
+
# Check LICENSE
|
|
33
|
+
if any(os.path.exists(os.path.join(root, f)) for f in ["LICENSE", "LICENSE.txt", "LICENSE.md"]):
|
|
34
|
+
results["LICENSE"] = "PASS"
|
|
35
|
+
|
|
36
|
+
# Check CI config
|
|
37
|
+
if os.path.exists(os.path.join(root, ".github")) or os.path.exists(os.path.join(root, ".gitlab-ci.yml")):
|
|
38
|
+
results["CI config"] = "PASS"
|
|
39
|
+
|
|
40
|
+
return results
|
repodoctor/todos.py
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import re
|
|
2
|
+
from typing import List
|
|
3
|
+
from .models import FileInfo, TodoItem
|
|
4
|
+
|
|
5
|
+
MARKERS = ["TODO", "FIXME", "HACK", "XXX", "BUG"]
|
|
6
|
+
MARKER_PATTERN = re.compile(r'\b(' + '|'.join(MARKERS) + r')\b')
|
|
7
|
+
|
|
8
|
+
def scan_todos(files: List[FileInfo]) -> List[TodoItem]:
|
|
9
|
+
todos = []
|
|
10
|
+
|
|
11
|
+
for f in files:
|
|
12
|
+
if f.is_binary:
|
|
13
|
+
continue
|
|
14
|
+
|
|
15
|
+
try:
|
|
16
|
+
with open(f.path, 'r', encoding='utf-8', errors='ignore') as file:
|
|
17
|
+
for line_idx, line in enumerate(file):
|
|
18
|
+
if MARKER_PATTERN.search(line):
|
|
19
|
+
# Extract the actual marker used
|
|
20
|
+
match = MARKER_PATTERN.search(line)
|
|
21
|
+
marker = match.group(1)
|
|
22
|
+
|
|
23
|
+
todos.append(TodoItem(
|
|
24
|
+
filepath=f.relative_path,
|
|
25
|
+
line_number=line_idx + 1,
|
|
26
|
+
text=line.strip(),
|
|
27
|
+
marker=marker
|
|
28
|
+
))
|
|
29
|
+
except Exception:
|
|
30
|
+
pass
|
|
31
|
+
|
|
32
|
+
return todos
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: repodoctor-cli
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Zero-dependency repository health analyser
|
|
5
|
+
Author: Tanish Jain, Harsh Kumawat
|
|
6
|
+
License: MIT
|
|
7
|
+
Classifier: Programming Language :: Python :: 3
|
|
8
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
9
|
+
Classifier: Operating System :: OS Independent
|
|
10
|
+
Classifier: Topic :: Software Development :: Quality Assurance
|
|
11
|
+
Classifier: Environment :: Console
|
|
12
|
+
Requires-Python: >=3.8
|
|
13
|
+
Description-Content-Type: text/markdown
|
|
14
|
+
Dynamic: requires-python
|
|
15
|
+
|
|
16
|
+
# RepoDoctor
|
|
17
|
+
|
|
18
|
+
> RepoDoctor diagnoses a codebase for maintainability, security, duplication, project-structure and Git issues using only the language standard library.
|
|
19
|
+
|
|
20
|
+
## Problem
|
|
21
|
+
Modern development tools rely on heavy dependency chains that are hard to audit, difficult to install in restricted environments, and prone to breaking changes.
|
|
22
|
+
|
|
23
|
+
## Solution
|
|
24
|
+
RepoDoctor is a production-quality CLI tool that analyzes a software repository and provides an actionable health, security, and maintainability report without a single third-party runtime dependency.
|
|
25
|
+
|
|
26
|
+
## Features
|
|
27
|
+
- **Multi-Threaded Parallel Scanning**: Asynchronously processes massive codebases in milliseconds.\n- **Animated Terminal UI**: Beautiful typewriter animations and progress spinners.\n- **Multi-Repository Aggregation**: Scan multiple codebases simultaneously and generate unified or independent reports across all flags (HTML, JSON, LLM prompt).\n- **Zero Runtime Dependencies**: Built entirely with Python's standard library.
|
|
28
|
+
- **Single-File Portability**: Can be compiled into a single `repodoctor_single.py` script for extreme portability.
|
|
29
|
+
- **Developer Mood Analyzer**: Scans code comments and commit messages to calculate the emotional state of the project team.
|
|
30
|
+
- **Code Clone Exposer**: Mathematically cross-references all files to expose the two most identical copy-pasted files in the project.
|
|
31
|
+
- **Micro-Linter Engine**: Instantly flags 30+ code smells including profanity filters, wildcard imports, massive JSON configs, and missing alt-text.
|
|
32
|
+
- **LLM Prompt Exporter**: Instantly bundle your entire codebase into a single text file ready for ChatGPT/Claude (`--export-prompt`).
|
|
33
|
+
- **SVG Badge Generator**: Generate valid GitHub-style SVG health badges without image processing libraries (`--badge`).
|
|
34
|
+
- **Terminal ASCII Tree & Bar Charts**: Visual breakdown of your project's folders (`--tree`) and languages natively in your terminal.
|
|
35
|
+
- **AST Cyclomatic Complexity**: Parses Python Abstract Syntax Trees mathematically to score code logic complexity.
|
|
36
|
+
- **Security Scanner**: Detects exposed API keys, credentials, and `.env` files and automatically redacts findings.
|
|
37
|
+
- **Git Analytics & Hotspots**: Leverages native Git to report top contributors, uncommitted changes, and your most frequently edited file (Hotspot).
|
|
38
|
+
- **Codebase Vocabulary Cloud**: Automatically extracts the most frequently used variable and function names across all your files.
|
|
39
|
+
- **Rich Output Formats**: Choose between Animated ANSI-colored terminal, HTML (`--html`), or JSON (`--json`).
|
|
40
|
+
- **Baseline Tracking**: Compare current scans against past reports (`--baseline`) to track regressions over time.
|
|
41
|
+
|
|
42
|
+
## Architecture
|
|
43
|
+
Modular Python architecture utilizing built-in `argparse`, `subprocess`, `ast`, and `unittest`. Data structures rely on `dataclasses`.
|
|
44
|
+
|
|
45
|
+
## Installation
|
|
46
|
+
No dependencies are required. Clone the repository or copy the `repodoctor` folder:
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
git clone https://github.com/example/repodoctor.git
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Usage
|
|
53
|
+
Run the package directory against your target repository:
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
python -m repodoctor /path/to/your/repo
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### CLI Options
|
|
60
|
+
|
|
61
|
+
| Flag | Description |
|
|
62
|
+
|---|---|
|
|
63
|
+
| `path` | Path to the repository (default: `.`) |
|
|
64
|
+
| `--json` | Output valid machine-readable JSON |
|
|
65
|
+
| `--html FILE` | Output a self-contained HTML dashboard report |
|
|
66
|
+
| `--export-prompt FILE`| Export the codebase into a single text file for LLM prompting |
|
|
67
|
+
| `--badge FILE` | Generate a GitHub-style SVG health badge |
|
|
68
|
+
| `--tree` | Print an ASCII project directory tree at the top of the report |
|
|
69
|
+
| `--baseline FILE` | Path to a previous JSON report to calculate delta trends |
|
|
70
|
+
| `--no-color` | Disable animated ANSI color output |
|
|
71
|
+
| `--ignore` | Comma-separated list of custom directories to ignore |
|
|
72
|
+
| `--large-file-lines` | Threshold for large file lines (default: 500) |
|
|
73
|
+
| `--duplicate-lines` | Minimum lines for duplicate detection (default: 8) |
|
|
74
|
+
| `--security` | Focus only on security analysis |
|
|
75
|
+
| `--todos` | Focus only on TODO/FIXME analysis |
|
|
76
|
+
| `--git` | Include Git analysis (Always attempts by default) |
|
|
77
|
+
| `--verbose` | Enable verbose logging |
|
|
78
|
+
| `--version` | Display version |
|
|
79
|
+
| `--help` | Display help |
|
|
80
|
+
|
|
81
|
+
### Exit Codes
|
|
82
|
+
- `0`: Successful scan, no serious findings (secrets or duplicates).
|
|
83
|
+
- `1`: Successful scan with findings.
|
|
84
|
+
- `2`: Invalid CLI usage.
|
|
85
|
+
|
|
86
|
+
## JSON Format
|
|
87
|
+
Use the `--json` flag to export data.
|
|
88
|
+
```json
|
|
89
|
+
{
|
|
90
|
+
"repository": { "path": ".", "name": "project" },
|
|
91
|
+
"summary": { "files": 247, "lines": 38421, "health_score": 78 },
|
|
92
|
+
"security": { "potential_secrets": 0, "findings": [] },
|
|
93
|
+
"maintainability": { "large_files": 0, "todos": 5, "duplicates": 0 },
|
|
94
|
+
"git": { "available": true, "branch": "main", "commits": 142, "uncommitted_changes": 0 },
|
|
95
|
+
"structure": { "README": "PASS", "Tests": "PASS" }
|
|
96
|
+
}
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## Performance
|
|
100
|
+
- Uses efficient filesystem walking (`os.walk`).
|
|
101
|
+
- Early bailing on binary files.
|
|
102
|
+
- Rolling window chunking for O(N) deduplication analysis.
|
|
103
|
+
|
|
104
|
+
## Security Model
|
|
105
|
+
- **Local Only**: No data is uploaded or transmitted.
|
|
106
|
+
- **Redacted Output**: Secrets are never dumped fully in terminal or JSON.
|
|
107
|
+
- **No Evaluation**: Source code is parsed statically (via AST/Regex), never executed.
|
|
108
|
+
- **Safe Execution**: Git commands strictly avoid shell interpolation to prevent injection.
|
|
109
|
+
|
|
110
|
+
## Limitations
|
|
111
|
+
- Language detection is extension-based.
|
|
112
|
+
- Duplicate detection is line-based rather than AST-based.
|
|
113
|
+
- Security scanner may yield false positives; human review is required.
|
|
114
|
+
|
|
115
|
+
## Zero-Dependency Proof
|
|
116
|
+
To verify, run within a fully clean virtual environment:
|
|
117
|
+
```bash
|
|
118
|
+
python -m venv /tmp/repodoctor-test
|
|
119
|
+
source /tmp/repodoctor-test/bin/activate
|
|
120
|
+
pip freeze # (Will be empty)
|
|
121
|
+
python -m repodoctor /path/to/repo1 /path/to/repo2
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
## Standard Library Substitutions
|
|
125
|
+
See [STDLIB.md](STDLIB.md) for details on how we substituted common third-party tools.
|
|
126
|
+
|
|
127
|
+
## Testing
|
|
128
|
+
Tested with Python `unittest`:
|
|
129
|
+
```bash
|
|
130
|
+
python -m unittest discover -s tests -v
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
## Hackathon Information
|
|
134
|
+
Built for the **Zero Dependency | 72-Hour Hackathon**.
|
|
135
|
+
|
|
136
|
+
## License
|
|
137
|
+
MIT
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
repodoctor/__init__.py,sha256=OEMGSsx5z9zhqMrY1Qiri25mFUKtpCbn3ZUUAbjl1C8,25
|
|
2
|
+
repodoctor/__main__.py,sha256=wMctmzk1VnpPrTtl1gPg2pLCcMI4qqsoacCIpsPiZoM,15140
|
|
3
|
+
repodoctor/baseline.py,sha256=4DF7XlEdoQoyaNO4-7B3RBQ2T_VWco6b6c4Wz3tuv1k,1557
|
|
4
|
+
repodoctor/cli.py,sha256=tk06dGY8pvi_SVuuc3H6fjBPVQOqh4c9AM2_VNjNGRU,2605
|
|
5
|
+
repodoctor/duplicates.py,sha256=ZPc01cEnj0yIscRaK3uKojVCJVZ6huxq2hXV-KISY80,2695
|
|
6
|
+
repodoctor/git.py,sha256=iZRneYlk6Y0vF7UbDjs0xr7u2sIA5MnVyNfMd_OmeMU,2186
|
|
7
|
+
repodoctor/languages.py,sha256=6IE9iFNIS3kfgTPDan6S_m29l0rCmdnSD4rMw4TThb0,695
|
|
8
|
+
repodoctor/linter.py,sha256=8GyufyGE30Y-lRCovkUXj4bMBSBGbgXBO2dDWJGbfKI,10383
|
|
9
|
+
repodoctor/metrics.py,sha256=nLLCcc3SODdJJX5fOfU87nwhTdTisu6dtWwH0NofHxU,2677
|
|
10
|
+
repodoctor/models.py,sha256=wFDFGjZKhkEIe6iDRu5Pv9c4gm7uKR3lftlo1iFRWkE,1626
|
|
11
|
+
repodoctor/report.py,sha256=lei0l-02Iw8BTchUbAEjqz-K55WAZ5sVAVHWWlcOIfA,16090
|
|
12
|
+
repodoctor/scanner.py,sha256=0cm3Cmde17EvpM12qI8cRwZVnRo3meP9Ka5s_ielJH4,5886
|
|
13
|
+
repodoctor/scoring.py,sha256=fIDySAxVmC0yL3UWRjCn5OwkAR6vWEVnRuPUuHEQXeY,1635
|
|
14
|
+
repodoctor/security.py,sha256=R1haBYjB9zZP_u7vWZmkzDvcVkWgfi2sJ1-lrG5BuSE,2521
|
|
15
|
+
repodoctor/spinner.py,sha256=bZ7cPXZnUXSZ_RM4jyrv6S_gKcy-mfffL43AyM63lLY,5670
|
|
16
|
+
repodoctor/structure.py,sha256=ViHaXTUjkBQwJ01kvkcAJ00PyYAhfufgVQCa34H3W2s,1247
|
|
17
|
+
repodoctor/todos.py,sha256=E0xsmGvkjkjrMcO2IdaK7Qwjq5kCmgtV6FSR6rkLIEM,1066
|
|
18
|
+
repodoctor_cli-1.0.0.dist-info/METADATA,sha256=eXATE7ibyE8jnO8IgHf8xJbQn-zwO2CrQEfztJ25Wzc,6641
|
|
19
|
+
repodoctor_cli-1.0.0.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
|
|
20
|
+
repodoctor_cli-1.0.0.dist-info/entry_points.txt,sha256=c51JinAxRwn80raaW9iGVHnbQyqQUXwyiVKtTuEch9k,56
|
|
21
|
+
repodoctor_cli-1.0.0.dist-info/top_level.txt,sha256=lheErZ2yZxbqhKX-I6kK39b8IOHHU24X4dLI1H252Gg,11
|
|
22
|
+
repodoctor_cli-1.0.0.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
repodoctor
|