sourcecode 1.31.9__py3-none-any.whl → 1.31.11__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/classifier.py +1 -0
- sourcecode/cli.py +6 -3
- sourcecode/confidence_analyzer.py +8 -1
- sourcecode/dependency_analyzer.py +8 -3
- sourcecode/detectors/java.py +29 -19
- sourcecode/doc_analyzer.py +6 -4
- sourcecode/mcp/server.py +46 -2
- sourcecode/prepare_context.py +54 -22
- sourcecode/repository_ir.py +354 -66
- sourcecode/semantic_analyzer.py +26 -17
- sourcecode/serializer.py +31 -5
- {sourcecode-1.31.9.dist-info → sourcecode-1.31.11.dist-info}/METADATA +3 -3
- {sourcecode-1.31.9.dist-info → sourcecode-1.31.11.dist-info}/RECORD +17 -17
- {sourcecode-1.31.9.dist-info → sourcecode-1.31.11.dist-info}/WHEEL +0 -0
- {sourcecode-1.31.9.dist-info → sourcecode-1.31.11.dist-info}/entry_points.txt +0 -0
- {sourcecode-1.31.9.dist-info → sourcecode-1.31.11.dist-info}/licenses/LICENSE +0 -0
sourcecode/__init__.py
CHANGED
sourcecode/classifier.py
CHANGED
sourcecode/cli.py
CHANGED
|
@@ -858,7 +858,10 @@ def main(
|
|
|
858
858
|
# to avoid polluting sub-project directories used in tests.
|
|
859
859
|
if _git_sha and (target / ".git").exists():
|
|
860
860
|
# Include every output-affecting flag so different flag combos never collide
|
|
861
|
+
# Include version so cache is invalidated on sourcecode upgrades
|
|
862
|
+
from sourcecode import __version__ as _sc_version
|
|
861
863
|
_flags_str = (
|
|
864
|
+
f"v={_sc_version},"
|
|
862
865
|
f"c={compact},ag={agent},fmt={format},full={full},"
|
|
863
866
|
f"co={changed_only},dep={dependencies},gm={graph_modules},"
|
|
864
867
|
f"docs={docs},fm={full_metrics},sem={semantics},"
|
|
@@ -2475,9 +2478,9 @@ def endpoints_cmd(
|
|
|
2475
2478
|
"""Extract REST API endpoint surface from Java source files.
|
|
2476
2479
|
|
|
2477
2480
|
\b
|
|
2478
|
-
Scans
|
|
2479
|
-
and @
|
|
2480
|
-
|
|
2481
|
+
Scans Spring MVC (@GetMapping/@PostMapping/@PutMapping/@DeleteMapping/@PatchMapping/@RequestMapping)
|
|
2482
|
+
and JAX-RS (@GET/@POST/@PUT/@DELETE/@PATCH with @Path) annotations.
|
|
2483
|
+
Extracts HTTP method, path, controller class, and handler method.
|
|
2481
2484
|
|
|
2482
2485
|
\b
|
|
2483
2486
|
Examples:
|
|
@@ -222,11 +222,18 @@ class ConfidenceAnalyzer:
|
|
|
222
222
|
))
|
|
223
223
|
|
|
224
224
|
# Spring profile documentation (comments in application-{profile}.yml)
|
|
225
|
+
# Only relevant for Spring Boot projects — Quarkus/Jakarta use similar file names
|
|
226
|
+
# but they're Quarkus profiles, not Spring profiles.
|
|
227
|
+
_is_spring_boot = any(
|
|
228
|
+
f.name in ("Spring Boot", "Spring")
|
|
229
|
+
for stack in sm.stacks
|
|
230
|
+
for f in getattr(stack, "frameworks", [])
|
|
231
|
+
)
|
|
225
232
|
_profile_ymls = [
|
|
226
233
|
p for p in sm.file_paths
|
|
227
234
|
if "application-" in p.rsplit("/", 1)[-1] and p.endswith((".yml", ".yaml", ".properties"))
|
|
228
235
|
]
|
|
229
|
-
if _profile_ymls:
|
|
236
|
+
if _is_spring_boot and _profile_ymls:
|
|
230
237
|
# Check for at least one comment line (# ...) across profile files
|
|
231
238
|
_root = Path(sm.metadata.analyzed_path) if sm.metadata.analyzed_path else None
|
|
232
239
|
_has_profile_docs = False
|
|
@@ -128,8 +128,13 @@ def _infer_role(name: str, ecosystem: str, scope: str) -> str:
|
|
|
128
128
|
return "runtime"
|
|
129
129
|
|
|
130
130
|
if ecosystem == "java":
|
|
131
|
+
# Scope is authoritative: provided and dev scopes are not runtime, regardless of
|
|
132
|
+
# artifact name. Checking artifact patterns first would mis-classify spring-boot-test
|
|
133
|
+
# (scope=test) as "runtime", inflating false-positive fan-in signals.
|
|
131
134
|
if scope == "provided":
|
|
132
135
|
return "provided"
|
|
136
|
+
if is_dev:
|
|
137
|
+
return "devtool"
|
|
133
138
|
artifact = n.split(":")[-1] if ":" in n else n
|
|
134
139
|
if any(x in artifact for x in ("spring-boot", "spring-security")):
|
|
135
140
|
return "runtime"
|
|
@@ -143,7 +148,7 @@ def _infer_role(name: str, ecosystem: str, scope: str) -> str:
|
|
|
143
148
|
return "parsing"
|
|
144
149
|
if any(x in artifact for x in ("jjwt", "nimbus-jose")):
|
|
145
150
|
return "runtime"
|
|
146
|
-
return "
|
|
151
|
+
return "runtime"
|
|
147
152
|
|
|
148
153
|
return "devtool" if is_dev else "runtime"
|
|
149
154
|
|
|
@@ -1142,7 +1147,7 @@ class DependencyAnalyzer:
|
|
|
1142
1147
|
records: list[DependencyRecord] = []
|
|
1143
1148
|
deps_elem = root_elem.find(f"{ns}dependencies")
|
|
1144
1149
|
if deps_elem is None:
|
|
1145
|
-
return [], ["java: pom.xml
|
|
1150
|
+
return [], ["java: pom.xml has no <dependencies> block"]
|
|
1146
1151
|
|
|
1147
1152
|
for dep in deps_elem.findall(f"{ns}dependency"):
|
|
1148
1153
|
group_id = (dep.findtext(f"{ns}groupId") or "").strip()
|
|
@@ -1190,7 +1195,7 @@ class DependencyAnalyzer:
|
|
|
1190
1195
|
|
|
1191
1196
|
limitations: list[str] = []
|
|
1192
1197
|
if not records:
|
|
1193
|
-
limitations.append("java: pom.xml
|
|
1198
|
+
limitations.append("java: pom.xml has no parseable dependencies (may use BOM or properties)")
|
|
1194
1199
|
|
|
1195
1200
|
# Warn when Spring Boot BOM manages transitive deps — they can't be resolved statically.
|
|
1196
1201
|
parent_artifact_local = (
|
sourcecode/detectors/java.py
CHANGED
|
@@ -34,13 +34,16 @@ _HTTP_PATH_RE = re.compile(
|
|
|
34
34
|
_REQUEST_METHOD_VERB_RE = re.compile(
|
|
35
35
|
r'method\s*=\s*RequestMethod\.([A-Z]+)'
|
|
36
36
|
)
|
|
37
|
-
#
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
)
|
|
37
|
+
# Custom security annotation registry — extend here for project-specific annotations.
|
|
38
|
+
# Each entry: annotation_simple_name → compiled params regex.
|
|
39
|
+
# Groups: (1) resource string literal, (2) resource constant ref, (3) level integer.
|
|
40
|
+
_CUSTOM_SECURITY_ANNOTATIONS: dict[str, re.Pattern] = {
|
|
41
|
+
"M3FiltroSeguridad": re.compile(
|
|
42
|
+
r'@M3FiltroSeguridad\s*\(\s*'
|
|
43
|
+
r'(?:nombreRecurso\s*=\s*(?:"([^"]*)"|([\w.]+)))?'
|
|
44
|
+
r'(?:[^)]*nivelRequerido\s*=\s*(\d+))?'
|
|
45
|
+
),
|
|
46
|
+
}
|
|
44
47
|
|
|
45
48
|
# Security config detection
|
|
46
49
|
_WEB_SECURITY_CONFIGURER_RE = re.compile(r'WebSecurityConfigurerAdapter\b')
|
|
@@ -436,7 +439,7 @@ class JavaDetector(AbstractDetector):
|
|
|
436
439
|
))
|
|
437
440
|
if (not has_controller and not has_filter and not has_security
|
|
438
441
|
and "ControllerAdvice" not in content
|
|
439
|
-
and
|
|
442
|
+
and not any(ann in content for ann in _CUSTOM_SECURITY_ANNOTATIONS)
|
|
440
443
|
and not has_jax_rs and not has_cdi and not has_spi):
|
|
441
444
|
return []
|
|
442
445
|
|
|
@@ -449,11 +452,13 @@ class JavaDetector(AbstractDetector):
|
|
|
449
452
|
elif verb_match:
|
|
450
453
|
http_path = f"[{verb_match.group(1)}]"
|
|
451
454
|
security_evidence = None
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
455
|
+
for _ann_name, _ann_re in _CUSTOM_SECURITY_ANNOTATIONS.items():
|
|
456
|
+
_m = _ann_re.search(content)
|
|
457
|
+
if _m:
|
|
458
|
+
nombre = _m.group(1) or _m.group(2) or ""
|
|
459
|
+
nivel = _m.group(3) or ""
|
|
460
|
+
security_evidence = f"@{_ann_name}(nombreRecurso={nombre!r}, nivelRequerido={nivel})"
|
|
461
|
+
break
|
|
457
462
|
return [EntryPoint(
|
|
458
463
|
path=rel_path, stack="java", kind="rest_controller",
|
|
459
464
|
source="annotation", confidence="high",
|
|
@@ -474,11 +479,13 @@ class JavaDetector(AbstractDetector):
|
|
|
474
479
|
elif verb_match:
|
|
475
480
|
http_path = f"[{verb_match.group(1)}]"
|
|
476
481
|
security_evidence = None
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
+
for _ann_name, _ann_re in _CUSTOM_SECURITY_ANNOTATIONS.items():
|
|
483
|
+
_m = _ann_re.search(content)
|
|
484
|
+
if _m:
|
|
485
|
+
nombre = _m.group(1) or _m.group(2) or ""
|
|
486
|
+
nivel = _m.group(3) or ""
|
|
487
|
+
security_evidence = f"@{_ann_name}(nombreRecurso={nombre!r}, nivelRequerido={nivel})"
|
|
488
|
+
break
|
|
482
489
|
return [EntryPoint(
|
|
483
490
|
path=rel_path, stack="java", kind="mvc_controller",
|
|
484
491
|
source="annotation", confidence="medium",
|
|
@@ -514,7 +521,10 @@ class JavaDetector(AbstractDetector):
|
|
|
514
521
|
)]
|
|
515
522
|
|
|
516
523
|
# --- JAX-RS resource class ---
|
|
517
|
-
|
|
524
|
+
# Guard uses annotation PRESENCE ("@Path" in content), not _JAX_RS_PATH_RE, because
|
|
525
|
+
# the value-parsing regex fails for @Path(CONSTANT) and @Path(PREFIX + "/sub").
|
|
526
|
+
# _JAX_RS_PATH_RE is still used to extract the http_path display string when parseable.
|
|
527
|
+
if has_jax_rs and "@Path" in content and _JAX_RS_VERB_RE.search(content):
|
|
518
528
|
path_m = _JAX_RS_PATH_RE.search(content)
|
|
519
529
|
verb_m = _JAX_RS_VERB_RE.search(content)
|
|
520
530
|
http_path: str | None = None
|
sourcecode/doc_analyzer.py
CHANGED
|
@@ -273,10 +273,11 @@ class DocAnalyzer:
|
|
|
273
273
|
name = class_m.group(1)
|
|
274
274
|
doc_text = self._clean_javadoc(jd_match.group(1))
|
|
275
275
|
records.append(DocRecord(
|
|
276
|
+
symbol=name,
|
|
277
|
+
kind="class",
|
|
278
|
+
language="java",
|
|
276
279
|
path=path,
|
|
277
280
|
workspace=workspace,
|
|
278
|
-
kind="class",
|
|
279
|
-
name=name,
|
|
280
281
|
doc_text=doc_text,
|
|
281
282
|
importance=self._infer_importance(path, "class", entry_points),
|
|
282
283
|
))
|
|
@@ -291,10 +292,11 @@ class DocAnalyzer:
|
|
|
291
292
|
continue
|
|
292
293
|
doc_text = self._clean_javadoc(jd_match.group(1))
|
|
293
294
|
records.append(DocRecord(
|
|
295
|
+
symbol=name,
|
|
296
|
+
kind="function",
|
|
297
|
+
language="java",
|
|
294
298
|
path=path,
|
|
295
299
|
workspace=workspace,
|
|
296
|
-
kind="function",
|
|
297
|
-
name=name,
|
|
298
300
|
doc_text=doc_text,
|
|
299
301
|
importance=self._infer_importance(path, "function", entry_points),
|
|
300
302
|
))
|
sourcecode/mcp/server.py
CHANGED
|
@@ -78,8 +78,9 @@ def get_endpoints(repo_path: str = ".") -> dict:
|
|
|
78
78
|
"""REST API endpoint surface extraction from Java source files.
|
|
79
79
|
|
|
80
80
|
Maps to: sourcecode endpoints <repo_path>
|
|
81
|
-
Returns: endpoints list with method, path, controller, handler
|
|
82
|
-
total
|
|
81
|
+
Returns: endpoints list with method, path, controller, handler fields;
|
|
82
|
+
total (int) and undocumented (int) counts.
|
|
83
|
+
Supports Spring MVC (@GetMapping etc.) and JAX-RS (@GET/@POST etc.).
|
|
83
84
|
repo_path: absolute path to the repository (default: current working directory).
|
|
84
85
|
"""
|
|
85
86
|
if not isinstance(repo_path, str):
|
|
@@ -179,6 +180,49 @@ def onboard_context(repo_path: str = ".") -> dict:
|
|
|
179
180
|
return _execute(["prepare-context", "onboard", repo_path])
|
|
180
181
|
|
|
181
182
|
|
|
183
|
+
@mcp.tool()
|
|
184
|
+
def explain_context(repo_path: str = ".") -> dict:
|
|
185
|
+
"""Architecture and entry-point explanation for a repository.
|
|
186
|
+
|
|
187
|
+
Maps to: sourcecode prepare-context explain <repo_path>
|
|
188
|
+
Returns: project summary, architecture, entry points, key dependencies.
|
|
189
|
+
repo_path: absolute path to the repository (default: current working directory).
|
|
190
|
+
"""
|
|
191
|
+
if not isinstance(repo_path, str):
|
|
192
|
+
return _err("repo_path must be a string", "INVALID_ARGUMENT")
|
|
193
|
+
return _execute(["prepare-context", "explain", repo_path])
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
@mcp.tool()
|
|
197
|
+
def refactor_context(repo_path: str = ".") -> dict:
|
|
198
|
+
"""Structural issues and refactor opportunities for a repository.
|
|
199
|
+
|
|
200
|
+
Maps to: sourcecode prepare-context refactor <repo_path>
|
|
201
|
+
Returns: structural issues, coupling hotspots, improvement opportunities.
|
|
202
|
+
repo_path: absolute path to the repository (default: current working directory).
|
|
203
|
+
"""
|
|
204
|
+
if not isinstance(repo_path, str):
|
|
205
|
+
return _err("repo_path must be a string", "INVALID_ARGUMENT")
|
|
206
|
+
return _execute(["prepare-context", "refactor", repo_path])
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
@mcp.tool()
|
|
210
|
+
def generate_tests_context(repo_path: str = ".", include_all: bool = False) -> dict:
|
|
211
|
+
"""Untested source files and test gap analysis for a repository.
|
|
212
|
+
|
|
213
|
+
Maps to: sourcecode prepare-context generate-tests <repo_path> [--all]
|
|
214
|
+
Returns: test_gaps list of untested files ranked by risk.
|
|
215
|
+
repo_path: absolute path to the repository (default: current working directory).
|
|
216
|
+
include_all: return full test_gaps list without truncating to top 20.
|
|
217
|
+
"""
|
|
218
|
+
if not isinstance(repo_path, str):
|
|
219
|
+
return _err("repo_path must be a string", "INVALID_ARGUMENT")
|
|
220
|
+
args = ["prepare-context", "generate-tests", repo_path]
|
|
221
|
+
if include_all:
|
|
222
|
+
args.append("--all")
|
|
223
|
+
return _execute(args)
|
|
224
|
+
|
|
225
|
+
|
|
182
226
|
_TELEMETRY_ACTIONS = frozenset({"status", "enable", "disable"})
|
|
183
227
|
|
|
184
228
|
|
sourcecode/prepare_context.py
CHANGED
|
@@ -573,17 +573,17 @@ def _read_code_signal_evidence(root: Path, file_path: str, artifact_type: str) -
|
|
|
573
573
|
|
|
574
574
|
_ARTIFACT_CHANGE_EFFECT: dict[str, str] = {
|
|
575
575
|
"entrypoint": "application entrypoint (framework bootstrap / CLI handler)",
|
|
576
|
-
"controller": "HTTP
|
|
577
|
-
"service": "business logic layer (@Service
|
|
578
|
-
"repository": "data access layer (persistence queries / ORM)",
|
|
576
|
+
"controller": "HTTP handler layer (Spring @RestController / JAX-RS @Path resource)",
|
|
577
|
+
"service": "business logic layer (Spring @Service / CDI @ApplicationScoped bean)",
|
|
578
|
+
"repository": "data access layer (persistence queries / ORM / CDI store)",
|
|
579
579
|
"mapper": "SQL-object mapping layer (MyBatis mapper / query template)",
|
|
580
580
|
"security": "security component (authentication / access control configuration)",
|
|
581
|
-
"spring_config": "
|
|
582
|
-
"spring_profile": "
|
|
581
|
+
"spring_config": "framework configuration class (Spring @Configuration / Quarkus @QuarkusApplication)",
|
|
582
|
+
"spring_profile": "environment-specific configuration override (Spring profile / Quarkus profile)",
|
|
583
583
|
"config": "configuration file (application properties / environment values)",
|
|
584
584
|
"build_manifest": "build manifest (dependency and plugin configuration)",
|
|
585
585
|
"db_migration": "database schema migration (DDL change pending execution)",
|
|
586
|
-
"domain_model": "domain entity (@Entity / value object)",
|
|
586
|
+
"domain_model": "domain entity (@Entity / CDI model / value object)",
|
|
587
587
|
"dto": "data transfer object (serialization contract)",
|
|
588
588
|
"test": "test file (no production code modified)",
|
|
589
589
|
"documentation": "documentation file (no runtime impact)",
|
|
@@ -1810,32 +1810,44 @@ class TaskContextBuilder:
|
|
|
1810
1810
|
)[:16000]
|
|
1811
1811
|
_pub_count = _content.count("public ")
|
|
1812
1812
|
_ann_count = (
|
|
1813
|
+
# Spring MVC / Spring Boot
|
|
1813
1814
|
_content.count("@Transactional")
|
|
1814
1815
|
+ _content.count("@RequestMapping")
|
|
1815
1816
|
+ _content.count("@GetMapping")
|
|
1816
1817
|
+ _content.count("@PostMapping")
|
|
1817
1818
|
+ _content.count("@PutMapping")
|
|
1818
1819
|
+ _content.count("@DeleteMapping")
|
|
1820
|
+
# JAX-RS
|
|
1821
|
+
+ _content.count("@Path")
|
|
1822
|
+
+ _content.count("@GET")
|
|
1823
|
+
+ _content.count("@POST")
|
|
1824
|
+
+ _content.count("@PUT")
|
|
1825
|
+
+ _content.count("@DELETE")
|
|
1826
|
+
+ _content.count("@PATCH")
|
|
1827
|
+
# CDI / Jakarta EE
|
|
1828
|
+
+ _content.count("@ApplicationScoped")
|
|
1829
|
+
+ _content.count("@RequestScoped")
|
|
1830
|
+
+ _content.count("@Singleton")
|
|
1819
1831
|
)
|
|
1820
1832
|
except OSError:
|
|
1821
1833
|
pass
|
|
1822
1834
|
_java_candidates.append({
|
|
1823
1835
|
"path": _p,
|
|
1824
1836
|
"public_method_count": _pub_count,
|
|
1825
|
-
"
|
|
1837
|
+
"has_framework_annotations": _ann_count > 0,
|
|
1826
1838
|
"_rank": _pub_count + _ann_count * 2,
|
|
1827
1839
|
})
|
|
1828
1840
|
|
|
1829
1841
|
_java_candidates.sort(
|
|
1830
|
-
key=lambda x: -(x["public_method_count"] * (1.5 if x["
|
|
1842
|
+
key=lambda x: -(x["public_method_count"] * (1.5 if x["has_framework_annotations"] else 1.0))
|
|
1831
1843
|
)
|
|
1832
1844
|
_top = _java_candidates if all_gaps else _java_candidates[:20]
|
|
1833
1845
|
test_gaps = [
|
|
1834
1846
|
{
|
|
1835
1847
|
"path": c["path"],
|
|
1836
1848
|
"public_method_count": c["public_method_count"],
|
|
1837
|
-
"
|
|
1838
|
-
"rank_score": round(c["public_method_count"] * (1.5 if c["
|
|
1849
|
+
"has_framework_annotations": c["has_framework_annotations"],
|
|
1850
|
+
"rank_score": round(c["public_method_count"] * (1.5 if c["has_framework_annotations"] else 1.0), 1),
|
|
1839
1851
|
}
|
|
1840
1852
|
for c in _top
|
|
1841
1853
|
]
|
|
@@ -1893,15 +1905,20 @@ class TaskContextBuilder:
|
|
|
1893
1905
|
|
|
1894
1906
|
conf_summary, analysis_gaps = ConfidenceAnalyzer().analyze(sm_for_conf)
|
|
1895
1907
|
confidence = conf_summary.overall
|
|
1908
|
+
_has_mybatis = any(
|
|
1909
|
+
f.name == "MyBatis"
|
|
1910
|
+
for s in stacks
|
|
1911
|
+
for f in getattr(s, "frameworks", [])
|
|
1912
|
+
)
|
|
1896
1913
|
if task_name in ("delta", "review-pr"):
|
|
1897
1914
|
# Use delta-specific gaps; ConfidenceAnalyzer gaps are about full-repo
|
|
1898
1915
|
# detection quality and are not meaningful for an incremental diff.
|
|
1899
1916
|
gaps = _delta_analysis_gaps
|
|
1900
|
-
if _mybatis_warning:
|
|
1917
|
+
if _mybatis_warning and _has_mybatis:
|
|
1901
1918
|
gaps.append(_mybatis_warning["reason"])
|
|
1902
1919
|
else:
|
|
1903
1920
|
gaps = [g.reason for g in analysis_gaps]
|
|
1904
|
-
if _mybatis_warning:
|
|
1921
|
+
if _mybatis_warning and _has_mybatis:
|
|
1905
1922
|
gaps.append(_mybatis_warning["reason"])
|
|
1906
1923
|
|
|
1907
1924
|
# ── 9. why_these_files ────────────────────────────────────────────────
|
|
@@ -2103,8 +2120,15 @@ class TaskContextBuilder:
|
|
|
2103
2120
|
_uncommitted = uncommitted_files or set()
|
|
2104
2121
|
_max_churn = max(_hotspots.values(), default=1)
|
|
2105
2122
|
|
|
2123
|
+
# Per-file note counts — feeds code_note_count into RankingEngine for all tasks.
|
|
2124
|
+
# RankingEngine is the sole ranking source; no ad-hoc annotation boost outside it.
|
|
2125
|
+
_note_counts: dict[str, int] = {}
|
|
2126
|
+
for _n in (code_notes or []):
|
|
2127
|
+
_np = getattr(_n, "path", "")
|
|
2128
|
+
if _np:
|
|
2129
|
+
_note_counts[_np] = _note_counts.get(_np, 0) + 1
|
|
2130
|
+
|
|
2106
2131
|
# Pre-compute fix-bug signals (used only when task_name == "fix-bug")
|
|
2107
|
-
_annotated_files: set[str] = set()
|
|
2108
2132
|
_dominant_stack = ""
|
|
2109
2133
|
_recently_changed_stacks: set[str] = set()
|
|
2110
2134
|
# Query-aware signals extracted from symptom (class names, exception types, tokens)
|
|
@@ -2113,10 +2137,6 @@ class TaskContextBuilder:
|
|
|
2113
2137
|
_symptom_tokens: set[str] = set() # all lowercase tokens
|
|
2114
2138
|
|
|
2115
2139
|
if task_name == "fix-bug":
|
|
2116
|
-
_bug_kinds = {"FIXME", "BUG", "HACK", "XXX"}
|
|
2117
|
-
for _n in (code_notes or []):
|
|
2118
|
-
if getattr(_n, "kind", "").upper() in _bug_kinds:
|
|
2119
|
-
_annotated_files.add(getattr(_n, "path", ""))
|
|
2120
2140
|
|
|
2121
2141
|
def _file_stack(p: str) -> str:
|
|
2122
2142
|
ext = Path(p).suffix.lower()
|
|
@@ -2167,13 +2187,15 @@ class TaskContextBuilder:
|
|
|
2167
2187
|
if is_test and task_name != "generate-tests":
|
|
2168
2188
|
continue
|
|
2169
2189
|
|
|
2170
|
-
# Structural + git signals from unified engine (task-weighted)
|
|
2190
|
+
# Structural + git signals from unified engine (task-weighted).
|
|
2191
|
+
# code_note_count routes annotation density through RankingEngine — single source.
|
|
2171
2192
|
fs = engine.score(
|
|
2172
2193
|
path,
|
|
2173
2194
|
is_entrypoint=(path in runtime_entry_set),
|
|
2174
2195
|
git_churn=_hotspots.get(path, 0),
|
|
2175
2196
|
max_churn=_max_churn,
|
|
2176
2197
|
is_changed=(path in _uncommitted),
|
|
2198
|
+
code_note_count=_note_counts.get(path, 0),
|
|
2177
2199
|
task=task_name,
|
|
2178
2200
|
)
|
|
2179
2201
|
|
|
@@ -2257,6 +2279,9 @@ class TaskContextBuilder:
|
|
|
2257
2279
|
# Single-token match: no boost — avoids OR explosion
|
|
2258
2280
|
|
|
2259
2281
|
# ── Git / annotation signals ──
|
|
2282
|
+
_note_ct = _note_counts.get(path, 0)
|
|
2283
|
+
if _note_ct > 0:
|
|
2284
|
+
_why_parts.append(f"annotation density ({_note_ct} FIXME/BUG/HACK notes)")
|
|
2260
2285
|
if path in _uncommitted:
|
|
2261
2286
|
content_boost += 0.40
|
|
2262
2287
|
_why_parts.append("uncommitted change (+0.40)")
|
|
@@ -2264,9 +2289,6 @@ class TaskContextBuilder:
|
|
|
2264
2289
|
if _recency > 0:
|
|
2265
2290
|
content_boost += _recency
|
|
2266
2291
|
_why_parts.append(f"recent commits (+{_recency:.2f})")
|
|
2267
|
-
if path in _annotated_files:
|
|
2268
|
-
content_boost += 0.20
|
|
2269
|
-
_why_parts.append("FIXME/BUG annotation (+0.20)")
|
|
2270
2292
|
_file_stk = _file_stack(path)
|
|
2271
2293
|
if _dominant_stack and _file_stk == _dominant_stack:
|
|
2272
2294
|
content_boost += 0.10
|
|
@@ -2357,12 +2379,22 @@ class TaskContextBuilder:
|
|
|
2357
2379
|
if task_name == "onboard":
|
|
2358
2380
|
def _arch_layer(p: str) -> str:
|
|
2359
2381
|
n = Path(p).name.lower()
|
|
2382
|
+
is_java = n.endswith(".java")
|
|
2383
|
+
# HTTP handler layer: Spring MVC controllers AND JAX-RS resources/endpoints
|
|
2384
|
+
# Restrict "resource"/"endpoint" to .java files to avoid matching
|
|
2385
|
+
# Maven's src/main/resources/ directory or XML resource files.
|
|
2360
2386
|
if "controller" in n:
|
|
2361
2387
|
return "controllers"
|
|
2362
|
-
if "
|
|
2388
|
+
if is_java and ("resource" in n or "endpoint" in n or "restadapter" in n):
|
|
2389
|
+
return "controllers"
|
|
2390
|
+
# Data access layer: Spring repos, DAOs, JDBC, JPA stores
|
|
2391
|
+
if "repository" in n or "mapper" in n or "dao" in n or "store" in n:
|
|
2363
2392
|
return "repositories"
|
|
2393
|
+
# Business logic: Spring @Service, CDI providers, factories
|
|
2364
2394
|
if "service" in n:
|
|
2365
2395
|
return "services"
|
|
2396
|
+
if is_java and ("provider" in n or "factory" in n or "manager" in n):
|
|
2397
|
+
return "services"
|
|
2366
2398
|
pn = p.replace("\\", "/")
|
|
2367
2399
|
if "entity" in n or "/entity/" in pn or "/domain/" in pn or "/model/" in pn:
|
|
2368
2400
|
return "domain"
|