kryptorious-devflow 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.
- devflow/__init__.py +6 -0
- devflow/cli.py +140 -0
- devflow/commands/__init__.py +1 -0
- devflow/commands/audit.py +508 -0
- devflow/commands/fix.py +175 -0
- devflow/commands/init.py +620 -0
- devflow/commands/ship.py +224 -0
- devflow/utils.py +87 -0
- kryptorious_devflow-1.0.0.dist-info/METADATA +201 -0
- kryptorious_devflow-1.0.0.dist-info/RECORD +13 -0
- kryptorious_devflow-1.0.0.dist-info/WHEEL +5 -0
- kryptorious_devflow-1.0.0.dist-info/entry_points.txt +2 -0
- kryptorious_devflow-1.0.0.dist-info/top_level.txt +1 -0
devflow/commands/fix.py
ADDED
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
"""devflow fix — Auto-fix common code issues.
|
|
2
|
+
|
|
3
|
+
Applies formatting, sorts imports, fixes lint violations, addresses security issues.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import List
|
|
8
|
+
|
|
9
|
+
from ..utils import (
|
|
10
|
+
console, print_header, print_success, print_error, print_warning, print_info,
|
|
11
|
+
find_project_root, check_tool_available, run_cmd
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def run(path: str, dry_run: bool, scope: str):
|
|
16
|
+
"""Auto-fix code issues."""
|
|
17
|
+
|
|
18
|
+
root = find_project_root(path)
|
|
19
|
+
mode = "DRY RUN" if dry_run else "APPLY"
|
|
20
|
+
|
|
21
|
+
print_header(f"DevFlow Fix — [bold yellow]{mode}[/bold yellow] on [bold cyan]{root.name}[/bold cyan]")
|
|
22
|
+
|
|
23
|
+
fixed_count = 0
|
|
24
|
+
actions: List[str] = []
|
|
25
|
+
|
|
26
|
+
if scope in ("all", "format"):
|
|
27
|
+
fixed, acts = _fix_formatting(root, dry_run)
|
|
28
|
+
fixed_count += fixed
|
|
29
|
+
actions.extend(acts)
|
|
30
|
+
|
|
31
|
+
if scope in ("all", "imports"):
|
|
32
|
+
fixed, acts = _fix_imports(root, dry_run)
|
|
33
|
+
fixed_count += fixed
|
|
34
|
+
actions.extend(acts)
|
|
35
|
+
|
|
36
|
+
if scope in ("all", "lint"):
|
|
37
|
+
fixed, acts = _fix_lint(root, dry_run)
|
|
38
|
+
fixed_count += fixed
|
|
39
|
+
actions.extend(acts)
|
|
40
|
+
|
|
41
|
+
if scope in ("all", "security"):
|
|
42
|
+
fixed, acts = _fix_security(root, dry_run)
|
|
43
|
+
fixed_count += fixed
|
|
44
|
+
actions.extend(acts)
|
|
45
|
+
|
|
46
|
+
# Summary
|
|
47
|
+
console.print()
|
|
48
|
+
if dry_run:
|
|
49
|
+
console.print(f"[bold yellow]DRY RUN:[/bold yellow] {fixed_count} issue(s) would be fixed:")
|
|
50
|
+
else:
|
|
51
|
+
console.print(f"[bold green]Done:[/bold green] {fixed_count} issue(s) fixed:")
|
|
52
|
+
|
|
53
|
+
for action in actions[:20]:
|
|
54
|
+
console.print(f" • {action}")
|
|
55
|
+
|
|
56
|
+
if len(actions) > 20:
|
|
57
|
+
console.print(f" ... and {len(actions) - 20} more")
|
|
58
|
+
|
|
59
|
+
if fixed_count > 0 and not dry_run:
|
|
60
|
+
print_success("All fixes applied. Run 'devflow audit' to verify.")
|
|
61
|
+
elif fixed_count == 0:
|
|
62
|
+
print_success("Everything looks clean. No fixes needed.")
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def _fix_formatting(root: Path, dry_run: bool) -> tuple[int, list[str]]:
|
|
66
|
+
"""Fix code formatting with black."""
|
|
67
|
+
if not check_tool_available("black"):
|
|
68
|
+
print_warning("black not installed. Install: pip install black")
|
|
69
|
+
return 0, []
|
|
70
|
+
|
|
71
|
+
target = str(root)
|
|
72
|
+
|
|
73
|
+
if dry_run:
|
|
74
|
+
code, out, err = run_cmd(["black", "--check", "--diff", target], cwd=str(root))
|
|
75
|
+
if code != 0:
|
|
76
|
+
changed = sum(1 for l in out.split("\n") if l.startswith("---") or l.startswith("+++"))
|
|
77
|
+
return changed // 2, [f"Formatting needed in {changed // 2} file(s)"]
|
|
78
|
+
return 0, []
|
|
79
|
+
|
|
80
|
+
code, out, err = run_cmd(["black", target], cwd=str(root))
|
|
81
|
+
if "reformatted" in out:
|
|
82
|
+
for line in out.split("\n"):
|
|
83
|
+
if "reformatted" in line:
|
|
84
|
+
print_success(f"Formatted: {line.strip()}")
|
|
85
|
+
break
|
|
86
|
+
return 1, ["Black formatting applied"]
|
|
87
|
+
elif "unchanged" in out:
|
|
88
|
+
print_info("All files already formatted.")
|
|
89
|
+
return 0, []
|
|
90
|
+
return 0, []
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def _fix_imports(root: Path, dry_run: bool) -> tuple[int, list[str]]:
|
|
94
|
+
"""Sort imports with isort or ruff."""
|
|
95
|
+
target = str(root)
|
|
96
|
+
|
|
97
|
+
if check_tool_available("ruff"):
|
|
98
|
+
if dry_run:
|
|
99
|
+
code, out, err = run_cmd(["ruff", "check", "--select", "I", "--diff", target], cwd=str(root))
|
|
100
|
+
if out.strip():
|
|
101
|
+
count = len([l for l in out.split("\n") if l.startswith("---")])
|
|
102
|
+
return count, [f"Import sorting needed in ~{count} file(s)"]
|
|
103
|
+
return 0, []
|
|
104
|
+
else:
|
|
105
|
+
code, out, err = run_cmd(["ruff", "check", "--select", "I", "--fix", target], cwd=str(root))
|
|
106
|
+
if "fixed" in (out + err).lower():
|
|
107
|
+
print_success("Imports sorted with ruff.")
|
|
108
|
+
return 1, ["Import sorting applied via ruff"]
|
|
109
|
+
return 0, []
|
|
110
|
+
|
|
111
|
+
if check_tool_available("isort"):
|
|
112
|
+
if dry_run:
|
|
113
|
+
code, out, err = run_cmd(["isort", "--check-only", "--diff", target], cwd=str(root))
|
|
114
|
+
if code != 0:
|
|
115
|
+
return 1, ["Import sorting needed (isort)"]
|
|
116
|
+
return 0, []
|
|
117
|
+
else:
|
|
118
|
+
code, out, err = run_cmd(["isort", target], cwd=str(root))
|
|
119
|
+
if code == 0:
|
|
120
|
+
print_success("Imports sorted with isort.")
|
|
121
|
+
return 1, ["Import sorting applied via isort"]
|
|
122
|
+
return 0, []
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def _fix_lint(root: Path, dry_run: bool) -> tuple[int, list[str]]:
|
|
126
|
+
"""Fix lint issues with ruff."""
|
|
127
|
+
target = str(root)
|
|
128
|
+
|
|
129
|
+
if check_tool_available("ruff"):
|
|
130
|
+
if dry_run:
|
|
131
|
+
code, out, err = run_cmd(["ruff", "check", "--diff", target], cwd=str(root))
|
|
132
|
+
if out.strip():
|
|
133
|
+
issues = len([l for l in out.split("\n") if l.strip() and not l.startswith("---") and not l.startswith("+++")])
|
|
134
|
+
return issues, [f"{issues} lint issue(s) found"]
|
|
135
|
+
return 0, []
|
|
136
|
+
else:
|
|
137
|
+
code, out, err = run_cmd(["ruff", "check", "--fix", target], cwd=str(root))
|
|
138
|
+
if "fixed" in (out + err):
|
|
139
|
+
# Count fixes
|
|
140
|
+
fixes = [l for l in (out + err).split("\n") if "fixed" in l.lower()]
|
|
141
|
+
for f in fixes:
|
|
142
|
+
if f.strip():
|
|
143
|
+
print_success(f.strip())
|
|
144
|
+
return len(fixes) or 1, ["Lint fixes applied via ruff"]
|
|
145
|
+
elif "no errors" in (out + err).lower() or "all good" in (out + err).lower():
|
|
146
|
+
print_info("No lint issues found.")
|
|
147
|
+
return 0, []
|
|
148
|
+
|
|
149
|
+
if check_tool_available("flake8"):
|
|
150
|
+
print_warning("flake8 detected but does not support auto-fix. Install ruff.")
|
|
151
|
+
return 0, []
|
|
152
|
+
|
|
153
|
+
return 0, []
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def _fix_security(root: Path, dry_run: bool) -> tuple[int, list[str]]:
|
|
157
|
+
"""Basic security fixes."""
|
|
158
|
+
target = str(root)
|
|
159
|
+
fixed = 0
|
|
160
|
+
actions = []
|
|
161
|
+
|
|
162
|
+
# Remove common hardcoded patterns (only in dry_run, never auto-remove secrets)
|
|
163
|
+
py_files = list(root.rglob("*.py"))
|
|
164
|
+
for pf in py_files:
|
|
165
|
+
if "test" in str(pf).lower():
|
|
166
|
+
continue
|
|
167
|
+
content = pf.read_text(errors="ignore")
|
|
168
|
+
if 'SECRET_KEY = "' in content or "SECRET_KEY = '" in content:
|
|
169
|
+
actions.append(f"Hardcoded SECRET_KEY in {pf.relative_to(root)} — move to .env")
|
|
170
|
+
if 'API_KEY = "' in content or "API_KEY = '" in content:
|
|
171
|
+
actions.append(f"Hardcoded API_KEY in {pf.relative_to(root)} — move to .env")
|
|
172
|
+
|
|
173
|
+
if dry_run:
|
|
174
|
+
return len(actions), actions
|
|
175
|
+
return 0, actions
|