sourcecode 1.31.1__py3-none-any.whl → 1.31.2__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 +206 -1
- sourcecode/confidence_analyzer.py +1 -1
- sourcecode/mcp/server.py +45 -8
- sourcecode/prepare_context.py +165 -35
- sourcecode/serializer.py +1 -1
- {sourcecode-1.31.1.dist-info → sourcecode-1.31.2.dist-info}/METADATA +3 -3
- {sourcecode-1.31.1.dist-info → sourcecode-1.31.2.dist-info}/RECORD +11 -11
- {sourcecode-1.31.1.dist-info → sourcecode-1.31.2.dist-info}/WHEEL +0 -0
- {sourcecode-1.31.1.dist-info → sourcecode-1.31.2.dist-info}/entry_points.txt +0 -0
- {sourcecode-1.31.1.dist-info → sourcecode-1.31.2.dist-info}/licenses/LICENSE +0 -0
sourcecode/__init__.py
CHANGED
sourcecode/cli.py
CHANGED
|
@@ -165,7 +165,7 @@ Compressed AI-ready context for Java/Spring enterprise codebases.
|
|
|
165
165
|
# Known subcommand names — tokens matching these are routed as subcommands,
|
|
166
166
|
# not consumed as a repository path.
|
|
167
167
|
_SUBCOMMANDS: frozenset[str] = frozenset(
|
|
168
|
-
{"telemetry", "prepare-context", "version", "config", "analyze", "repo-ir", "mcp"}
|
|
168
|
+
{"telemetry", "prepare-context", "version", "config", "analyze", "repo-ir", "mcp", "endpoints"}
|
|
169
169
|
)
|
|
170
170
|
|
|
171
171
|
# Mutable container holding the path extracted by _preprocess_argv().
|
|
@@ -2037,6 +2037,27 @@ def prepare_context_cmd(
|
|
|
2037
2037
|
out["changed_files"] = output.changed_files
|
|
2038
2038
|
if _task_include("affected_entry_points") and output.affected_entry_points:
|
|
2039
2039
|
out["affected_entry_points"] = output.affected_entry_points
|
|
2040
|
+
# compact_base fields — included for all non-delta/review-pr tasks (Fix #1)
|
|
2041
|
+
if task not in ("delta", "review-pr"):
|
|
2042
|
+
if output.entry_points_structured:
|
|
2043
|
+
out["entry_points"] = output.entry_points_structured
|
|
2044
|
+
if output.deployment:
|
|
2045
|
+
out["deployment"] = output.deployment
|
|
2046
|
+
if output.deployment_risks:
|
|
2047
|
+
out["deployment_risks"] = output.deployment_risks
|
|
2048
|
+
if output.security_surface:
|
|
2049
|
+
out["security_surface"] = output.security_surface
|
|
2050
|
+
if output.mybatis:
|
|
2051
|
+
out["mybatis"] = output.mybatis
|
|
2052
|
+
if output.transactional_boundaries:
|
|
2053
|
+
out["transactional_boundaries"] = output.transactional_boundaries
|
|
2054
|
+
if output.spring_profiles_info:
|
|
2055
|
+
out["spring_profiles"] = output.spring_profiles_info
|
|
2056
|
+
if output.angular_analysis and (
|
|
2057
|
+
output.angular_analysis.get("component_count", 0) > 0
|
|
2058
|
+
or output.angular_analysis.get("angular_version")
|
|
2059
|
+
):
|
|
2060
|
+
out["angular_analysis"] = output.angular_analysis
|
|
2040
2061
|
# Delta-specific impact fields
|
|
2041
2062
|
if task == "delta":
|
|
2042
2063
|
if output.error_code:
|
|
@@ -2420,6 +2441,190 @@ def repo_ir_cmd(
|
|
|
2420
2441
|
_sys.stdout.write("\n")
|
|
2421
2442
|
|
|
2422
2443
|
|
|
2444
|
+
# ── endpoints ─────────────────────────────────────────────────────────────────
|
|
2445
|
+
|
|
2446
|
+
def _extract_java_endpoints(root: "Path") -> "dict[str, Any]":
|
|
2447
|
+
"""Extract REST endpoint surface from Java source files.
|
|
2448
|
+
|
|
2449
|
+
Scans all .java files for @RequestMapping/@GetMapping/@PostMapping/@PutMapping/
|
|
2450
|
+
@DeleteMapping/@PatchMapping and @M3FiltroSeguridad annotations.
|
|
2451
|
+
Returns JSON-serializable dict with endpoints list, total, and undocumented count.
|
|
2452
|
+
"""
|
|
2453
|
+
import re as _re
|
|
2454
|
+
from pathlib import Path as _Path
|
|
2455
|
+
|
|
2456
|
+
_HTTP_MAPPING_RE = _re.compile(
|
|
2457
|
+
r'@(Get|Post|Put|Delete|Patch|Request)Mapping\s*'
|
|
2458
|
+
r'(?:\(\s*(?:value\s*=\s*)?(?:"([^"]*)"|\{[^}]*\}|[^)]*)\s*\))?',
|
|
2459
|
+
)
|
|
2460
|
+
_CLASS_RE = _re.compile(r'(?:class|interface)\s+(\w+)')
|
|
2461
|
+
_METHOD_RE = _re.compile(
|
|
2462
|
+
r'(?:public|protected|private)\s+\S+\s+(\w+)\s*\(',
|
|
2463
|
+
)
|
|
2464
|
+
_FILTRO_RE = _re.compile(
|
|
2465
|
+
r'@M3FiltroSeguridad\s*\(\s*(?:nombreRecurso\s*=\s*)?["\']([^"\']+)["\']',
|
|
2466
|
+
)
|
|
2467
|
+
_CLASS_PATH_RE = _re.compile(
|
|
2468
|
+
r'@RequestMapping\s*\(\s*(?:value\s*=\s*)?["\']([^"\']+)["\']',
|
|
2469
|
+
)
|
|
2470
|
+
|
|
2471
|
+
_HTTP_METHOD_MAP = {
|
|
2472
|
+
"Get": "GET", "Post": "POST", "Put": "PUT",
|
|
2473
|
+
"Delete": "DELETE", "Patch": "PATCH", "Request": "GET",
|
|
2474
|
+
}
|
|
2475
|
+
|
|
2476
|
+
endpoints: list[dict] = []
|
|
2477
|
+
seen: set[tuple] = set()
|
|
2478
|
+
|
|
2479
|
+
java_files = [
|
|
2480
|
+
p for p in root.rglob("*.java")
|
|
2481
|
+
if "/test/" not in str(p).replace("\\", "/")
|
|
2482
|
+
and "/tests/" not in str(p).replace("\\", "/")
|
|
2483
|
+
and "target/" not in str(p).replace("\\", "/")
|
|
2484
|
+
]
|
|
2485
|
+
|
|
2486
|
+
for java_file in java_files:
|
|
2487
|
+
try:
|
|
2488
|
+
content = java_file.read_text(encoding="utf-8", errors="replace")
|
|
2489
|
+
except OSError:
|
|
2490
|
+
continue
|
|
2491
|
+
|
|
2492
|
+
# Only process files with REST controller or mapping annotations
|
|
2493
|
+
if not any(x in content for x in ("@RestController", "@Controller", "@RequestMapping")):
|
|
2494
|
+
continue
|
|
2495
|
+
|
|
2496
|
+
try:
|
|
2497
|
+
rel_path = str(java_file.relative_to(root)).replace("\\", "/")
|
|
2498
|
+
except ValueError:
|
|
2499
|
+
rel_path = str(java_file).replace("\\", "/")
|
|
2500
|
+
|
|
2501
|
+
# Extract class name
|
|
2502
|
+
cls_m = _CLASS_RE.search(content)
|
|
2503
|
+
class_name = cls_m.group(1) if cls_m else java_file.stem
|
|
2504
|
+
|
|
2505
|
+
# Extract class-level base path from @RequestMapping on the class
|
|
2506
|
+
class_base = ""
|
|
2507
|
+
lines = content.splitlines()
|
|
2508
|
+
for i, line in enumerate(lines):
|
|
2509
|
+
if "@RequestMapping" in line and i < len(lines) - 1:
|
|
2510
|
+
# Check if next non-blank line is class declaration or it's on same block
|
|
2511
|
+
block = "\n".join(lines[max(0, i - 1): i + 5])
|
|
2512
|
+
if "class " in block or "interface " in block:
|
|
2513
|
+
path_m = _CLASS_PATH_RE.search(block)
|
|
2514
|
+
if path_m:
|
|
2515
|
+
class_base = path_m.group(1).rstrip("/")
|
|
2516
|
+
break
|
|
2517
|
+
|
|
2518
|
+
# Extract method-level endpoints
|
|
2519
|
+
# Parse line-by-line to associate annotations with methods
|
|
2520
|
+
pending_annotations: list[tuple[str, str]] = [] # (http_verb, path_suffix)
|
|
2521
|
+
pending_filtro: Optional[str] = None
|
|
2522
|
+
|
|
2523
|
+
for i, line in enumerate(lines):
|
|
2524
|
+
stripped = line.strip()
|
|
2525
|
+
|
|
2526
|
+
# Check for @M3FiltroSeguridad
|
|
2527
|
+
fm = _FILTRO_RE.search(stripped)
|
|
2528
|
+
if fm:
|
|
2529
|
+
pending_filtro = fm.group(1)
|
|
2530
|
+
continue
|
|
2531
|
+
|
|
2532
|
+
# Check for HTTP mapping annotations
|
|
2533
|
+
hm = _HTTP_MAPPING_RE.search(stripped)
|
|
2534
|
+
if hm:
|
|
2535
|
+
verb_key = hm.group(1)
|
|
2536
|
+
http_verb = _HTTP_METHOD_MAP.get(verb_key, "GET")
|
|
2537
|
+
path_suffix = (hm.group(2) or "").strip()
|
|
2538
|
+
pending_annotations.append((http_verb, path_suffix))
|
|
2539
|
+
continue
|
|
2540
|
+
|
|
2541
|
+
# Check for method declaration — flush pending annotations
|
|
2542
|
+
if pending_annotations and ("public " in stripped or "protected " in stripped):
|
|
2543
|
+
mm = _METHOD_RE.search(stripped)
|
|
2544
|
+
handler = mm.group(1) if mm else ""
|
|
2545
|
+
if handler and not handler.startswith("class"):
|
|
2546
|
+
for http_verb, path_suffix in pending_annotations:
|
|
2547
|
+
full_path = (class_base + "/" + path_suffix).replace("//", "/").rstrip("/") or "/"
|
|
2548
|
+
if not full_path.startswith("/"):
|
|
2549
|
+
full_path = "/" + full_path
|
|
2550
|
+
key = (class_name, handler, http_verb)
|
|
2551
|
+
if key not in seen:
|
|
2552
|
+
seen.add(key)
|
|
2553
|
+
entry: dict[str, Any] = {
|
|
2554
|
+
"method": http_verb,
|
|
2555
|
+
"path": full_path,
|
|
2556
|
+
"controller": class_name,
|
|
2557
|
+
"handler": handler,
|
|
2558
|
+
}
|
|
2559
|
+
if pending_filtro:
|
|
2560
|
+
entry["required_permission"] = pending_filtro
|
|
2561
|
+
endpoints.append(entry)
|
|
2562
|
+
pending_annotations = []
|
|
2563
|
+
pending_filtro = None
|
|
2564
|
+
continue
|
|
2565
|
+
|
|
2566
|
+
# Non-annotation, non-method line — reset if it's a closing brace or blank
|
|
2567
|
+
if stripped in ("}", "{", "") or stripped.startswith("//") or stripped.startswith("*"):
|
|
2568
|
+
if stripped == "}":
|
|
2569
|
+
pending_annotations = []
|
|
2570
|
+
pending_filtro = None
|
|
2571
|
+
|
|
2572
|
+
endpoints.sort(key=lambda e: (e.get("controller", ""), e.get("path", "")))
|
|
2573
|
+
undocumented = sum(1 for e in endpoints if "required_permission" not in e)
|
|
2574
|
+
|
|
2575
|
+
return {
|
|
2576
|
+
"endpoints": endpoints,
|
|
2577
|
+
"total": len(endpoints),
|
|
2578
|
+
"undocumented": undocumented,
|
|
2579
|
+
}
|
|
2580
|
+
|
|
2581
|
+
|
|
2582
|
+
@app.command("endpoints")
|
|
2583
|
+
def endpoints_cmd(
|
|
2584
|
+
path: Path = typer.Argument(
|
|
2585
|
+
Path("."),
|
|
2586
|
+
help="Repository path to scan for REST endpoints (default: current directory)",
|
|
2587
|
+
),
|
|
2588
|
+
output_path: Optional[Path] = typer.Option(
|
|
2589
|
+
None, "--output", "-o",
|
|
2590
|
+
help="Write output to a file instead of stdout.",
|
|
2591
|
+
),
|
|
2592
|
+
) -> None:
|
|
2593
|
+
"""Extract REST API endpoint surface from Java source files.
|
|
2594
|
+
|
|
2595
|
+
\b
|
|
2596
|
+
Scans all @GetMapping/@PostMapping/@PutMapping/@DeleteMapping/@PatchMapping
|
|
2597
|
+
and @RequestMapping annotations. Extracts HTTP method, path, controller class,
|
|
2598
|
+
handler method, and @M3FiltroSeguridad permission resource name.
|
|
2599
|
+
|
|
2600
|
+
\b
|
|
2601
|
+
Examples:
|
|
2602
|
+
sourcecode endpoints .
|
|
2603
|
+
sourcecode endpoints /path/to/repo
|
|
2604
|
+
sourcecode endpoints . --output endpoints.json
|
|
2605
|
+
"""
|
|
2606
|
+
import sys as _sys
|
|
2607
|
+
|
|
2608
|
+
target = path.resolve()
|
|
2609
|
+
if not target.exists() or not target.is_dir():
|
|
2610
|
+
typer.echo(f"Error: '{target}' is not a valid directory.", err=True)
|
|
2611
|
+
raise typer.Exit(code=1)
|
|
2612
|
+
|
|
2613
|
+
data = _extract_java_endpoints(target)
|
|
2614
|
+
output = json.dumps(data, indent=2, ensure_ascii=False)
|
|
2615
|
+
|
|
2616
|
+
if output_path is not None:
|
|
2617
|
+
output_path.write_text(output, encoding="utf-8")
|
|
2618
|
+
typer.echo(
|
|
2619
|
+
f"Endpoints written to {output_path} ({data['total']} endpoints)",
|
|
2620
|
+
err=True,
|
|
2621
|
+
)
|
|
2622
|
+
else:
|
|
2623
|
+
_sys.stdout.buffer.write(output.encode("utf-8"))
|
|
2624
|
+
_sys.stdout.buffer.write(b"\n")
|
|
2625
|
+
_sys.stdout.buffer.flush()
|
|
2626
|
+
|
|
2627
|
+
|
|
2423
2628
|
# ── version ───────────────────────────────────────────────────────────────────
|
|
2424
2629
|
|
|
2425
2630
|
@app.command("version")
|
|
@@ -175,7 +175,7 @@ class ConfidenceAnalyzer:
|
|
|
175
175
|
if dep_summary is None or not dep_summary.requested:
|
|
176
176
|
gaps.append(AnalysisGap(
|
|
177
177
|
area="dependencies",
|
|
178
|
-
reason="Dependencies not analyzed —
|
|
178
|
+
reason="Dependencies not analyzed — use the full analyze command with dependency flags for complete context",
|
|
179
179
|
impact="medium",
|
|
180
180
|
))
|
|
181
181
|
elif dep_summary.requested and dep_summary.total_count == 0:
|
sourcecode/mcp/server.py
CHANGED
|
@@ -15,9 +15,10 @@ from typing import Any
|
|
|
15
15
|
|
|
16
16
|
from mcp.server.fastmcp import FastMCP
|
|
17
17
|
|
|
18
|
+
from sourcecode import __version__
|
|
18
19
|
from sourcecode.mcp.runner import run_command
|
|
19
20
|
|
|
20
|
-
mcp = FastMCP("sourcecode")
|
|
21
|
+
mcp = FastMCP("sourcecode", version=__version__)
|
|
21
22
|
|
|
22
23
|
|
|
23
24
|
def _ok(data: Any) -> dict:
|
|
@@ -63,16 +64,16 @@ def get_agent_context(repo_path: str = ".") -> dict:
|
|
|
63
64
|
|
|
64
65
|
@mcp.tool()
|
|
65
66
|
def get_endpoints(repo_path: str = ".") -> dict:
|
|
66
|
-
"""API endpoint surface extraction.
|
|
67
|
+
"""REST API endpoint surface extraction from Java source files.
|
|
67
68
|
|
|
68
|
-
Maps to: sourcecode <repo_path>
|
|
69
|
+
Maps to: sourcecode endpoints <repo_path>
|
|
70
|
+
Returns: endpoints list with method, path, controller, handler, required_permission;
|
|
71
|
+
total count and undocumented count.
|
|
69
72
|
repo_path: absolute path to the repository (default: current working directory).
|
|
70
73
|
"""
|
|
71
|
-
|
|
72
|
-
"
|
|
73
|
-
|
|
74
|
-
"NOT_IMPLEMENTED",
|
|
75
|
-
)
|
|
74
|
+
if not isinstance(repo_path, str):
|
|
75
|
+
return _err("repo_path must be a string", "INVALID_ARGUMENT")
|
|
76
|
+
return _execute(["endpoints", repo_path])
|
|
76
77
|
|
|
77
78
|
|
|
78
79
|
@mcp.tool()
|
|
@@ -117,3 +118,39 @@ def get_ir_summary(repo_path: str = ".") -> dict:
|
|
|
117
118
|
if not isinstance(repo_path, str):
|
|
118
119
|
return _err("repo_path must be a string", "INVALID_ARGUMENT")
|
|
119
120
|
return _execute(["repo-ir", repo_path, "--summary-only"])
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
@mcp.tool()
|
|
124
|
+
def fix_bug_context(repo_path: str = ".", symptom: str = "") -> dict:
|
|
125
|
+
"""Risk-ranked files for bug investigation, optionally focused by symptom.
|
|
126
|
+
|
|
127
|
+
Maps to: sourcecode prepare-context fix-bug <repo_path> [--symptom <symptom>]
|
|
128
|
+
Includes compact_base: security_surface, transactional_boundaries, spring_profiles.
|
|
129
|
+
repo_path: absolute path to the repository (default: current working directory).
|
|
130
|
+
symptom: optional error message or class name to focus the file ranking
|
|
131
|
+
(e.g. "NullPointerException in EstructuraRrHhRestController").
|
|
132
|
+
"""
|
|
133
|
+
if not isinstance(repo_path, str):
|
|
134
|
+
return _err("repo_path must be a string", "INVALID_ARGUMENT")
|
|
135
|
+
args = ["prepare-context", "fix-bug", repo_path]
|
|
136
|
+
if symptom and isinstance(symptom, str) and symptom.strip():
|
|
137
|
+
args.extend(["--symptom", symptom.strip()])
|
|
138
|
+
return _execute(args)
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
@mcp.tool()
|
|
142
|
+
def review_pr_context(repo_path: str = ".", since: str = "") -> dict:
|
|
143
|
+
"""Execution paths and risk analysis for changed files in a pull request.
|
|
144
|
+
|
|
145
|
+
Maps to: sourcecode prepare-context review-pr <repo_path> [--since <since>]
|
|
146
|
+
Returns: compact_base + execution_paths (diff-scoped) + hotspots for changed files.
|
|
147
|
+
repo_path: absolute path to the repository (default: current working directory).
|
|
148
|
+
since: git ref to diff against (e.g. HEAD~3, main, origin/main).
|
|
149
|
+
If omitted, diffs against uncommitted changes or HEAD~1 fallback.
|
|
150
|
+
"""
|
|
151
|
+
if not isinstance(repo_path, str):
|
|
152
|
+
return _err("repo_path must be a string", "INVALID_ARGUMENT")
|
|
153
|
+
args = ["prepare-context", "review-pr", repo_path]
|
|
154
|
+
if since and isinstance(since, str) and since.strip():
|
|
155
|
+
args.extend(["--since", since.strip()])
|
|
156
|
+
return _execute(args)
|
sourcecode/prepare_context.py
CHANGED
|
@@ -322,7 +322,7 @@ class TaskOutput:
|
|
|
322
322
|
relevant_files: list[RelevantFile]
|
|
323
323
|
suspected_areas: list[str]
|
|
324
324
|
improvement_opportunities: list[str]
|
|
325
|
-
test_gaps: list[str]
|
|
325
|
+
test_gaps: list # list[str] for non-Java; list[dict] for Java (has path/public_method_count/has_spring_annotations)
|
|
326
326
|
key_dependencies: list[dict[str, Any]]
|
|
327
327
|
code_notes_summary: Optional[dict[str, Any]]
|
|
328
328
|
limitations: list[str]
|
|
@@ -378,6 +378,15 @@ class TaskOutput:
|
|
|
378
378
|
uncommitted_changes: list[dict] = field(default_factory=list)
|
|
379
379
|
# transparency: explicit diff scope for every command
|
|
380
380
|
analysis_scope: dict = field(default_factory=dict)
|
|
381
|
+
# compact_base fields — enriched in all prepare-context tasks (Fix #1)
|
|
382
|
+
security_surface: Optional[dict] = None
|
|
383
|
+
mybatis: Optional[dict] = None
|
|
384
|
+
transactional_boundaries: Optional[dict] = None
|
|
385
|
+
spring_profiles_info: Optional[dict] = None
|
|
386
|
+
angular_analysis: Optional[dict] = None
|
|
387
|
+
deployment_risks: list[str] = field(default_factory=list)
|
|
388
|
+
deployment: Optional[dict] = None
|
|
389
|
+
entry_points_structured: Optional[dict] = None
|
|
381
390
|
|
|
382
391
|
|
|
383
392
|
@dataclass
|
|
@@ -902,6 +911,53 @@ class TaskContextBuilder:
|
|
|
902
911
|
from sourcecode.context_summarizer import ContextSummarizer
|
|
903
912
|
sm.context_summary = ContextSummarizer(self.root).generate(sm)
|
|
904
913
|
|
|
914
|
+
# ── 3b. Compact-base enrichment (Fix #1) ───────────────────────────
|
|
915
|
+
# Enrich sm with Java-specific fields — same as main CLI pipeline
|
|
916
|
+
_java_stack_cb = next((s for s in stacks if s.stack == "java"), None)
|
|
917
|
+
if _java_stack_cb is not None:
|
|
918
|
+
sm.language_version = getattr(_java_stack_cb, "language_version", None) or None
|
|
919
|
+
sm.spring_profiles = getattr(_java_stack_cb, "spring_profiles", []) or []
|
|
920
|
+
sm.app_server_hint = getattr(_java_stack_cb, "app_server_hint", None) or None
|
|
921
|
+
sm.packaging = getattr(_java_stack_cb, "packaging", None) or None
|
|
922
|
+
|
|
923
|
+
# Import serializer helpers — same functions used by --compact
|
|
924
|
+
from sourcecode.serializer import (
|
|
925
|
+
_security_surface_from_eps as _cb_sec_fn,
|
|
926
|
+
_mybatis_pairing as _cb_mybatis_fn,
|
|
927
|
+
_transactional_summary as _cb_trans_fn,
|
|
928
|
+
_spring_profiles_context as _cb_spring_fn,
|
|
929
|
+
_angular_analysis as _cb_angular_fn,
|
|
930
|
+
_project_deployment_risks as _cb_deploy_risks_fn,
|
|
931
|
+
_bootstrap_structured as _cb_bootstrap_fn,
|
|
932
|
+
_spring_boot_version as _cb_sbver_fn,
|
|
933
|
+
_jndi_datasources as _cb_jndi_fn,
|
|
934
|
+
)
|
|
935
|
+
|
|
936
|
+
_cb_security_surface = _cb_sec_fn(entry_points, root=self.root, file_paths=all_paths)
|
|
937
|
+
_cb_mybatis = _cb_mybatis_fn(sm)
|
|
938
|
+
_cb_transactional = _cb_trans_fn(sm)
|
|
939
|
+
_cb_spring_profiles = _cb_spring_fn(sm)
|
|
940
|
+
_cb_angular = _cb_angular_fn(sm)
|
|
941
|
+
_cb_deploy_risks = _cb_deploy_risks_fn(sm)
|
|
942
|
+
_cb_bootstrap = _cb_bootstrap_fn(entry_points)
|
|
943
|
+
|
|
944
|
+
_cb_sb_ver = _cb_sbver_fn(sm)
|
|
945
|
+
_cb_deployment: Optional[dict] = None
|
|
946
|
+
_cb_packaging = getattr(sm, "packaging", None)
|
|
947
|
+
_cb_app_server = getattr(sm, "app_server_hint", None)
|
|
948
|
+
if _cb_sb_ver or _cb_packaging or _cb_app_server:
|
|
949
|
+
_cb_deployment = {}
|
|
950
|
+
if _cb_sb_ver:
|
|
951
|
+
_cb_deployment["spring_boot_version"] = _cb_sb_ver
|
|
952
|
+
if _cb_packaging:
|
|
953
|
+
_cb_deployment["packaging"] = _cb_packaging
|
|
954
|
+
if _cb_app_server:
|
|
955
|
+
_cb_deployment["app_server_hint"] = _cb_app_server
|
|
956
|
+
_cb_jndi = _cb_jndi_fn(sm)
|
|
957
|
+
if _cb_jndi:
|
|
958
|
+
_cb_deployment = _cb_deployment or {}
|
|
959
|
+
_cb_deployment["jndi_datasources"] = _cb_jndi
|
|
960
|
+
|
|
905
961
|
# ── 4. Dependencies ────────────────────────────────────────────────
|
|
906
962
|
key_dependencies: list[dict[str, Any]] = []
|
|
907
963
|
limitations: list[str] = []
|
|
@@ -1647,42 +1703,107 @@ class TaskContextBuilder:
|
|
|
1647
1703
|
symptom_hint = _fe_redirect
|
|
1648
1704
|
|
|
1649
1705
|
# ── 7. Test gaps (generate-tests only) ────────────────────────────
|
|
1650
|
-
test_gaps: list
|
|
1706
|
+
test_gaps: list = []
|
|
1651
1707
|
if task_name == "generate-tests" and not fast:
|
|
1652
|
-
|
|
1653
|
-
# Java
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1708
|
+
if _is_java:
|
|
1709
|
+
# Java-aware algorithm (Fix #2): find Service/RestController/Repository/Mapper
|
|
1710
|
+
# files with no matching test pair in src/test/**
|
|
1711
|
+
_JAVA_TARGET_SUFFIXES = (
|
|
1712
|
+
"Service.java", "RestController.java",
|
|
1713
|
+
"Repository.java", "Mapper.java",
|
|
1714
|
+
)
|
|
1715
|
+
# Build set of test stems (FooTest → Foo, FooIT → Foo, etc.)
|
|
1716
|
+
_java_test_stems: set[str] = set()
|
|
1717
|
+
for _tp in all_paths:
|
|
1718
|
+
if not _tp.endswith(".java"):
|
|
1719
|
+
continue
|
|
1720
|
+
if not self._is_test(_tp):
|
|
1721
|
+
continue
|
|
1722
|
+
_ts = Path(_tp).stem
|
|
1723
|
+
for _suf in ("Test", "IT", "Tests", "Spec"):
|
|
1724
|
+
if _ts.endswith(_suf):
|
|
1725
|
+
_ts = _ts[: -len(_suf)]
|
|
1726
|
+
break
|
|
1727
|
+
if _ts.startswith("Test") and len(_ts) > 4 and _ts[4].isupper():
|
|
1728
|
+
_ts = _ts[4:]
|
|
1729
|
+
_java_test_stems.add(_ts)
|
|
1730
|
+
|
|
1731
|
+
_java_candidates: list[dict] = []
|
|
1732
|
+
for _p in all_paths:
|
|
1733
|
+
if not any(_p.endswith(_s) for _s in _JAVA_TARGET_SUFFIXES):
|
|
1734
|
+
continue
|
|
1735
|
+
if self._is_test(_p):
|
|
1736
|
+
continue
|
|
1737
|
+
_pnorm = _p.replace("\\", "/")
|
|
1738
|
+
if "src/main/resources" in _pnorm or "target/" in _pnorm:
|
|
1739
|
+
continue
|
|
1740
|
+
if Path(_p).stem in _java_test_stems:
|
|
1741
|
+
continue
|
|
1742
|
+
_pub_count = 0
|
|
1743
|
+
_ann_count = 0
|
|
1744
|
+
try:
|
|
1745
|
+
_content = (self.root / _p).read_text(
|
|
1746
|
+
encoding="utf-8", errors="replace"
|
|
1747
|
+
)[:16000]
|
|
1748
|
+
_pub_count = _content.count("public ")
|
|
1749
|
+
_ann_count = (
|
|
1750
|
+
_content.count("@Transactional")
|
|
1751
|
+
+ _content.count("@RequestMapping")
|
|
1752
|
+
+ _content.count("@GetMapping")
|
|
1753
|
+
+ _content.count("@PostMapping")
|
|
1754
|
+
+ _content.count("@PutMapping")
|
|
1755
|
+
+ _content.count("@DeleteMapping")
|
|
1756
|
+
)
|
|
1757
|
+
except OSError:
|
|
1758
|
+
pass
|
|
1759
|
+
_java_candidates.append({
|
|
1760
|
+
"path": _p,
|
|
1761
|
+
"public_method_count": _pub_count,
|
|
1762
|
+
"has_spring_annotations": _ann_count > 0,
|
|
1763
|
+
"_rank": _pub_count + _ann_count * 2,
|
|
1764
|
+
})
|
|
1765
|
+
|
|
1766
|
+
_java_candidates.sort(key=lambda x: -x["_rank"])
|
|
1767
|
+
test_gaps = [
|
|
1768
|
+
{
|
|
1769
|
+
"path": c["path"],
|
|
1770
|
+
"public_method_count": c["public_method_count"],
|
|
1771
|
+
"has_spring_annotations": c["has_spring_annotations"],
|
|
1772
|
+
}
|
|
1773
|
+
for c in _java_candidates
|
|
1774
|
+
]
|
|
1775
|
+
else:
|
|
1776
|
+
# Non-Java algorithm (unchanged)
|
|
1777
|
+
def _normalize_test_stem(stem: str) -> str:
|
|
1778
|
+
if stem.endswith("Tests"):
|
|
1779
|
+
return stem[:-5]
|
|
1780
|
+
if stem.endswith("Test"):
|
|
1781
|
+
return stem[:-4]
|
|
1782
|
+
if stem.startswith("Test") and len(stem) > 4 and stem[4].isupper():
|
|
1783
|
+
return stem[4:]
|
|
1784
|
+
return stem.removeprefix("test_").removesuffix("_test")
|
|
1785
|
+
|
|
1786
|
+
_CONFIG_EXCLUDE_PATTERNS = (
|
|
1787
|
+
".eslintrc", ".prettierrc", "eslint.config",
|
|
1788
|
+
"karma.conf", "jest.config", "babel.config",
|
|
1789
|
+
"webpack.config", "vite.config", "rollup.config",
|
|
1790
|
+
"tsconfig", "angular.json", ".claude/",
|
|
1791
|
+
)
|
|
1671
1792
|
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1793
|
+
def _is_config_file(p: str) -> bool:
|
|
1794
|
+
name = Path(p).name.lower()
|
|
1795
|
+
norm = p.replace("\\", "/")
|
|
1796
|
+
return any(pat in name or pat in norm for pat in _CONFIG_EXCLUDE_PATTERNS)
|
|
1797
|
+
|
|
1798
|
+
test_stems = {_normalize_test_stem(Path(p).stem) for p in test_set}
|
|
1799
|
+
untested = [
|
|
1800
|
+
p for p in source_set
|
|
1801
|
+
if Path(p).stem not in test_stems
|
|
1802
|
+
and not any(pen in p for pen in spec.ranking_penalties)
|
|
1803
|
+
and (include_config or not _is_config_file(p))
|
|
1804
|
+
]
|
|
1805
|
+
untested.sort(key=lambda p: (len(p.split("/")), p))
|
|
1806
|
+
test_gaps = untested[:15]
|
|
1686
1807
|
|
|
1687
1808
|
# ── 8. Confidence + gaps ──────────────────────────────────────────────
|
|
1688
1809
|
from sourcecode.confidence_analyzer import ConfidenceAnalyzer
|
|
@@ -1809,6 +1930,15 @@ class TaskContextBuilder:
|
|
|
1809
1930
|
diff_validation_status=_delta_baseline.get("diff_validation_status") if task_name == "delta" else None,
|
|
1810
1931
|
warnings=_delta_baseline.get("warnings", []) if task_name == "delta" else [],
|
|
1811
1932
|
symptom_hint=symptom_hint if task_name == "fix-bug" else None,
|
|
1933
|
+
# compact_base fields (Fix #1) — superset of --compact for all tasks
|
|
1934
|
+
security_surface=_cb_security_surface,
|
|
1935
|
+
mybatis=_cb_mybatis,
|
|
1936
|
+
transactional_boundaries=_cb_transactional,
|
|
1937
|
+
spring_profiles_info=_cb_spring_profiles,
|
|
1938
|
+
angular_analysis=_cb_angular,
|
|
1939
|
+
deployment_risks=_cb_deploy_risks,
|
|
1940
|
+
deployment=_cb_deployment,
|
|
1941
|
+
entry_points_structured=_cb_bootstrap,
|
|
1812
1942
|
)
|
|
1813
1943
|
|
|
1814
1944
|
def render_prompt(self, output: TaskOutput) -> str:
|
sourcecode/serializer.py
CHANGED
|
@@ -2012,7 +2012,7 @@ def agent_view(sm: SourceMap, *, full: bool = False) -> dict[str, Any]:
|
|
|
2012
2012
|
if not sm.dependency_summary or not sm.dependency_summary.requested:
|
|
2013
2013
|
analysis_gaps.append({
|
|
2014
2014
|
"area": "dependencies",
|
|
2015
|
-
"reason": "Dependencies not analyzed —
|
|
2015
|
+
"reason": "Dependencies not analyzed — use the full analyze command with dependency flags for complete context",
|
|
2016
2016
|
"impact": "medium",
|
|
2017
2017
|
})
|
|
2018
2018
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sourcecode
|
|
3
|
-
Version: 1.31.
|
|
3
|
+
Version: 1.31.2
|
|
4
4
|
Summary: Deterministic codebase context for AI coding agents
|
|
5
5
|
License: Apache License
|
|
6
6
|
Version 2.0, January 2004
|
|
@@ -225,7 +225,7 @@ Description-Content-Type: text/markdown
|
|
|
225
225
|
|
|
226
226
|
**Deterministic, behavior-aware codebase context for AI agents and PR review.**
|
|
227
227
|
|
|
228
|
-

|
|
229
229
|

|
|
230
230
|
|
|
231
231
|
---
|
|
@@ -261,7 +261,7 @@ pipx install sourcecode
|
|
|
261
261
|
|
|
262
262
|
```bash
|
|
263
263
|
sourcecode version
|
|
264
|
-
# sourcecode 1.31.
|
|
264
|
+
# sourcecode 1.31.2
|
|
265
265
|
```
|
|
266
266
|
|
|
267
267
|
---
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
sourcecode/__init__.py,sha256=
|
|
1
|
+
sourcecode/__init__.py,sha256=BlIGNpIZ2KvAHJhMqzRGPET0plDw5AYOTlGs0F8h4hU,103
|
|
2
2
|
sourcecode/adaptive_scanner.py,sha256=XffluXKzJUXrMtjEiAOnSNPZnztdIcts17T9ouHeID0,10521
|
|
3
3
|
sourcecode/architecture_analyzer.py,sha256=MyBa0Hf5HmkudZQDLKrjcWDKETXETXl0mQX1swtTwAA,39091
|
|
4
4
|
sourcecode/architecture_summary.py,sha256=z34_6v7cSwy98cof2UVciGho7SCrZ93tiqMmq5WNzRQ,20405
|
|
5
5
|
sourcecode/ast_extractor.py,sha256=XgrZg2DcWcUm9r87cRG3KGO7IK2TIL_N-CvhSbUmmh4,49901
|
|
6
6
|
sourcecode/classifier.py,sha256=-0t0HLc9L9UleMLfclfLM3AXhBjUb_AYyBPDbvgWtac,7755
|
|
7
|
-
sourcecode/cli.py,sha256=
|
|
7
|
+
sourcecode/cli.py,sha256=qYMuo7DY6erJD5HwjU1Rot07xR54kXil4YzVytyU1Po,120897
|
|
8
8
|
sourcecode/code_notes_analyzer.py,sha256=y1MJBnPZHYp4i6cQCXUb9ATIyifS_qMQWjw_8lPkpsU,9215
|
|
9
|
-
sourcecode/confidence_analyzer.py,sha256=
|
|
9
|
+
sourcecode/confidence_analyzer.py,sha256=ZUn-Nywi5TEQcuozqK_vfOyPT-a1dYYO42elAtVFV-k,16412
|
|
10
10
|
sourcecode/context_scorer.py,sha256=QpChSpsmaAYz91rXA4Ue5xzQmNz_ZboZN09YOHScq1U,14679
|
|
11
11
|
sourcecode/context_summarizer.py,sha256=CiQrfBEzun949bWvmLabWoj2HhPn6Lw62ofqnsy0FlQ,6503
|
|
12
12
|
sourcecode/contract_model.py,sha256=nRxJKPMs1VHwFTa8AVXhGmaLjti3Lr2sjHDpWgv1bfE,3917
|
|
@@ -22,7 +22,7 @@ sourcecode/git_analyzer.py,sha256=0Gyj-vMpIIN4nfriKXVRouNYBeJ59s6pQDX2Xu9Pq-U,13
|
|
|
22
22
|
sourcecode/graph_analyzer.py,sha256=iUK-7pSV-cvGqqD2hENdYmhnm0wcXFEyK-xnu5ul8OU,62515
|
|
23
23
|
sourcecode/metrics_analyzer.py,sha256=m0ENgtqKeBL17kUIK3fmGkgo7UfXBNHxCMj0H_Y5K7c,22750
|
|
24
24
|
sourcecode/pr_comment_renderer.py,sha256=smHslxiG14lrytCkq5nFrFu-qTHgA-t-LFYfdrfjz2o,14423
|
|
25
|
-
sourcecode/prepare_context.py,sha256=
|
|
25
|
+
sourcecode/prepare_context.py,sha256=vmZnoFneipRCE-hP6drvhqNXb3FwLrVub449e0MzT0Y,179908
|
|
26
26
|
sourcecode/progress.py,sha256=qn30sWaHOkjTgXsSBmiPkz7Rsbwc5oSlIe6JNEMYp_k,3149
|
|
27
27
|
sourcecode/ranking_engine.py,sha256=ZAucq_YX2KkWUuAZf4P0lhtQ_38vEFnUhuGtSZd1S0E,12970
|
|
28
28
|
sourcecode/redactor.py,sha256=xuGcadGEHaPw4qZXlMDvzMCsr4VOkdp3oBQptHyJk8c,2884
|
|
@@ -33,7 +33,7 @@ sourcecode/runtime_classifier.py,sha256=zWX3r3HCKHc-qtIobErOa8aKMmaoPYREtJKvPcBG
|
|
|
33
33
|
sourcecode/scanner.py,sha256=WdOQ78mMzjR1NjmKTlbxdgwinnCTfAhxCVLBEFQiFHU,8899
|
|
34
34
|
sourcecode/schema.py,sha256=fj3BZ3IcnNV4j21BFIEvz8Qnw_vZoqIbzzRg-qQ-nd0,24530
|
|
35
35
|
sourcecode/semantic_analyzer.py,sha256=12TwXYkYbDcBdu0heX_EmfPM2EkO8a_r5osf0SaeQbs,88956
|
|
36
|
-
sourcecode/serializer.py,sha256=
|
|
36
|
+
sourcecode/serializer.py,sha256=KHVqwUK53axF10detPzqgmIY2P31rjLLJ_9T9Eyqp-E,111647
|
|
37
37
|
sourcecode/summarizer.py,sha256=lPlKhMh28nueXkPo2xKeD3DUFYVGRlJMIdY-8TSM-ls,17486
|
|
38
38
|
sourcecode/tree_utils.py,sha256=8GAkIfQAsvtEudIeW1l4ooH_oRtrWR8cpJQJsEa_Pfw,2093
|
|
39
39
|
sourcecode/workspace.py,sha256=X_6NmNnitvT3_38V-JDChydo_sR68s249hLFlrQskU0,8271
|
|
@@ -60,7 +60,7 @@ sourcecode/detectors/terraform.py,sha256=cxORPR_zVLOJpHlh4e9JnFpkQsn_UnqMMom5yG6
|
|
|
60
60
|
sourcecode/detectors/tooling.py,sha256=8CKbtxwQoABP-WyBRNmdAmHDOvAH57AR1cF4UKuWEdQ,2074
|
|
61
61
|
sourcecode/mcp/__init__.py,sha256=XU4HfRGbdid8wdUA0x_4f7uKZD1z3mv_XUY_WU_T9Mw,179
|
|
62
62
|
sourcecode/mcp/runner.py,sha256=7PnFjKYbgxFeDnqVeSntXHxZX7ZtK3-krDkEuVjI24M,1386
|
|
63
|
-
sourcecode/mcp/server.py,sha256=
|
|
63
|
+
sourcecode/mcp/server.py,sha256=01_b0MZnv4Yq2ES3FJSrqp1AJwCVdLVwOzQUVo3BtxY,6322
|
|
64
64
|
sourcecode/mcp/onboarding/__init__.py,sha256=sj2PWqEBmMc4zBNkomg89WtL0M6S7A9yb7_wAuSWNP4,66
|
|
65
65
|
sourcecode/mcp/onboarding/applier.py,sha256=yfSMT0NKdZsjavtLkC8yQ7OtkfepOl5IXGByqg6bdEY,1894
|
|
66
66
|
sourcecode/mcp/onboarding/backup.py,sha256=ihqGOR8QTX8HASRSEDyfFyXr5bkXrygPHamv4p9KTmk,1452
|
|
@@ -72,8 +72,8 @@ sourcecode/telemetry/consent.py,sha256=wLMvGNJeSSyZoNkQXpoUioY6mMv4Qdvuw7S9jAEWn
|
|
|
72
72
|
sourcecode/telemetry/events.py,sha256=oEvvulfsv5GIDWG2174gSS6tNB95w38AIYiYeifGKlE,2294
|
|
73
73
|
sourcecode/telemetry/filters.py,sha256=Asa71oRl7q3Wt_FMwuufIZJFzSYdgRNKS8LHCIyFeYE,4805
|
|
74
74
|
sourcecode/telemetry/transport.py,sha256=KJeIPCPWMdmbCP3ySGs2iUlia34U6vWne2dZsUezesw,1560
|
|
75
|
-
sourcecode-1.31.
|
|
76
|
-
sourcecode-1.31.
|
|
77
|
-
sourcecode-1.31.
|
|
78
|
-
sourcecode-1.31.
|
|
79
|
-
sourcecode-1.31.
|
|
75
|
+
sourcecode-1.31.2.dist-info/METADATA,sha256=CaHPC5ti8mv3vU8hOaq1vV5lvAHw8kPIH4IEvbk3x2k,29083
|
|
76
|
+
sourcecode-1.31.2.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
|
|
77
|
+
sourcecode-1.31.2.dist-info/entry_points.txt,sha256=ex3F9rmbXeyDIoFQHtkEqTsKSaJow8F0LrVu8XfIktQ,57
|
|
78
|
+
sourcecode-1.31.2.dist-info/licenses/LICENSE,sha256=7DdHrU9Z_3e7dSvq4ISijZNjnuHo5NIHNiHDouMQ9JU,10491
|
|
79
|
+
sourcecode-1.31.2.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|