devarch 0.2.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.
@@ -0,0 +1,2 @@
1
+ """Utility helpers for Dev Archaeologist."""
2
+
devarch/utils/fs.py ADDED
@@ -0,0 +1,165 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+ from pathlib import Path
5
+
6
+ try:
7
+ from pathspec import PathSpec # type: ignore
8
+ except ModuleNotFoundError: # pragma: no cover - fallback for minimal environments
9
+ class PathSpec: # type: ignore[no-redef]
10
+ def __init__(self, patterns: list[str]) -> None:
11
+ self.patterns = patterns
12
+
13
+ @classmethod
14
+ def from_lines(cls, _style: str, lines: list[str]) -> "PathSpec":
15
+ return cls(lines)
16
+
17
+ def match_file(self, rel_path: str) -> bool:
18
+ candidate = rel_path.replace("\\", "/")
19
+ for pattern in self.patterns:
20
+ pattern = pattern.strip()
21
+ if not pattern:
22
+ continue
23
+ normalized = pattern.replace("\\", "/")
24
+ if normalized.endswith("/"):
25
+ prefix = normalized[:-1]
26
+ if candidate == prefix or candidate.startswith(f"{prefix}/"):
27
+ return True
28
+ elif candidate == normalized or candidate.endswith(f"/{normalized}"):
29
+ return True
30
+ return False
31
+
32
+
33
+ DEFAULT_IGNORE_DIRS = {
34
+ ".git",
35
+ ".hg",
36
+ ".svn",
37
+ ".tox",
38
+ ".venv",
39
+ "venv",
40
+ "node_modules",
41
+ "__pycache__",
42
+ ".mypy_cache",
43
+ ".pytest_cache",
44
+ "dist",
45
+ "build",
46
+ ".next",
47
+ }
48
+
49
+ DEFAULT_IGNORE_FILES = {
50
+ "devarch-report.md",
51
+ "devarch-report.markdown",
52
+ "devarch-report.html",
53
+ "devarch-report.json",
54
+ "devarch-report.pdf",
55
+ }
56
+
57
+ TEXT_EXTENSIONS = {
58
+ ".py",
59
+ ".pyi",
60
+ ".js",
61
+ ".jsx",
62
+ ".ts",
63
+ ".tsx",
64
+ ".json",
65
+ ".md",
66
+ ".txt",
67
+ ".yml",
68
+ ".yaml",
69
+ ".toml",
70
+ ".ini",
71
+ ".cfg",
72
+ ".css",
73
+ ".html",
74
+ ".sh",
75
+ ".bat",
76
+ ".ps1",
77
+ ".mjs",
78
+ ".cjs",
79
+ ".sql",
80
+ }
81
+
82
+ ASSET_EXTENSIONS = {
83
+ ".png",
84
+ ".jpg",
85
+ ".jpeg",
86
+ ".gif",
87
+ ".webp",
88
+ ".svg",
89
+ ".ico",
90
+ ".bmp",
91
+ ".mp3",
92
+ ".mp4",
93
+ ".mov",
94
+ ".webm",
95
+ ".wav",
96
+ ".pdf",
97
+ ".zip",
98
+ }
99
+
100
+
101
+ @dataclass(slots=True)
102
+ class RepoView:
103
+ root: Path
104
+ files: list[Path]
105
+ directories: list[Path]
106
+ ignore_spec: PathSpec | None
107
+
108
+
109
+ def read_gitignore(root: Path) -> PathSpec | None:
110
+ patterns: list[str] = []
111
+ gitignore = root / ".gitignore"
112
+ if gitignore.exists():
113
+ patterns.extend(
114
+ line.strip()
115
+ for line in gitignore.read_text(encoding="utf-8", errors="ignore").splitlines()
116
+ if line.strip() and not line.strip().startswith("#")
117
+ )
118
+ patterns.extend(f"{name}/" for name in DEFAULT_IGNORE_DIRS)
119
+ patterns.extend(DEFAULT_IGNORE_FILES)
120
+ return PathSpec.from_lines("gitwildmatch", patterns) if patterns else None
121
+
122
+
123
+ def is_ignored(path: Path, root: Path, ignore_spec: PathSpec | None) -> bool:
124
+ rel = path.relative_to(root).as_posix()
125
+ if ignore_spec and ignore_spec.match_file(rel):
126
+ return True
127
+ return False
128
+
129
+
130
+ def collect_repository(root: Path) -> RepoView:
131
+ root = root.resolve()
132
+ ignore_spec = read_gitignore(root)
133
+ files: list[Path] = []
134
+ directories: list[Path] = []
135
+ for path in sorted(root.rglob("*")):
136
+ if path.is_dir():
137
+ if path == root:
138
+ continue
139
+ if is_ignored(path, root, ignore_spec):
140
+ directories.append(path)
141
+ continue
142
+ if is_ignored(path, root, ignore_spec):
143
+ continue
144
+ files.append(path)
145
+ return RepoView(root=root, files=files, directories=directories, ignore_spec=ignore_spec)
146
+
147
+
148
+ def read_text(path: Path) -> str:
149
+ return path.read_text(encoding="utf-8", errors="ignore")
150
+
151
+
152
+ def safe_stat(path: Path) -> int:
153
+ try:
154
+ return path.stat().st_size
155
+ except OSError:
156
+ return 0
157
+
158
+
159
+ def path_kind(path: Path) -> str:
160
+ suffix = path.suffix.lower()
161
+ if suffix in TEXT_EXTENSIONS:
162
+ return "text"
163
+ if suffix in ASSET_EXTENSIONS:
164
+ return "asset"
165
+ return "binary"
@@ -0,0 +1,64 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+ from datetime import datetime, timezone
5
+ from pathlib import Path
6
+ import subprocess
7
+
8
+
9
+ @dataclass(slots=True)
10
+ class GitSummary:
11
+ available: bool
12
+ commit_count: int = 0
13
+ repository_age_days: int = 0
14
+ first_commit: datetime | None = None
15
+ last_commit: datetime | None = None
16
+ most_modified_files: list[tuple[str, int]] = None # type: ignore[assignment]
17
+
18
+
19
+ def _run_git(root: Path, *args: str) -> str | None:
20
+ try:
21
+ result = subprocess.run(
22
+ ["git", "-C", str(root), *args],
23
+ capture_output=True,
24
+ text=True,
25
+ check=True,
26
+ )
27
+ except (OSError, subprocess.CalledProcessError):
28
+ return None
29
+ return result.stdout.strip()
30
+
31
+
32
+ def collect_git_summary(root: Path) -> GitSummary:
33
+ log_format = "%ct"
34
+ commit_count_raw = _run_git(root, "rev-list", "--count", "HEAD")
35
+ if commit_count_raw is None:
36
+ return GitSummary(available=False, most_modified_files=[])
37
+
38
+ first_commit_raw = _run_git(root, "log", "--reverse", "--format=%ct", "HEAD")
39
+ last_commit_raw = _run_git(root, "log", "-1", "--format=%ct", "HEAD")
40
+ stats_raw = _run_git(root, "log", "--name-only", "--pretty=format:")
41
+
42
+ first_commit = datetime.fromtimestamp(int(first_commit_raw.splitlines()[0]), tz=timezone.utc) if first_commit_raw else None
43
+ last_commit = datetime.fromtimestamp(int(last_commit_raw.splitlines()[0]), tz=timezone.utc) if last_commit_raw else None
44
+ repository_age_days = 0
45
+ if first_commit and last_commit:
46
+ repository_age_days = max((last_commit - first_commit).days, 0)
47
+
48
+ file_counts: dict[str, int] = {}
49
+ if stats_raw:
50
+ for line in stats_raw.splitlines():
51
+ if line.strip():
52
+ file_counts[line.strip()] = file_counts.get(line.strip(), 0) + 1
53
+
54
+ most_modified_files = sorted(file_counts.items(), key=lambda item: item[1], reverse=True)[:10]
55
+
56
+ return GitSummary(
57
+ available=True,
58
+ commit_count=int(commit_count_raw),
59
+ repository_age_days=repository_age_days,
60
+ first_commit=first_commit,
61
+ last_commit=last_commit,
62
+ most_modified_files=most_modified_files,
63
+ )
64
+
@@ -0,0 +1,107 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Iterable
4
+
5
+ from rich.console import Console
6
+ from rich.panel import Panel
7
+ from rich.table import Table
8
+ from rich.text import Text
9
+
10
+ from ..models import Artifact, ScanSummary
11
+
12
+
13
+ console = Console()
14
+
15
+
16
+ def fmt_days(days: int | None) -> str:
17
+ if days is None:
18
+ return "n/a"
19
+ if days < 30:
20
+ return f"{days} days"
21
+ years = days / 365
22
+ return f"{years:.1f} years"
23
+
24
+
25
+ def health_badge(score: int) -> str:
26
+ if score >= 85:
27
+ return "Healthy"
28
+ if score >= 65:
29
+ return "Worn"
30
+ if score >= 45:
31
+ return "Unearthed Debt"
32
+ return "Critical"
33
+
34
+
35
+ def render_header(title: str, subtitle: str | None = None) -> None:
36
+ text = Text(title, style="bold cyan")
37
+ if subtitle:
38
+ text.append(f"\n{subtitle}", style="dim")
39
+ console.print(Panel(text, border_style="cyan"))
40
+
41
+
42
+ def render_metric_grid(summary: ScanSummary) -> None:
43
+ table = Table.grid(expand=True)
44
+ table.add_column()
45
+ table.add_column()
46
+ table.add_column()
47
+ table.add_row(
48
+ f"[bold]Artifacts[/bold]\n{summary.artifact_count}",
49
+ f"[bold]Ancient[/bold]\n{summary.ancient_count}",
50
+ f"[bold]Health[/bold]\n{summary.health_score}/100",
51
+ )
52
+ table.add_row(
53
+ f"[bold]TODOs[/bold]\n{summary.todo_count}",
54
+ f"[bold]Duplicates[/bold]\n{summary.duplicate_count}",
55
+ f"[bold]Debt[/bold]\n{summary.technical_debt_estimate:.1f}",
56
+ )
57
+ console.print(Panel(table, title="Excavation Summary", border_style="green"))
58
+
59
+
60
+ def render_artifacts(title: str, artifacts: Iterable[Artifact]) -> None:
61
+ table = Table(title=title, show_lines=False, header_style="bold magenta")
62
+ table.add_column("File", overflow="fold")
63
+ table.add_column("Age", style="yellow", no_wrap=True)
64
+ table.add_column("Size", style="cyan", no_wrap=True)
65
+ table.add_column("Risk", style="red", no_wrap=True)
66
+ table.add_column("Confidence", style="green", no_wrap=True)
67
+ table.add_column("Detail", overflow="fold")
68
+ count = 0
69
+ for artifact in artifacts:
70
+ count += 1
71
+ table.add_row(
72
+ str(artifact.path),
73
+ f"{artifact.age_days} days" if artifact.age_days is not None else "n/a",
74
+ f"{artifact.size_bytes} B" if artifact.size_bytes is not None else "n/a",
75
+ artifact.risk,
76
+ f"{artifact.confidence:.0%}" if artifact.confidence is not None else "n/a",
77
+ artifact.detail or artifact.kind,
78
+ )
79
+ if count:
80
+ console.print(table)
81
+ else:
82
+ console.print(Panel("No artifacts found.", border_style="green"))
83
+
84
+
85
+ def render_notice(message: str, style: str = "yellow") -> None:
86
+ console.print(Panel(message, border_style=style))
87
+
88
+
89
+ def render_kv(title: str, rows: list[tuple[str, str]], border_style: str = "cyan") -> None:
90
+ table = Table.grid(expand=True)
91
+ table.add_column(justify="left")
92
+ table.add_column(justify="left")
93
+ for label, value in rows:
94
+ table.add_row(f"[bold]{label}[/bold]", value)
95
+ console.print(Panel(table, title=title, border_style=border_style))
96
+
97
+
98
+ def render_bars(title: str, rows: list[tuple[str, float]]) -> None:
99
+ table = Table(title=title, header_style="bold magenta")
100
+ table.add_column("Bucket", overflow="fold")
101
+ table.add_column("Score", justify="right")
102
+ table.add_column("Bar", overflow="fold")
103
+ max_score = max((score for _, score in rows), default=0.0) or 1.0
104
+ for label, score in rows:
105
+ width = max(1, int((score / max_score) * 20))
106
+ table.add_row(label, f"{score:.1f}", "#" * width)
107
+ console.print(table)
devarch/version.py ADDED
@@ -0,0 +1,3 @@
1
+ """Package version."""
2
+
3
+ __version__ = "0.2.0"
@@ -0,0 +1,317 @@
1
+ Metadata-Version: 2.4
2
+ Name: devarch
3
+ Version: 0.2.0
4
+ Summary: Dev Archaeologist: excavate dead code, technical debt, and forgotten artifacts in software projects.
5
+ Author: magnexis
6
+ Requires-Python: >=3.12
7
+ Description-Content-Type: text/markdown
8
+ License-Expression: MIT
9
+ License-File: LICENSE
10
+ Requires-Dist: typer>=0.12.3
11
+ Requires-Dist: rich>=13.7.1
12
+ Requires-Dist: pathspec>=0.12.1
13
+ Requires-Dist: radon>=6.0.1 ; extra == "extended"
14
+ Requires-Dist: networkx>=3.3 ; extra == "extended"
15
+ Requires-Dist: gitpython>=3.1.43 ; extra == "extended"
16
+ Requires-Dist: tree-sitter>=0.22.3 ; extra == "extended"
17
+ Requires-Dist: reportlab>=4.2.2 ; extra == "pdf"
18
+ Requires-Dist: pytest>=8.2.2 ; extra == "test"
19
+ Provides-Extra: extended
20
+ Provides-Extra: pdf
21
+ Provides-Extra: test
22
+
23
+ # Dev Archaeologist
24
+
25
+ <p align="center">
26
+ <img src="assets/devarch-logo.png" alt="Dev Archaeologist logo" width="220">
27
+ </p>
28
+
29
+ <p align="center">
30
+ <em>Software archaeology and repository intelligence from Magnexis<img src="assets/magnexis-logo.png" alt="Magnexis logo" width="35"></em>
31
+ </p>
32
+
33
+
34
+ <p align="center">
35
+ <a href="#install"><img alt="Python 3.12+" src="https://img.shields.io/badge/Python-3.12%2B-blue.svg"></a>
36
+ <a href="#license"><img alt="MIT License" src="https://img.shields.io/badge/License-MIT-green.svg"></a>
37
+ <a href="#use"><img alt="CLI" src="https://img.shields.io/badge/CLI-devarch-8A6A3A.svg"></a>
38
+ <a href="#project-overview"><img alt="Magnexis" src="https://img.shields.io/badge/Brand-Magnexis-1F1F1F.svg"></a>
39
+ <a href="#release-artifacts"><img alt="Build sdist" src="https://img.shields.io/badge/Build-sdist%20%2B%20wheel-5E4B35.svg"></a>
40
+ <a href="#release-artifacts"><img alt="Release zip" src="https://img.shields.io/badge/Build-release%20zip-444444.svg"></a>
41
+ <a href="#release-artifacts"><img alt="Release manifest" src="https://img.shields.io/badge/Build-manifest%20%2B%20checksums-B8894D.svg"></a>
42
+ <a href="#project-overview"><img alt="Version" src="https://img.shields.io/badge/Version-0.2.0-444444.svg"></a>
43
+ </p>
44
+
45
+ Dev Archaeologist is a Magnexis-built Python CLI for excavating hidden technical debt, structural decay, and forgotten implementation artifacts in software repositories.
46
+
47
+ It treats every codebase like an archaeological dig site and helps you answer questions like:
48
+
49
+ - What code is ancient and likely abandoned?
50
+ - Where is the repository accumulating risk?
51
+ - Which files are structural weak points?
52
+ - What can be safely removed, refactored, or archived?
53
+ - How is the project evolving over time?
54
+
55
+ ## Project Overview
56
+
57
+ The tool scans a repository and turns the results into a rich, terminal-first excavation report.
58
+
59
+ It can surface:
60
+
61
+ - dead code
62
+ - ancient files
63
+ - TODO, FIXME, HACK, BUG, TEMP, and XXX markers
64
+ - duplicated logic
65
+ - unused assets and empty directories
66
+ - suspicious backup-style filenames
67
+ - oversized, complex "monster" files
68
+ - dependency hotspots and fragile chains
69
+ - architectural drift and release readiness issues
70
+ - remediation suggestions with estimated effort
71
+
72
+ The design goals are:
73
+
74
+ - strong terminal UX
75
+ - modular analyzers
76
+ - readable artifact reports
77
+ - release-friendly packaging
78
+ - future plugin support
79
+
80
+ ## Magnexis Brand
81
+
82
+ Dev Archaeologist is presented as part of the Magnexis tooling line.
83
+
84
+ Brand cues used in this repository:
85
+
86
+ - the archaeological emblem in the project logo
87
+ - the Magnexis name treatment in the README header
88
+ - the Magnexis mark used as a small brand seal
89
+ - a dedicated brand badge in the top badge row
90
+ - consistent earth-toned release and CLI styling
91
+
92
+ ## Installation
93
+
94
+ Install from PyPI:
95
+
96
+ ```bash
97
+ pip install devarch
98
+ ```
99
+
100
+ Install with optional extras:
101
+
102
+ ```bash
103
+ pip install devarch[extended]
104
+ pip install devarch[release]
105
+ pip install devarch[test]
106
+ ```
107
+
108
+ The `release` extra is useful if you want to build local distributions.
109
+
110
+ ## Quick Start
111
+
112
+ Run a full excavation over the current directory:
113
+
114
+ ```bash
115
+ devarch scan .
116
+ ```
117
+
118
+ List every available command:
119
+
120
+ ```bash
121
+ devarch help
122
+ ```
123
+
124
+ Generate a markdown report:
125
+
126
+ ```bash
127
+ devarch export markdown
128
+ ```
129
+
130
+ Generate a PDF report:
131
+
132
+ ```bash
133
+ devarch report pdf
134
+ ```
135
+
136
+ ## Release Artifacts
137
+
138
+ Dev Archaeologist includes a repeatable release build flow for local packaging and CI artifact generation.
139
+
140
+ Build the release bundle locally:
141
+
142
+ ```bash
143
+ pip install .[release]
144
+ python scripts/build_release.py
145
+ ```
146
+
147
+ This produces the following artifacts in `dist/`:
148
+
149
+ - `*.whl` for Python wheel distribution
150
+ - `*.tar.gz` for source distribution
151
+ - `*-release.zip` for a bundled release archive
152
+ - `release-manifest.json` for artifact metadata
153
+ - `SHA256SUMS.txt` for checksum verification
154
+
155
+ The zip bundle is convenient for sharing the release set as a single downloadable package.
156
+
157
+ See [RELEASE_NOTES.md](RELEASE_NOTES.md) for the full release summary and artifact inventory.
158
+
159
+ ## Command Reference
160
+
161
+ ### Core excavation
162
+
163
+ ```bash
164
+ devarch scan .
165
+ devarch help
166
+ devarch ancient .
167
+ devarch dead-code .
168
+ devarch todos .
169
+ devarch duplicates .
170
+ devarch monsters .
171
+ devarch ruins .
172
+ devarch suspicious .
173
+ devarch inspect src/app.py
174
+ devarch trace auth
175
+ devarch evidence auth
176
+ devarch bugmark src/app.py --line 128
177
+ devarch errorcode "ModuleNotFoundError: No module named 'rich'"
178
+ ```
179
+
180
+ ### Repository intelligence
181
+
182
+ ```bash
183
+ devarch dependencies .
184
+ devarch genealogy .
185
+ devarch civilizations .
186
+ devarch debt .
187
+ devarch timeline .
188
+ devarch personality .
189
+ devarch forecast .
190
+ devarch explore .
191
+ devarch investigate .
192
+ devarch weaknesses .
193
+ devarch quake .
194
+ devarch architecture .
195
+ devarch contributors .
196
+ devarch mutations .
197
+ devarch map .
198
+ devarch survival .
199
+ devarch notes .
200
+ ```
201
+
202
+ ### Forensic helpers
203
+
204
+ ```bash
205
+ devarch investigate .
206
+ devarch inspect src/app.py
207
+ devarch trace auth
208
+ devarch evidence auth
209
+ devarch bugmark src/app.py --line 128
210
+ devarch errorcode "ModuleNotFoundError: No module named 'rich'"
211
+ ```
212
+
213
+ ### Recovery and maintenance
214
+
215
+ ```bash
216
+ devarch plan .
217
+ devarch delete-check src/legacy_auth.py
218
+ devarch refactor .
219
+ devarch routes .
220
+ devarch configs .
221
+ devarch migrations .
222
+ devarch deps .
223
+ devarch drift .
224
+ devarch pr-report .
225
+ devarch status .
226
+ devarch baseline .
227
+ devarch regressions .
228
+ devarch budget .
229
+ devarch release-check .
230
+ devarch ownership .
231
+ devarch dependency-health .
232
+ devarch cleanup .
233
+ devarch standards .
234
+ devarch history .
235
+ devarch recommend .
236
+ devarch prescribe .
237
+ devarch repair-plan .
238
+ ```
239
+
240
+ ### Reporting
241
+
242
+ ```bash
243
+ devarch export json
244
+ devarch export markdown
245
+ devarch export html
246
+ devarch report markdown
247
+ devarch report html
248
+ devarch report pdf
249
+ ```
250
+
251
+ ## Output Philosophy
252
+
253
+ Each finding is designed to be actionable instead of just descriptive.
254
+
255
+ Typical output includes:
256
+
257
+ - problem
258
+ - evidence
259
+ - impact
260
+ - confidence
261
+ - recommended fix
262
+ - estimated effort
263
+ - risk level
264
+
265
+ That makes the tool useful not just for audits, but also for cleanup planning, code review, and release preparation.
266
+
267
+ ## Plugin Architecture
268
+
269
+ Dev Archaeologist exposes a lightweight plugin registry via the `devarch.plugins` entry-point group so future extensions can hook into the excavation pipeline.
270
+
271
+ Planned extension areas include:
272
+
273
+ - `devarch-plugin-security`
274
+ - `devarch-plugin-ai`
275
+ - `devarch-plugin-performance`
276
+
277
+ ## Development
278
+
279
+ Project layout:
280
+
281
+ ```text
282
+ devarch/
283
+ ├── analyzers/
284
+ ├── cli/
285
+ ├── reports/
286
+ ├── scanner/
287
+ ├── utils/
288
+ └── tests/
289
+ ```
290
+
291
+ Useful commands:
292
+
293
+ ```bash
294
+ python -m pytest -q
295
+ python -m compileall devarch
296
+ python scripts/build_release.py
297
+ ```
298
+
299
+ ## Repository Maintenance
300
+
301
+ The maintenance engine supports:
302
+
303
+ - baseline snapshots
304
+ - regression detection
305
+ - debt budgets
306
+ - release readiness checks
307
+ - ownership analysis
308
+ - dependency health monitoring
309
+ - cleanup recommendations
310
+ - standards checks
311
+ - health history
312
+ - remediation prescriptions
313
+
314
+ ## License
315
+
316
+ MIT
317
+
@@ -0,0 +1,33 @@
1
+ devarch/__init__.py,sha256=YVazM_ZDhQW5KVzwqysNM_cG9XZA_hutMJlMEdFi8yY,68
2
+ devarch/__main__.py,sha256=6LIynMFySr5bFjaGgaHQh8rXbH7-wFEhlvb7OkeaQIA,34
3
+ devarch/models.py,sha256=sIJh7KVrbP85ZkoGf4Kr207JBZ6N3qKHcQYItSrEpdA,1080
4
+ devarch/plugins.py,sha256=vnUDXxkpTSmxD0qBkbjrR5wLf-t3S7_U-SLFTNlA6og,665
5
+ devarch/version.py,sha256=tMP78-EG1UxDi5nOPt1f8aHzscjq4bCIjndKFu8fxp4,46
6
+ devarch/analyzers/__init__.py,sha256=b1IduLIS67cyWSMmmOIpp1N-yLP4rPfLAUi0yKuc89g,51
7
+ devarch/analyzers/ancient.py,sha256=qzP1KjpFXFQDTspaRiZ6P_W2QOuURHSgXbLVwjp0P7Y,1440
8
+ devarch/analyzers/dead_code.py,sha256=z_bEtkAO1uZKZlvFu62kG7IGM-Dg35oPPOCefyUm5Co,3516
9
+ devarch/analyzers/duplicates.py,sha256=vxtHpvqi3XTiJKDGPqvycY8_RFIpuvpUdCQMjwWZVWA,3508
10
+ devarch/analyzers/health.py,sha256=GmoD1iCqSYzoVDclJKlCL7CJhP4N2TZz17S_qjtA2jQ,1589
11
+ devarch/analyzers/maintenance.py,sha256=FW4J17ars3dJ9nYCZim7elFlPAYY2UCBCNMZ7K_C1MU,35776
12
+ devarch/analyzers/monsters.py,sha256=2Yw8Cta10Bt06lPb1Z24l956CX0w2upTlkMZDPH19UI,2124
13
+ devarch/analyzers/recovery.py,sha256=e6CHFpQdXPXnPcUEcr86Fq-cZIus3AyccHLc5USxyI8,12665
14
+ devarch/analyzers/ruins.py,sha256=bAfHXmG2-4RVQxypyv8_BaqzSRKlFCT2f7SgJguoxAE,1487
15
+ devarch/analyzers/suspicious.py,sha256=UHr5rxb04FisKPIUbLdsZNn9kqoKGIqmLA7x8GqjHuk,881
16
+ devarch/analyzers/todos.py,sha256=Wu9nanQWWqtespS9yh_ws-5xh5SqdPyRVuNilMMSa6E,1623
17
+ devarch/cli/__init__.py,sha256=wO5goN2mKPI5VdamtBJOdfRN4oqKsJbzrArrALuarIk,46
18
+ devarch/cli/main.py,sha256=cdzYALw3xHN4BE_1aASLms1e5Wi5PiEl8qfLdM2maHs,70361
19
+ devarch/reports/__init__.py,sha256=BYiAYWxgepd0BL-_jV86fjVE2whvB7EDTgFhmmgVkO8,47
20
+ devarch/reports/exporters.py,sha256=afG7l3jyKK0ac6YPGwliW-5fqI9BopalNcx698WzznQ,12811
21
+ devarch/scanner/__init__.py,sha256=Wy_Kdf_pg8qZGIhWQ2dSyznZ30jS3wh57QNCRssKjJc,36
22
+ devarch/scanner/core.py,sha256=FKjQdyHX31Z8mUVvE7A1P-vRDj0S08Kw4DNIkYtPggg,356
23
+ devarch/scanner/discovery.py,sha256=z2liHJLCOFSUG8KjdRGzVHk50QTBMukFcPVjmQN9dMU,2809
24
+ devarch/scanner/intelligence.py,sha256=l18bzDnJ6ARoPK37sKoRHyWlLlh918mKkCQoSIArePQ,63849
25
+ devarch/utils/__init__.py,sha256=a9S2-DWQjB-FBUrjD_z13mYXFsxktzUysn10OuuuaMw,46
26
+ devarch/utils/fs.py,sha256=htWvNPLThrw2EWCXveaZh_9BZHLqfqkRxBSfWPi5R2g,3949
27
+ devarch/utils/git_info.py,sha256=lc2mBb9n2fEVbvqJDrMG1pJNJGKqb2OnmQZHTHZ59Rk,2216
28
+ devarch/utils/rich_ui.py,sha256=5XSsD29nbOTpD5RrLXGAOUEOUj14ICx2BgZlq_nOmaE,3580
29
+ devarch-0.2.0.dist-info/entry_points.txt,sha256=kS4r4c52fXX01tnkcJtz1Bj30kODX2DUEQwk5OnNZxM,48
30
+ devarch-0.2.0.dist-info/licenses/LICENSE,sha256=jg1VkRpDaIEH3ryczciGYD9-hKUH-fsY4Ny11PlGdsU,1088
31
+ devarch-0.2.0.dist-info/WHEEL,sha256=G2gURzTEtmeR8nrdXUJfNiB3VYVxigPQ-bEQujpNiNs,82
32
+ devarch-0.2.0.dist-info/METADATA,sha256=G9dFbAbcecH5iCVBD_X0-3t0Yp2ke3RZcnzP5Ho8yQY,7585
33
+ devarch-0.2.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: flit 3.12.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,3 @@
1
+ [console_scripts]
2
+ devarch=devarch.cli.main:app
3
+