sourcecode 1.33.4__py3-none-any.whl → 1.33.6__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.
- sourcecode/__init__.py +1 -1
- sourcecode/cli.py +12 -1
- sourcecode/mcp/orchestrator.py +708 -0
- sourcecode/mcp/server.py +232 -2
- sourcecode/repository_ir.py +59 -6
- {sourcecode-1.33.4.dist-info → sourcecode-1.33.6.dist-info}/METADATA +1 -1
- {sourcecode-1.33.4.dist-info → sourcecode-1.33.6.dist-info}/RECORD +10 -9
- {sourcecode-1.33.4.dist-info → sourcecode-1.33.6.dist-info}/WHEEL +0 -0
- {sourcecode-1.33.4.dist-info → sourcecode-1.33.6.dist-info}/entry_points.txt +0 -0
- {sourcecode-1.33.4.dist-info → sourcecode-1.33.6.dist-info}/licenses/LICENSE +0 -0
sourcecode/__init__.py
CHANGED
sourcecode/cli.py
CHANGED
|
@@ -1225,13 +1225,24 @@ def main(
|
|
|
1225
1225
|
_uncommitted = False
|
|
1226
1226
|
_hit_source = "L2_view" if (_view_key and _core_hash) else "L1_core"
|
|
1227
1227
|
_data_scope = "COMPACT" if compact else ("AGENT" if agent else "FULL")
|
|
1228
|
+
# Recover generated_at from cached content before overwriting _cache block.
|
|
1229
|
+
_cached_generated_at = None
|
|
1230
|
+
try:
|
|
1231
|
+
import json as _json_ga
|
|
1232
|
+
_cached_generated_at = (
|
|
1233
|
+
_json_ga.loads(_cache_hit_content)
|
|
1234
|
+
.get("_cache", {})
|
|
1235
|
+
.get("generated_at")
|
|
1236
|
+
)
|
|
1237
|
+
except Exception:
|
|
1238
|
+
pass
|
|
1228
1239
|
_cache_hit_content = _inject_cache_meta(_cache_hit_content, {
|
|
1229
1240
|
"cache_source": _hit_source,
|
|
1230
1241
|
"git_head_at_generation": _git_sha,
|
|
1231
1242
|
"current_git_head": _git_sha,
|
|
1232
1243
|
"is_stale": False,
|
|
1233
1244
|
"has_uncommitted_changes": _uncommitted,
|
|
1234
|
-
"generated_at":
|
|
1245
|
+
"generated_at": _cached_generated_at,
|
|
1235
1246
|
"data_scope": _data_scope,
|
|
1236
1247
|
})
|
|
1237
1248
|
write_output(_cache_hit_content, output=output)
|
|
@@ -0,0 +1,708 @@
|
|
|
1
|
+
"""Agent Runtime Layer — session state machine, intent detection, workflow orchestrators.
|
|
2
|
+
|
|
3
|
+
Converts the MCP from a flat tool collection into a guided agent operating system:
|
|
4
|
+
- start_session: single entry point that determines state and tool sequence
|
|
5
|
+
- analyze_task: intent detection and targeted tool sequence recommendation
|
|
6
|
+
- run_pr_review_flow: auto-chains delta + review_pr + blast radius
|
|
7
|
+
- run_bug_investigation_flow: auto-chains fix_bug + impact + IR context
|
|
8
|
+
- run_feature_flow: auto-chains context + endpoints + delta + structural awareness
|
|
9
|
+
"""
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import re
|
|
13
|
+
import time
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
from typing import Any, Optional
|
|
16
|
+
|
|
17
|
+
from sourcecode.mcp.runner import run_command
|
|
18
|
+
|
|
19
|
+
# ---------------------------------------------------------------------------
|
|
20
|
+
# Session state constants
|
|
21
|
+
# ---------------------------------------------------------------------------
|
|
22
|
+
|
|
23
|
+
SESSION_INIT = "INIT" # no RIS, no context
|
|
24
|
+
SESSION_CONTEXT_LOADED = "CONTEXT_LOADED" # RIS fresh + complete
|
|
25
|
+
SESSION_STALE_CONTEXT = "STALE_CONTEXT" # RIS exists but HEAD changed
|
|
26
|
+
SESSION_INCOMPLETE_CONTEXT = "INCOMPLETE_CONTEXT" # RIS missing critical sections
|
|
27
|
+
SESSION_TASK_INTENT_DETECTED = "TASK_INTENT_DETECTED"
|
|
28
|
+
SESSION_READY_FOR_REVIEW = "READY_FOR_REVIEW" # flow complete
|
|
29
|
+
|
|
30
|
+
# ---------------------------------------------------------------------------
|
|
31
|
+
# Intent constants
|
|
32
|
+
# ---------------------------------------------------------------------------
|
|
33
|
+
|
|
34
|
+
INTENT_PR_REVIEW = "pr_review"
|
|
35
|
+
INTENT_BUG_INVESTIGATION = "bug_investigation"
|
|
36
|
+
INTENT_FEATURE_IMPLEMENTATION = "feature_implementation"
|
|
37
|
+
INTENT_REFACTOR = "refactor"
|
|
38
|
+
INTENT_TEST_GENERATION = "test_generation"
|
|
39
|
+
INTENT_ORIENTATION = "orientation"
|
|
40
|
+
|
|
41
|
+
# ---------------------------------------------------------------------------
|
|
42
|
+
# Workflow sequences: intent → ordered tool names the agent should call
|
|
43
|
+
# ---------------------------------------------------------------------------
|
|
44
|
+
|
|
45
|
+
WORKFLOW_SEQUENCES: dict[str, list[str]] = {
|
|
46
|
+
INTENT_PR_REVIEW: ["get_delta", "review_pr_context", "get_impact_context"],
|
|
47
|
+
INTENT_BUG_INVESTIGATION: ["fix_bug_context", "get_impact_context"],
|
|
48
|
+
INTENT_FEATURE_IMPLEMENTATION: ["get_compact_context", "get_endpoints", "get_delta"],
|
|
49
|
+
INTENT_REFACTOR: ["get_agent_context", "modernize_context", "get_ir_summary"],
|
|
50
|
+
INTENT_TEST_GENERATION: ["generate_tests_context"],
|
|
51
|
+
INTENT_ORIENTATION: ["get_compact_context"],
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
WORKFLOW_DESCRIPTIONS: dict[str, str] = {
|
|
55
|
+
INTENT_PR_REVIEW: "PR review: delta → execution paths → blast radius of changed classes",
|
|
56
|
+
INTENT_BUG_INVESTIGATION: "Bug investigation: risk-ranked files → impact of suspect class",
|
|
57
|
+
INTENT_FEATURE_IMPLEMENTATION: "Feature implementation: context → API surface → recent changes",
|
|
58
|
+
INTENT_REFACTOR: "Refactor: deep context → modernization opportunities → IR coupling",
|
|
59
|
+
INTENT_TEST_GENERATION: "Test generation: untested files ranked by risk",
|
|
60
|
+
INTENT_ORIENTATION: "Orientation: compact context overview",
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
FLOW_RUNNERS: dict[str, str] = {
|
|
64
|
+
INTENT_PR_REVIEW: "run_pr_review_flow",
|
|
65
|
+
INTENT_BUG_INVESTIGATION: "run_bug_investigation_flow",
|
|
66
|
+
INTENT_FEATURE_IMPLEMENTATION: "run_feature_flow",
|
|
67
|
+
INTENT_REFACTOR: "run_feature_flow",
|
|
68
|
+
INTENT_TEST_GENERATION: "generate_tests_context",
|
|
69
|
+
INTENT_ORIENTATION: "get_compact_context",
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
# ---------------------------------------------------------------------------
|
|
73
|
+
# Intent detection
|
|
74
|
+
# ---------------------------------------------------------------------------
|
|
75
|
+
|
|
76
|
+
_INTENT_PATTERNS: list[tuple[str, list[str]]] = [
|
|
77
|
+
(INTENT_PR_REVIEW, [
|
|
78
|
+
r"\bpr\b", r"pull request", r"review pr", r"\bdiff\b", r"merge request",
|
|
79
|
+
r"code review", r"changes in branch", r"review.*branch", r"branch.*review",
|
|
80
|
+
]),
|
|
81
|
+
(INTENT_BUG_INVESTIGATION, [
|
|
82
|
+
r"\bbug\b", r"\berror\b", r"\bexception\b", r"\bcrash\b", r"\bnpe\b",
|
|
83
|
+
r"\bfix\b", r"\bbroken\b", r"\bfail(s|ing)?\b", r"stack.?trace",
|
|
84
|
+
r"null.?pointer", r"wrong.?behav", r"\bincident\b", r"not.?work",
|
|
85
|
+
]),
|
|
86
|
+
(INTENT_FEATURE_IMPLEMENTATION, [
|
|
87
|
+
r"\bfeature\b", r"\bimplement\b", r"\bdevelop\b", r"new endpoint",
|
|
88
|
+
r"new service", r"add.*(endpoint|service|api|feature)", r"create.*class",
|
|
89
|
+
r"\bbuild\b.*new",
|
|
90
|
+
]),
|
|
91
|
+
(INTENT_REFACTOR, [
|
|
92
|
+
r"\brefactor\b", r"\bmodernize\b", r"clean.?up", r"technical.?debt",
|
|
93
|
+
r"\bdebt\b", r"\brewrite\b", r"\brestructure\b", r"\bclean.*code\b",
|
|
94
|
+
]),
|
|
95
|
+
(INTENT_TEST_GENERATION, [
|
|
96
|
+
r"\btest(s|ing)?\b", r"\bcoverage\b", r"unit.?test", r"\bspec\b",
|
|
97
|
+
r"write.?test", r"add.?test",
|
|
98
|
+
]),
|
|
99
|
+
]
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def detect_intent(task_description: str) -> tuple[str, float]:
|
|
103
|
+
"""Return (intent, confidence). Confidence 1.0 = explicit match, 0.5 = fallback."""
|
|
104
|
+
t = task_description.lower()
|
|
105
|
+
for intent, patterns in _INTENT_PATTERNS:
|
|
106
|
+
for pat in patterns:
|
|
107
|
+
if re.search(pat, t):
|
|
108
|
+
return intent, 1.0
|
|
109
|
+
return INTENT_ORIENTATION, 0.5
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def _extract_symptom(task_description: str) -> str:
|
|
113
|
+
"""Heuristic: extract error class or quoted string from task description."""
|
|
114
|
+
# Quoted string
|
|
115
|
+
m = re.search(r'"([^"]+)"', task_description)
|
|
116
|
+
if m:
|
|
117
|
+
return m.group(1)
|
|
118
|
+
# Exception/Error class name
|
|
119
|
+
m = re.search(r'\b(\w+(?:Exception|Error|Fault))\b', task_description)
|
|
120
|
+
if m:
|
|
121
|
+
return m.group(1)
|
|
122
|
+
# "in ClassName"
|
|
123
|
+
m = re.search(r'\bin\s+(\w+(?:Service|Controller|Repository|Handler|Manager|Rest))\b', task_description)
|
|
124
|
+
if m:
|
|
125
|
+
return m.group(1)
|
|
126
|
+
return ""
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
# ---------------------------------------------------------------------------
|
|
130
|
+
# Orchestration rules (executable, not docs)
|
|
131
|
+
# ---------------------------------------------------------------------------
|
|
132
|
+
|
|
133
|
+
def apply_orchestration_rules(
|
|
134
|
+
freshness: str,
|
|
135
|
+
is_java: bool,
|
|
136
|
+
api_surface_complete: bool,
|
|
137
|
+
repo_class_count: int,
|
|
138
|
+
intent: str,
|
|
139
|
+
sequence: list[str],
|
|
140
|
+
) -> tuple[list[str], list[str]]:
|
|
141
|
+
"""Return (adjusted_sequence, rules_applied).
|
|
142
|
+
|
|
143
|
+
Rules applied in priority order:
|
|
144
|
+
R1 stale_cache → prepend get_delta (always sync before deep analysis)
|
|
145
|
+
R2 java_no_endpoints → prepend get_endpoints (api_surface must exist first)
|
|
146
|
+
R3 large_repo (>1000) → note RIS path preferred (informational, no seq change)
|
|
147
|
+
R4 no_symptom_bug_flow → quality warning issued by caller (not a seq change)
|
|
148
|
+
"""
|
|
149
|
+
seq = list(sequence)
|
|
150
|
+
rules: list[str] = []
|
|
151
|
+
|
|
152
|
+
# R1: stale cache + any flow → prepend delta refresh
|
|
153
|
+
if freshness == "stale" and "get_delta" not in seq:
|
|
154
|
+
seq.insert(0, "get_delta")
|
|
155
|
+
rules.append("R1:stale_cache→prepend_delta")
|
|
156
|
+
|
|
157
|
+
# R2: Java + no endpoint index → prepend get_endpoints
|
|
158
|
+
if is_java and not api_surface_complete and "get_endpoints" not in seq:
|
|
159
|
+
seq.insert(0, "get_endpoints")
|
|
160
|
+
rules.append("R2:java_no_endpoints→prepend_get_endpoints")
|
|
161
|
+
|
|
162
|
+
# R3: large repo informational flag
|
|
163
|
+
if repo_class_count > 1000:
|
|
164
|
+
rules.append("R3:large_repo→RIS_path_preferred")
|
|
165
|
+
|
|
166
|
+
return seq, rules
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
# ---------------------------------------------------------------------------
|
|
170
|
+
# Internal execute helper
|
|
171
|
+
# ---------------------------------------------------------------------------
|
|
172
|
+
|
|
173
|
+
def _exec(args: list[str]) -> dict[str, Any]:
|
|
174
|
+
"""Call run_command, return result dict. On error returns dict with 'error' key."""
|
|
175
|
+
try:
|
|
176
|
+
result = run_command(args)
|
|
177
|
+
if isinstance(result, dict):
|
|
178
|
+
return result
|
|
179
|
+
return {"raw": result}
|
|
180
|
+
except Exception as exc:
|
|
181
|
+
return {"_exec_error": f"{type(exc).__name__}: {exc}"}
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
# ---------------------------------------------------------------------------
|
|
185
|
+
# RIS helpers
|
|
186
|
+
# ---------------------------------------------------------------------------
|
|
187
|
+
|
|
188
|
+
def _cold_start(repo_path: str) -> dict[str, Any]:
|
|
189
|
+
from sourcecode.ris import get_cold_start_context as _gcs
|
|
190
|
+
return _gcs(Path(repo_path))
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
def _freshness(ris_status: str) -> str:
|
|
194
|
+
if ris_status == "cold_start_ready":
|
|
195
|
+
return "fresh"
|
|
196
|
+
if ris_status in ("cold_start_stale", "cold_start_incomplete"):
|
|
197
|
+
return "stale"
|
|
198
|
+
return "missing"
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
def _status_to_session_state(ris_status: str) -> str:
|
|
202
|
+
return {
|
|
203
|
+
"cold_start_ready": SESSION_CONTEXT_LOADED,
|
|
204
|
+
"cold_start_stale": SESSION_STALE_CONTEXT,
|
|
205
|
+
"cold_start_incomplete": SESSION_INCOMPLETE_CONTEXT,
|
|
206
|
+
"no_ris": SESSION_INIT,
|
|
207
|
+
}.get(ris_status, SESSION_INIT)
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
def _is_java_repo(repo_path: str) -> bool:
|
|
211
|
+
p = Path(repo_path)
|
|
212
|
+
return (p / "pom.xml").exists() or (p / "build.gradle").exists() or (p / "build.gradle.kts").exists()
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
def _default_sequence_for_state(
|
|
216
|
+
session_state: str, is_java: bool, api_complete: bool,
|
|
217
|
+
) -> list[str]:
|
|
218
|
+
if session_state == SESSION_INIT:
|
|
219
|
+
return ["get_compact_context"]
|
|
220
|
+
if session_state == SESSION_STALE_CONTEXT:
|
|
221
|
+
return ["get_delta", "analyze_task"]
|
|
222
|
+
if session_state == SESSION_INCOMPLETE_CONTEXT and is_java:
|
|
223
|
+
return ["get_endpoints", "analyze_task"]
|
|
224
|
+
if session_state == SESSION_CONTEXT_LOADED:
|
|
225
|
+
return ["analyze_task"]
|
|
226
|
+
return ["get_compact_context"]
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
def _risk_level(freshness: str, stale: bool, class_count: int) -> str:
|
|
230
|
+
if freshness == "missing":
|
|
231
|
+
return "unknown"
|
|
232
|
+
if stale or freshness == "stale":
|
|
233
|
+
return "medium"
|
|
234
|
+
if class_count > 2000:
|
|
235
|
+
return "high"
|
|
236
|
+
if class_count > 500:
|
|
237
|
+
return "medium"
|
|
238
|
+
return "low"
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
# ---------------------------------------------------------------------------
|
|
242
|
+
# start_session
|
|
243
|
+
# ---------------------------------------------------------------------------
|
|
244
|
+
|
|
245
|
+
def start_session_impl(repo_path: str, task_description: str = "") -> dict[str, Any]:
|
|
246
|
+
"""Core logic for start_session MCP tool."""
|
|
247
|
+
t0 = time.monotonic()
|
|
248
|
+
|
|
249
|
+
cold = _cold_start(repo_path)
|
|
250
|
+
ris_status = cold.get("status", "no_ris")
|
|
251
|
+
freshness = _freshness(ris_status)
|
|
252
|
+
session_state = _status_to_session_state(ris_status)
|
|
253
|
+
|
|
254
|
+
is_java = _is_java_repo(repo_path)
|
|
255
|
+
api_surface_complete = cold.get("api_surface_complete", True)
|
|
256
|
+
validation = cold.get("validation", {})
|
|
257
|
+
spring_detected = validation.get("spring_detected", False)
|
|
258
|
+
endpoints_count = validation.get("endpoints_found", len(cold.get("endpoints", [])))
|
|
259
|
+
|
|
260
|
+
summary = cold.get("summary", {})
|
|
261
|
+
repo_class_count: int = (
|
|
262
|
+
summary.get("class_count")
|
|
263
|
+
or summary.get("total_classes")
|
|
264
|
+
or 0
|
|
265
|
+
)
|
|
266
|
+
|
|
267
|
+
# Intent detection
|
|
268
|
+
intent: Optional[str] = None
|
|
269
|
+
intent_confidence = 0.5
|
|
270
|
+
flow_runner: Optional[str] = None
|
|
271
|
+
if task_description.strip():
|
|
272
|
+
intent, intent_confidence = detect_intent(task_description)
|
|
273
|
+
flow_runner = FLOW_RUNNERS.get(intent)
|
|
274
|
+
base_seq = list(WORKFLOW_SEQUENCES.get(intent, ["get_compact_context"]))
|
|
275
|
+
else:
|
|
276
|
+
base_seq = _default_sequence_for_state(session_state, is_java, api_surface_complete)
|
|
277
|
+
|
|
278
|
+
# Apply orchestration rules
|
|
279
|
+
seq, rules_applied = apply_orchestration_rules(
|
|
280
|
+
freshness=freshness,
|
|
281
|
+
is_java=is_java,
|
|
282
|
+
api_surface_complete=api_surface_complete,
|
|
283
|
+
repo_class_count=repo_class_count,
|
|
284
|
+
intent=intent or INTENT_ORIENTATION,
|
|
285
|
+
sequence=base_seq,
|
|
286
|
+
)
|
|
287
|
+
|
|
288
|
+
# Recommended next action
|
|
289
|
+
next_tool = seq[0] if seq else "get_compact_context"
|
|
290
|
+
if task_description.strip() and intent and flow_runner:
|
|
291
|
+
next_tool = flow_runner
|
|
292
|
+
next_reason = WORKFLOW_DESCRIPTIONS.get(intent, "")
|
|
293
|
+
elif freshness == "missing":
|
|
294
|
+
next_reason = "No RIS found — build context first (~8s for large repos, instant after)"
|
|
295
|
+
elif freshness == "stale":
|
|
296
|
+
next_reason = "RIS outdated — refresh delta before deeper analysis"
|
|
297
|
+
elif session_state == SESSION_INCOMPLETE_CONTEXT and is_java:
|
|
298
|
+
next_reason = "Java repo with no endpoint index — populate API surface first"
|
|
299
|
+
else:
|
|
300
|
+
next_reason = "RIS fresh — describe your task to get a targeted tool sequence"
|
|
301
|
+
|
|
302
|
+
recommended_args: dict[str, Any] = {"repo_path": repo_path}
|
|
303
|
+
if task_description.strip() and intent == INTENT_BUG_INVESTIGATION:
|
|
304
|
+
symptom = _extract_symptom(task_description)
|
|
305
|
+
if symptom:
|
|
306
|
+
recommended_args["symptom"] = symptom
|
|
307
|
+
|
|
308
|
+
ttfca_ms = int((time.monotonic() - t0) * 1000)
|
|
309
|
+
|
|
310
|
+
effective_state = SESSION_TASK_INTENT_DETECTED if (task_description.strip() and intent) else session_state
|
|
311
|
+
|
|
312
|
+
result: dict[str, Any] = {
|
|
313
|
+
"session_state": effective_state,
|
|
314
|
+
"repo_type": "java_spring" if spring_detected else ("java" if is_java else "unknown"),
|
|
315
|
+
"cache_freshness": freshness,
|
|
316
|
+
"recommended_next_action": {
|
|
317
|
+
"tool": next_tool,
|
|
318
|
+
"reason": next_reason,
|
|
319
|
+
"args": recommended_args,
|
|
320
|
+
},
|
|
321
|
+
"required_tools_sequence": seq,
|
|
322
|
+
"risk_level": _risk_level(freshness, cold.get("stale", False), repo_class_count),
|
|
323
|
+
"entrypoint_candidates": cold.get("entrypoints", []),
|
|
324
|
+
"endpoints_count": endpoints_count,
|
|
325
|
+
"affected_modules": [],
|
|
326
|
+
"session_meta": {
|
|
327
|
+
"ttfca_ms": ttfca_ms,
|
|
328
|
+
"tools_suggested": len(seq),
|
|
329
|
+
"agent_decision_reduction": f"{len(seq)}/18 tools exposed",
|
|
330
|
+
"orchestration_rules_applied": rules_applied,
|
|
331
|
+
},
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
if intent:
|
|
335
|
+
result["intent"] = intent
|
|
336
|
+
result["intent_confidence"] = intent_confidence
|
|
337
|
+
result["workflow_description"] = WORKFLOW_DESCRIPTIONS.get(intent, "")
|
|
338
|
+
|
|
339
|
+
# Include lightweight context when available
|
|
340
|
+
if session_state in (SESSION_CONTEXT_LOADED, SESSION_STALE_CONTEXT, SESSION_INCOMPLETE_CONTEXT):
|
|
341
|
+
result["ris_summary"] = {
|
|
342
|
+
"git_head": cold.get("git_head", ""),
|
|
343
|
+
"last_updated_at": cold.get("last_updated_at", ""),
|
|
344
|
+
"has_uncommitted_changes": cold.get("has_uncommitted_changes", False),
|
|
345
|
+
"hotspots": cold.get("hotspots", [])[:5],
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
if is_java and not api_surface_complete:
|
|
349
|
+
result["missing_data_hint"] = (
|
|
350
|
+
"Java repo detected but endpoint index is empty. "
|
|
351
|
+
"Call get_endpoints to populate API surface."
|
|
352
|
+
)
|
|
353
|
+
|
|
354
|
+
if freshness == "missing":
|
|
355
|
+
result["bootstrap_hint"] = (
|
|
356
|
+
"No RIS found. Call get_compact_context to bootstrap "
|
|
357
|
+
"(~8s for large repos; subsequent calls instant via RIS)."
|
|
358
|
+
)
|
|
359
|
+
|
|
360
|
+
return result
|
|
361
|
+
|
|
362
|
+
|
|
363
|
+
# ---------------------------------------------------------------------------
|
|
364
|
+
# analyze_task
|
|
365
|
+
# ---------------------------------------------------------------------------
|
|
366
|
+
|
|
367
|
+
def analyze_task_impl(repo_path: str, task_description: str) -> dict[str, Any]:
|
|
368
|
+
"""Core logic for analyze_task MCP tool."""
|
|
369
|
+
t0 = time.monotonic()
|
|
370
|
+
|
|
371
|
+
intent, confidence = detect_intent(task_description)
|
|
372
|
+
symptom = _extract_symptom(task_description) if intent == INTENT_BUG_INVESTIGATION else ""
|
|
373
|
+
|
|
374
|
+
cold = _cold_start(repo_path)
|
|
375
|
+
freshness = _freshness(cold.get("status", "no_ris"))
|
|
376
|
+
is_java = _is_java_repo(repo_path)
|
|
377
|
+
api_surface_complete = cold.get("api_surface_complete", True)
|
|
378
|
+
|
|
379
|
+
base_seq = list(WORKFLOW_SEQUENCES.get(intent, ["get_compact_context"]))
|
|
380
|
+
flow_runner = FLOW_RUNNERS.get(intent)
|
|
381
|
+
|
|
382
|
+
seq, rules = apply_orchestration_rules(
|
|
383
|
+
freshness=freshness,
|
|
384
|
+
is_java=is_java,
|
|
385
|
+
api_surface_complete=api_surface_complete,
|
|
386
|
+
repo_class_count=0,
|
|
387
|
+
intent=intent,
|
|
388
|
+
sequence=base_seq,
|
|
389
|
+
)
|
|
390
|
+
|
|
391
|
+
extracted_params: dict[str, Any] = {}
|
|
392
|
+
if symptom:
|
|
393
|
+
extracted_params["symptom"] = symptom
|
|
394
|
+
|
|
395
|
+
recommended_args: dict[str, Any] = {"repo_path": repo_path}
|
|
396
|
+
if symptom and intent == INTENT_BUG_INVESTIGATION:
|
|
397
|
+
recommended_args["symptom"] = symptom
|
|
398
|
+
|
|
399
|
+
ttfca_ms = int((time.monotonic() - t0) * 1000)
|
|
400
|
+
|
|
401
|
+
result: dict[str, Any] = {
|
|
402
|
+
"session_state": SESSION_TASK_INTENT_DETECTED,
|
|
403
|
+
"intent": intent,
|
|
404
|
+
"intent_confidence": confidence,
|
|
405
|
+
"workflow_description": WORKFLOW_DESCRIPTIONS.get(intent, ""),
|
|
406
|
+
"required_tools_sequence": seq,
|
|
407
|
+
"recommended_next_action": {
|
|
408
|
+
"tool": flow_runner or (seq[0] if seq else "get_compact_context"),
|
|
409
|
+
"reason": WORKFLOW_DESCRIPTIONS.get(intent, ""),
|
|
410
|
+
"args": recommended_args,
|
|
411
|
+
},
|
|
412
|
+
"extracted_params": extracted_params,
|
|
413
|
+
"session_meta": {
|
|
414
|
+
"ttfca_ms": ttfca_ms,
|
|
415
|
+
"orchestration_rules_applied": rules,
|
|
416
|
+
},
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
if intent == INTENT_BUG_INVESTIGATION and not symptom:
|
|
420
|
+
result["quality_warning"] = (
|
|
421
|
+
"No error class or message extracted from task description. "
|
|
422
|
+
"Pass symptom= to fix_bug_context for focused file ranking."
|
|
423
|
+
)
|
|
424
|
+
|
|
425
|
+
return result
|
|
426
|
+
|
|
427
|
+
|
|
428
|
+
# ---------------------------------------------------------------------------
|
|
429
|
+
# Flow: PR Review
|
|
430
|
+
# ---------------------------------------------------------------------------
|
|
431
|
+
|
|
432
|
+
def run_pr_review_flow_impl(repo_path: str, since: str = "") -> dict[str, Any]:
|
|
433
|
+
"""PR Review Flow: delta → execution paths → blast radius of top changed classes.
|
|
434
|
+
|
|
435
|
+
Auto-detects merge-base with origin/main or origin/master when since is omitted.
|
|
436
|
+
Runs get_impact_context for up to 3 changed Java classes automatically.
|
|
437
|
+
Returns consolidated output — agent makes zero sequencing decisions.
|
|
438
|
+
"""
|
|
439
|
+
t0 = time.monotonic()
|
|
440
|
+
steps: list[str] = []
|
|
441
|
+
quality_warnings: list[str] = []
|
|
442
|
+
output: dict[str, Any] = {}
|
|
443
|
+
|
|
444
|
+
# Check freshness
|
|
445
|
+
cold = _cold_start(repo_path)
|
|
446
|
+
if _freshness(cold.get("status", "no_ris")) == "stale":
|
|
447
|
+
quality_warnings.append("RIS_stale—snapshot_may_not_reflect_current_HEAD")
|
|
448
|
+
|
|
449
|
+
# Auto-detect since
|
|
450
|
+
if not since:
|
|
451
|
+
import subprocess as _sp
|
|
452
|
+
for base in ("origin/main", "origin/master"):
|
|
453
|
+
try:
|
|
454
|
+
r = _sp.run(
|
|
455
|
+
["git", "-C", repo_path, "merge-base", "HEAD", base],
|
|
456
|
+
capture_output=True, text=True, timeout=5,
|
|
457
|
+
)
|
|
458
|
+
if r.returncode == 0 and r.stdout.strip():
|
|
459
|
+
since = r.stdout.strip()
|
|
460
|
+
break
|
|
461
|
+
except Exception:
|
|
462
|
+
pass
|
|
463
|
+
if not since:
|
|
464
|
+
since = "HEAD~1"
|
|
465
|
+
|
|
466
|
+
# Step 1: delta
|
|
467
|
+
delta = _exec(["prepare-context", "delta", repo_path, "--since", since])
|
|
468
|
+
steps.append(f"get_delta(since={since[:12]})")
|
|
469
|
+
if "_exec_error" not in delta:
|
|
470
|
+
output["delta_context"] = delta
|
|
471
|
+
else:
|
|
472
|
+
quality_warnings.append(f"delta_failed: {delta['_exec_error']}")
|
|
473
|
+
|
|
474
|
+
# Step 2: PR context
|
|
475
|
+
pr_args = ["prepare-context", "review-pr", repo_path, "--since", since]
|
|
476
|
+
pr = _exec(pr_args)
|
|
477
|
+
steps.append("review_pr_context")
|
|
478
|
+
if "_exec_error" not in pr:
|
|
479
|
+
output["pr_context"] = pr
|
|
480
|
+
else:
|
|
481
|
+
quality_warnings.append(f"review_pr_failed: {pr['_exec_error']}")
|
|
482
|
+
|
|
483
|
+
# Step 3: impact for top changed classes (up to 3)
|
|
484
|
+
changed_classes = _extract_changed_classes_from_delta(delta)
|
|
485
|
+
impact_results: list[dict[str, Any]] = []
|
|
486
|
+
for cls in changed_classes[:3]:
|
|
487
|
+
imp = _exec(["impact", cls, repo_path, "--depth", "3"])
|
|
488
|
+
steps.append(f"get_impact_context({cls})")
|
|
489
|
+
if "_exec_error" not in imp:
|
|
490
|
+
impact_results.append({"target": cls, "result": imp})
|
|
491
|
+
else:
|
|
492
|
+
quality_warnings.append(f"impact_failed({cls}): {imp['_exec_error']}")
|
|
493
|
+
if impact_results:
|
|
494
|
+
output["impact_analysis"] = impact_results
|
|
495
|
+
|
|
496
|
+
ttfca_ms = int((time.monotonic() - t0) * 1000)
|
|
497
|
+
|
|
498
|
+
return {
|
|
499
|
+
"session_state": SESSION_READY_FOR_REVIEW,
|
|
500
|
+
"flow": "pr_review",
|
|
501
|
+
"since": since,
|
|
502
|
+
"steps_executed": steps,
|
|
503
|
+
"quality_warnings": quality_warnings,
|
|
504
|
+
"consolidated_output": output,
|
|
505
|
+
"session_meta": {
|
|
506
|
+
"ttfca_ms": ttfca_ms,
|
|
507
|
+
"steps_auto_executed": len(steps),
|
|
508
|
+
"tools_suggested_to_agent": 0,
|
|
509
|
+
},
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
|
|
513
|
+
def _extract_changed_classes_from_delta(delta: dict[str, Any]) -> list[str]:
|
|
514
|
+
if "_exec_error" in delta:
|
|
515
|
+
return []
|
|
516
|
+
changed_files: list[Any] = (
|
|
517
|
+
delta.get("changed_files")
|
|
518
|
+
or delta.get("files")
|
|
519
|
+
or delta.get("data", {}).get("changed_files", [])
|
|
520
|
+
or []
|
|
521
|
+
)
|
|
522
|
+
classes: list[str] = []
|
|
523
|
+
for f in changed_files:
|
|
524
|
+
if isinstance(f, dict):
|
|
525
|
+
path = f.get("path") or f.get("file") or ""
|
|
526
|
+
elif isinstance(f, str):
|
|
527
|
+
path = f
|
|
528
|
+
else:
|
|
529
|
+
continue
|
|
530
|
+
if isinstance(path, str) and path.endswith(".java"):
|
|
531
|
+
name = path.split("/")[-1].replace(".java", "")
|
|
532
|
+
if name and name not in classes:
|
|
533
|
+
classes.append(name)
|
|
534
|
+
return classes
|
|
535
|
+
|
|
536
|
+
|
|
537
|
+
# ---------------------------------------------------------------------------
|
|
538
|
+
# Flow: Bug Investigation
|
|
539
|
+
# ---------------------------------------------------------------------------
|
|
540
|
+
|
|
541
|
+
def run_bug_investigation_flow_impl(repo_path: str, symptom: str = "") -> dict[str, Any]:
|
|
542
|
+
"""Bug Investigation Flow: risk-ranked files → impact of top suspect → IR context.
|
|
543
|
+
|
|
544
|
+
symptom should be an error message, exception class, or affected class name.
|
|
545
|
+
Without symptom, ranking is generic (not focused) — quality_warnings will note this.
|
|
546
|
+
"""
|
|
547
|
+
t0 = time.monotonic()
|
|
548
|
+
steps: list[str] = []
|
|
549
|
+
quality_warnings: list[str] = []
|
|
550
|
+
output: dict[str, Any] = {}
|
|
551
|
+
|
|
552
|
+
if not symptom:
|
|
553
|
+
quality_warnings.append(
|
|
554
|
+
"no_symptom_provided: file ranking is generic, not focused. "
|
|
555
|
+
"Pass symptom= with error message or class name for targeted analysis."
|
|
556
|
+
)
|
|
557
|
+
|
|
558
|
+
# Step 1: fix-bug context
|
|
559
|
+
fix_args = ["prepare-context", "fix-bug", repo_path]
|
|
560
|
+
if symptom:
|
|
561
|
+
fix_args.extend(["--symptom", symptom])
|
|
562
|
+
fix = _exec(fix_args)
|
|
563
|
+
steps.append(f"fix_bug_context(symptom={symptom!r})" if symptom else "fix_bug_context(no_symptom)")
|
|
564
|
+
if "_exec_error" not in fix:
|
|
565
|
+
output["risk_ranked_files"] = fix
|
|
566
|
+
else:
|
|
567
|
+
quality_warnings.append(f"fix_bug_failed: {fix['_exec_error']}")
|
|
568
|
+
|
|
569
|
+
# Step 2: impact for top suspect class
|
|
570
|
+
suspect = _extract_class_from_symptom(symptom) or _top_class_from_fix(fix)
|
|
571
|
+
if suspect:
|
|
572
|
+
imp = _exec(["impact", suspect, repo_path, "--depth", "4"])
|
|
573
|
+
steps.append(f"get_impact_context({suspect})")
|
|
574
|
+
if "_exec_error" not in imp:
|
|
575
|
+
output["impact_analysis"] = {"target": suspect, "result": imp}
|
|
576
|
+
else:
|
|
577
|
+
quality_warnings.append(f"impact_failed({suspect}): {imp['_exec_error']}")
|
|
578
|
+
|
|
579
|
+
# Step 3: IR summary for Java (dependency context)
|
|
580
|
+
if _is_java_repo(repo_path):
|
|
581
|
+
ir = _exec(["repo-ir", repo_path, "--summary-only"])
|
|
582
|
+
steps.append("get_ir_summary")
|
|
583
|
+
if "_exec_error" not in ir:
|
|
584
|
+
output["ir_summary"] = ir
|
|
585
|
+
else:
|
|
586
|
+
quality_warnings.append(f"ir_summary_failed: {ir['_exec_error']}")
|
|
587
|
+
|
|
588
|
+
ttfca_ms = int((time.monotonic() - t0) * 1000)
|
|
589
|
+
|
|
590
|
+
return {
|
|
591
|
+
"session_state": SESSION_READY_FOR_REVIEW,
|
|
592
|
+
"flow": "bug_investigation",
|
|
593
|
+
"symptom": symptom,
|
|
594
|
+
"suspect_class": suspect,
|
|
595
|
+
"steps_executed": steps,
|
|
596
|
+
"quality_warnings": quality_warnings,
|
|
597
|
+
"consolidated_output": output,
|
|
598
|
+
"session_meta": {
|
|
599
|
+
"ttfca_ms": ttfca_ms,
|
|
600
|
+
"steps_auto_executed": len(steps),
|
|
601
|
+
"tools_suggested_to_agent": 0,
|
|
602
|
+
},
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
|
|
606
|
+
def _extract_class_from_symptom(symptom: str) -> str:
|
|
607
|
+
if not symptom:
|
|
608
|
+
return ""
|
|
609
|
+
m = re.search(
|
|
610
|
+
r'\b([A-Z][a-zA-Z0-9]+(?:Service|Controller|Repository|Handler|Manager|Util|Helper|DAO|Rest|Api)?)\b',
|
|
611
|
+
symptom,
|
|
612
|
+
)
|
|
613
|
+
return m.group(1) if m else ""
|
|
614
|
+
|
|
615
|
+
|
|
616
|
+
def _top_class_from_fix(fix: dict[str, Any]) -> str:
|
|
617
|
+
if "_exec_error" in fix:
|
|
618
|
+
return ""
|
|
619
|
+
files: list[Any] = (
|
|
620
|
+
fix.get("files")
|
|
621
|
+
or fix.get("ranked_files")
|
|
622
|
+
or fix.get("top_files")
|
|
623
|
+
or fix.get("data", {}).get("files", [])
|
|
624
|
+
or []
|
|
625
|
+
)
|
|
626
|
+
for f in files:
|
|
627
|
+
if isinstance(f, dict):
|
|
628
|
+
path = str(f.get("path") or f.get("file") or "")
|
|
629
|
+
elif isinstance(f, str):
|
|
630
|
+
path = f
|
|
631
|
+
else:
|
|
632
|
+
continue
|
|
633
|
+
if path.endswith(".java"):
|
|
634
|
+
return path.split("/")[-1].replace(".java", "")
|
|
635
|
+
return ""
|
|
636
|
+
|
|
637
|
+
|
|
638
|
+
# ---------------------------------------------------------------------------
|
|
639
|
+
# Flow: Feature Implementation
|
|
640
|
+
# ---------------------------------------------------------------------------
|
|
641
|
+
|
|
642
|
+
def run_feature_flow_impl(repo_path: str, feature_description: str = "") -> dict[str, Any]:
|
|
643
|
+
"""Feature Implementation Flow: context → API surface → recent changes → structural awareness.
|
|
644
|
+
|
|
645
|
+
Provides everything an agent needs to implement a new feature:
|
|
646
|
+
structural context, existing API surface, what changed recently, and coupling hotspots.
|
|
647
|
+
"""
|
|
648
|
+
t0 = time.monotonic()
|
|
649
|
+
steps: list[str] = []
|
|
650
|
+
quality_warnings: list[str] = []
|
|
651
|
+
output: dict[str, Any] = {}
|
|
652
|
+
|
|
653
|
+
is_java = _is_java_repo(repo_path)
|
|
654
|
+
|
|
655
|
+
# Step 1: compact context (use RIS fast path if available)
|
|
656
|
+
cold = _cold_start(repo_path)
|
|
657
|
+
freshness = _freshness(cold.get("status", "no_ris"))
|
|
658
|
+
if freshness == "fresh" and cold.get("summary"):
|
|
659
|
+
output["context_summary"] = cold["summary"]
|
|
660
|
+
steps.append("get_context(RIS_fast_path)")
|
|
661
|
+
else:
|
|
662
|
+
ctx = _exec([repo_path, "--compact"])
|
|
663
|
+
steps.append("get_compact_context")
|
|
664
|
+
if "_exec_error" not in ctx:
|
|
665
|
+
output["context_summary"] = ctx
|
|
666
|
+
else:
|
|
667
|
+
quality_warnings.append(f"compact_context_failed: {ctx['_exec_error']}")
|
|
668
|
+
|
|
669
|
+
# Step 2: API surface (Java only)
|
|
670
|
+
if is_java:
|
|
671
|
+
ep = _exec(["endpoints", repo_path])
|
|
672
|
+
steps.append("get_endpoints")
|
|
673
|
+
if "_exec_error" not in ep:
|
|
674
|
+
output["api_surface"] = ep
|
|
675
|
+
else:
|
|
676
|
+
quality_warnings.append(f"endpoints_failed: {ep['_exec_error']}")
|
|
677
|
+
|
|
678
|
+
# Step 3: recent delta (last 3 commits for context on active areas)
|
|
679
|
+
delta = _exec(["prepare-context", "delta", repo_path, "--since", "HEAD~3"])
|
|
680
|
+
steps.append("get_delta(HEAD~3)")
|
|
681
|
+
if "_exec_error" not in delta:
|
|
682
|
+
output["recent_changes"] = delta
|
|
683
|
+
else:
|
|
684
|
+
quality_warnings.append(f"delta_failed: {delta['_exec_error']}")
|
|
685
|
+
|
|
686
|
+
# Step 4: structural context (coupling, hotspots, refactor opportunities)
|
|
687
|
+
refactor = _exec(["prepare-context", "refactor", repo_path])
|
|
688
|
+
steps.append("refactor_context")
|
|
689
|
+
if "_exec_error" not in refactor:
|
|
690
|
+
output["structural_context"] = refactor
|
|
691
|
+
else:
|
|
692
|
+
quality_warnings.append(f"refactor_context_failed: {refactor['_exec_error']}")
|
|
693
|
+
|
|
694
|
+
ttfca_ms = int((time.monotonic() - t0) * 1000)
|
|
695
|
+
|
|
696
|
+
return {
|
|
697
|
+
"session_state": SESSION_READY_FOR_REVIEW,
|
|
698
|
+
"flow": "feature_implementation",
|
|
699
|
+
"feature_description": feature_description,
|
|
700
|
+
"steps_executed": steps,
|
|
701
|
+
"quality_warnings": quality_warnings,
|
|
702
|
+
"consolidated_output": output,
|
|
703
|
+
"session_meta": {
|
|
704
|
+
"ttfca_ms": ttfca_ms,
|
|
705
|
+
"steps_auto_executed": len(steps),
|
|
706
|
+
"tools_suggested_to_agent": 0,
|
|
707
|
+
},
|
|
708
|
+
}
|
sourcecode/mcp/server.py
CHANGED
|
@@ -109,12 +109,242 @@ def _check_repo_path(path: str) -> "CallToolResult | None":
|
|
|
109
109
|
return None
|
|
110
110
|
|
|
111
111
|
|
|
112
|
+
@mcp.tool()
|
|
113
|
+
def start_session(repo_path: str = ".", task_description: str = "") -> dict:
|
|
114
|
+
"""PRIMARY ENTRY POINT — call this first on every new MCP session.
|
|
115
|
+
|
|
116
|
+
Single entry point that replaces manual tool selection. Determines session state,
|
|
117
|
+
checks RIS freshness, detects repo type, and returns a ready-to-execute tool
|
|
118
|
+
sequence. Agent never has to guess which tool to call next.
|
|
119
|
+
|
|
120
|
+
With task_description: detects intent (pr_review, bug_investigation,
|
|
121
|
+
feature_implementation, refactor, test_generation) and returns the exact
|
|
122
|
+
flow runner to call with pre-filled args. Zero sequencing decisions for agent.
|
|
123
|
+
|
|
124
|
+
Without task_description: returns session state + recommended_next_action
|
|
125
|
+
based on RIS freshness and repo type (java_spring, java, etc.).
|
|
126
|
+
|
|
127
|
+
Returns:
|
|
128
|
+
session_state — INIT | CONTEXT_LOADED | STALE_CONTEXT |
|
|
129
|
+
INCOMPLETE_CONTEXT | TASK_INTENT_DETECTED
|
|
130
|
+
repo_type — java_spring | java | nodejs | python | unknown
|
|
131
|
+
cache_freshness — fresh | stale | missing
|
|
132
|
+
recommended_next_action — {tool, reason, args} — call this next, no guessing
|
|
133
|
+
required_tools_sequence — ordered list of tools the agent should call
|
|
134
|
+
risk_level — low | medium | high | unknown
|
|
135
|
+
entrypoint_candidates — top entry points from RIS
|
|
136
|
+
endpoints_count — Java endpoint count from RIS api_surface
|
|
137
|
+
session_meta — {ttfca_ms, tools_suggested, agent_decision_reduction,
|
|
138
|
+
orchestration_rules_applied}
|
|
139
|
+
intent — detected intent when task_description provided
|
|
140
|
+
intent_confidence — detection confidence (1.0 = explicit match)
|
|
141
|
+
ris_summary — lightweight RIS snapshot when context loaded
|
|
142
|
+
missing_data_hint — what to build next when critical data absent
|
|
143
|
+
bootstrap_hint — how to build RIS when missing
|
|
144
|
+
|
|
145
|
+
Orchestration rules applied automatically:
|
|
146
|
+
R1: stale cache → prepend get_delta to any tool sequence
|
|
147
|
+
R2: Java + no endpoint index → prepend get_endpoints
|
|
148
|
+
R3: repo > 1000 classes → RIS path flagged as preferred
|
|
149
|
+
|
|
150
|
+
KPIs tracked in session_meta:
|
|
151
|
+
ttfca_ms — time_to_first_correct_action in milliseconds
|
|
152
|
+
tools_suggested — agent decision reduction (1-3 vs 18 free tools)
|
|
153
|
+
orchestration_rules_applied — which rules fired
|
|
154
|
+
|
|
155
|
+
repo_path: absolute path to the repository (default: current working directory).
|
|
156
|
+
task_description: optional natural language task description for intent detection.
|
|
157
|
+
Examples: "review the PR on the auth module",
|
|
158
|
+
"NullPointerException in UserService.findById",
|
|
159
|
+
"implement a new endpoint for password reset"
|
|
160
|
+
"""
|
|
161
|
+
_raw = repo_path
|
|
162
|
+
try:
|
|
163
|
+
if not isinstance(repo_path, str):
|
|
164
|
+
return _err("repo_path must be a string", "INVALID_ARGUMENT")
|
|
165
|
+
repo_path = _normalize_repo_path(repo_path)
|
|
166
|
+
_path_err = _check_repo_path(repo_path)
|
|
167
|
+
if _path_err is not None:
|
|
168
|
+
return _path_err
|
|
169
|
+
from sourcecode.mcp.orchestrator import start_session_impl
|
|
170
|
+
return _ok(start_session_impl(repo_path, task_description or ""))
|
|
171
|
+
except Exception as exc:
|
|
172
|
+
return _err(
|
|
173
|
+
f"Internal error: {type(exc).__name__}: {exc} — repo_path: {_raw}",
|
|
174
|
+
"INTERNAL_ERROR",
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
@mcp.tool()
|
|
179
|
+
def analyze_task(repo_path: str = ".", task_description: str = "") -> dict:
|
|
180
|
+
"""Detect task intent and return targeted tool sequence. No tool guessing needed.
|
|
181
|
+
|
|
182
|
+
Given a natural language description of what you need to do, returns:
|
|
183
|
+
- Detected intent (pr_review, bug_investigation, feature_implementation, etc.)
|
|
184
|
+
- Ordered tool sequence to execute
|
|
185
|
+
- Recommended flow runner to call with pre-filled args
|
|
186
|
+
- Extracted parameters (symptom for bugs, etc.)
|
|
187
|
+
- Orchestration rules that were applied
|
|
188
|
+
|
|
189
|
+
Prefer start_session(task_description=...) for combined bootstrap + intent detection.
|
|
190
|
+
Use analyze_task standalone when session is already loaded and you have a new task.
|
|
191
|
+
|
|
192
|
+
Quality warnings included when:
|
|
193
|
+
- Bug intent detected but no error class/message found in description
|
|
194
|
+
(ranking will be generic, not focused)
|
|
195
|
+
|
|
196
|
+
repo_path: absolute path to the repository (default: current working directory).
|
|
197
|
+
task_description: natural language task description (required for meaningful output).
|
|
198
|
+
Examples: "fix the NPE in PaymentService",
|
|
199
|
+
"review PR changes in the billing module",
|
|
200
|
+
"add new REST endpoint for user preferences"
|
|
201
|
+
"""
|
|
202
|
+
_raw = repo_path
|
|
203
|
+
try:
|
|
204
|
+
if not isinstance(repo_path, str):
|
|
205
|
+
return _err("repo_path must be a string", "INVALID_ARGUMENT")
|
|
206
|
+
if not isinstance(task_description, str) or not task_description.strip():
|
|
207
|
+
return _err("task_description must be a non-empty string", "INVALID_ARGUMENT")
|
|
208
|
+
repo_path = _normalize_repo_path(repo_path)
|
|
209
|
+
_path_err = _check_repo_path(repo_path)
|
|
210
|
+
if _path_err is not None:
|
|
211
|
+
return _path_err
|
|
212
|
+
from sourcecode.mcp.orchestrator import analyze_task_impl
|
|
213
|
+
return _ok(analyze_task_impl(repo_path, task_description))
|
|
214
|
+
except Exception as exc:
|
|
215
|
+
return _err(
|
|
216
|
+
f"Internal error: {type(exc).__name__}: {exc} — repo_path: {_raw}",
|
|
217
|
+
"INTERNAL_ERROR",
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
@mcp.tool()
|
|
222
|
+
def run_pr_review_flow(repo_path: str = ".", since: str = "") -> dict:
|
|
223
|
+
"""PR Review Flow — auto-chains 3 tools: delta → execution paths → blast radius.
|
|
224
|
+
|
|
225
|
+
Auto-executes the complete PR review pipeline in one call:
|
|
226
|
+
1. get_delta(since) — changed files and context since merge base
|
|
227
|
+
2. review_pr_context — execution paths and hotspots for changed files
|
|
228
|
+
3. get_impact_context — blast radius for up to 3 changed Java classes
|
|
229
|
+
|
|
230
|
+
Auto-detects merge base with origin/main or origin/master when since is omitted.
|
|
231
|
+
Falls back to HEAD~1 when no remote branch found.
|
|
232
|
+
|
|
233
|
+
Returns consolidated_output with all three results merged.
|
|
234
|
+
session_meta.ttfca_ms shows total wall-clock time.
|
|
235
|
+
quality_warnings list any partial failures (steps still return partial output).
|
|
236
|
+
|
|
237
|
+
Use this instead of calling get_delta + review_pr_context + get_impact_context
|
|
238
|
+
separately — agent makes zero sequencing decisions.
|
|
239
|
+
|
|
240
|
+
JAVA ONLY for blast-radius step. Delta and execution paths work on all repos.
|
|
241
|
+
|
|
242
|
+
repo_path: absolute path to the repository (default: current working directory).
|
|
243
|
+
since: git ref to diff against. Auto-detected from origin/main if omitted.
|
|
244
|
+
"""
|
|
245
|
+
_raw = repo_path
|
|
246
|
+
try:
|
|
247
|
+
if not isinstance(repo_path, str):
|
|
248
|
+
return _err("repo_path must be a string", "INVALID_ARGUMENT")
|
|
249
|
+
repo_path = _normalize_repo_path(repo_path)
|
|
250
|
+
_path_err = _check_repo_path(repo_path)
|
|
251
|
+
if _path_err is not None:
|
|
252
|
+
return _path_err
|
|
253
|
+
from sourcecode.mcp.orchestrator import run_pr_review_flow_impl
|
|
254
|
+
return _ok(run_pr_review_flow_impl(repo_path, since or ""))
|
|
255
|
+
except Exception as exc:
|
|
256
|
+
return _err(
|
|
257
|
+
f"Internal error: {type(exc).__name__}: {exc} — repo_path: {_raw}",
|
|
258
|
+
"INTERNAL_ERROR",
|
|
259
|
+
)
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
@mcp.tool()
|
|
263
|
+
def run_bug_investigation_flow(repo_path: str = ".", symptom: str = "") -> dict:
|
|
264
|
+
"""Bug Investigation Flow — auto-chains 3 tools: risk-rank → impact → IR context.
|
|
265
|
+
|
|
266
|
+
Auto-executes the complete bug investigation pipeline in one call:
|
|
267
|
+
1. fix_bug_context(symptom) — files ranked by risk, focused on symptom
|
|
268
|
+
2. get_impact_context — blast radius of the top suspect class
|
|
269
|
+
3. get_ir_summary — Java IR dependency context (Java repos only)
|
|
270
|
+
|
|
271
|
+
symptom should be: error message, exception class name, or affected class.
|
|
272
|
+
Without symptom: ranking is generic (not focused). quality_warnings will note this.
|
|
273
|
+
|
|
274
|
+
Returns consolidated_output with all results plus suspect_class (auto-extracted
|
|
275
|
+
from symptom or top ranked file). session_meta.ttfca_ms shows total wall time.
|
|
276
|
+
|
|
277
|
+
Use this instead of calling fix_bug_context + get_impact_context + get_ir_summary
|
|
278
|
+
separately — agent makes zero sequencing decisions.
|
|
279
|
+
|
|
280
|
+
repo_path: absolute path to the repository (default: current working directory).
|
|
281
|
+
symptom: error message, exception class, or class name.
|
|
282
|
+
Examples: "NullPointerException in UserService.findById",
|
|
283
|
+
"PaymentController", "AuthenticationException"
|
|
284
|
+
"""
|
|
285
|
+
_raw = repo_path
|
|
286
|
+
try:
|
|
287
|
+
if not isinstance(repo_path, str):
|
|
288
|
+
return _err("repo_path must be a string", "INVALID_ARGUMENT")
|
|
289
|
+
repo_path = _normalize_repo_path(repo_path)
|
|
290
|
+
_path_err = _check_repo_path(repo_path)
|
|
291
|
+
if _path_err is not None:
|
|
292
|
+
return _path_err
|
|
293
|
+
from sourcecode.mcp.orchestrator import run_bug_investigation_flow_impl
|
|
294
|
+
return _ok(run_bug_investigation_flow_impl(repo_path, symptom or ""))
|
|
295
|
+
except Exception as exc:
|
|
296
|
+
return _err(
|
|
297
|
+
f"Internal error: {type(exc).__name__}: {exc} — repo_path: {_raw}",
|
|
298
|
+
"INTERNAL_ERROR",
|
|
299
|
+
)
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
@mcp.tool()
|
|
303
|
+
def run_feature_flow(repo_path: str = ".", feature_description: str = "") -> dict:
|
|
304
|
+
"""Feature Implementation Flow — auto-chains 4 tools: context → API → delta → structure.
|
|
305
|
+
|
|
306
|
+
Auto-executes the complete feature planning pipeline in one call:
|
|
307
|
+
1. get_compact_context (or RIS fast-path if fresh) — project orientation
|
|
308
|
+
2. get_endpoints — existing API surface (Java repos only)
|
|
309
|
+
3. get_delta(HEAD~3) — what changed recently (active development areas)
|
|
310
|
+
4. refactor_context — structural coupling and hotspot awareness
|
|
311
|
+
|
|
312
|
+
Returns consolidated_output with all four results. Use feature_description to
|
|
313
|
+
help the agent understand what you are building (stored in output for traceability).
|
|
314
|
+
|
|
315
|
+
Use this instead of calling the four tools separately — agent makes zero
|
|
316
|
+
sequencing decisions. Typical wall time: 2-10s depending on repo size and cache.
|
|
317
|
+
|
|
318
|
+
repo_path: absolute path to the repository (default: current working directory).
|
|
319
|
+
feature_description: optional description of the feature to implement.
|
|
320
|
+
Example: "add password reset flow with email verification"
|
|
321
|
+
"""
|
|
322
|
+
_raw = repo_path
|
|
323
|
+
try:
|
|
324
|
+
if not isinstance(repo_path, str):
|
|
325
|
+
return _err("repo_path must be a string", "INVALID_ARGUMENT")
|
|
326
|
+
repo_path = _normalize_repo_path(repo_path)
|
|
327
|
+
_path_err = _check_repo_path(repo_path)
|
|
328
|
+
if _path_err is not None:
|
|
329
|
+
return _path_err
|
|
330
|
+
from sourcecode.mcp.orchestrator import run_feature_flow_impl
|
|
331
|
+
return _ok(run_feature_flow_impl(repo_path, feature_description or ""))
|
|
332
|
+
except Exception as exc:
|
|
333
|
+
return _err(
|
|
334
|
+
f"Internal error: {type(exc).__name__}: {exc} — repo_path: {_raw}",
|
|
335
|
+
"INTERNAL_ERROR",
|
|
336
|
+
)
|
|
337
|
+
|
|
338
|
+
|
|
112
339
|
@mcp.tool()
|
|
113
340
|
def get_cold_start_context(repo_path: str = ".") -> dict:
|
|
114
341
|
"""Instant session bootstrap from persisted Repository Intelligence Snapshot (RIS).
|
|
115
342
|
|
|
116
|
-
|
|
117
|
-
|
|
343
|
+
PREFER start_session over this tool — it provides orchestration guidance on top
|
|
344
|
+
of the same RIS data. Use get_cold_start_context when you only need the raw
|
|
345
|
+
RIS bootstrap object without tool sequencing recommendations.
|
|
346
|
+
|
|
347
|
+
Returns cached structural context built from prior analysis runs — zero re-analysis cost.
|
|
118
348
|
|
|
119
349
|
status values:
|
|
120
350
|
"cold_start_ready" — RIS exists and matches the current git HEAD.
|
sourcecode/repository_ir.py
CHANGED
|
@@ -201,6 +201,20 @@ _FILTER_SECURITY_ANNOTATIONS: frozenset[str] = frozenset({
|
|
|
201
201
|
"@EnableGlobalMethodSecurity",
|
|
202
202
|
})
|
|
203
203
|
|
|
204
|
+
# Programmatic security: method-call patterns that indicate runtime auth enforcement.
|
|
205
|
+
_PROGRAMMATIC_SECURITY_RE = re.compile(
|
|
206
|
+
r"\b(?:hasRole|hasAuthority|isAuthenticated|requirePermission|checkPermission"
|
|
207
|
+
r"|assertAuthorized|authenticate)\s*\("
|
|
208
|
+
r"|(?:Authentication|SecurityContext|Principal|AuthorizationManager|AccessDecisionManager)\b"
|
|
209
|
+
r"|throw\s+new\s+(?:AccessDeniedException|UnauthorizedException|ForbiddenException|AuthenticationException)\b",
|
|
210
|
+
re.MULTILINE,
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
def _has_programmatic_security(source: str) -> bool:
|
|
215
|
+
return bool(_PROGRAMMATIC_SECURITY_RE.search(source))
|
|
216
|
+
|
|
217
|
+
|
|
204
218
|
_MODIFIER_WORDS: frozenset[str] = frozenset({
|
|
205
219
|
"public", "private", "protected", "static", "final", "abstract",
|
|
206
220
|
"synchronized", "native", "strictfp", "transient", "volatile", "default",
|
|
@@ -2365,6 +2379,7 @@ def _build_route_surface(
|
|
|
2365
2379
|
|
|
2366
2380
|
routes: list[dict] = []
|
|
2367
2381
|
seen: set[tuple] = set()
|
|
2382
|
+
_prog_sec_cache: dict[str, Optional[bool]] = {} # declaring_file → has_programmatic
|
|
2368
2383
|
|
|
2369
2384
|
# Phase 2: emit own endpoint symbols and record them per class.
|
|
2370
2385
|
# Each method emits one route per resolved effective prefix.
|
|
@@ -2414,6 +2429,19 @@ def _build_route_surface(
|
|
|
2414
2429
|
_cls_sym_for_sec = class_sym_by_simple.get(cls_simple)
|
|
2415
2430
|
_sec = _route_security_from_sym(sym, _cls_sym_for_sec)
|
|
2416
2431
|
|
|
2432
|
+
# Programmatic security fallback: scan controller file when no annotation found.
|
|
2433
|
+
if _sec is None:
|
|
2434
|
+
_decl_file = sym.declaring_file or ""
|
|
2435
|
+
if _decl_file and _decl_file not in _prog_sec_cache:
|
|
2436
|
+
try:
|
|
2437
|
+
_prog_sec_cache[_decl_file] = _has_programmatic_security(
|
|
2438
|
+
Path(_decl_file).read_text(encoding="utf-8", errors="ignore")
|
|
2439
|
+
)
|
|
2440
|
+
except Exception:
|
|
2441
|
+
_prog_sec_cache[_decl_file] = False
|
|
2442
|
+
if _prog_sec_cache.get(_decl_file):
|
|
2443
|
+
_sec = {"policy": "programmatic"}
|
|
2444
|
+
|
|
2417
2445
|
for prefix in effective_prefixes:
|
|
2418
2446
|
# P1 fix: re.sub collapses any number of consecutive slashes (///, //, etc.)
|
|
2419
2447
|
# Single .replace("//", "/") fails for triple-slash from prefix="/" + suffix="/{id}".
|
|
@@ -2434,8 +2462,7 @@ def _build_route_surface(
|
|
|
2434
2462
|
"stable_id": sym.stable_id,
|
|
2435
2463
|
"inheritance_depth": 0,
|
|
2436
2464
|
}
|
|
2437
|
-
|
|
2438
|
-
_route_entry["security_annotations"] = _sec
|
|
2465
|
+
_route_entry["security_annotations"] = _sec
|
|
2439
2466
|
routes.append(_route_entry)
|
|
2440
2467
|
|
|
2441
2468
|
# Phase 3: inheritance projection — subclasses with zero own endpoints
|
|
@@ -2966,10 +2993,10 @@ def extract_java_endpoints(root: Path) -> "dict[str, Any]":
|
|
|
2966
2993
|
# Use security_annotations already extracted by _build_route_surface
|
|
2967
2994
|
# via the canonical _route_security_from_sym extractor.
|
|
2968
2995
|
security_info = route.get("security_annotations")
|
|
2996
|
+
entry["security"] = security_info # always present; None = no security signal
|
|
2969
2997
|
if security_info:
|
|
2970
|
-
entry["security"] = security_info
|
|
2971
2998
|
# Backward compat: keep required_permission for custom annotation
|
|
2972
|
-
if security_info.get("policy") == "custom_permission":
|
|
2999
|
+
if isinstance(security_info, dict) and security_info.get("policy") == "custom_permission":
|
|
2973
3000
|
entry["required_permission"] = security_info["required_permission"]
|
|
2974
3001
|
endpoints.append(entry)
|
|
2975
3002
|
|
|
@@ -2988,7 +3015,7 @@ def extract_java_endpoints(root: Path) -> "dict[str, Any]":
|
|
|
2988
3015
|
# "no_security_signal" = no recognized security annotation at method OR class level.
|
|
2989
3016
|
# Note: repos may use framework-level security (e.g. Keycloak itself) with no
|
|
2990
3017
|
# per-endpoint annotations — this count reflects annotation-based coverage only.
|
|
2991
|
-
no_security_signal = sum(1 for e in endpoints if "security"
|
|
3018
|
+
no_security_signal = sum(1 for e in endpoints if not e.get("security"))
|
|
2992
3019
|
|
|
2993
3020
|
# Detect filter-based security: centralized Spring Security config class.
|
|
2994
3021
|
# When present, high no_security_signal is expected — security is enforced by
|
|
@@ -3007,7 +3034,7 @@ def extract_java_endpoints(root: Path) -> "dict[str, Any]":
|
|
|
3007
3034
|
for sym in _class_syms
|
|
3008
3035
|
)
|
|
3009
3036
|
)
|
|
3010
|
-
_has_annotation_security = any("security"
|
|
3037
|
+
_has_annotation_security = any(e.get("security") for e in endpoints)
|
|
3011
3038
|
if _filter_based and _has_annotation_security:
|
|
3012
3039
|
security_model = "mixed"
|
|
3013
3040
|
elif _filter_based:
|
|
@@ -3090,8 +3117,34 @@ def compute_blast_radius(
|
|
|
3090
3117
|
subsystems: list[dict] = ir.get("subsystems") or []
|
|
3091
3118
|
|
|
3092
3119
|
# ── 1. Resolve target → one or more FQNs ─────────────────────────────────
|
|
3120
|
+
_path_like = "/" in target or "\\" in target or target.endswith(".java")
|
|
3093
3121
|
resolution, matched_fqns = _resolve_target(target, reverse_graph, graph_nodes)
|
|
3094
3122
|
|
|
3123
|
+
# File-path input with ambiguous resolution: require the user to be specific.
|
|
3124
|
+
if _path_like and len(matched_fqns) > 1:
|
|
3125
|
+
_candidates = sorted(matched_fqns)
|
|
3126
|
+
return {
|
|
3127
|
+
"target": target,
|
|
3128
|
+
"resolution": "ambiguous_path",
|
|
3129
|
+
"message": (
|
|
3130
|
+
f"Path '{target}' matches {len(matched_fqns)} classes in the IR. "
|
|
3131
|
+
"Pass the full FQN to select one."
|
|
3132
|
+
),
|
|
3133
|
+
"candidates": _candidates,
|
|
3134
|
+
"direct_callers": [],
|
|
3135
|
+
"indirect_callers": [],
|
|
3136
|
+
"endpoints_affected": [],
|
|
3137
|
+
"mappers_affected": [],
|
|
3138
|
+
"security_surface_affected": [],
|
|
3139
|
+
"cross_module_impact": [],
|
|
3140
|
+
"transactional_boundaries_touched": [],
|
|
3141
|
+
"risk_score": 0.0,
|
|
3142
|
+
"risk_level": "unknown",
|
|
3143
|
+
"confidence_score": 0.0,
|
|
3144
|
+
"confidence_level": "low",
|
|
3145
|
+
"explanation": f"Ambiguous path — {len(matched_fqns)} candidates found.",
|
|
3146
|
+
}
|
|
3147
|
+
|
|
3095
3148
|
if not matched_fqns:
|
|
3096
3149
|
# Build a short candidate list to help the user
|
|
3097
3150
|
_candidates = _blast_radius_candidates(target, reverse_graph, graph_nodes)
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
sourcecode/__init__.py,sha256=
|
|
1
|
+
sourcecode/__init__.py,sha256=nlWaSRIp4YDFp-4K9qjOa7A4V1btxwQms-q412HBvyA,103
|
|
2
2
|
sourcecode/adaptive_scanner.py,sha256=XffluXKzJUXrMtjEiAOnSNPZnztdIcts17T9ouHeID0,10521
|
|
3
3
|
sourcecode/architecture_analyzer.py,sha256=qh749a7ykPtGmQI1MR9y6j8TtL_jBdVYFx9YRsLqOMw,44121
|
|
4
4
|
sourcecode/architecture_summary.py,sha256=z34_6v7cSwy98cof2UVciGho7SCrZ93tiqMmq5WNzRQ,20405
|
|
@@ -7,7 +7,7 @@ sourcecode/cache.py,sha256=wAyPrXN5DqiGivnMpeEuun2xHDKfBer2_oBsh6kj_vc,30447
|
|
|
7
7
|
sourcecode/cache.tmp_new,sha256=-IvV7CojiZjqeKMln1m-lqI0QVA2uFGWmYir4XRFOUk,27970
|
|
8
8
|
sourcecode/canonical_ir.py,sha256=_HM3AUmKSdna9u4dCoU6rpgSA6HdF8gzOKZykIUCNGY,23277
|
|
9
9
|
sourcecode/classifier.py,sha256=2lYoSH3vOTkXZYPU7Go2WIet1-IuNzTWVhc-ULnXtgw,8024
|
|
10
|
-
sourcecode/cli.py,sha256=
|
|
10
|
+
sourcecode/cli.py,sha256=EMs-sz83Fpl4evsU56xuU_S6ZL0TMzZp9nW9V1bra-I,181396
|
|
11
11
|
sourcecode/code_notes_analyzer.py,sha256=EJemNCNc9Dn-1RZYu-aNbK0ELzmsyC4s6FdHi3XyNEI,9392
|
|
12
12
|
sourcecode/confidence_analyzer.py,sha256=_jckZSxksV-OU38vbkxfVNBnWCtlCq8Vwfg23x1uspA,19054
|
|
13
13
|
sourcecode/context_scorer.py,sha256=QpChSpsmaAYz91rXA4Ue5xzQmNz_ZboZN09YOHScq1U,14679
|
|
@@ -35,7 +35,7 @@ sourcecode/ranking_engine.py,sha256=ZAucq_YX2KkWUuAZf4P0lhtQ_38vEFnUhuGtSZd1S0E,
|
|
|
35
35
|
sourcecode/redactor.py,sha256=SB4hwIvg8h-hvcqKcDWaZvA-aSyn-at-BIRwa0tUv5E,3227
|
|
36
36
|
sourcecode/relevance_scorer.py,sha256=MYF4FFkveAQps9SmTeTlh6ODiBz2F--_hWNeHMLtUHQ,8405
|
|
37
37
|
sourcecode/repo_classifier.py,sha256=FG1vaWKdWXsWdl-S8hjVMiTqcwgaRXkDyvK4rPcOGtQ,22681
|
|
38
|
-
sourcecode/repository_ir.py,sha256
|
|
38
|
+
sourcecode/repository_ir.py,sha256=WtUPRVrvJju6Lrvm_7iTDk57qJFCAh-LJalLtX8jzIk,155595
|
|
39
39
|
sourcecode/ris.py,sha256=vkIe_v-jjceeb0Adhn-Kw5eFXE_nIkGHflOnQYPycTk,17411
|
|
40
40
|
sourcecode/runtime_classifier.py,sha256=uTAD6BDCiBLUZEDRfqk718kM4RTT_vAbfkcOI2_Xx58,18432
|
|
41
41
|
sourcecode/scanner.py,sha256=WdOQ78mMzjR1NjmKTlbxdgwinnCTfAhxCVLBEFQiFHU,8899
|
|
@@ -67,8 +67,9 @@ sourcecode/detectors/systems.py,sha256=nYaKbGDFu0EOXFcd_1doWFT3tTUdkbxc2DjHUF5Tc
|
|
|
67
67
|
sourcecode/detectors/terraform.py,sha256=cxORPR_zVLOJpHlh4e9JnFpkQsn_UnqMMom5yG65hZ4,1693
|
|
68
68
|
sourcecode/detectors/tooling.py,sha256=8CKbtxwQoABP-WyBRNmdAmHDOvAH57AR1cF4UKuWEdQ,2074
|
|
69
69
|
sourcecode/mcp/__init__.py,sha256=XU4HfRGbdid8wdUA0x_4f7uKZD1z3mv_XUY_WU_T9Mw,179
|
|
70
|
+
sourcecode/mcp/orchestrator.py,sha256=BMi1D6liJHI3DXiaC8yeBLLP0wXajpCP3-vnRGqrvnw,26850
|
|
70
71
|
sourcecode/mcp/runner.py,sha256=YSw2DXEICau6mCBr3Gfia3D_tKxMbRvIIXEh4cHC1SY,1390
|
|
71
|
-
sourcecode/mcp/server.py,sha256=
|
|
72
|
+
sourcecode/mcp/server.py,sha256=QyQxOXA9CXZbEl9ybbL7Vwr9YW18YjTAtKW_z9TocdA,39010
|
|
72
73
|
sourcecode/mcp/onboarding/__init__.py,sha256=sj2PWqEBmMc4zBNkomg89WtL0M6S7A9yb7_wAuSWNP4,66
|
|
73
74
|
sourcecode/mcp/onboarding/applier.py,sha256=B9CneieWTpaDSDIyW3S5nrlRlBpvfqUcgi93-mm_ApQ,2135
|
|
74
75
|
sourcecode/mcp/onboarding/backup.py,sha256=ihqGOR8QTX8HASRSEDyfFyXr5bkXrygPHamv4p9KTmk,1452
|
|
@@ -80,8 +81,8 @@ sourcecode/telemetry/consent.py,sha256=wLMvGNJeSSyZoNkQXpoUioY6mMv4Qdvuw7S9jAEWn
|
|
|
80
81
|
sourcecode/telemetry/events.py,sha256=oEvvulfsv5GIDWG2174gSS6tNB95w38AIYiYeifGKlE,2294
|
|
81
82
|
sourcecode/telemetry/filters.py,sha256=Asa71oRl7q3Wt_FMwuufIZJFzSYdgRNKS8LHCIyFeYE,4805
|
|
82
83
|
sourcecode/telemetry/transport.py,sha256=KJeIPCPWMdmbCP3ySGs2iUlia34U6vWne2dZsUezesw,1560
|
|
83
|
-
sourcecode-1.33.
|
|
84
|
-
sourcecode-1.33.
|
|
85
|
-
sourcecode-1.33.
|
|
86
|
-
sourcecode-1.33.
|
|
87
|
-
sourcecode-1.33.
|
|
84
|
+
sourcecode-1.33.6.dist-info/METADATA,sha256=LLg8G7P4foz_LBXFecU6K_eoolBXDiPKhrRNr86avik,16440
|
|
85
|
+
sourcecode-1.33.6.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
|
|
86
|
+
sourcecode-1.33.6.dist-info/entry_points.txt,sha256=ex3F9rmbXeyDIoFQHtkEqTsKSaJow8F0LrVu8XfIktQ,57
|
|
87
|
+
sourcecode-1.33.6.dist-info/licenses/LICENSE,sha256=7DdHrU9Z_3e7dSvq4ISijZNjnuHo5NIHNiHDouMQ9JU,10491
|
|
88
|
+
sourcecode-1.33.6.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|