k-cli-for-devs 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.
- k_cli/__init__.py +77 -0
- k_cli/agents/__init__.py +0 -0
- k_cli/agents/adversarial_swarm.py +338 -0
- k_cli/agents/agent_core.py +255 -0
- k_cli/agents/background_daemon.py +141 -0
- k_cli/agents/orchestrator.py +376 -0
- k_cli/agents/persona.py +649 -0
- k_cli/agents/scaffold_engine.py +121 -0
- k_cli/agents/strands_agent.py +832 -0
- k_cli/agents/subagents.py +1496 -0
- k_cli/cli.py +3297 -0
- k_cli/core/__init__.py +0 -0
- k_cli/core/airgap.py +95 -0
- k_cli/core/credentials.py +548 -0
- k_cli/core/intent_sensor.py +177 -0
- k_cli/core/llm_driver.py +1028 -0
- k_cli/core/model_manager.py +1109 -0
- k_cli/core/models_hub.py +913 -0
- k_cli/core/prompting.py +41 -0
- k_cli/core/sdk.py +322 -0
- k_cli/core/session.py +826 -0
- k_cli/core/smart_router.py +230 -0
- k_cli/core/storage_manager.py +176 -0
- k_cli/core/viewport_engine.py +117 -0
- k_cli/demo/demo_runner.py +579 -0
- k_cli/git/__init__.py +0 -0
- k_cli/git/ai_bisect.py +208 -0
- k_cli/git/conflict_resolver.py +1039 -0
- k_cli/git/git_guard.py +417 -0
- k_cli/git/patcher.py +1175 -0
- k_cli/git/repo_map.py +1780 -0
- k_cli/git/smart_git.py +928 -0
- k_cli/git/verifier.py +969 -0
- k_cli/github/__init__.py +0 -0
- k_cli/github/dedup_engine.py +787 -0
- k_cli/github/github_client.py +1702 -0
- k_cli/github/github_engine.py +641 -0
- k_cli/github/local_hub.py +209 -0
- k_cli/github/pr_watcher.py +129 -0
- k_cli/github/trending.py +205 -0
- k_cli/tools/__init__.py +0 -0
- k_cli/tools/audit.py +79 -0
- k_cli/tools/chaos_immunity.py +377 -0
- k_cli/tools/codebase_qa.py +106 -0
- k_cli/tools/command_runner.py +256 -0
- k_cli/tools/diagram_generator.py +547 -0
- k_cli/tools/doc_retriever.py +1332 -0
- k_cli/tools/feature.py +105 -0
- k_cli/tools/ghost_daemon.py +122 -0
- k_cli/tools/incident_triage.py +1365 -0
- k_cli/tools/mcp_client.py +1846 -0
- k_cli/tools/repo_gardener.py +142 -0
- k_cli/tools/rules.py +109 -0
- k_cli/tools/security.py +52 -0
- k_cli/tools/security_healer.py +999 -0
- k_cli/tools/synapse_graph.py +155 -0
- k_cli/tui/__init__.py +0 -0
- k_cli/tui/diff_viewer.py +223 -0
- k_cli/tui/tui.py +1145 -0
- k_cli/tui/tui_animations.py +648 -0
- k_cli/tui/tui_app.py +2788 -0
- k_cli/ui/__init__.py +10 -0
- k_cli/ui/simple_repl.py +315 -0
- k_cli/web/__init__.py +7 -0
- k_cli/web/server.py +624 -0
- k_cli/web/static/app.js +830 -0
- k_cli/web/static/index.html +495 -0
- k_cli/web/static/monitor.html +189 -0
- k_cli/web/static/style.css +838 -0
- k_cli_for_devs-1.0.0.dist-info/METADATA +461 -0
- k_cli_for_devs-1.0.0.dist-info/RECORD +75 -0
- k_cli_for_devs-1.0.0.dist-info/WHEEL +5 -0
- k_cli_for_devs-1.0.0.dist-info/entry_points.txt +2 -0
- k_cli_for_devs-1.0.0.dist-info/licenses/LICENSE +21 -0
- k_cli_for_devs-1.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
"""
|
|
2
|
+
repo_gardener.py - Nightly Autonomous Repo Maintenance & Health Engine for K-CLI
|
|
3
|
+
Project Bankai v1.0.0
|
|
4
|
+
|
|
5
|
+
Scans repository for dead code, unreferenced symbols, outdated dependencies,
|
|
6
|
+
untracked technical debt, and formats actionable cleanup PRs.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import ast
|
|
12
|
+
import os
|
|
13
|
+
import re
|
|
14
|
+
import subprocess
|
|
15
|
+
from dataclasses import dataclass, field
|
|
16
|
+
from pathlib import Path
|
|
17
|
+
from typing import Any, Dict, List, Optional, Set
|
|
18
|
+
|
|
19
|
+
from k_cli.git.repo_map import RepoMap
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@dataclass
|
|
23
|
+
class GardenFinding:
|
|
24
|
+
"""A single technical debt or hygiene finding."""
|
|
25
|
+
category: str # "dead_code", "outdated_dependency", "stale_branch", "security_hygiene"
|
|
26
|
+
file_path: str
|
|
27
|
+
symbol_or_item: str
|
|
28
|
+
message: str
|
|
29
|
+
severity: str = "MEDIUM" # "HIGH", "MEDIUM", "LOW"
|
|
30
|
+
suggested_action: str = ""
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@dataclass
|
|
34
|
+
class GardenReport:
|
|
35
|
+
"""Consolidated repo hygiene and maintenance report."""
|
|
36
|
+
total_files_scanned: int
|
|
37
|
+
total_findings: int
|
|
38
|
+
findings: List[GardenFinding] = field(default_factory=list)
|
|
39
|
+
dead_code_count: int = 0
|
|
40
|
+
dependency_issues: int = 0
|
|
41
|
+
health_score: float = 100.0 # 0 to 100
|
|
42
|
+
|
|
43
|
+
def render_markdown(self) -> str:
|
|
44
|
+
"""Renders formatted markdown report."""
|
|
45
|
+
lines = [
|
|
46
|
+
f"# 🌿 K-CLI Repo Health & Maintenance Report",
|
|
47
|
+
f"**Overall Health Score**: `{self.health_score:.1f}/100` | **Files Scanned**: {self.total_files_scanned} | **Total Findings**: {self.total_findings}",
|
|
48
|
+
"",
|
|
49
|
+
"## Summary",
|
|
50
|
+
f"- 🧹 **Dead / Unused Functions**: {self.dead_code_count}",
|
|
51
|
+
f"- 📦 **Dependency Issues**: {self.dependency_issues}",
|
|
52
|
+
"",
|
|
53
|
+
"## Detailed Findings",
|
|
54
|
+
]
|
|
55
|
+
if not self.findings:
|
|
56
|
+
lines.append("✨ Workspace is perfectly pruned! Zero technical debt detected.")
|
|
57
|
+
else:
|
|
58
|
+
for f in self.findings:
|
|
59
|
+
lines.append(f"- **[{f.severity}]** `{f.file_path}`: `{f.symbol_or_item}` — {f.message} *(Fix: {f.suggested_action})*")
|
|
60
|
+
return "\n".join(lines)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class RepoGardener:
|
|
64
|
+
"""
|
|
65
|
+
Autonomous Repository Maintenance Gardener.
|
|
66
|
+
"""
|
|
67
|
+
|
|
68
|
+
def __init__(self, repo_path: str = "."):
|
|
69
|
+
self.repo_path = Path(repo_path).resolve()
|
|
70
|
+
|
|
71
|
+
def scan_dead_code(self) -> List[GardenFinding]:
|
|
72
|
+
"""Scans python codebase for defined functions never referenced across the project."""
|
|
73
|
+
findings: List[GardenFinding] = []
|
|
74
|
+
defined_functions: Dict[str, Tuple[str, int]] = {} # name -> (file_path, lineno)
|
|
75
|
+
ignored_dirs = {".venv", "k_cli_env", ".git", ".pytest_cache", "__pycache__", "build", "dist", "data"}
|
|
76
|
+
py_files = [
|
|
77
|
+
p for p in self.repo_path.rglob("*.py")
|
|
78
|
+
if not any(ig in p.parts for ig in ignored_dirs) and not p.name.startswith("test_")
|
|
79
|
+
]
|
|
80
|
+
|
|
81
|
+
for p in py_files[:100]:
|
|
82
|
+
try:
|
|
83
|
+
content = p.read_text(encoding="utf-8", errors="ignore")
|
|
84
|
+
all_code_text += "\n" + content
|
|
85
|
+
tree = ast.parse(content)
|
|
86
|
+
for node in ast.walk(tree):
|
|
87
|
+
if isinstance(node, ast.FunctionDef) and not node.name.startswith("_") and node.name not in ("main", "app", "compose", "on_mount"):
|
|
88
|
+
defined_functions[node.name] = (str(p.relative_to(self.repo_path)), node.lineno)
|
|
89
|
+
except Exception:
|
|
90
|
+
pass
|
|
91
|
+
|
|
92
|
+
for func_name, (rel_path, lineno) in defined_functions.items():
|
|
93
|
+
occurrences = len(re.findall(r"\b" + re.escape(func_name) + r"\b", all_code_text))
|
|
94
|
+
if occurrences <= 1: # Only occurs at its own definition
|
|
95
|
+
findings.append(GardenFinding(
|
|
96
|
+
category="dead_code",
|
|
97
|
+
file_path=f"{rel_path}:{lineno}",
|
|
98
|
+
symbol_or_item=func_name,
|
|
99
|
+
message=f"Function `{func_name}` appears unreferenced anywhere else in project.",
|
|
100
|
+
severity="LOW",
|
|
101
|
+
suggested_action=f"Safe to remove or mark private `_{func_name}`",
|
|
102
|
+
))
|
|
103
|
+
|
|
104
|
+
return findings
|
|
105
|
+
|
|
106
|
+
def scan_dependencies(self) -> List[GardenFinding]:
|
|
107
|
+
"""Inspects pyproject.toml or requirements.txt for unpinned or obsolete libraries."""
|
|
108
|
+
findings: List[GardenFinding] = []
|
|
109
|
+
req_file = self.repo_path / "requirements.txt"
|
|
110
|
+
if req_file.exists():
|
|
111
|
+
for line in req_file.read_text(encoding="utf-8").splitlines():
|
|
112
|
+
line = line.strip()
|
|
113
|
+
if line and not line.startswith("#"):
|
|
114
|
+
if "==" not in line and ">=" not in line:
|
|
115
|
+
findings.append(GardenFinding(
|
|
116
|
+
category="outdated_dependency",
|
|
117
|
+
file_path="requirements.txt",
|
|
118
|
+
symbol_or_item=line,
|
|
119
|
+
message=f"Dependency `{line}` lacks version pin.",
|
|
120
|
+
severity="MEDIUM",
|
|
121
|
+
suggested_action=f"Pin to minimum supported version e.g. `{line}>=1.0.0`",
|
|
122
|
+
))
|
|
123
|
+
return findings
|
|
124
|
+
|
|
125
|
+
def run_garden_sweep(self) -> GardenReport:
|
|
126
|
+
"""Executes full repo garden sweep."""
|
|
127
|
+
dead_code = self.scan_dead_code()
|
|
128
|
+
dep_issues = self.scan_dependencies()
|
|
129
|
+
|
|
130
|
+
all_findings = dead_code + dep_issues
|
|
131
|
+
total_files = len(list(self.repo_path.rglob("*.py")))
|
|
132
|
+
|
|
133
|
+
score = max(0.0, 100.0 - (len(dead_code) * 2.5) - (len(dep_issues) * 5.0))
|
|
134
|
+
|
|
135
|
+
return GardenReport(
|
|
136
|
+
total_files_scanned=total_files,
|
|
137
|
+
total_findings=len(all_findings),
|
|
138
|
+
findings=all_findings,
|
|
139
|
+
dead_code_count=len(dead_code),
|
|
140
|
+
dependency_issues=len(dep_issues),
|
|
141
|
+
health_score=score,
|
|
142
|
+
)
|
k_cli/tools/rules.py
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
"""
|
|
2
|
+
rules.py - Project Rules, Custom Developer Instructions & Workspace Guidance Loader
|
|
3
|
+
Project Bankai v1.0.0
|
|
4
|
+
|
|
5
|
+
Loads and manages developer-specific instructions for the AI from:
|
|
6
|
+
1. Workspace files: .kclirules, K_RULES.md, .cursorrules, .kcli/rules.md, CLAUDE.md, AGENTS.md
|
|
7
|
+
2. User-level global instructions: ~/.kcli/rules.md or ~/.kcli/custom_instructions.md
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import os
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
from typing import Union, Optional, List
|
|
15
|
+
|
|
16
|
+
MAX_RULE_BYTES = 65_536
|
|
17
|
+
|
|
18
|
+
DEFAULT_RULES_TEMPLATE = """# K-CLI Custom Developer Instructions (.kclirules)
|
|
19
|
+
# Project Bankai Autonomous AI Engine
|
|
20
|
+
|
|
21
|
+
## Architecture & Code Standards
|
|
22
|
+
- Write clean, modular, and type-annotated code (Python 3.12+ / Rust / TypeScript).
|
|
23
|
+
- Never remove existing docstrings, tests, or exception handlers unless refactoring.
|
|
24
|
+
- Always include robust error handling with zero crashes on edge cases.
|
|
25
|
+
- Perform strict AST verification and validate all generated functions against test suites.
|
|
26
|
+
|
|
27
|
+
## Developer Preferences
|
|
28
|
+
- Coding Style: Clean, production-grade, minimal dependencies.
|
|
29
|
+
- Test Framework: pytest (Python) / cargo test (Rust) / vitest (TS).
|
|
30
|
+
- Security Guardrails: Zero raw secrets in code; use environment variables or Credential Vault.
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
RULE_FILE_CANDIDATES = [
|
|
34
|
+
".kclirules",
|
|
35
|
+
"K_RULES.md",
|
|
36
|
+
".cursorrules",
|
|
37
|
+
".kcli/rules.md",
|
|
38
|
+
".kcli/instructions.md",
|
|
39
|
+
"CLAUDE.md",
|
|
40
|
+
"AGENTS.md",
|
|
41
|
+
]
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def load_project_rules(
|
|
45
|
+
workspace_dir: Union[str, Path] = ".",
|
|
46
|
+
rules_file: Optional[Union[str, Path]] = None,
|
|
47
|
+
) -> str:
|
|
48
|
+
"""
|
|
49
|
+
Load project-level coding rules and custom developer instructions from workspace
|
|
50
|
+
or global user preferences.
|
|
51
|
+
"""
|
|
52
|
+
workspace = Path(workspace_dir).resolve()
|
|
53
|
+
target_file: Optional[Path] = None
|
|
54
|
+
|
|
55
|
+
if rules_file is not None:
|
|
56
|
+
rf_path = Path(rules_file)
|
|
57
|
+
if not rf_path.is_absolute():
|
|
58
|
+
rf_path = (workspace / rf_path).resolve()
|
|
59
|
+
else:
|
|
60
|
+
rf_path = rf_path.resolve()
|
|
61
|
+
|
|
62
|
+
try:
|
|
63
|
+
rf_path.relative_to(workspace)
|
|
64
|
+
except ValueError:
|
|
65
|
+
raise ValueError("Rules file must be inside the workspace directory.")
|
|
66
|
+
|
|
67
|
+
target_file = rf_path
|
|
68
|
+
else:
|
|
69
|
+
# Search workspace candidates
|
|
70
|
+
for cand in RULE_FILE_CANDIDATES:
|
|
71
|
+
p = workspace / cand
|
|
72
|
+
if p.exists() and p.is_file():
|
|
73
|
+
target_file = p
|
|
74
|
+
break
|
|
75
|
+
|
|
76
|
+
# If no workspace file found, check global user instructions
|
|
77
|
+
if target_file is None or not target_file.exists():
|
|
78
|
+
global_p = Path.home() / ".kcli" / "rules.md"
|
|
79
|
+
if global_p.exists() and global_p.is_file():
|
|
80
|
+
target_file = global_p
|
|
81
|
+
|
|
82
|
+
if target_file is None or not target_file.exists():
|
|
83
|
+
return ""
|
|
84
|
+
|
|
85
|
+
content_bytes = target_file.read_bytes()
|
|
86
|
+
if len(content_bytes) > MAX_RULE_BYTES:
|
|
87
|
+
raise ValueError(f"Rules file exceeds byte limit ({len(content_bytes)} > {MAX_RULE_BYTES})")
|
|
88
|
+
|
|
89
|
+
content = content_bytes.decode("utf-8", errors="replace")
|
|
90
|
+
return f"### 📋 Custom Developer Instructions & Workspace Rules ({target_file.name}) [untrusted repository context]:\n{content.strip()}\n"
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def create_default_rules_file(workspace_dir: Union[str, Path] = ".", force: bool = False) -> Path:
|
|
94
|
+
"""Creates a starter .kclirules template in the workspace root."""
|
|
95
|
+
workspace = Path(workspace_dir).resolve()
|
|
96
|
+
target = workspace / ".kclirules"
|
|
97
|
+
if target.exists() and not force:
|
|
98
|
+
return target
|
|
99
|
+
target.write_text(DEFAULT_RULES_TEMPLATE, encoding="utf-8")
|
|
100
|
+
return target
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def set_global_rules(instructions: str) -> Path:
|
|
104
|
+
"""Sets global custom developer instructions in ~/.kcli/rules.md."""
|
|
105
|
+
global_dir = Path.home() / ".kcli"
|
|
106
|
+
global_dir.mkdir(parents=True, exist_ok=True)
|
|
107
|
+
target = global_dir / "rules.md"
|
|
108
|
+
target.write_text(instructions.strip(), encoding="utf-8")
|
|
109
|
+
return target
|
k_cli/tools/security.py
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"""Small, dependency-free secret hygiene checks for K-CLI workspaces."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import re
|
|
6
|
+
from dataclasses import dataclass
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import List
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
IGNORED = {".git", ".venv", "venv", "k_cli_env", "node_modules", "__pycache__", "data"}
|
|
12
|
+
SOURCE_SUFFIXES = {".py", ".js", ".ts", ".json", ".yaml", ".yml", ".toml", ".sh"}
|
|
13
|
+
RULES = {
|
|
14
|
+
"Hugging Face token": re.compile(r"\bhf_[A-Za-z0-9]{20,}\b"),
|
|
15
|
+
"OpenAI-style key": re.compile(r"\bsk-[A-Za-z0-9_-]{20,}\b"),
|
|
16
|
+
"private key": re.compile(r"-----BEGIN (?:RSA |EC |OPENSSH )?PRIVATE KEY-----"),
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@dataclass(frozen=True)
|
|
21
|
+
class SecurityFinding:
|
|
22
|
+
path: str
|
|
23
|
+
line: int
|
|
24
|
+
rule: str
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def scan_workspace(root_dir: str | Path = ".", max_findings: int = 25) -> List[SecurityFinding]:
|
|
28
|
+
"""Find likely committed credentials without returning sensitive values."""
|
|
29
|
+
root = Path(root_dir).resolve()
|
|
30
|
+
findings: List[SecurityFinding] = []
|
|
31
|
+
for path in root.rglob("*"):
|
|
32
|
+
if len(findings) >= max_findings:
|
|
33
|
+
break
|
|
34
|
+
if not path.is_file() or path.suffix.lower() not in SOURCE_SUFFIXES:
|
|
35
|
+
continue
|
|
36
|
+
if any(part in IGNORED for part in path.parts):
|
|
37
|
+
continue
|
|
38
|
+
try:
|
|
39
|
+
lines = path.read_text(encoding="utf-8").splitlines()
|
|
40
|
+
except (OSError, UnicodeDecodeError):
|
|
41
|
+
continue
|
|
42
|
+
for line_number, line in enumerate(lines, start=1):
|
|
43
|
+
if any(dummy in line.lower() for dummy in ("mock", "example", "placeholder", "dummy", "sample", "test_key", "sk-ant-api03", "sk-proj-test", "sk-mock")):
|
|
44
|
+
continue
|
|
45
|
+
for rule_name, pattern in RULES.items():
|
|
46
|
+
if pattern.search(line):
|
|
47
|
+
findings.append(SecurityFinding(path=path.relative_to(root).as_posix(), line=line_number, rule=rule_name))
|
|
48
|
+
if len(findings) >= max_findings:
|
|
49
|
+
break
|
|
50
|
+
if len(findings) >= max_findings:
|
|
51
|
+
break
|
|
52
|
+
return findings
|