sourcecode 1.30.26__py3-none-any.whl → 1.30.28__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 +19 -5
- sourcecode/prepare_context.py +30 -3
- sourcecode/ranking_engine.py +92 -0
- sourcecode/serializer.py +89 -66
- {sourcecode-1.30.26.dist-info → sourcecode-1.30.28.dist-info}/METADATA +3 -3
- {sourcecode-1.30.26.dist-info → sourcecode-1.30.28.dist-info}/RECORD +10 -10
- {sourcecode-1.30.26.dist-info → sourcecode-1.30.28.dist-info}/WHEEL +0 -0
- {sourcecode-1.30.26.dist-info → sourcecode-1.30.28.dist-info}/entry_points.txt +0 -0
- {sourcecode-1.30.26.dist-info → sourcecode-1.30.28.dist-info}/licenses/LICENSE +0 -0
sourcecode/__init__.py
CHANGED
sourcecode/cli.py
CHANGED
|
@@ -424,7 +424,7 @@ def main(
|
|
|
424
424
|
no_redact: bool = typer.Option(
|
|
425
425
|
False,
|
|
426
426
|
"--no-redact",
|
|
427
|
-
help="Disable secret redaction.
|
|
427
|
+
help="Disable secret redaction of output strings. Note: env var values from the OS are never included in output regardless of this flag (security policy).",
|
|
428
428
|
),
|
|
429
429
|
version: Optional[bool] = typer.Option(
|
|
430
430
|
None,
|
|
@@ -437,7 +437,7 @@ def main(
|
|
|
437
437
|
depth: int = typer.Option(
|
|
438
438
|
4,
|
|
439
439
|
"--depth",
|
|
440
|
-
help="File tree traversal depth (default: 4). Java/Maven projects auto-adjust to 12.",
|
|
440
|
+
help="File tree traversal depth (default: 4). Java/Maven projects auto-adjust to a minimum of 12; values below 12 have no effect on Java projects.",
|
|
441
441
|
min=1,
|
|
442
442
|
max=20,
|
|
443
443
|
),
|
|
@@ -1734,6 +1734,11 @@ def prepare_context_cmd(
|
|
|
1734
1734
|
help="Emit per-phase timing to stderr (git scan ms, symptom scoring ms, total ms)",
|
|
1735
1735
|
hidden=True,
|
|
1736
1736
|
),
|
|
1737
|
+
fast: bool = typer.Option(
|
|
1738
|
+
False,
|
|
1739
|
+
"--fast",
|
|
1740
|
+
help="Skip deep analysis (content search, test gap discovery, code annotations). Uses manifest/metadata only. Target: < 6 s.",
|
|
1741
|
+
),
|
|
1737
1742
|
) -> None:
|
|
1738
1743
|
"""Task-specific context for AI coding agents.
|
|
1739
1744
|
|
|
@@ -1808,9 +1813,14 @@ def prepare_context_cmd(
|
|
|
1808
1813
|
if since:
|
|
1809
1814
|
_phase += f" since {since}"
|
|
1810
1815
|
_progress.start(_phase)
|
|
1816
|
+
if not fast:
|
|
1817
|
+
import sys as _sys
|
|
1818
|
+
if _sys.stderr.isatty():
|
|
1819
|
+
_sys.stderr.write(f"Analyzing ({task})... (deep scan may take 15–35 s for large codebases)\n")
|
|
1820
|
+
_sys.stderr.flush()
|
|
1811
1821
|
_t0 = _time.perf_counter()
|
|
1812
1822
|
try:
|
|
1813
|
-
output = builder.build(task, since=since, symptom=symptom)
|
|
1823
|
+
output = builder.build(task, since=since, symptom=symptom, fast=fast)
|
|
1814
1824
|
finally:
|
|
1815
1825
|
_progress.finish()
|
|
1816
1826
|
_t_total = (_time.perf_counter() - _t0) * 1000
|
|
@@ -1960,8 +1970,8 @@ def prepare_context_cmd(
|
|
|
1960
1970
|
_sys.stdout.buffer.write(b"\n")
|
|
1961
1971
|
_sys.stdout.buffer.flush()
|
|
1962
1972
|
if copy:
|
|
1963
|
-
_copy_to_clipboard(_nc_json)
|
|
1964
|
-
|
|
1973
|
+
if _copy_to_clipboard(_nc_json):
|
|
1974
|
+
typer.echo("✓ copied to clipboard", err=True)
|
|
1965
1975
|
raise typer.Exit()
|
|
1966
1976
|
if output.ci_decision:
|
|
1967
1977
|
out["ci_decision"] = output.ci_decision
|
|
@@ -2082,6 +2092,10 @@ def prepare_context_cmd(
|
|
|
2082
2092
|
out["symptom_note"] = output.symptom_note
|
|
2083
2093
|
if output.symptom_explain:
|
|
2084
2094
|
out["symptom_explain"] = output.symptom_explain
|
|
2095
|
+
if getattr(output, "symptom_hint", None):
|
|
2096
|
+
out["symptom_hint"] = output.symptom_hint
|
|
2097
|
+
if getattr(output, "warnings", None):
|
|
2098
|
+
out["warnings"] = output.warnings
|
|
2085
2099
|
if llm_prompt:
|
|
2086
2100
|
out["llm_prompt"] = builder.render_prompt(output)
|
|
2087
2101
|
|
sourcecode/prepare_context.py
CHANGED
|
@@ -335,6 +335,7 @@ class TaskOutput:
|
|
|
335
335
|
related_notes: list[dict] = field(default_factory=list) # fix-bug + symptom only
|
|
336
336
|
symptom_note: Optional[str] = None # fix-bug: cross-layer synonym note
|
|
337
337
|
symptom_explain: Optional[dict] = None # fix-bug: structured evidence breakdown
|
|
338
|
+
symptom_hint: Optional[str] = None # fix-bug: redirect hint when term not found in this module
|
|
338
339
|
# delta-specific impact fields
|
|
339
340
|
impact_summary: Optional[str] = None
|
|
340
341
|
affected_modules: list[str] = field(default_factory=list)
|
|
@@ -348,6 +349,7 @@ class TaskOutput:
|
|
|
348
349
|
error_code: Optional[str] = None
|
|
349
350
|
error_message: Optional[str] = None
|
|
350
351
|
error_hints: list[str] = field(default_factory=list)
|
|
352
|
+
warnings: list[dict] = field(default_factory=list) # structured warnings (REF_NOT_FOUND, etc.)
|
|
351
353
|
# CI decision state machine — machine-decidable signal
|
|
352
354
|
ci_decision: Optional[str] = None # "no_changes" | "analysis_success" | "git_ref_error" | "no_git_repo"
|
|
353
355
|
# git baseline resolution metadata
|
|
@@ -683,7 +685,7 @@ class TaskContextBuilder:
|
|
|
683
685
|
def __init__(self, root: Path) -> None:
|
|
684
686
|
self.root = root
|
|
685
687
|
|
|
686
|
-
def build(self, task_name: str, *, since: Optional[str] = None, symptom: Optional[str] = None) -> TaskOutput:
|
|
688
|
+
def build(self, task_name: str, *, since: Optional[str] = None, symptom: Optional[str] = None, fast: bool = False) -> TaskOutput:
|
|
687
689
|
if task_name not in TASKS:
|
|
688
690
|
raise ValueError(
|
|
689
691
|
f"Unknown task '{task_name}'. Available: {', '.join(TASKS)}"
|
|
@@ -880,7 +882,7 @@ class TaskContextBuilder:
|
|
|
880
882
|
improvement_opportunities: list[str] = []
|
|
881
883
|
cn_notes_for_ranking: list = []
|
|
882
884
|
|
|
883
|
-
if spec.enable_code_notes:
|
|
885
|
+
if spec.enable_code_notes and not fast:
|
|
884
886
|
from dataclasses import asdict
|
|
885
887
|
from sourcecode.code_notes_analyzer import CodeNotesAnalyzer
|
|
886
888
|
|
|
@@ -1317,6 +1319,7 @@ class TaskContextBuilder:
|
|
|
1317
1319
|
related_notes: list[dict] = []
|
|
1318
1320
|
symptom_note: Optional[str] = None
|
|
1319
1321
|
symptom_explain: Optional[dict] = None
|
|
1322
|
+
symptom_hint: Optional[str] = None
|
|
1320
1323
|
if task_name == "fix-bug" and symptom:
|
|
1321
1324
|
import re as _re
|
|
1322
1325
|
_camel_expanded = _re.sub(r'([a-z])([A-Z])', r'\1 \2', symptom)
|
|
@@ -1537,9 +1540,30 @@ class TaskContextBuilder:
|
|
|
1537
1540
|
),
|
|
1538
1541
|
}
|
|
1539
1542
|
|
|
1543
|
+
# BUG #4: LOW confidence + 0 content matches → clear suspected_areas,
|
|
1544
|
+
# emit actionable redirect instead of unrelated files.
|
|
1545
|
+
if _sx_confidence == "LOW" and not _sx_content:
|
|
1546
|
+
suspected_areas = []
|
|
1547
|
+
_is_fe_term = any(kw in _FRONTEND_SYMPTOM_MAP for kw in symptom_keywords)
|
|
1548
|
+
_root_name = self.root.name
|
|
1549
|
+
if _is_fe_term:
|
|
1550
|
+
_fe_redirect = (
|
|
1551
|
+
f"Term {symptom!r} not found in sources under {_root_name!r}. "
|
|
1552
|
+
f"This appears to be a frontend symptom. "
|
|
1553
|
+
f"Try: prepare-context fix-bug . --symptom {symptom!r} "
|
|
1554
|
+
f"(monorepo root) or target a frontend sub-project directly."
|
|
1555
|
+
)
|
|
1556
|
+
else:
|
|
1557
|
+
_fe_redirect = (
|
|
1558
|
+
f"Term {symptom!r} not found in sources under {_root_name!r}. "
|
|
1559
|
+
f"Verify the spelling or try a related term. "
|
|
1560
|
+
f"If this is a frontend symptom, run against the frontend sub-project."
|
|
1561
|
+
)
|
|
1562
|
+
symptom_hint = _fe_redirect
|
|
1563
|
+
|
|
1540
1564
|
# ── 7. Test gaps (generate-tests only) ────────────────────────────
|
|
1541
1565
|
test_gaps: list[str] = []
|
|
1542
|
-
if task_name == "generate-tests":
|
|
1566
|
+
if task_name == "generate-tests" and not fast:
|
|
1543
1567
|
def _normalize_test_stem(stem: str) -> str:
|
|
1544
1568
|
# Java: FooTest / FooTests → Foo; TestFoo → Foo
|
|
1545
1569
|
if stem.endswith("Tests"):
|
|
@@ -1683,6 +1707,8 @@ class TaskContextBuilder:
|
|
|
1683
1707
|
resolved_since_ref=_delta_baseline.get("resolved_ref") if task_name == "delta" else None,
|
|
1684
1708
|
resolution_path=_delta_baseline.get("resolution_path") if task_name == "delta" else None,
|
|
1685
1709
|
diff_validation_status=_delta_baseline.get("diff_validation_status") if task_name == "delta" else None,
|
|
1710
|
+
warnings=_delta_baseline.get("warnings", []) if task_name == "delta" else [],
|
|
1711
|
+
symptom_hint=symptom_hint if task_name == "fix-bug" else None,
|
|
1686
1712
|
)
|
|
1687
1713
|
|
|
1688
1714
|
def render_prompt(self, output: TaskOutput) -> str:
|
|
@@ -3261,6 +3287,7 @@ class TaskContextBuilder:
|
|
|
3261
3287
|
"resolution_path": "head_minus_1_fallback",
|
|
3262
3288
|
"diff_validation_status": "invalid_ref", # original ref unresolved
|
|
3263
3289
|
"error": False,
|
|
3290
|
+
"warnings": [{"code": "REF_NOT_FOUND", "ref": since, "resolved_to": "HEAD~1"}],
|
|
3264
3291
|
}
|
|
3265
3292
|
|
|
3266
3293
|
# All stages failed
|
sourcecode/ranking_engine.py
CHANGED
|
@@ -251,3 +251,95 @@ class RankingEngine:
|
|
|
251
251
|
|
|
252
252
|
def is_auxiliary(self, path: str) -> bool:
|
|
253
253
|
return self._scorer.is_auxiliary(path)
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
# ---------------------------------------------------------------------------
|
|
257
|
+
# Mandatory scoring formula — deterministic 5-component impact model
|
|
258
|
+
# ---------------------------------------------------------------------------
|
|
259
|
+
|
|
260
|
+
# runtime_impact: execution-path role of the file
|
|
261
|
+
_RUNTIME_IMPACT: dict[str | None, float] = {
|
|
262
|
+
"api_endpoint": 1.0, # @RestController / @Controller
|
|
263
|
+
"security": 1.0, # @EnableWebSecurity / security filters
|
|
264
|
+
"runtime_core": 1.0, # confirmed production entrypoint
|
|
265
|
+
"cli_entrypoint": 1.0,
|
|
266
|
+
"exception_handler": 0.8, # @ControllerAdvice
|
|
267
|
+
"business_logic": 0.7, # @Service (with or without @Transactional)
|
|
268
|
+
"api_layer": 0.7, # API framework import (non-annotation evidence)
|
|
269
|
+
"data_access": 0.5, # @Repository / @Mapper
|
|
270
|
+
"database_layer": 0.5, # DB framework import
|
|
271
|
+
"infrastructure": 0.5, # infra dependency import
|
|
272
|
+
"configuration": 0.4, # @Configuration
|
|
273
|
+
"application_logic": 0.3, # code defs + imports, no framework annotation
|
|
274
|
+
"domain_model": 0.3, # @Entity / domain models
|
|
275
|
+
"dto": 0.2, # @Data / pure data carriers
|
|
276
|
+
"build_system": 0.15,
|
|
277
|
+
"tests": 0.1,
|
|
278
|
+
"tooling": 0.05,
|
|
279
|
+
None: 0.15, # unclassified source file
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
# framework_signal_strength: annotation / import evidence quality
|
|
283
|
+
# Spring annotation → 0.3; security component → +0.2 (total 0.5); import only → 0.2
|
|
284
|
+
_FRAMEWORK_SIGNAL: dict[str | None, float] = {
|
|
285
|
+
"security": 0.5, # Spring Security annotation + security component
|
|
286
|
+
"api_endpoint": 0.3, # @RestController / @Controller
|
|
287
|
+
"exception_handler": 0.3, # @ControllerAdvice
|
|
288
|
+
"business_logic": 0.3, # @Service
|
|
289
|
+
"data_access": 0.3, # @Repository / @Mapper
|
|
290
|
+
"configuration": 0.3, # @Configuration
|
|
291
|
+
"domain_model": 0.3, # JPA @Entity
|
|
292
|
+
"dto": 0.3, # Lombok @Data
|
|
293
|
+
"api_layer": 0.2, # framework import (weaker than annotation)
|
|
294
|
+
"database_layer": 0.2,
|
|
295
|
+
"infrastructure": 0.2,
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
# Normalization ceiling per evidence tier — used when spread < 0.40
|
|
299
|
+
_NORM_TARGET_HI: dict[str, float] = {
|
|
300
|
+
"api_endpoint": 0.90,
|
|
301
|
+
"security": 0.90,
|
|
302
|
+
"exception_handler": 0.82,
|
|
303
|
+
"business_logic": 0.80,
|
|
304
|
+
"api_layer": 0.80,
|
|
305
|
+
"data_access": 0.70,
|
|
306
|
+
"database_layer": 0.70,
|
|
307
|
+
"infrastructure": 0.70,
|
|
308
|
+
"configuration": 0.65,
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
def resolve_runtime_impact(category: str | None) -> float:
|
|
313
|
+
"""Map FileClassifier category → runtime_impact [0.0, 1.0]."""
|
|
314
|
+
return _RUNTIME_IMPACT.get(category, _RUNTIME_IMPACT[None])
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
def resolve_framework_signal(category: str | None) -> float:
|
|
318
|
+
"""Map FileClassifier category → framework_signal_strength [0.0, 0.5]."""
|
|
319
|
+
return _FRAMEWORK_SIGNAL.get(category, 0.0)
|
|
320
|
+
|
|
321
|
+
|
|
322
|
+
def compute_impact_score(
|
|
323
|
+
runtime_impact: float,
|
|
324
|
+
dependency_centrality: float,
|
|
325
|
+
framework_signal_strength: float,
|
|
326
|
+
change_type_severity: float,
|
|
327
|
+
test_risk_factor: float,
|
|
328
|
+
) -> float:
|
|
329
|
+
"""Mandatory weighted scoring formula.
|
|
330
|
+
|
|
331
|
+
score = 0.35×runtime_impact + 0.25×dependency_centrality
|
|
332
|
+
+ 0.20×framework_signal_strength + 0.10×change_type_severity
|
|
333
|
+
+ 0.10×test_risk_factor
|
|
334
|
+
|
|
335
|
+
All inputs [0.0, 1.0]. Output clamped [0.0, 1.0].
|
|
336
|
+
Deterministic: same inputs always produce same output.
|
|
337
|
+
"""
|
|
338
|
+
raw = (
|
|
339
|
+
0.35 * runtime_impact
|
|
340
|
+
+ 0.25 * dependency_centrality
|
|
341
|
+
+ 0.20 * framework_signal_strength
|
|
342
|
+
+ 0.10 * change_type_severity
|
|
343
|
+
+ 0.10 * test_risk_factor
|
|
344
|
+
)
|
|
345
|
+
return max(0.0, min(1.0, raw))
|
sourcecode/serializer.py
CHANGED
|
@@ -248,6 +248,16 @@ def _compact_git_context(sm: "SourceMap") -> "Optional[dict[str, Any]]":
|
|
|
248
248
|
for f, n in _fc.most_common(5)
|
|
249
249
|
]
|
|
250
250
|
ctx["hotspots_source"] = "recent_commits"
|
|
251
|
+
if gc.recent_commits:
|
|
252
|
+
ctx["recent_commits"] = [
|
|
253
|
+
{
|
|
254
|
+
"hash": c.hash[:8],
|
|
255
|
+
"message": (c.message or "")[:80],
|
|
256
|
+
"date": (c.date or "")[:10],
|
|
257
|
+
"author": c.author or "",
|
|
258
|
+
}
|
|
259
|
+
for c in gc.recent_commits[:5]
|
|
260
|
+
]
|
|
251
261
|
return ctx if ctx else None
|
|
252
262
|
|
|
253
263
|
|
|
@@ -802,13 +812,13 @@ def _file_relevance(sm: SourceMap, *, limit: int = _FILE_RELEVANCE_LIMIT) -> lis
|
|
|
802
812
|
semantic_hub_scores[p] = h.get("importance_score", 0.0) / max_importance
|
|
803
813
|
|
|
804
814
|
entry_paths = {ep.path for ep in sm.entry_points}
|
|
805
|
-
# REST/MVC controllers are HTTP surface — surface before @Transactional services
|
|
806
|
-
_rest_ctrl_paths = {
|
|
807
|
-
ep.path for ep in sm.entry_points
|
|
808
|
-
if getattr(ep, "kind", "") in {"rest_controller", "mvc_controller"}
|
|
809
|
-
}
|
|
810
815
|
scored: list[tuple[float, dict[str, Any]]] = []
|
|
811
816
|
|
|
817
|
+
from sourcecode.ranking_engine import (
|
|
818
|
+
compute_impact_score, resolve_runtime_impact, resolve_framework_signal,
|
|
819
|
+
_NORM_TARGET_HI,
|
|
820
|
+
)
|
|
821
|
+
|
|
812
822
|
for path in sm.file_paths:
|
|
813
823
|
file_class = classifier.classify(path)
|
|
814
824
|
fs = engine.score(
|
|
@@ -821,78 +831,64 @@ def _file_relevance(sm: SourceMap, *, limit: int = _FILE_RELEVANCE_LIMIT) -> lis
|
|
|
821
831
|
if fs.score < -50: # hard noise
|
|
822
832
|
continue
|
|
823
833
|
|
|
824
|
-
content_rel = file_class.relevance if file_class else 0.0
|
|
825
|
-
# Semantic hub bonus: normalised call-graph centrality adds up to +0.30
|
|
826
|
-
sem_hub = semantic_hub_scores.get(path, 0.0) * 0.30
|
|
827
|
-
pre_bonus_combined = fs.score + content_rel + sem_hub
|
|
828
|
-
# REST controller boost: surface above @Transactional service files
|
|
829
|
-
if path in _rest_ctrl_paths:
|
|
830
|
-
pre_bonus_combined += 2.0
|
|
831
|
-
|
|
832
|
-
# M3: Structural importance scoring — SORT ORDER ONLY.
|
|
833
|
-
# These bonuses drive ranking but must NOT inflate the displayed score.
|
|
834
834
|
stem = Path(path).stem
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
#
|
|
862
|
-
#
|
|
863
|
-
|
|
864
|
-
|
|
835
|
+
cat = file_class.category if file_class else None
|
|
836
|
+
|
|
837
|
+
# ── Component 1: runtime_impact — execution-path role ─────────────────
|
|
838
|
+
runtime_impact = resolve_runtime_impact(cat)
|
|
839
|
+
|
|
840
|
+
# ── Component 2: dependency_centrality — call-graph importance ─────────
|
|
841
|
+
# Entry points treat external HTTP/CLI callers as high centrality.
|
|
842
|
+
# Isolated files (no semantic data) score 0.0 — negative weighting by omission.
|
|
843
|
+
dep_centrality = semantic_hub_scores.get(path, 0.0)
|
|
844
|
+
if path in entry_paths:
|
|
845
|
+
dep_centrality = max(dep_centrality, 0.8)
|
|
846
|
+
|
|
847
|
+
# ── Component 3: framework_signal_strength — annotation quality ─────────
|
|
848
|
+
fw_signal = resolve_framework_signal(cat)
|
|
849
|
+
|
|
850
|
+
# ── Component 4: change_type_severity — git churn as structural proxy ───
|
|
851
|
+
churn = git_churn.get(path, 0)
|
|
852
|
+
change_sev = min(churn / max(max_churn, 1), 1.0) * 0.8
|
|
853
|
+
|
|
854
|
+
# ── Component 5: test_risk_factor — no per-file coverage data ──────────
|
|
855
|
+
test_risk = 0.2
|
|
856
|
+
|
|
857
|
+
formula_raw = compute_impact_score(
|
|
858
|
+
runtime_impact, dep_centrality, fw_signal, change_sev, test_risk
|
|
859
|
+
)
|
|
860
|
+
|
|
861
|
+
# T1 override: confirmed production entrypoints → 0.92–1.00.
|
|
862
|
+
# Only runtime_core / cli_entrypoint categories justify scores ≥ 0.92.
|
|
863
|
+
if path in entry_paths and cat in ("runtime_core", "cli_entrypoint"):
|
|
864
|
+
score_val = round(
|
|
865
|
+
min(1.0, max(0.92, file_class.relevance if file_class else 0.92)), 3
|
|
866
|
+
)
|
|
867
|
+
else:
|
|
868
|
+
score_val = round(formula_raw, 3)
|
|
869
|
+
|
|
870
|
+
# Visibility threshold: formula score or high-relevance content exception.
|
|
871
|
+
if score_val < _FILE_RELEVANCE_MIN_COMBINED:
|
|
865
872
|
if not (file_class
|
|
866
873
|
and file_class.relevance > 0.45
|
|
867
874
|
and file_class.confidence in {"high", "medium"}):
|
|
868
875
|
continue
|
|
869
876
|
|
|
870
|
-
# Suppress low-confidence auxiliary/config files
|
|
877
|
+
# Suppress low-confidence auxiliary/config files
|
|
871
878
|
if (file_class
|
|
872
879
|
and file_class.confidence == "low"
|
|
873
880
|
and file_class.category in {"config", "auxiliary"}
|
|
874
|
-
and
|
|
881
|
+
and score_val < 0.45):
|
|
875
882
|
continue
|
|
876
883
|
|
|
877
|
-
#
|
|
878
|
-
#
|
|
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)
|
|
884
|
+
# relevance: content evidence only — intentionally diverges from score
|
|
885
|
+
# when dep_centrality or churn are non-zero (score ≠ relevance invariant)
|
|
892
886
|
relevance_val = round(file_class.relevance, 3) if file_class else round(
|
|
893
|
-
min(0.39, max(0.10,
|
|
887
|
+
min(0.39, max(0.10, runtime_impact)), 3
|
|
894
888
|
)
|
|
895
889
|
|
|
890
|
+
# sem_hub retained for signal reporting only — not used in score formula
|
|
891
|
+
sem_hub = semantic_hub_scores.get(path, 0.0) * 0.30
|
|
896
892
|
ranking_reasons = [r for r in fs.reasons if r != "source file"]
|
|
897
893
|
if sem_hub >= 0.15:
|
|
898
894
|
ranking_reasons.append("call graph hub")
|
|
@@ -913,7 +909,7 @@ def _file_relevance(sm: SourceMap, *, limit: int = _FILE_RELEVANCE_LIMIT) -> lis
|
|
|
913
909
|
if ranking_reasons:
|
|
914
910
|
item["ranking_reasons"] = ranking_reasons
|
|
915
911
|
|
|
916
|
-
# Override
|
|
912
|
+
# Override: universal base controller classes score as runtime_core
|
|
917
913
|
if any(k in stem for k in ("GenericRestController", "GenericCRUDRestController")):
|
|
918
914
|
item["category"] = "runtime_core"
|
|
919
915
|
item["score"] = 0.95
|
|
@@ -926,11 +922,38 @@ def _file_relevance(sm: SourceMap, *, limit: int = _FILE_RELEVANCE_LIMIT) -> lis
|
|
|
926
922
|
item["ranking_reasons"] = ["universal base class", "exception handling contract"]
|
|
927
923
|
item["signals"] = [{"type": "framework_annotation", "strength": "strong"}]
|
|
928
924
|
|
|
929
|
-
|
|
925
|
+
# sort_key = final score (rank consistency: score order = output order)
|
|
926
|
+
scored.append((item["score"], item))
|
|
930
927
|
|
|
931
|
-
#
|
|
928
|
+
# Initial sort: score desc, path asc for deterministic tie-break
|
|
932
929
|
scored.sort(key=lambda x: (-x[0], x[1]["path"]))
|
|
933
930
|
|
|
931
|
+
# Normalization: enforce minimum spread ≥ 0.4 among non-T1 files.
|
|
932
|
+
# T1 files (confirmed entrypoints, 0.92–1.0) are excluded — already correct.
|
|
933
|
+
# Prevents score compression when structural signals (--semantics, git) absent.
|
|
934
|
+
_nonep = [(sk, it) for sk, it in scored if it["path"] not in entry_paths]
|
|
935
|
+
if len(_nonep) > 1:
|
|
936
|
+
_vals = [it["score"] for _, it in _nonep]
|
|
937
|
+
_lo, _hi = min(_vals), max(_vals)
|
|
938
|
+
_spread = _hi - _lo
|
|
939
|
+
if _spread < 0.40:
|
|
940
|
+
_top_cat = max(_nonep, key=lambda x: x[1]["score"])[1].get("category", "")
|
|
941
|
+
_target_hi = _NORM_TARGET_HI.get(_top_cat, 0.60)
|
|
942
|
+
_target_lo = max(0.10, _target_hi - 0.50)
|
|
943
|
+
if _spread > 0:
|
|
944
|
+
_scale = (_target_hi - _target_lo) / _spread
|
|
945
|
+
for _, it in _nonep:
|
|
946
|
+
it["score"] = round(
|
|
947
|
+
max(0.0, min(1.0, _target_lo + (it["score"] - _lo) * _scale)), 3
|
|
948
|
+
)
|
|
949
|
+
else:
|
|
950
|
+
_mid = round((_target_hi + _target_lo) / 2.0, 3)
|
|
951
|
+
for _, it in _nonep:
|
|
952
|
+
it["score"] = _mid
|
|
953
|
+
|
|
954
|
+
# Re-sort by final score to guarantee rank consistency after normalization
|
|
955
|
+
scored.sort(key=lambda x: (-x[1]["score"], x[1]["path"]))
|
|
956
|
+
|
|
934
957
|
# Diversity cap: at most half the budget from any single category.
|
|
935
958
|
# Prevents 10/10 controllers drowning out services, repositories, domain.
|
|
936
959
|
_CAT_CAP = max(1, limit // 2)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sourcecode
|
|
3
|
-
Version: 1.30.
|
|
3
|
+
Version: 1.30.28
|
|
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.28
|
|
261
261
|
```
|
|
262
262
|
|
|
263
263
|
---
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
sourcecode/__init__.py,sha256=
|
|
1
|
+
sourcecode/__init__.py,sha256=q-bCo7uGXns3U7O05C-GiuHhFOyvaN-90_kC3JSQMn0,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=NbVyJAEQ4Q0ngkgW_lXyvlDJcD8TTrxZEXY6RIVkBAI,95261
|
|
8
8
|
sourcecode/code_notes_analyzer.py,sha256=y1MJBnPZHYp4i6cQCXUb9ATIyifS_qMQWjw_8lPkpsU,9215
|
|
9
9
|
sourcecode/confidence_analyzer.py,sha256=H9VHYRzZhqMFlSCZffjtsMUGYLnDvrq1g5FjzyQ1hxE,16381
|
|
10
10
|
sourcecode/context_scorer.py,sha256=QpChSpsmaAYz91rXA4Ue5xzQmNz_ZboZN09YOHScq1U,14679
|
|
@@ -22,9 +22,9 @@ 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=O3gpZIJLzCH2-9zr91zOP0D4gwHND2BbgBOCplwQXj8,168540
|
|
26
26
|
sourcecode/progress.py,sha256=qn30sWaHOkjTgXsSBmiPkz7Rsbwc5oSlIe6JNEMYp_k,3149
|
|
27
|
-
sourcecode/ranking_engine.py,sha256=
|
|
27
|
+
sourcecode/ranking_engine.py,sha256=ZAucq_YX2KkWUuAZf4P0lhtQ_38vEFnUhuGtSZd1S0E,12970
|
|
28
28
|
sourcecode/redactor.py,sha256=xuGcadGEHaPw4qZXlMDvzMCsr4VOkdp3oBQptHyJk8c,2884
|
|
29
29
|
sourcecode/relevance_scorer.py,sha256=MYF4FFkveAQps9SmTeTlh6ODiBz2F--_hWNeHMLtUHQ,8405
|
|
30
30
|
sourcecode/repo_classifier.py,sha256=FG1vaWKdWXsWdl-S8hjVMiTqcwgaRXkDyvK4rPcOGtQ,22681
|
|
@@ -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=svaLLYyAeAd3ZWsgh0YBwu7RKm00JaKuyz3bQbLipHA,106064
|
|
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.28.dist-info/METADATA,sha256=cL4bkJZO4jc-T5k1GtC5OXS99NZLU4bZSl6WxVbo70w,28956
|
|
68
|
+
sourcecode-1.30.28.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
|
|
69
|
+
sourcecode-1.30.28.dist-info/entry_points.txt,sha256=ex3F9rmbXeyDIoFQHtkEqTsKSaJow8F0LrVu8XfIktQ,57
|
|
70
|
+
sourcecode-1.30.28.dist-info/licenses/LICENSE,sha256=7DdHrU9Z_3e7dSvq4ISijZNjnuHo5NIHNiHDouMQ9JU,10491
|
|
71
|
+
sourcecode-1.30.28.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|