codegraph-engine 3.0.0__1-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.
- codegraph/__init__.py +37 -0
- codegraph/agent.py +26 -0
- codegraph/agent_brain.py +2212 -0
- codegraph/agent_capabilities.py +4058 -0
- codegraph/agent_rules.py +531 -0
- codegraph/api_drift.py +455 -0
- codegraph/architecture.py +371 -0
- codegraph/async_queue/__init__.py +50 -0
- codegraph/async_queue/detectors/__init__.py +48 -0
- codegraph/async_queue/detectors/celery.py +160 -0
- codegraph/async_queue/detectors/kafka.py +168 -0
- codegraph/async_queue/detectors/rabbitmq.py +116 -0
- codegraph/async_queue/detectors/redis.py +130 -0
- codegraph/async_queue/detectors/sqs.py +135 -0
- codegraph/async_queue/models.py +161 -0
- codegraph/async_queue/otel_models.py +74 -0
- codegraph/async_queue/otel_parser.py +337 -0
- codegraph/async_queue/reconciler.py +427 -0
- codegraph/async_queue/scanner.py +291 -0
- codegraph/async_queue/traversal.py +447 -0
- codegraph/audit.py +106 -0
- codegraph/binding_resolver.py +616 -0
- codegraph/branch_comparison.py +197 -0
- codegraph/cache.py +95 -0
- codegraph/change_impact.py +547 -0
- codegraph/cli.py +2436 -0
- codegraph/cli_output.py +205 -0
- codegraph/config.py +45 -0
- codegraph/constraints.py +238 -0
- codegraph/context.py +2182 -0
- codegraph/context_freshness.py +351 -0
- codegraph/database/__init__.py +72 -0
- codegraph/database/extractor.py +3651 -0
- codegraph/database/interrogation.py +1494 -0
- codegraph/database/models.py +310 -0
- codegraph/database/schema_drift.py +862 -0
- codegraph/epistemic.py +200 -0
- codegraph/errors.py +282 -0
- codegraph/evidence/__init__.py +15 -0
- codegraph/evidence/citations.py +418 -0
- codegraph/evidence_contract.py +486 -0
- codegraph/frameworks.py +960 -0
- codegraph/frameworks_nestjs.py +502 -0
- codegraph/frameworks_nextjs.py +337 -0
- codegraph/frameworks_react.py +457 -0
- codegraph/freshness.py +335 -0
- codegraph/frontend_html.py +109 -0
- codegraph/git.py +362 -0
- codegraph/git_state.py +545 -0
- codegraph/graph/__init__.py +46 -0
- codegraph/graph/models.py +56 -0
- codegraph/graph/traversal.py +1993 -0
- codegraph/indexing/__init__.py +24 -0
- codegraph/indexing/classifier.py +564 -0
- codegraph/indexing/indexer.py +2578 -0
- codegraph/indexing/models.py +427 -0
- codegraph/indexing/parser.py +3404 -0
- codegraph/indexing/pool.py +136 -0
- codegraph/indexing/scanner.py +211 -0
- codegraph/indexing/telemetry.py +326 -0
- codegraph/indexing/templates.py +418 -0
- codegraph/indexing/test_framework.py +116 -0
- codegraph/installer.py +1702 -0
- codegraph/interrogation.py +2355 -0
- codegraph/llm/__init__.py +3 -0
- codegraph/llm/base.py +15 -0
- codegraph/llm/context.py +20 -0
- codegraph/mcp/__init__.py +3 -0
- codegraph/mcp/server.py +1476 -0
- codegraph/mcp_diagnostics.py +882 -0
- codegraph/memory/__init__.py +3 -0
- codegraph/memory/store.py +46 -0
- codegraph/models.py +310 -0
- codegraph/monorepo.py +925 -0
- codegraph/observability.py +262 -0
- codegraph/optimizer.py +1014 -0
- codegraph/planner.py +417 -0
- codegraph/process_lifecycle.py +1153 -0
- codegraph/py.typed +1 -0
- codegraph/query_expansion.py +199 -0
- codegraph/ranking.py +408 -0
- codegraph/refactor/__init__.py +36 -0
- codegraph/refactor/models.py +114 -0
- codegraph/refactor/renamer.py +844 -0
- codegraph/refactor/transactions.py +414 -0
- codegraph/resolver.py +3968 -0
- codegraph/resources/__init__.py +46 -0
- codegraph/resources/cache.py +117 -0
- codegraph/resources/coalescer.py +83 -0
- codegraph/resources/debouncer.py +98 -0
- codegraph/resources/governor.py +219 -0
- codegraph/resources/monitor.py +102 -0
- codegraph/resources/policy.py +123 -0
- codegraph/retrieval_policy.py +320 -0
- codegraph/roblox.py +2174 -0
- codegraph/route_composer.py +363 -0
- codegraph/route_topology.py +172 -0
- codegraph/runtime/__init__.py +51 -0
- codegraph/runtime/collector.py +155 -0
- codegraph/runtime/db_watcher.py +191 -0
- codegraph/runtime/ingestor.py +692 -0
- codegraph/runtime/models.py +130 -0
- codegraph/runtime/port_inspector.py +304 -0
- codegraph/runtime/reconciliation.py +492 -0
- codegraph/runtime/runner.py +353 -0
- codegraph/runtime/trace_linker.py +175 -0
- codegraph/search/__init__.py +25 -0
- codegraph/search/hybrid.py +846 -0
- codegraph/search/semantic.py +28 -0
- codegraph/security/__init__.py +51 -0
- codegraph/security/paths.py +189 -0
- codegraph/security/redaction.py +633 -0
- codegraph/semantic_conflicts.py +270 -0
- codegraph/semantic_decorators.py +372 -0
- codegraph/structural_diff.py +542 -0
- codegraph/styles.py +144 -0
- codegraph/symbol_history.py +277 -0
- codegraph/target_resolver.py +348 -0
- codegraph/task.py +637 -0
- codegraph/tool_selection_eval.py +1158 -0
- codegraph/tsconfig.py +236 -0
- codegraph/ui/__init__.py +6 -0
- codegraph/ui/server.py +755 -0
- codegraph/watcher.py +323 -0
- codegraph_engine-3.0.0.dist-info/METADATA +859 -0
- codegraph_engine-3.0.0.dist-info/RECORD +130 -0
- codegraph_engine-3.0.0.dist-info/WHEEL +6 -0
- codegraph_engine-3.0.0.dist-info/entry_points.txt +2 -0
- codegraph_engine-3.0.0.dist-info/licenses/LICENSE +21 -0
- codegraph_engine-3.0.0.dist-info/top_level.txt +1 -0
codegraph/git.py
ADDED
|
@@ -0,0 +1,362 @@
|
|
|
1
|
+
"""Read-only Git intelligence.
|
|
2
|
+
|
|
3
|
+
All Git operations use an explicit allowlist of commands and validated
|
|
4
|
+
arguments. No arbitrary shell execution. No network access.
|
|
5
|
+
"""
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import sqlite3
|
|
9
|
+
import subprocess
|
|
10
|
+
from dataclasses import dataclass
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@dataclass
|
|
15
|
+
class CommitInfo:
|
|
16
|
+
sha: str
|
|
17
|
+
author: str
|
|
18
|
+
date: str
|
|
19
|
+
message: str
|
|
20
|
+
|
|
21
|
+
def as_dict(self) -> dict[str, str]:
|
|
22
|
+
return {"sha": self.sha, "author": self.author, "date": self.date, "message": self.message}
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@dataclass
|
|
26
|
+
class FileDiff:
|
|
27
|
+
path: str
|
|
28
|
+
status: str # M=modified, A=added, D=deleted, R=renamed
|
|
29
|
+
diff_snippet: str = ""
|
|
30
|
+
|
|
31
|
+
def as_dict(self) -> dict[str, str]:
|
|
32
|
+
return {"path": self.path, "status": self.status, "diff_snippet": self.diff_snippet}
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _git(repository: Path, *args: str, timeout: int = 15) -> str | None:
|
|
36
|
+
"""Run a git subprocess with validated arguments only."""
|
|
37
|
+
# Validate: no shell metacharacters, no absolute paths from user input
|
|
38
|
+
for arg in args:
|
|
39
|
+
if any(c in arg for c in (";", "|", "&", "`", "$", "\n", "\r")):
|
|
40
|
+
return None
|
|
41
|
+
try:
|
|
42
|
+
result = subprocess.run(
|
|
43
|
+
["git", *args],
|
|
44
|
+
cwd=str(repository),
|
|
45
|
+
capture_output=True,
|
|
46
|
+
text=True,
|
|
47
|
+
encoding="utf-8",
|
|
48
|
+
errors="replace",
|
|
49
|
+
timeout=timeout,
|
|
50
|
+
)
|
|
51
|
+
return result.stdout if result.returncode == 0 else None
|
|
52
|
+
except (FileNotFoundError, subprocess.TimeoutExpired, OSError):
|
|
53
|
+
return None
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def is_git_repository(repository: Path) -> bool:
|
|
57
|
+
return _git(repository, "rev-parse", "--git-dir") is not None
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def current_commit(repository: Path) -> str | None:
|
|
61
|
+
"""Return the current HEAD commit SHA, or None if not a git repository."""
|
|
62
|
+
raw = _git(repository, "rev-parse", "HEAD")
|
|
63
|
+
return raw.strip() if raw else None
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def recent_commits(
|
|
67
|
+
repository: Path,
|
|
68
|
+
n: int = 10,
|
|
69
|
+
path: str | None = None,
|
|
70
|
+
) -> list[CommitInfo]:
|
|
71
|
+
"""Return the last N commits, optionally scoped to a file path."""
|
|
72
|
+
if not 1 <= n <= 100:
|
|
73
|
+
n = 10
|
|
74
|
+
args = ["log", f"-{n}", "--format=%H\x1f%an\x1f%ai\x1f%s"]
|
|
75
|
+
if path:
|
|
76
|
+
# Sanitize: path must not be absolute or contain traversal
|
|
77
|
+
if ".." in path or path.startswith("/"):
|
|
78
|
+
return []
|
|
79
|
+
args += ["--", path]
|
|
80
|
+
raw = _git(repository, *args)
|
|
81
|
+
if not raw:
|
|
82
|
+
return []
|
|
83
|
+
commits = []
|
|
84
|
+
for line in raw.strip().splitlines():
|
|
85
|
+
parts = line.split("\x1f", 3)
|
|
86
|
+
if len(parts) == 4:
|
|
87
|
+
commits.append(CommitInfo(*parts))
|
|
88
|
+
return commits
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def changed_files(
|
|
92
|
+
repository: Path,
|
|
93
|
+
since: str = "HEAD~10",
|
|
94
|
+
until: str = "HEAD",
|
|
95
|
+
) -> list[FileDiff]:
|
|
96
|
+
"""Return files changed between two commits."""
|
|
97
|
+
# Validate refs — allow only hex SHAs, HEAD, HEAD~N, branch names (alphanumeric + /-_.)
|
|
98
|
+
import re
|
|
99
|
+
_REF = re.compile(r"^[A-Za-z0-9_.~^/-]{1,100}$")
|
|
100
|
+
if not _REF.match(since) or not _REF.match(until):
|
|
101
|
+
return []
|
|
102
|
+
raw = _git(repository, "diff", "--name-status", since, until)
|
|
103
|
+
if not raw:
|
|
104
|
+
return []
|
|
105
|
+
diffs: list[FileDiff] = []
|
|
106
|
+
for line in raw.strip().splitlines():
|
|
107
|
+
parts = line.split("\t", 1)
|
|
108
|
+
if len(parts) == 2:
|
|
109
|
+
status, path = parts[0].strip()[0], parts[1].strip()
|
|
110
|
+
diffs.append(FileDiff(path=path, status=status))
|
|
111
|
+
return diffs
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def file_diff(
|
|
115
|
+
repository: Path,
|
|
116
|
+
path: str,
|
|
117
|
+
since: str = "HEAD~1",
|
|
118
|
+
until: str = "HEAD",
|
|
119
|
+
max_lines: int = 50,
|
|
120
|
+
) -> str:
|
|
121
|
+
"""Return a bounded unified diff for a single file."""
|
|
122
|
+
import re
|
|
123
|
+
_REF = re.compile(r"^[A-Za-z0-9_.~^/-]{1,100}$")
|
|
124
|
+
if not _REF.match(since) or not _REF.match(until):
|
|
125
|
+
return ""
|
|
126
|
+
if ".." in path or path.startswith("/"):
|
|
127
|
+
return ""
|
|
128
|
+
raw = _git(repository, "diff", since, until, "--", path)
|
|
129
|
+
if not raw:
|
|
130
|
+
return ""
|
|
131
|
+
lines = raw.splitlines()[:max_lines]
|
|
132
|
+
return "\n".join(lines)
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def blame_line(
|
|
136
|
+
repository: Path,
|
|
137
|
+
path: str,
|
|
138
|
+
line: int,
|
|
139
|
+
) -> dict[str, str] | None:
|
|
140
|
+
"""Return blame info for a single line."""
|
|
141
|
+
if ".." in path or path.startswith("/"):
|
|
142
|
+
return None
|
|
143
|
+
if not 1 <= line <= 100_000:
|
|
144
|
+
return None
|
|
145
|
+
raw = _git(
|
|
146
|
+
repository, "blame", f"-L{line},{line}", "--porcelain", "--", path
|
|
147
|
+
)
|
|
148
|
+
if not raw:
|
|
149
|
+
return None
|
|
150
|
+
info: dict[str, str] = {}
|
|
151
|
+
lines = raw.splitlines()
|
|
152
|
+
if lines:
|
|
153
|
+
info["sha"] = lines[0].split()[0] if lines else ""
|
|
154
|
+
for bl in lines[1:]:
|
|
155
|
+
if bl.startswith("author "):
|
|
156
|
+
info["author"] = bl[7:]
|
|
157
|
+
elif bl.startswith("author-time "):
|
|
158
|
+
info["timestamp"] = bl[12:]
|
|
159
|
+
elif bl.startswith("summary "):
|
|
160
|
+
info["summary"] = bl[8:]
|
|
161
|
+
return info or None
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def changed_symbols_since(
|
|
165
|
+
repository: Path,
|
|
166
|
+
since: str,
|
|
167
|
+
symbol_names: set[str],
|
|
168
|
+
) -> list[str]:
|
|
169
|
+
"""Return subset of symbol_names that appear in changed lines since `since`.
|
|
170
|
+
|
|
171
|
+
This is a heuristic: it checks whether the symbol name appears in the
|
|
172
|
+
diff text. Results are LOW confidence.
|
|
173
|
+
"""
|
|
174
|
+
import re
|
|
175
|
+
_REF = re.compile(r"^[A-Za-z0-9_.~^/-]{1,100}$")
|
|
176
|
+
if not _REF.match(since):
|
|
177
|
+
return []
|
|
178
|
+
raw = _git(repository, "diff", since, "HEAD")
|
|
179
|
+
if not raw:
|
|
180
|
+
return []
|
|
181
|
+
found = []
|
|
182
|
+
for name in symbol_names:
|
|
183
|
+
# Only match if the name appears in added/removed diff lines
|
|
184
|
+
if re.search(rf"^[+-].*\b{re.escape(name)}\b", raw, re.MULTILINE):
|
|
185
|
+
found.append(name)
|
|
186
|
+
return found
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def get_file_history(
|
|
190
|
+
repository: Path,
|
|
191
|
+
path: str,
|
|
192
|
+
n: int = 10,
|
|
193
|
+
) -> list[dict[str, str]]:
|
|
194
|
+
"""Return commit history for a single file path."""
|
|
195
|
+
commits = recent_commits(repository, n=n, path=path)
|
|
196
|
+
return [c.as_dict() for c in commits]
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
def get_symbol_history(
|
|
200
|
+
repository: Path,
|
|
201
|
+
path: str,
|
|
202
|
+
symbol: str,
|
|
203
|
+
n: int = 10,
|
|
204
|
+
) -> list[dict[str, str]]:
|
|
205
|
+
"""Return commits where a specific symbol name was modified in a file."""
|
|
206
|
+
all_file_commits = recent_commits(repository, n=n * 2, path=path)
|
|
207
|
+
matching: list[dict[str, str]] = []
|
|
208
|
+
import re
|
|
209
|
+
for c in all_file_commits:
|
|
210
|
+
diff_text = file_diff(repository, path, since=f"{c.sha}~1", until=c.sha, max_lines=200)
|
|
211
|
+
if re.search(rf"\b{re.escape(symbol)}\b", diff_text):
|
|
212
|
+
matching.append(c.as_dict())
|
|
213
|
+
if len(matching) >= n:
|
|
214
|
+
break
|
|
215
|
+
return matching
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
def get_change_context(
|
|
219
|
+
repository: Path,
|
|
220
|
+
since: str = "HEAD~1",
|
|
221
|
+
until: str = "HEAD",
|
|
222
|
+
) -> dict[str, object]:
|
|
223
|
+
"""Return summary of changed files and bounded snippets."""
|
|
224
|
+
diffs = changed_files(repository, since=since, until=until)
|
|
225
|
+
file_summaries: list[dict[str, str]] = []
|
|
226
|
+
for d in diffs[:20]:
|
|
227
|
+
snippet = file_diff(repository, d.path, since=since, until=until, max_lines=30)
|
|
228
|
+
file_summaries.append(
|
|
229
|
+
{
|
|
230
|
+
"path": d.path,
|
|
231
|
+
"status": d.status,
|
|
232
|
+
"diff_snippet": snippet,
|
|
233
|
+
}
|
|
234
|
+
)
|
|
235
|
+
return {
|
|
236
|
+
"since": since,
|
|
237
|
+
"until": until,
|
|
238
|
+
"total_changed": len(diffs),
|
|
239
|
+
"files": file_summaries,
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
def analyze_change_impact(
|
|
244
|
+
con: sqlite3.Connection,
|
|
245
|
+
repository: Path,
|
|
246
|
+
since: str = "HEAD~1",
|
|
247
|
+
until: str = "HEAD",
|
|
248
|
+
) -> dict[str, object]:
|
|
249
|
+
"""Analyze downstream callers, routes, providers, and tests affected by changes between since and until."""
|
|
250
|
+
diffs = changed_files(repository, since=since, until=until)
|
|
251
|
+
changed_paths = [d.path for d in diffs]
|
|
252
|
+
changed_symbols: list[str] = []
|
|
253
|
+
removed_symbols: list[str] = []
|
|
254
|
+
affected_callers: list[dict[str, object]] = []
|
|
255
|
+
affected_tests: list[dict[str, object]] = []
|
|
256
|
+
affected_routes: list[dict[str, object]] = []
|
|
257
|
+
affected_providers: list[dict[str, object]] = []
|
|
258
|
+
impact_items: list[dict[str, object]] = []
|
|
259
|
+
|
|
260
|
+
from codegraph.graph.traversal import analyze_impact
|
|
261
|
+
|
|
262
|
+
# Check for removed symbols via git diff
|
|
263
|
+
for d in diffs:
|
|
264
|
+
f_diff = file_diff(repository, d.path, since=since, until=until, max_lines=500)
|
|
265
|
+
for line in f_diff.splitlines():
|
|
266
|
+
if line.startswith("-def ") or line.startswith("-class "):
|
|
267
|
+
rem_name = line[5:].split("(")[0].split(":")[0].strip()
|
|
268
|
+
if rem_name and rem_name not in removed_symbols:
|
|
269
|
+
removed_symbols.append(rem_name)
|
|
270
|
+
impact_items.append({
|
|
271
|
+
"source": rem_name,
|
|
272
|
+
"target": None,
|
|
273
|
+
"relationship": "UNKNOWN",
|
|
274
|
+
"category": "DIRECT",
|
|
275
|
+
"evidence_class": "UNKNOWN",
|
|
276
|
+
"freshness": "STALE",
|
|
277
|
+
"reason": "deleted_symbol",
|
|
278
|
+
"impact_reason": "deleted_symbol",
|
|
279
|
+
"file": d.path,
|
|
280
|
+
"rank": 8,
|
|
281
|
+
})
|
|
282
|
+
|
|
283
|
+
seen_callers: set[str] = set()
|
|
284
|
+
seen_tests: set[str] = set()
|
|
285
|
+
|
|
286
|
+
for p in changed_paths:
|
|
287
|
+
rows = con.execute("SELECT name, qualified_name FROM symbols WHERE path=?", (p,)).fetchall()
|
|
288
|
+
sym_names = {r["name"] for r in rows}
|
|
289
|
+
matched = changed_symbols_since(repository, since, sym_names)
|
|
290
|
+
for s in matched:
|
|
291
|
+
changed_symbols.append(s)
|
|
292
|
+
|
|
293
|
+
# Run rich analyze_impact for each modified symbol
|
|
294
|
+
impact_res = analyze_impact(con, s, max_depth=2, max_results=20)
|
|
295
|
+
|
|
296
|
+
raw_items = impact_res.get("impact_items")
|
|
297
|
+
if isinstance(raw_items, list):
|
|
298
|
+
for item in raw_items:
|
|
299
|
+
if isinstance(item, dict):
|
|
300
|
+
impact_items.append(item)
|
|
301
|
+
|
|
302
|
+
raw_callers = impact_res.get("direct_callers")
|
|
303
|
+
if isinstance(raw_callers, list):
|
|
304
|
+
for c in raw_callers:
|
|
305
|
+
if isinstance(c, dict):
|
|
306
|
+
c_key = f"{c.get('file')}:{c.get('symbol')}"
|
|
307
|
+
if c_key not in seen_callers:
|
|
308
|
+
seen_callers.add(c_key)
|
|
309
|
+
affected_callers.append(c)
|
|
310
|
+
|
|
311
|
+
raw_tests = impact_res.get("related_tests")
|
|
312
|
+
if isinstance(raw_tests, list):
|
|
313
|
+
for t in raw_tests:
|
|
314
|
+
if isinstance(t, dict) and "result" not in t:
|
|
315
|
+
t_key = f"{t.get('file')}:{t.get('symbol')}"
|
|
316
|
+
if t_key not in seen_tests:
|
|
317
|
+
seen_tests.add(t_key)
|
|
318
|
+
affected_tests.append(t)
|
|
319
|
+
|
|
320
|
+
raw_apis = impact_res.get("related_apis")
|
|
321
|
+
if isinstance(raw_apis, list):
|
|
322
|
+
for r in raw_apis:
|
|
323
|
+
if isinstance(r, dict):
|
|
324
|
+
affected_routes.append(r)
|
|
325
|
+
|
|
326
|
+
cats = impact_res.get("categories")
|
|
327
|
+
if isinstance(cats, dict):
|
|
328
|
+
di_list = cats.get("DI")
|
|
329
|
+
if isinstance(di_list, list):
|
|
330
|
+
for di in di_list:
|
|
331
|
+
if isinstance(di, dict):
|
|
332
|
+
affected_providers.append(di)
|
|
333
|
+
|
|
334
|
+
# Monorepo / Package boundary impact analysis
|
|
335
|
+
from codegraph.monorepo import detect_workspace
|
|
336
|
+
ws = detect_workspace(repository, con=con)
|
|
337
|
+
affected_packages: set[str] = set()
|
|
338
|
+
for p in changed_paths:
|
|
339
|
+
pkg = ws.get_package_for_file(p)
|
|
340
|
+
if pkg:
|
|
341
|
+
affected_packages.add(pkg.package_id)
|
|
342
|
+
|
|
343
|
+
affected_dependent_packages: set[str] = set()
|
|
344
|
+
for pkg_id in affected_packages:
|
|
345
|
+
for dep_pkg in ws.get_dependent_packages(pkg_id):
|
|
346
|
+
affected_dependent_packages.add(dep_pkg.package_id)
|
|
347
|
+
|
|
348
|
+
return {
|
|
349
|
+
"since": since,
|
|
350
|
+
"until": until,
|
|
351
|
+
"changed_files": [d.as_dict() for d in diffs],
|
|
352
|
+
"changed_symbols": changed_symbols,
|
|
353
|
+
"removed_symbols": removed_symbols,
|
|
354
|
+
"affected_callers": affected_callers,
|
|
355
|
+
"affected_tests": affected_tests,
|
|
356
|
+
"affected_routes": affected_routes,
|
|
357
|
+
"affected_providers": affected_providers,
|
|
358
|
+
"affected_packages": sorted(affected_packages),
|
|
359
|
+
"affected_dependent_packages": sorted(affected_dependent_packages),
|
|
360
|
+
"impact_items": impact_items,
|
|
361
|
+
}
|
|
362
|
+
|