sourcecode 1.30.24__py3-none-any.whl → 1.30.26__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 +25 -0
- sourcecode/confidence_analyzer.py +10 -2
- sourcecode/file_classifier.py +11 -10
- sourcecode/pr_comment_renderer.py +43 -42
- sourcecode/prepare_context.py +22 -4
- sourcecode/serializer.py +153 -23
- {sourcecode-1.30.24.dist-info → sourcecode-1.30.26.dist-info}/METADATA +3 -3
- {sourcecode-1.30.24.dist-info → sourcecode-1.30.26.dist-info}/RECORD +12 -12
- {sourcecode-1.30.24.dist-info → sourcecode-1.30.26.dist-info}/WHEEL +0 -0
- {sourcecode-1.30.24.dist-info → sourcecode-1.30.26.dist-info}/entry_points.txt +0 -0
- {sourcecode-1.30.24.dist-info → sourcecode-1.30.26.dist-info}/licenses/LICENSE +0 -0
sourcecode/__init__.py
CHANGED
sourcecode/cli.py
CHANGED
|
@@ -2030,6 +2030,10 @@ def prepare_context_cmd(
|
|
|
2030
2030
|
out["runtime_changes"] = output.runtime_changes
|
|
2031
2031
|
if output.build_changes:
|
|
2032
2032
|
out["build_changes"] = output.build_changes
|
|
2033
|
+
if output.committed_changes:
|
|
2034
|
+
out["committed_changes"] = output.committed_changes
|
|
2035
|
+
if output.uncommitted_changes:
|
|
2036
|
+
out["uncommitted_changes"] = output.uncommitted_changes
|
|
2033
2037
|
if output.review_hotspots:
|
|
2034
2038
|
out["review_hotspots"] = output.review_hotspots
|
|
2035
2039
|
if output.suggested_review_order:
|
|
@@ -2047,6 +2051,27 @@ def prepare_context_cmd(
|
|
|
2047
2051
|
"files": output.scope_files,
|
|
2048
2052
|
"repo_root": output.repo_root or "",
|
|
2049
2053
|
}
|
|
2054
|
+
# analysis_limiter: consolidate missing graph signals into one field
|
|
2055
|
+
_missing_signals: list[str] = []
|
|
2056
|
+
_dgraph = output.dependency_graph_summary or {}
|
|
2057
|
+
if not _dgraph.get("has_graph_evidence"):
|
|
2058
|
+
_missing_signals.append("dependency_graph")
|
|
2059
|
+
_has_import_ev = any(
|
|
2060
|
+
(rc.get("role") or {}).get("has_annotation_signal") or
|
|
2061
|
+
(rc.get("role") or {}).get("has_symbol_signal")
|
|
2062
|
+
for rc in (output.runtime_changes or [])
|
|
2063
|
+
)
|
|
2064
|
+
if not _has_import_ev:
|
|
2065
|
+
_missing_signals.append("import_graph")
|
|
2066
|
+
if _missing_signals:
|
|
2067
|
+
out["analysis_limiter"] = {"missing_signals": _missing_signals}
|
|
2068
|
+
# classification_confidence: global note when all runtime files are low confidence
|
|
2069
|
+
_all_low = bool(output.runtime_changes) and all(
|
|
2070
|
+
(rc.get("change_effect") or {}).get("epistemic_level") == "INFERRED (LOW CONFIDENCE)"
|
|
2071
|
+
for rc in output.runtime_changes
|
|
2072
|
+
)
|
|
2073
|
+
if _all_low:
|
|
2074
|
+
out["classification_confidence"] = "low"
|
|
2050
2075
|
if output.limitations:
|
|
2051
2076
|
out["limitations"] = output.limitations
|
|
2052
2077
|
if output.symptom:
|
|
@@ -290,10 +290,18 @@ class ConfidenceAnalyzer:
|
|
|
290
290
|
elif production_eps and all(ep.runtime_relevance == "low" for ep in production_eps):
|
|
291
291
|
overall = _min_confidence([overall, "low"])
|
|
292
292
|
|
|
293
|
-
# Factor in architecture confidence when available
|
|
293
|
+
# Factor in architecture confidence when available.
|
|
294
|
+
# Key rule: if a pattern was detected (not None/"unknown"), arch.confidence="low"
|
|
295
|
+
# typically reflects missing documentation (no OpenAPI/ADR), not structural uncertainty.
|
|
296
|
+
# In that case, clamp the downgrade to "medium" so that high stack + high entry_points
|
|
297
|
+
# is not contradicted by a docs gap.
|
|
294
298
|
arch = sm.architecture
|
|
295
299
|
if arch is not None and arch.requested:
|
|
296
|
-
|
|
300
|
+
arch_conf_for_overall = arch.confidence
|
|
301
|
+
if arch.confidence == "low" and arch.pattern not in (None, "unknown"):
|
|
302
|
+
# Pattern was detected — low is docs-only, not structural; cap downgrade at medium
|
|
303
|
+
arch_conf_for_overall = "medium"
|
|
304
|
+
overall = _min_confidence([overall, arch_conf_for_overall])
|
|
297
305
|
if arch.pattern in (None, "unknown"):
|
|
298
306
|
# Architecture could not be inferred — don't let stack alone push to high
|
|
299
307
|
if overall == "high":
|
sourcecode/file_classifier.py
CHANGED
|
@@ -84,16 +84,17 @@ _JAVA_ANNOTATION_RE = re.compile(r'@(RestController|Controller|Service|Repositor
|
|
|
84
84
|
# (annotation_set, category, relevance, why_template)
|
|
85
85
|
# Checked in priority order; first match wins.
|
|
86
86
|
_JAVA_STEREOTYPE_RULES: list[tuple[frozenset, str, float, str]] = [
|
|
87
|
-
(frozenset({"EnableWebSecurity"}), "security",
|
|
88
|
-
(frozenset({"RestController"}), "api_endpoint",
|
|
89
|
-
(frozenset({"Controller", "RequestMapping"}), "api_endpoint",
|
|
90
|
-
(frozenset({"
|
|
91
|
-
(frozenset({"Service"}),
|
|
92
|
-
(frozenset({"
|
|
93
|
-
(frozenset({"
|
|
94
|
-
(frozenset({"
|
|
95
|
-
(frozenset({"
|
|
96
|
-
(frozenset({"
|
|
87
|
+
(frozenset({"EnableWebSecurity"}), "security", 0.85, "Spring Security configuration"),
|
|
88
|
+
(frozenset({"RestController"}), "api_endpoint", 0.90, "Spring REST controller — defines HTTP API surface"),
|
|
89
|
+
(frozenset({"Controller", "RequestMapping"}), "api_endpoint", 0.80, "Spring MVC controller"),
|
|
90
|
+
(frozenset({"ControllerAdvice"}), "exception_handler", 0.75, "Spring @ControllerAdvice — cross-cutting exception handling"),
|
|
91
|
+
(frozenset({"Service", "Transactional"}), "business_logic", 0.75, "Transactional service — business logic boundary"),
|
|
92
|
+
(frozenset({"Service"}), "business_logic", 0.65, "Spring service component"),
|
|
93
|
+
(frozenset({"Repository"}), "data_access", 0.65, "Spring repository — data access layer"),
|
|
94
|
+
(frozenset({"Mapper"}), "data_access", 0.65, "MyBatis mapper — SQL data access"),
|
|
95
|
+
(frozenset({"Configuration"}), "configuration", 0.70, "Spring configuration class"),
|
|
96
|
+
(frozenset({"Entity"}), "domain_model", 0.50, "JPA entity — domain model"),
|
|
97
|
+
(frozenset({"Data"}), "dto", 0.40, "Lombok DTO"),
|
|
97
98
|
]
|
|
98
99
|
|
|
99
100
|
# Categories produced by Java stereotype detection — used downstream to apply direct relevance
|
|
@@ -171,13 +171,31 @@ def _section_priority_order(out: dict) -> str:
|
|
|
171
171
|
lines.append("*No priority order available — diff scope too narrow to rank.*")
|
|
172
172
|
return "\n".join(lines)
|
|
173
173
|
|
|
174
|
-
|
|
174
|
+
visible = order[:_MAX_PRIORITY_FILES]
|
|
175
|
+
all_low = visible and all(
|
|
176
|
+
(cls_map.get(p, {}).get("change_effect") or {}).get("epistemic_level")
|
|
177
|
+
== "INFERRED (LOW CONFIDENCE)"
|
|
178
|
+
for p in visible
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
for i, path in enumerate(visible, 1):
|
|
175
182
|
rc = cls_map.get(path, {})
|
|
176
183
|
effect = rc.get("change_effect", {})
|
|
177
184
|
stmt = effect.get("statement", "application source") if isinstance(effect, dict) else "application source"
|
|
178
|
-
ep = effect.get("epistemic_level", "STRUCTURAL SIGNAL") if isinstance(effect, dict) else "STRUCTURAL SIGNAL"
|
|
179
185
|
atype = rc.get("artifact_type", "source")
|
|
180
|
-
|
|
186
|
+
if all_low:
|
|
187
|
+
lines.append(f"{i}. `{_short(path)}` — {atype} — {stmt}")
|
|
188
|
+
else:
|
|
189
|
+
ep = effect.get("epistemic_level", "STRUCTURAL SIGNAL") if isinstance(effect, dict) else "STRUCTURAL SIGNAL"
|
|
190
|
+
lines.append(f"{i}. `{_short(path)}` — {_badge(ep)}: {atype} — {stmt}")
|
|
191
|
+
|
|
192
|
+
if all_low:
|
|
193
|
+
lines.append("")
|
|
194
|
+
lines.append(
|
|
195
|
+
f"*Classification confidence: low for all changed files "
|
|
196
|
+
f"{_badge('INFERRED (LOW CONFIDENCE)')} — "
|
|
197
|
+
"no annotation/import signals found in changed files.*"
|
|
198
|
+
)
|
|
181
199
|
|
|
182
200
|
return "\n".join(lines)
|
|
183
201
|
|
|
@@ -269,50 +287,26 @@ def _section_signals(out: dict) -> str:
|
|
|
269
287
|
def _section_guidance(out: dict) -> str:
|
|
270
288
|
lines = ["### 5. Review Guidance", ""]
|
|
271
289
|
|
|
272
|
-
# Inspect first
|
|
273
|
-
order: list[str] = out.get("suggested_review_order", []) or out.get("review_hotspots", [])
|
|
274
|
-
runtime: list[dict] = out.get("runtime_changes", [])
|
|
275
|
-
cls_map: dict[str, dict] = {rc["path"]: rc for rc in runtime}
|
|
276
|
-
|
|
277
|
-
if order:
|
|
278
|
-
lines.append("**Inspect first:**")
|
|
279
|
-
for path in order[:3]:
|
|
280
|
-
rc = cls_map.get(path, {})
|
|
281
|
-
effect = rc.get("change_effect", {})
|
|
282
|
-
stmt = effect.get("statement", "") if isinstance(effect, dict) else ""
|
|
283
|
-
ep = effect.get("epistemic_level", "STRUCTURAL SIGNAL") if isinstance(effect, dict) else "STRUCTURAL SIGNAL"
|
|
284
|
-
label = f" — {stmt}" if stmt else ""
|
|
285
|
-
lines.append(f"- `{_short(path)}`{label} {_badge(ep)}")
|
|
286
|
-
lines.append("")
|
|
287
|
-
|
|
288
|
-
# Uncertain — heuristic-only behavioral paths
|
|
289
|
-
behavioral: list[dict] = out.get("behavioral_impact", [])
|
|
290
|
-
weak_paths = [
|
|
291
|
-
bi for bi in behavioral
|
|
292
|
-
if bi.get("evidence_level", "none") == "heuristic_only"
|
|
293
|
-
]
|
|
294
|
-
if weak_paths:
|
|
295
|
-
lines.append("**Uncertain (heuristic-only — verify manually):**")
|
|
296
|
-
for bi in weak_paths[:_MAX_UNCERTAIN]:
|
|
297
|
-
entry = bi.get("entry_point", "")
|
|
298
|
-
affected: list[str] = bi.get("affected_path", [])
|
|
299
|
-
path_str = " → ".join(affected) if affected else "unknown"
|
|
300
|
-
lines.append(
|
|
301
|
-
f"- `{entry}` → {path_str} "
|
|
302
|
-
f"{_badge('INFERRED (LOW CONFIDENCE)')}: class reference only, no injection/call proof"
|
|
303
|
-
)
|
|
304
|
-
lines.append("")
|
|
305
|
-
|
|
306
|
-
# Evidence gaps
|
|
290
|
+
# Evidence gaps ONLY — "Inspect first" covered by §3, "Uncertain" covered by §2
|
|
307
291
|
gaps: list[str] = out.get("gaps", [])
|
|
308
292
|
cov = out.get("test_coverage_risk", {})
|
|
309
293
|
test_files: list[str] = cov.get("changed_files_without_tests", [])
|
|
310
294
|
test_basis = cov.get("basis", "")
|
|
311
295
|
cov_ep = cov.get("epistemic_level", "INFERRED (LOW CONFIDENCE)")
|
|
312
296
|
|
|
313
|
-
|
|
297
|
+
# Skip graph-absence gaps already captured in analysis_limiter
|
|
298
|
+
missing_signals: set[str] = set(
|
|
299
|
+
(out.get("analysis_limiter") or {}).get("missing_signals", [])
|
|
300
|
+
)
|
|
301
|
+
gap_items: list[str] = [
|
|
302
|
+
g for g in gaps
|
|
303
|
+
if not missing_signals or not any(
|
|
304
|
+
sig.replace("_", " ") in g.lower() for sig in missing_signals
|
|
305
|
+
)
|
|
306
|
+
]
|
|
307
|
+
|
|
314
308
|
if test_files:
|
|
315
|
-
shown = test_files[:
|
|
309
|
+
shown = test_files[:3]
|
|
316
310
|
omitted = len(test_files) - len(shown)
|
|
317
311
|
names = ", ".join(f"`{_short(f)}`" for f in shown)
|
|
318
312
|
suffix = f" +{omitted} more" if omitted else ""
|
|
@@ -323,12 +317,19 @@ def _section_guidance(out: dict) -> str:
|
|
|
323
317
|
)
|
|
324
318
|
|
|
325
319
|
if gap_items:
|
|
326
|
-
lines.append("**Evidence gaps
|
|
327
|
-
for g in gap_items:
|
|
320
|
+
lines.append("**Evidence gaps:**")
|
|
321
|
+
for g in gap_items[:3]:
|
|
328
322
|
lines.append(f"- {g}")
|
|
329
323
|
else:
|
|
330
324
|
lines.append("*No evidence gaps detected.*")
|
|
331
325
|
|
|
326
|
+
if missing_signals:
|
|
327
|
+
lines.append("")
|
|
328
|
+
lines.append(
|
|
329
|
+
f"{_badge('OMITTED')}: {', '.join(sorted(missing_signals))} — "
|
|
330
|
+
"graph-based impact analysis unavailable"
|
|
331
|
+
)
|
|
332
|
+
|
|
332
333
|
return "\n".join(lines)
|
|
333
334
|
|
|
334
335
|
|
sourcecode/prepare_context.py
CHANGED
|
@@ -1271,9 +1271,9 @@ class TaskContextBuilder:
|
|
|
1271
1271
|
"evidence": _evidence,
|
|
1272
1272
|
"change_effect": {
|
|
1273
1273
|
"statement": _ARTIFACT_CHANGE_EFFECT.get(_f_atype, "application source file (role could not be confirmed)"),
|
|
1274
|
-
"classification_method":
|
|
1274
|
+
"classification_method": _role_basis,
|
|
1275
1275
|
"epistemic_level": (
|
|
1276
|
-
"STRUCTURAL SIGNAL" if
|
|
1276
|
+
"STRUCTURAL SIGNAL" if _has_code_ev
|
|
1277
1277
|
else "INFERRED (LOW CONFIDENCE)"
|
|
1278
1278
|
),
|
|
1279
1279
|
},
|
|
@@ -2054,10 +2054,18 @@ class TaskContextBuilder:
|
|
|
2054
2054
|
if since is None and not committed_files and not uncommitted_files:
|
|
2055
2055
|
head_ok = _run("git", "rev-parse", "--verify", "HEAD~1")
|
|
2056
2056
|
if head_ok is not None: # HEAD~1 exists
|
|
2057
|
-
head_committed = _run("git", "diff", "--name-only", "--relative", "HEAD~1
|
|
2057
|
+
head_committed = _run("git", "diff", "--name-only", "--relative", "HEAD~1..HEAD")
|
|
2058
2058
|
if head_committed:
|
|
2059
2059
|
committed_files = head_committed
|
|
2060
2060
|
sources.append(DiffSourceType.HEAD_MINUS_1.value)
|
|
2061
|
+
else:
|
|
2062
|
+
# First commit — no HEAD~1; diff against git empty tree
|
|
2063
|
+
_GIT_EMPTY_TREE = "4b825dc642cb6eb9a060e54bf8d69288fbee4904"
|
|
2064
|
+
first_committed = _run("git", "diff", "--name-only", "--relative",
|
|
2065
|
+
_GIT_EMPTY_TREE, "HEAD")
|
|
2066
|
+
if first_committed:
|
|
2067
|
+
committed_files = first_committed
|
|
2068
|
+
sources.append("initial_commit")
|
|
2061
2069
|
|
|
2062
2070
|
# ── Drop paths outside self.root ──────────────────────────────────────
|
|
2063
2071
|
def _drop_outside(lst: list[str]) -> list[str]:
|
|
@@ -2588,6 +2596,10 @@ class TaskContextBuilder:
|
|
|
2588
2596
|
# These are INFERRED (LOW CONFIDENCE) — stem match, not annotation evidence.
|
|
2589
2597
|
if any(kw in stem_lower for kw in ("validator", "validation")):
|
|
2590
2598
|
return "validation_component"
|
|
2599
|
+
if any(kw in stem_lower for kw in ("filter", "interceptor", "aspect")):
|
|
2600
|
+
return "runtime_filter"
|
|
2601
|
+
if any(kw in stem_lower for kw in ("advice", "advise", "exceptionhandler", "errorhandler")):
|
|
2602
|
+
return "exception_handler"
|
|
2591
2603
|
if any(kw in stem_lower for kw in ("controller", "resource", "endpoint", "rest")):
|
|
2592
2604
|
return "external_interface"
|
|
2593
2605
|
if any(kw in stem_lower for kw in ("service", "svc", "usecase", "facade")):
|
|
@@ -3280,8 +3292,14 @@ class TaskContextBuilder:
|
|
|
3280
3292
|
files = _diff("HEAD~1")
|
|
3281
3293
|
if files is not None:
|
|
3282
3294
|
return _make(files or [], "HEAD~1", "head_minus_1_fallback")
|
|
3295
|
+
else:
|
|
3296
|
+
# First commit — no HEAD~1; diff against git empty tree
|
|
3297
|
+
_GIT_EMPTY_TREE = "4b825dc642cb6eb9a060e54bf8d69288fbee4904"
|
|
3298
|
+
files = _diff(_GIT_EMPTY_TREE)
|
|
3299
|
+
if files is not None:
|
|
3300
|
+
return _make(files or [], _GIT_EMPTY_TREE, "initial_commit_fallback")
|
|
3283
3301
|
|
|
3284
|
-
# Confirmed no changes:
|
|
3302
|
+
# Confirmed no changes: empty repo
|
|
3285
3303
|
return {
|
|
3286
3304
|
"files": [],
|
|
3287
3305
|
"resolved_ref": "HEAD",
|
sourcecode/serializer.py
CHANGED
|
@@ -660,6 +660,116 @@ def _jndi_datasources(sm: "SourceMap") -> "Optional[list[dict[str, Any]]]":
|
|
|
660
660
|
return datasources
|
|
661
661
|
|
|
662
662
|
|
|
663
|
+
def _tiered_display_score(
|
|
664
|
+
pre_bonus_combined: float,
|
|
665
|
+
file_class: Any,
|
|
666
|
+
path: str,
|
|
667
|
+
entry_paths: set,
|
|
668
|
+
has_structural_signals: bool = False,
|
|
669
|
+
) -> float:
|
|
670
|
+
"""Evidence-tiered display score [0.0, 1.0].
|
|
671
|
+
|
|
672
|
+
Tiers enforce: strong evidence > medium evidence > filesystem/path only.
|
|
673
|
+
M3 sort bonuses must NOT be included in pre_bonus_combined — they are for
|
|
674
|
+
ordering only and must not inflate the displayed score.
|
|
675
|
+
|
|
676
|
+
Tier ceilings:
|
|
677
|
+
T1 confirmed production entrypoint 0.92–1.00
|
|
678
|
+
T2 entrypoint (weaker category) 0.80–0.91
|
|
679
|
+
T3 annotation-confirmed stereotype 0.40–0.90 (table-calibrated)
|
|
680
|
+
T4 framework import evidence 0.55–0.79
|
|
681
|
+
T5 code definitions + imports 0.38–0.54
|
|
682
|
+
T6 build manifest / tooling / test 0.25–0.45
|
|
683
|
+
T7 path/filesystem signal only 0.10–0.39
|
|
684
|
+
"""
|
|
685
|
+
from sourcecode.file_classifier import JAVA_STEREOTYPE_CATEGORIES
|
|
686
|
+
|
|
687
|
+
cat = file_class.category if file_class else None
|
|
688
|
+
base_rel = file_class.relevance if file_class else 0.0
|
|
689
|
+
|
|
690
|
+
# T1: confirmed production entrypoint
|
|
691
|
+
if path in entry_paths and cat in ("runtime_core", "cli_entrypoint"):
|
|
692
|
+
return round(min(1.0, max(0.92, base_rel)), 3)
|
|
693
|
+
|
|
694
|
+
# T2: in entry_paths but weaker evidence category
|
|
695
|
+
if path in entry_paths:
|
|
696
|
+
return round(min(0.91, max(0.80, pre_bonus_combined / 2.0)), 3)
|
|
697
|
+
|
|
698
|
+
# T3: annotation-confirmed stereotype — table values are already calibrated
|
|
699
|
+
if file_class and cat in JAVA_STEREOTYPE_CATEGORIES:
|
|
700
|
+
return round(base_rel, 3)
|
|
701
|
+
|
|
702
|
+
# T4: framework import evidence (medium strength)
|
|
703
|
+
if cat in ("api_layer", "database_layer", "infrastructure"):
|
|
704
|
+
return round(min(0.79, max(0.55, pre_bonus_combined / 2.0)), 3)
|
|
705
|
+
|
|
706
|
+
# T5: code definitions with imports (medium-low)
|
|
707
|
+
if cat in ("application_logic", "domain_model"):
|
|
708
|
+
return round(min(0.54, max(0.38, pre_bonus_combined / 2.0)), 3)
|
|
709
|
+
|
|
710
|
+
# T6: build manifest / tooling / test
|
|
711
|
+
if cat == "build_system":
|
|
712
|
+
return round(min(0.45, base_rel), 3)
|
|
713
|
+
if cat in ("tests", "tooling"):
|
|
714
|
+
return round(min(0.35, base_rel), 3)
|
|
715
|
+
|
|
716
|
+
# T7: no content classification — filesystem/structural signals only
|
|
717
|
+
# has_structural_signals: fan_in, churn, export — allows up to 0.54
|
|
718
|
+
# pure path/filename only — hard cap 0.39
|
|
719
|
+
if has_structural_signals:
|
|
720
|
+
return round(min(0.54, max(0.10, pre_bonus_combined / 2.0)), 3)
|
|
721
|
+
return round(min(0.39, max(0.10, pre_bonus_combined / 2.0)), 3)
|
|
722
|
+
|
|
723
|
+
|
|
724
|
+
def _build_file_signals(
|
|
725
|
+
file_class: Any,
|
|
726
|
+
path: str,
|
|
727
|
+
entry_paths: set,
|
|
728
|
+
fs_reasons: list,
|
|
729
|
+
sem_hub: float,
|
|
730
|
+
) -> list[dict]:
|
|
731
|
+
"""Minimal per-file signal breakdown: what contributed to this file's score."""
|
|
732
|
+
from sourcecode.file_classifier import JAVA_STEREOTYPE_CATEGORIES
|
|
733
|
+
|
|
734
|
+
signals: list[dict] = []
|
|
735
|
+
|
|
736
|
+
if path in entry_paths:
|
|
737
|
+
signals.append({"type": "runtime_entrypoint", "strength": "strong"})
|
|
738
|
+
|
|
739
|
+
if file_class:
|
|
740
|
+
cat = file_class.category
|
|
741
|
+
if cat in JAVA_STEREOTYPE_CATEGORIES:
|
|
742
|
+
signals.append({"type": "framework_annotation", "strength": "strong"})
|
|
743
|
+
elif cat in ("api_layer", "database_layer", "infrastructure"):
|
|
744
|
+
ev = [e for e in (file_class.evidence or [])[:2] if e]
|
|
745
|
+
signals.append({"type": "framework_import", "strength": "medium", "evidence": ev})
|
|
746
|
+
elif cat in ("application_logic",):
|
|
747
|
+
signals.append({"type": "code_definitions_with_imports", "strength": "medium"})
|
|
748
|
+
elif cat in ("domain_model",):
|
|
749
|
+
signals.append({"type": "domain_model_definitions", "strength": "medium"})
|
|
750
|
+
elif cat in ("build_system",):
|
|
751
|
+
signals.append({"type": "build_manifest", "strength": "medium"})
|
|
752
|
+
|
|
753
|
+
for r in fs_reasons:
|
|
754
|
+
r_lower = r.lower()
|
|
755
|
+
if "import centrality" in r_lower or "imported by" in r_lower:
|
|
756
|
+
signals.append({"type": "import_centrality", "strength": "medium"})
|
|
757
|
+
elif "hub module" in r_lower:
|
|
758
|
+
signals.append({"type": "hub_module", "strength": "medium"})
|
|
759
|
+
elif "recent churn" in r_lower:
|
|
760
|
+
signals.append({"type": "git_churn", "strength": "medium"})
|
|
761
|
+
elif "uncommitted" in r_lower:
|
|
762
|
+
signals.append({"type": "uncommitted_changes", "strength": "medium"})
|
|
763
|
+
|
|
764
|
+
if sem_hub >= 0.15:
|
|
765
|
+
signals.append({"type": "call_graph_hub", "strength": "strong"})
|
|
766
|
+
|
|
767
|
+
if not signals:
|
|
768
|
+
signals.append({"type": "filesystem_path", "strength": "weak"})
|
|
769
|
+
|
|
770
|
+
return signals
|
|
771
|
+
|
|
772
|
+
|
|
663
773
|
def _file_relevance(sm: SourceMap, *, limit: int = _FILE_RELEVANCE_LIMIT) -> list[dict[str, Any]]:
|
|
664
774
|
from sourcecode.ranking_engine import RankingEngine
|
|
665
775
|
|
|
@@ -714,40 +824,44 @@ def _file_relevance(sm: SourceMap, *, limit: int = _FILE_RELEVANCE_LIMIT) -> lis
|
|
|
714
824
|
content_rel = file_class.relevance if file_class else 0.0
|
|
715
825
|
# Semantic hub bonus: normalised call-graph centrality adds up to +0.30
|
|
716
826
|
sem_hub = semantic_hub_scores.get(path, 0.0) * 0.30
|
|
717
|
-
|
|
827
|
+
pre_bonus_combined = fs.score + content_rel + sem_hub
|
|
718
828
|
# REST controller boost: surface above @Transactional service files
|
|
719
829
|
if path in _rest_ctrl_paths:
|
|
720
|
-
|
|
830
|
+
pre_bonus_combined += 2.0
|
|
721
831
|
|
|
722
|
-
# M3: Structural importance scoring —
|
|
832
|
+
# M3: Structural importance scoring — SORT ORDER ONLY.
|
|
833
|
+
# These bonuses drive ranking but must NOT inflate the displayed score.
|
|
723
834
|
stem = Path(path).stem
|
|
724
835
|
stem_lower = stem.lower()
|
|
725
836
|
path_lower = path.lower()
|
|
837
|
+
sort_bonus = 0.0
|
|
726
838
|
# +10 application entry points
|
|
727
839
|
if path in entry_paths and any(
|
|
728
840
|
k in stem for k in ("Application", "Main", "Initializer", "Bootstrap", "Startup")
|
|
729
841
|
):
|
|
730
|
-
|
|
842
|
+
sort_bonus = 10.0
|
|
731
843
|
# +8 known base/infrastructure classes
|
|
732
844
|
elif any(k in stem for k in (
|
|
733
845
|
"GenericRestController", "GenericCRUDRestController", "GenericController",
|
|
734
846
|
"AkitaBaseService", "BaseService", "FilterConfig", "AbstractController",
|
|
735
847
|
)):
|
|
736
|
-
|
|
848
|
+
sort_bonus = 8.0
|
|
737
849
|
# +6 security configuration
|
|
738
850
|
elif any(k in stem for k in (
|
|
739
851
|
"SecurityConfig", "SecurityStrategy", "WebSecurityConfig", "SecurityFilter",
|
|
740
852
|
"JwtFilter", "AuthConfig", "SecurityConfiguration",
|
|
741
853
|
)):
|
|
742
|
-
|
|
854
|
+
sort_bonus = 6.0
|
|
743
855
|
# +2 shared utilities
|
|
744
856
|
elif any(k in path_lower for k in ("util", "shared", "common", "helper", "constant")):
|
|
745
|
-
|
|
857
|
+
sort_bonus = 2.0
|
|
858
|
+
|
|
859
|
+
sort_key = pre_bonus_combined + sort_bonus
|
|
746
860
|
|
|
747
861
|
# Visibility threshold: require meaningful combined signal.
|
|
748
862
|
# Exception: high/medium-confidence files with strong content relevance
|
|
749
863
|
# can survive even if structural score is weak.
|
|
750
|
-
if
|
|
864
|
+
if sort_key < _FILE_RELEVANCE_MIN_COMBINED:
|
|
751
865
|
if not (file_class
|
|
752
866
|
and file_class.relevance > 0.45
|
|
753
867
|
and file_class.confidence in {"high", "medium"}):
|
|
@@ -757,48 +871,64 @@ def _file_relevance(sm: SourceMap, *, limit: int = _FILE_RELEVANCE_LIMIT) -> lis
|
|
|
757
871
|
if (file_class
|
|
758
872
|
and file_class.confidence == "low"
|
|
759
873
|
and file_class.category in {"config", "auxiliary"}
|
|
760
|
-
and
|
|
874
|
+
and sort_key < 0.45):
|
|
761
875
|
continue
|
|
762
876
|
|
|
763
|
-
#
|
|
764
|
-
# the
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
877
|
+
# Detect whether structural signals (fan_in, churn, etc.) are present
|
|
878
|
+
# beyond the base path-relevance reason — used by fallback tier.
|
|
879
|
+
structural_signal_reasons = [
|
|
880
|
+
r for r in fs.reasons
|
|
881
|
+
if r not in ("source file", "workspace source root", "runtime entrypoint")
|
|
882
|
+
and "path" not in r.lower()
|
|
883
|
+
]
|
|
884
|
+
has_structural = bool(structural_signal_reasons)
|
|
885
|
+
|
|
886
|
+
# Tiered display score: evidence-gated, tier-capped.
|
|
887
|
+
# pre_bonus_combined is used (M3 sort_bonus excluded).
|
|
888
|
+
score_val = _tiered_display_score(
|
|
889
|
+
pre_bonus_combined, file_class, path, entry_paths, has_structural,
|
|
890
|
+
)
|
|
891
|
+
# relevance: raw evidence strength from FileClassifier (content signal only)
|
|
892
|
+
relevance_val = round(file_class.relevance, 3) if file_class else round(
|
|
893
|
+
min(0.39, max(0.10, pre_bonus_combined / 2.0)), 3
|
|
894
|
+
)
|
|
895
|
+
|
|
896
|
+
ranking_reasons = [r for r in fs.reasons if r != "source file"]
|
|
897
|
+
if sem_hub >= 0.15:
|
|
898
|
+
ranking_reasons.append("call graph hub")
|
|
899
|
+
|
|
900
|
+
signals = _build_file_signals(file_class, path, entry_paths, ranking_reasons, sem_hub)
|
|
770
901
|
|
|
771
902
|
item: dict[str, Any] = {
|
|
772
903
|
"path": path,
|
|
773
904
|
"category": file_class.category if file_class else "source",
|
|
774
905
|
"confidence": file_class.confidence if file_class else "low",
|
|
906
|
+
"score": score_val,
|
|
775
907
|
"relevance": relevance_val,
|
|
776
|
-
"score": relevance_val,
|
|
777
908
|
"reason": file_class.reason if file_class else (fs.reasons[0] if fs.reasons else "source file"),
|
|
778
909
|
"evidence": file_class.evidence if file_class else [],
|
|
910
|
+
"signals": signals,
|
|
779
911
|
}
|
|
780
912
|
|
|
781
|
-
ranking_reasons = [r for r in fs.reasons if r != "source file"]
|
|
782
|
-
if sem_hub >= 0.15:
|
|
783
|
-
ranking_reasons.append("call graph hub")
|
|
784
913
|
if ranking_reasons:
|
|
785
914
|
item["ranking_reasons"] = ranking_reasons
|
|
786
915
|
|
|
787
916
|
# Override metadata for known M3 base controller classes
|
|
788
917
|
if any(k in stem for k in ("GenericRestController", "GenericCRUDRestController")):
|
|
789
918
|
item["category"] = "runtime_core"
|
|
790
|
-
item["relevance"] = 0.95
|
|
791
919
|
item["score"] = 0.95
|
|
920
|
+
item["relevance"] = 0.95
|
|
792
921
|
item["reason"] = (
|
|
793
922
|
"base class for all REST controllers — extends this to get "
|
|
794
923
|
"centralized exception handling via handlerException()"
|
|
795
924
|
)
|
|
796
925
|
item["evidence"] = ["base_rest_controller"]
|
|
797
926
|
item["ranking_reasons"] = ["universal base class", "exception handling contract"]
|
|
927
|
+
item["signals"] = [{"type": "framework_annotation", "strength": "strong"}]
|
|
798
928
|
|
|
799
|
-
scored.append((
|
|
929
|
+
scored.append((sort_key, item))
|
|
800
930
|
|
|
801
|
-
# Deterministic sort:
|
|
931
|
+
# Deterministic sort: sort_key desc, then path asc
|
|
802
932
|
scored.sort(key=lambda x: (-x[0], x[1]["path"]))
|
|
803
933
|
|
|
804
934
|
# Diversity cap: at most half the budget from any single category.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sourcecode
|
|
3
|
-
Version: 1.30.
|
|
3
|
+
Version: 1.30.26
|
|
4
4
|
Summary: Deterministic codebase context for AI coding agents
|
|
5
5
|
License: Apache License
|
|
6
6
|
Version 2.0, January 2004
|
|
@@ -221,7 +221,7 @@ Description-Content-Type: text/markdown
|
|
|
221
221
|
|
|
222
222
|
**Deterministic, behavior-aware codebase context for AI agents and PR review.**
|
|
223
223
|
|
|
224
|
-

|
|
225
225
|

|
|
226
226
|
|
|
227
227
|
---
|
|
@@ -257,7 +257,7 @@ pipx install sourcecode
|
|
|
257
257
|
|
|
258
258
|
```bash
|
|
259
259
|
sourcecode version
|
|
260
|
-
# sourcecode 1.30.
|
|
260
|
+
# sourcecode 1.30.26
|
|
261
261
|
```
|
|
262
262
|
|
|
263
263
|
---
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
sourcecode/__init__.py,sha256=
|
|
1
|
+
sourcecode/__init__.py,sha256=a9rwjUy_I3zn65HxrEVjs67OAO8DAvBj0eb-iK1rvho,104
|
|
2
2
|
sourcecode/adaptive_scanner.py,sha256=RTNExwWPXzjgLaRueT7UuxkPj5ZEToWjGbx1j0LSZ9E,10250
|
|
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=pYve2J1LqtYssU3lYLMDz18PT-CjN5c18QYE7R_IG1Q,7507
|
|
7
|
-
sourcecode/cli.py,sha256=
|
|
7
|
+
sourcecode/cli.py,sha256=xNbK_xwsqdhUbAJT4VieJDitGlhMu_O_Y5xzY8yrc9Q,94497
|
|
8
8
|
sourcecode/code_notes_analyzer.py,sha256=y1MJBnPZHYp4i6cQCXUb9ATIyifS_qMQWjw_8lPkpsU,9215
|
|
9
|
-
sourcecode/confidence_analyzer.py,sha256=
|
|
9
|
+
sourcecode/confidence_analyzer.py,sha256=H9VHYRzZhqMFlSCZffjtsMUGYLnDvrq1g5FjzyQ1hxE,16381
|
|
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
|
|
@@ -16,13 +16,13 @@ sourcecode/dependency_analyzer.py,sha256=p4ljXhkcGBbFlhaZuPrsjOVjDXaKLTg0Gor2p4q
|
|
|
16
16
|
sourcecode/doc_analyzer.py,sha256=afA4uJFwXZ_uR2l4J0pQwbeTkRkGmKdN9KhRVYePBUw,24331
|
|
17
17
|
sourcecode/entrypoint_classifier.py,sha256=gvKgl0f5T8ol1r4JMmkeqGHuZTfZJiOwFOWdc7EYwYw,4061
|
|
18
18
|
sourcecode/env_analyzer.py,sha256=GxCidahAAIptTdDFIlVB6URd4HBnBlIX_SqUov3MBRQ,22076
|
|
19
|
-
sourcecode/file_classifier.py,sha256=
|
|
19
|
+
sourcecode/file_classifier.py,sha256=ur6xDjxIVOj2Or3Ic8LTD0nLguUXFWVRBhamWAKFKeU,11738
|
|
20
20
|
sourcecode/flow_analyzer.py,sha256=dSiuY4w49k29jW_EPXUOND9B5uVbuCA7kjnuHi-pIWA,28781
|
|
21
21
|
sourcecode/git_analyzer.py,sha256=0Gyj-vMpIIN4nfriKXVRouNYBeJ59s6pQDX2Xu9Pq-U,13177
|
|
22
22
|
sourcecode/graph_analyzer.py,sha256=iUK-7pSV-cvGqqD2hENdYmhnm0wcXFEyK-xnu5ul8OU,62515
|
|
23
23
|
sourcecode/metrics_analyzer.py,sha256=m0ENgtqKeBL17kUIK3fmGkgo7UfXBNHxCMj0H_Y5K7c,22750
|
|
24
|
-
sourcecode/pr_comment_renderer.py,sha256=
|
|
25
|
-
sourcecode/prepare_context.py,sha256=
|
|
24
|
+
sourcecode/pr_comment_renderer.py,sha256=smHslxiG14lrytCkq5nFrFu-qTHgA-t-LFYfdrfjz2o,14423
|
|
25
|
+
sourcecode/prepare_context.py,sha256=4BwSEXUEuWiYaRZ8wPUcClSW5_Pl1BuUGe-VqM9MSPQ,166688
|
|
26
26
|
sourcecode/progress.py,sha256=qn30sWaHOkjTgXsSBmiPkz7Rsbwc5oSlIe6JNEMYp_k,3149
|
|
27
27
|
sourcecode/ranking_engine.py,sha256=virVglafZufioHpZpwktjMvUiL0TZELWQCQnQNV8dFo,9360
|
|
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=aM3h9-DCQ3xKpeHpHYdo2vX6T5P95HA_YwZbkAVNwmo,8288
|
|
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=bPpZKu9LxrBgpDlGX4qyyO2eQ0PJIFi8bSPb2oC5vhQ,105008
|
|
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
|
|
@@ -64,8 +64,8 @@ sourcecode/telemetry/consent.py,sha256=wLMvGNJeSSyZoNkQXpoUioY6mMv4Qdvuw7S9jAEWn
|
|
|
64
64
|
sourcecode/telemetry/events.py,sha256=oEvvulfsv5GIDWG2174gSS6tNB95w38AIYiYeifGKlE,2294
|
|
65
65
|
sourcecode/telemetry/filters.py,sha256=Asa71oRl7q3Wt_FMwuufIZJFzSYdgRNKS8LHCIyFeYE,4805
|
|
66
66
|
sourcecode/telemetry/transport.py,sha256=KJeIPCPWMdmbCP3ySGs2iUlia34U6vWne2dZsUezesw,1560
|
|
67
|
-
sourcecode-1.30.
|
|
68
|
-
sourcecode-1.30.
|
|
69
|
-
sourcecode-1.30.
|
|
70
|
-
sourcecode-1.30.
|
|
71
|
-
sourcecode-1.30.
|
|
67
|
+
sourcecode-1.30.26.dist-info/METADATA,sha256=rSYUxd-EVgFu_xcd6eXXfarTlqmqAaMQeP5VlDKfqrU,28956
|
|
68
|
+
sourcecode-1.30.26.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
|
|
69
|
+
sourcecode-1.30.26.dist-info/entry_points.txt,sha256=ex3F9rmbXeyDIoFQHtkEqTsKSaJow8F0LrVu8XfIktQ,57
|
|
70
|
+
sourcecode-1.30.26.dist-info/licenses/LICENSE,sha256=7DdHrU9Z_3e7dSvq4ISijZNjnuHo5NIHNiHDouMQ9JU,10491
|
|
71
|
+
sourcecode-1.30.26.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|