cortexcode 0.1.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.
- cortexcode/__init__.py +3 -0
- cortexcode/analysis.py +331 -0
- cortexcode/cli.py +845 -0
- cortexcode/context.py +298 -0
- cortexcode/dashboard.py +152 -0
- cortexcode/docs.py +1266 -0
- cortexcode/git_diff.py +157 -0
- cortexcode/indexer.py +1860 -0
- cortexcode/lsp_server.py +315 -0
- cortexcode/mcp_server.py +455 -0
- cortexcode/plugins.py +188 -0
- cortexcode/semantic_search.py +237 -0
- cortexcode/vuln_scan.py +241 -0
- cortexcode/watcher.py +122 -0
- cortexcode/workspace.py +180 -0
- cortexcode-0.1.0.dist-info/METADATA +448 -0
- cortexcode-0.1.0.dist-info/RECORD +21 -0
- cortexcode-0.1.0.dist-info/WHEEL +5 -0
- cortexcode-0.1.0.dist-info/entry_points.txt +2 -0
- cortexcode-0.1.0.dist-info/licenses/LICENSE +21 -0
- cortexcode-0.1.0.dist-info/top_level.txt +1 -0
cortexcode/git_diff.py
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
"""Git diff-aware context — show only changed symbols."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import subprocess
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def get_changed_files(root: Path, ref: str = "HEAD") -> list[str]:
|
|
10
|
+
"""Get list of files changed since ref (default: uncommitted changes)."""
|
|
11
|
+
try:
|
|
12
|
+
# Unstaged + staged changes
|
|
13
|
+
result = subprocess.run(
|
|
14
|
+
["git", "diff", "--name-only", ref],
|
|
15
|
+
capture_output=True, text=True, cwd=str(root)
|
|
16
|
+
)
|
|
17
|
+
files = set(result.stdout.strip().split("\n")) if result.stdout.strip() else set()
|
|
18
|
+
|
|
19
|
+
# Also include staged changes
|
|
20
|
+
result2 = subprocess.run(
|
|
21
|
+
["git", "diff", "--cached", "--name-only"],
|
|
22
|
+
capture_output=True, text=True, cwd=str(root)
|
|
23
|
+
)
|
|
24
|
+
if result2.stdout.strip():
|
|
25
|
+
files.update(result2.stdout.strip().split("\n"))
|
|
26
|
+
|
|
27
|
+
# Also include untracked files
|
|
28
|
+
result3 = subprocess.run(
|
|
29
|
+
["git", "ls-files", "--others", "--exclude-standard"],
|
|
30
|
+
capture_output=True, text=True, cwd=str(root)
|
|
31
|
+
)
|
|
32
|
+
if result3.stdout.strip():
|
|
33
|
+
files.update(result3.stdout.strip().split("\n"))
|
|
34
|
+
|
|
35
|
+
return [f for f in files if f]
|
|
36
|
+
except (subprocess.SubprocessError, FileNotFoundError):
|
|
37
|
+
return []
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def get_changed_lines(root: Path, file_path: str, ref: str = "HEAD") -> list[tuple[int, int]]:
|
|
41
|
+
"""Get ranges of changed lines in a file."""
|
|
42
|
+
try:
|
|
43
|
+
result = subprocess.run(
|
|
44
|
+
["git", "diff", "-U0", ref, "--", file_path],
|
|
45
|
+
capture_output=True, text=True, cwd=str(root)
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
ranges = []
|
|
49
|
+
for line in result.stdout.split("\n"):
|
|
50
|
+
if line.startswith("@@"):
|
|
51
|
+
# Parse @@ -old,count +new,count @@
|
|
52
|
+
parts = line.split("+")
|
|
53
|
+
if len(parts) >= 2:
|
|
54
|
+
new_part = parts[1].split(" ")[0].split(",")
|
|
55
|
+
start = int(new_part[0])
|
|
56
|
+
count = int(new_part[1]) if len(new_part) > 1 else 1
|
|
57
|
+
ranges.append((start, start + count))
|
|
58
|
+
|
|
59
|
+
return ranges
|
|
60
|
+
except (subprocess.SubprocessError, FileNotFoundError, ValueError):
|
|
61
|
+
return []
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def get_diff_context(index_path: Path, ref: str = "HEAD") -> dict[str, Any]:
|
|
65
|
+
"""Get context for only the changed symbols since ref.
|
|
66
|
+
|
|
67
|
+
Returns symbols that are in files that have been modified,
|
|
68
|
+
with indicators of which ones are in changed line ranges.
|
|
69
|
+
"""
|
|
70
|
+
index = json.loads(index_path.read_text(encoding="utf-8"))
|
|
71
|
+
files = index.get("files", {})
|
|
72
|
+
call_graph = index.get("call_graph", {})
|
|
73
|
+
root = Path(index.get("project_root", "."))
|
|
74
|
+
|
|
75
|
+
changed_files = get_changed_files(root, ref)
|
|
76
|
+
if not changed_files:
|
|
77
|
+
return {
|
|
78
|
+
"ref": ref,
|
|
79
|
+
"changed_files": 0,
|
|
80
|
+
"changed_symbols": [],
|
|
81
|
+
"affected_symbols": [],
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
changed_symbols = []
|
|
85
|
+
affected_symbol_names = set()
|
|
86
|
+
|
|
87
|
+
for changed_file in changed_files:
|
|
88
|
+
# Normalize path separators
|
|
89
|
+
norm_file = changed_file.replace("\\", "/")
|
|
90
|
+
|
|
91
|
+
# Find matching file in index
|
|
92
|
+
file_data = None
|
|
93
|
+
matched_path = None
|
|
94
|
+
for rel_path, data in files.items():
|
|
95
|
+
if rel_path.replace("\\", "/") == norm_file:
|
|
96
|
+
file_data = data
|
|
97
|
+
matched_path = rel_path
|
|
98
|
+
break
|
|
99
|
+
|
|
100
|
+
if not file_data or not isinstance(file_data, dict):
|
|
101
|
+
continue
|
|
102
|
+
|
|
103
|
+
symbols = file_data.get("symbols", [])
|
|
104
|
+
changed_ranges = get_changed_lines(root, changed_file, ref)
|
|
105
|
+
|
|
106
|
+
for sym in symbols:
|
|
107
|
+
sym_line = sym.get("line", 0)
|
|
108
|
+
in_changed_range = any(start <= sym_line <= end for start, end in changed_ranges) if changed_ranges else True
|
|
109
|
+
|
|
110
|
+
entry = {
|
|
111
|
+
"name": sym.get("name"),
|
|
112
|
+
"type": sym.get("type"),
|
|
113
|
+
"file": matched_path,
|
|
114
|
+
"line": sym_line,
|
|
115
|
+
"changed": in_changed_range,
|
|
116
|
+
"params": sym.get("params", []),
|
|
117
|
+
"calls": sym.get("calls", []),
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if sym.get("doc"):
|
|
121
|
+
entry["doc"] = sym["doc"]
|
|
122
|
+
|
|
123
|
+
changed_symbols.append(entry)
|
|
124
|
+
|
|
125
|
+
if in_changed_range:
|
|
126
|
+
affected_symbol_names.add(sym.get("name"))
|
|
127
|
+
|
|
128
|
+
# Find symbols affected by the changes (callers of changed symbols)
|
|
129
|
+
affected_symbols = []
|
|
130
|
+
for name, calls in call_graph.items():
|
|
131
|
+
if any(c in affected_symbol_names for c in calls):
|
|
132
|
+
if name not in affected_symbol_names:
|
|
133
|
+
# Find the file for this symbol
|
|
134
|
+
for rel_path, data in files.items():
|
|
135
|
+
if not isinstance(data, dict):
|
|
136
|
+
continue
|
|
137
|
+
for sym in data.get("symbols", []):
|
|
138
|
+
if sym.get("name") == name:
|
|
139
|
+
affected_symbols.append({
|
|
140
|
+
"name": name,
|
|
141
|
+
"type": sym.get("type"),
|
|
142
|
+
"file": rel_path,
|
|
143
|
+
"line": sym.get("line"),
|
|
144
|
+
"reason": f"calls changed symbol",
|
|
145
|
+
"calls_changed": [c for c in calls if c in affected_symbol_names],
|
|
146
|
+
})
|
|
147
|
+
break
|
|
148
|
+
else:
|
|
149
|
+
continue
|
|
150
|
+
break
|
|
151
|
+
|
|
152
|
+
return {
|
|
153
|
+
"ref": ref,
|
|
154
|
+
"changed_files": len(changed_files),
|
|
155
|
+
"changed_symbols": changed_symbols,
|
|
156
|
+
"affected_symbols": affected_symbols[:20],
|
|
157
|
+
}
|