diffcontext 0.5.2__tar.gz → 0.5.3__tar.gz
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.
- {diffcontext-0.5.2 → diffcontext-0.5.3}/PKG-INFO +1 -1
- {diffcontext-0.5.2 → diffcontext-0.5.3}/diffcontext/__init__.py +1 -1
- {diffcontext-0.5.2 → diffcontext-0.5.3}/diffcontext/cli/__init__.py +13 -3
- {diffcontext-0.5.2 → diffcontext-0.5.3}/diffcontext/context/compiler.py +5 -8
- {diffcontext-0.5.2 → diffcontext-0.5.3}/diffcontext/mcp_server.py +33 -9
- {diffcontext-0.5.2 → diffcontext-0.5.3}/diffcontext/pipeline.py +42 -3
- {diffcontext-0.5.2 → diffcontext-0.5.3}/diffcontext.egg-info/PKG-INFO +1 -1
- {diffcontext-0.5.2 → diffcontext-0.5.3}/tests/test_compiler_meta.py +1 -2
- {diffcontext-0.5.2 → diffcontext-0.5.3}/tests/test_core.py +55 -0
- {diffcontext-0.5.2 → diffcontext-0.5.3}/LICENSE +0 -0
- {diffcontext-0.5.2 → diffcontext-0.5.3}/README.md +0 -0
- {diffcontext-0.5.2 → diffcontext-0.5.3}/diffcontext/_warn_once.py +0 -0
- {diffcontext-0.5.2 → diffcontext-0.5.3}/diffcontext/cache.py +0 -0
- {diffcontext-0.5.2 → diffcontext-0.5.3}/diffcontext/context/__init__.py +0 -0
- {diffcontext-0.5.2 → diffcontext-0.5.3}/diffcontext/context/selector.py +0 -0
- {diffcontext-0.5.2 → diffcontext-0.5.3}/diffcontext/diff/__init__.py +0 -0
- {diffcontext-0.5.2 → diffcontext-0.5.3}/diffcontext/diff/git_diff.py +0 -0
- {diffcontext-0.5.2 → diffcontext-0.5.3}/diffcontext/diff/state_manager.py +0 -0
- {diffcontext-0.5.2 → diffcontext-0.5.3}/diffcontext/graph_builder.py +0 -0
- {diffcontext-0.5.2 → diffcontext-0.5.3}/diffcontext/history.py +0 -0
- {diffcontext-0.5.2 → diffcontext-0.5.3}/diffcontext/impact/__init__.py +0 -0
- {diffcontext-0.5.2 → diffcontext-0.5.3}/diffcontext/impact/blast_radius.py +0 -0
- {diffcontext-0.5.2 → diffcontext-0.5.3}/diffcontext/impact/scoring.py +0 -0
- {diffcontext-0.5.2 → diffcontext-0.5.3}/diffcontext/impact/traversal.py +0 -0
- {diffcontext-0.5.2 → diffcontext-0.5.3}/diffcontext/impact/visualizer.py +0 -0
- {diffcontext-0.5.2 → diffcontext-0.5.3}/diffcontext/languages/__init__.py +0 -0
- {diffcontext-0.5.2 → diffcontext-0.5.3}/diffcontext/languages/typescript.py +0 -0
- {diffcontext-0.5.2 → diffcontext-0.5.3}/diffcontext/lexical.py +0 -0
- {diffcontext-0.5.2 → diffcontext-0.5.3}/diffcontext/models.py +0 -0
- {diffcontext-0.5.2 → diffcontext-0.5.3}/diffcontext/parser.py +0 -0
- {diffcontext-0.5.2 → diffcontext-0.5.3}/diffcontext/py.typed +0 -0
- {diffcontext-0.5.2 → diffcontext-0.5.3}/diffcontext/rerank/__init__.py +0 -0
- {diffcontext-0.5.2 → diffcontext-0.5.3}/diffcontext/rerank/features.py +0 -0
- {diffcontext-0.5.2 → diffcontext-0.5.3}/diffcontext/rerank/model.py +0 -0
- {diffcontext-0.5.2 → diffcontext-0.5.3}/diffcontext/resolver.py +0 -0
- {diffcontext-0.5.2 → diffcontext-0.5.3}/diffcontext/scanner.py +0 -0
- {diffcontext-0.5.2 → diffcontext-0.5.3}/diffcontext/symbols.py +0 -0
- {diffcontext-0.5.2 → diffcontext-0.5.3}/diffcontext/verify/__init__.py +0 -0
- {diffcontext-0.5.2 → diffcontext-0.5.3}/diffcontext/verify/cases.py +0 -0
- {diffcontext-0.5.2 → diffcontext-0.5.3}/diffcontext/verify/history.py +0 -0
- {diffcontext-0.5.2 → diffcontext-0.5.3}/diffcontext/verify/sufficiency.py +0 -0
- {diffcontext-0.5.2 → diffcontext-0.5.3}/diffcontext.egg-info/SOURCES.txt +0 -0
- {diffcontext-0.5.2 → diffcontext-0.5.3}/diffcontext.egg-info/dependency_links.txt +0 -0
- {diffcontext-0.5.2 → diffcontext-0.5.3}/diffcontext.egg-info/entry_points.txt +0 -0
- {diffcontext-0.5.2 → diffcontext-0.5.3}/diffcontext.egg-info/requires.txt +0 -0
- {diffcontext-0.5.2 → diffcontext-0.5.3}/diffcontext.egg-info/top_level.txt +0 -0
- {diffcontext-0.5.2 → diffcontext-0.5.3}/pyproject.toml +0 -0
- {diffcontext-0.5.2 → diffcontext-0.5.3}/setup.cfg +0 -0
- {diffcontext-0.5.2 → diffcontext-0.5.3}/tests/test_cache.py +0 -0
- {diffcontext-0.5.2 → diffcontext-0.5.3}/tests/test_cutoff.py +0 -0
- {diffcontext-0.5.2 → diffcontext-0.5.3}/tests/test_determinism.py +0 -0
- {diffcontext-0.5.2 → diffcontext-0.5.3}/tests/test_downstream_eval.py +0 -0
- {diffcontext-0.5.2 → diffcontext-0.5.3}/tests/test_git_diff.py +0 -0
- {diffcontext-0.5.2 → diffcontext-0.5.3}/tests/test_harness_api.py +0 -0
- {diffcontext-0.5.2 → diffcontext-0.5.3}/tests/test_history.py +0 -0
- {diffcontext-0.5.2 → diffcontext-0.5.3}/tests/test_incremental.py +0 -0
- {diffcontext-0.5.2 → diffcontext-0.5.3}/tests/test_index_scoping.py +0 -0
- {diffcontext-0.5.2 → diffcontext-0.5.3}/tests/test_lexical.py +0 -0
- {diffcontext-0.5.2 → diffcontext-0.5.3}/tests/test_mcp_server.py +0 -0
- {diffcontext-0.5.2 → diffcontext-0.5.3}/tests/test_rerank.py +0 -0
- {diffcontext-0.5.2 → diffcontext-0.5.3}/tests/test_semantic_ablation.py +0 -0
- {diffcontext-0.5.2 → diffcontext-0.5.3}/tests/test_semantic_audit.py +0 -0
- {diffcontext-0.5.2 → diffcontext-0.5.3}/tests/test_semantic_gap.py +0 -0
- {diffcontext-0.5.2 → diffcontext-0.5.3}/tests/test_semantic_metrics.py +0 -0
- {diffcontext-0.5.2 → diffcontext-0.5.3}/tests/test_semantic_mine.py +0 -0
- {diffcontext-0.5.2 → diffcontext-0.5.3}/tests/test_service_clone.py +0 -0
- {diffcontext-0.5.2 → diffcontext-0.5.3}/tests/test_src_layout.py +0 -0
- {diffcontext-0.5.2 → diffcontext-0.5.3}/tests/test_stats.py +0 -0
- {diffcontext-0.5.2 → diffcontext-0.5.3}/tests/test_token_budget.py +0 -0
- {diffcontext-0.5.2 → diffcontext-0.5.3}/tests/test_typescript_adapter.py +0 -0
- {diffcontext-0.5.2 → diffcontext-0.5.3}/tests/test_verify.py +0 -0
- {diffcontext-0.5.2 → diffcontext-0.5.3}/tests/test_version.py +0 -0
|
@@ -32,7 +32,7 @@ Usage as a library:
|
|
|
32
32
|
print(ctx.reduction_pct) # how much code was filtered out
|
|
33
33
|
"""
|
|
34
34
|
|
|
35
|
-
__version__ = "0.5.
|
|
35
|
+
__version__ = "0.5.3"
|
|
36
36
|
|
|
37
37
|
# Public, semver-covered API. Everything not listed here (graph_builder,
|
|
38
38
|
# resolver, symbols, scanner, parser internals) is importable but carries no
|
|
@@ -23,6 +23,7 @@ import time
|
|
|
23
23
|
from ..pipeline import index_repository, analyze_impact, compile, warn_unknown_symbols
|
|
24
24
|
from ..diff.git_diff import find_changed_symbols
|
|
25
25
|
from ..impact.visualizer import render_blast_radius, render_verification
|
|
26
|
+
from .. import __version__ as DC_VERSION
|
|
26
27
|
|
|
27
28
|
|
|
28
29
|
def main():
|
|
@@ -37,6 +38,7 @@ def main():
|
|
|
37
38
|
prog="diffcontext",
|
|
38
39
|
description="Static-analysis-powered repository context compiler for LLMs",
|
|
39
40
|
)
|
|
41
|
+
parser.add_argument("--version", action="version", version=f"diffcontext {DC_VERSION}")
|
|
40
42
|
sub = parser.add_subparsers(dest="command", help="Available commands")
|
|
41
43
|
|
|
42
44
|
# --- index ---
|
|
@@ -113,14 +115,20 @@ def main():
|
|
|
113
115
|
"reaches related files with no call or lexical connection)",
|
|
114
116
|
)
|
|
115
117
|
p_compile.add_argument("--notes", type=str, default=None, help="Developer notes to prepend to the context output")
|
|
118
|
+
p_compile.add_argument(
|
|
119
|
+
"--query-text", type=str, default=None,
|
|
120
|
+
help="Bug report or issue text. Biases retrieval toward symbols "
|
|
121
|
+
"semantically related to the described problem — the one "
|
|
122
|
+
"signal the graph alone can't provide.",
|
|
123
|
+
)
|
|
116
124
|
p_compile.add_argument(
|
|
117
125
|
"--meta", choices=["full", "compact", "off"], default="full",
|
|
118
126
|
help=(
|
|
119
127
|
"Disclosure-header level. 'full' (default): counts, architecture "
|
|
120
128
|
"snapshot, dropped manifest, graph confidence, warnings. 'compact': "
|
|
121
|
-
"counts + dropped top-3 + warnings only (~60%% smaller
|
|
122
|
-
"
|
|
123
|
-
"
|
|
129
|
+
"counts + dropped top-3 + warnings only (~60%% smaller). 'off': no "
|
|
130
|
+
"meta-header, just code sections. The pass@1 effect of meta level "
|
|
131
|
+
"is UNMEASURED."
|
|
124
132
|
),
|
|
125
133
|
)
|
|
126
134
|
p_compile.add_argument("--json", action="store_true", help="Output as JSON")
|
|
@@ -384,9 +392,11 @@ def _cmd_compile(args):
|
|
|
384
392
|
from ..history import CoChangeIndex
|
|
385
393
|
history = CoChangeIndex(args.repo)
|
|
386
394
|
|
|
395
|
+
query_weight = 0.3 if args.query_text else 0.0
|
|
387
396
|
impact = analyze_impact(
|
|
388
397
|
idx, changed, max_depth=args.depth, hybrid=not args.graph_only,
|
|
389
398
|
history=history,
|
|
399
|
+
query_text=args.query_text, query_weight=query_weight,
|
|
390
400
|
)
|
|
391
401
|
max_tokens = args.max_tokens if args.max_tokens > 0 else None
|
|
392
402
|
top_k = args.top_k * len(changed) if args.top_k > 0 else None
|
|
@@ -167,11 +167,9 @@ def compile_context(
|
|
|
167
167
|
compacted so meta can't dwarf the code it annotates.
|
|
168
168
|
meta: Disclosure level: "full" (default — counts, architecture
|
|
169
169
|
snapshot, dropped manifest, graph confidence, warnings),
|
|
170
|
-
"compact" (counts + dropped top-3 only — ~60% smaller
|
|
171
|
-
"off" (no meta-header
|
|
172
|
-
|
|
173
|
-
tokens by displacing code; "compact" is the measured
|
|
174
|
-
middle ground.
|
|
170
|
+
"compact" (counts + dropped top-3 only — ~60% smaller,
|
|
171
|
+
keeps warnings), "off" (no meta-header, just code). The
|
|
172
|
+
pass@1 effect of meta level is UNMEASURED.
|
|
175
173
|
"""
|
|
176
174
|
dropped_ids = dropped_ids or []
|
|
177
175
|
skipped_files = skipped_files or []
|
|
@@ -421,9 +419,8 @@ def _build_meta_header(
|
|
|
421
419
|
|
|
422
420
|
# --- Repository Architecture Snapshot ---
|
|
423
421
|
# Skipped entirely in compact mode — it's the largest meta component
|
|
424
|
-
# (per-module listing + docstrings)
|
|
425
|
-
#
|
|
426
|
-
# top-3 only.
|
|
422
|
+
# (per-module listing + docstrings). Compact keeps counts + dropped
|
|
423
|
+
# top-3 only. The pass@1 effect of meta level is UNMEASURED.
|
|
427
424
|
if not compact:
|
|
428
425
|
# Build rel_file -> absolute_path mapping from symbol table.
|
|
429
426
|
# sym_id gives us relative path; sym.file gives us the absolute path we
|
|
@@ -68,6 +68,8 @@ def main():
|
|
|
68
68
|
args = parser.parse_args()
|
|
69
69
|
default_repo = os.path.abspath(args.repo)
|
|
70
70
|
|
|
71
|
+
from diffcontext import __version__ as dc_version
|
|
72
|
+
|
|
71
73
|
server = MCPServer(
|
|
72
74
|
name="diffcontext",
|
|
73
75
|
description=(
|
|
@@ -75,7 +77,7 @@ def main():
|
|
|
75
77
|
"the code that matters for a change and packs it into a token "
|
|
76
78
|
"budget. Measures whether it works on your repo."
|
|
77
79
|
),
|
|
78
|
-
version=
|
|
80
|
+
version=dc_version,
|
|
79
81
|
)
|
|
80
82
|
|
|
81
83
|
@server.tool()
|
|
@@ -83,8 +85,9 @@ def main():
|
|
|
83
85
|
repo_path: str = "",
|
|
84
86
|
changed_symbols: Optional[List[str]] = None,
|
|
85
87
|
git_ref: Optional[str] = None,
|
|
88
|
+
task_description: Optional[str] = None,
|
|
86
89
|
max_tokens: int = 8000,
|
|
87
|
-
meta: str = "
|
|
90
|
+
meta: str = "full",
|
|
88
91
|
) -> str:
|
|
89
92
|
"""Compile LLM-ready context for a change.
|
|
90
93
|
|
|
@@ -93,6 +96,10 @@ def main():
|
|
|
93
96
|
related functions the model needs to make the change safely — packed
|
|
94
97
|
into max_tokens with a disclosure header showing what was dropped.
|
|
95
98
|
|
|
99
|
+
Optionally pass task_description (the bug report or issue text) to
|
|
100
|
+
bias retrieval toward symbols relevant to the described problem —
|
|
101
|
+
the one signal the graph alone can't provide.
|
|
102
|
+
|
|
96
103
|
Args:
|
|
97
104
|
repo_path: Absolute path to the repository. If omitted, uses
|
|
98
105
|
the --repo from server startup.
|
|
@@ -100,19 +107,36 @@ def main():
|
|
|
100
107
|
["./src/auth.py:validate_jwt"]). Mutually exclusive with
|
|
101
108
|
git_ref.
|
|
102
109
|
git_ref: Git ref to detect changes from (e.g. "HEAD~1").
|
|
103
|
-
Mutually exclusive with changed_symbols.
|
|
110
|
+
Mutually exclusive with changed_symbols. When only
|
|
111
|
+
task_description is given (no changed_symbols or git_ref),
|
|
112
|
+
defaults to "HEAD".
|
|
113
|
+
task_description: The bug report or issue text. Biases retrieval
|
|
114
|
+
toward symbols semantically related to the described problem,
|
|
115
|
+
not just structurally near the changed symbols.
|
|
104
116
|
max_tokens: Token budget for the context (default 8000).
|
|
105
|
-
meta: Disclosure header level: "full", "compact"
|
|
106
|
-
|
|
117
|
+
meta: Disclosure header level: "full" (default), "compact", or "off".
|
|
118
|
+
The pass@1 effect of meta level is UNMEASURED.
|
|
107
119
|
"""
|
|
108
120
|
from diffcontext.pipeline import analyze_impact, compile
|
|
109
121
|
|
|
110
122
|
repo = repo_path or default_repo
|
|
111
123
|
idx = _get_index(repo)
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
124
|
+
|
|
125
|
+
# When only task_description is given, auto-detect changes from HEAD.
|
|
126
|
+
effective_ref = git_ref
|
|
127
|
+
if not changed_symbols and not git_ref and task_description:
|
|
128
|
+
effective_ref = "HEAD"
|
|
129
|
+
|
|
130
|
+
changed = _resolve_changed(idx, changed_symbols, effective_ref, repo)
|
|
131
|
+
if not changed and not task_description:
|
|
132
|
+
return "No changed symbols found. Pass changed_symbols, git_ref, or task_description."
|
|
133
|
+
|
|
134
|
+
# query_weight > 0 only when task_description is provided — the
|
|
135
|
+
# signal is opt-in and has no effect when absent (backwards compat).
|
|
136
|
+
query_weight = 0.3 if task_description else 0.0
|
|
137
|
+
impact = analyze_impact(
|
|
138
|
+
idx, changed, query_text=task_description, query_weight=query_weight,
|
|
139
|
+
)
|
|
116
140
|
ctx = compile(idx, impact, max_tokens=max_tokens, meta=meta)
|
|
117
141
|
return ctx.text
|
|
118
142
|
|
|
@@ -585,6 +585,8 @@ def _blend_hybrid(
|
|
|
585
585
|
adaptive: bool = True,
|
|
586
586
|
history_scores: Optional[Dict[str, float]] = None,
|
|
587
587
|
history_weight: float = 0.15,
|
|
588
|
+
query_text: Optional[str] = None,
|
|
589
|
+
query_weight: float = 0.0,
|
|
588
590
|
) -> Dict[str, float]:
|
|
589
591
|
"""
|
|
590
592
|
Blend graph impact scores with BM25 and same-file signals.
|
|
@@ -605,6 +607,16 @@ def _blend_hybrid(
|
|
|
605
607
|
file that historically co-changed with the changed files gets
|
|
606
608
|
`history_weight * association` added — the only signal that can reach
|
|
607
609
|
co-change partners with no structural or lexical connection at all.
|
|
610
|
+
|
|
611
|
+
`query_text` (the bug report / issue text / task description) is an
|
|
612
|
+
optional fifth signal: BM25 scores the query text against every
|
|
613
|
+
symbol's source code, and `query_weight * normalized_score` is added
|
|
614
|
+
to each candidate. This is the one signal that ranks by relevance to
|
|
615
|
+
the *described problem* rather than structural nearness to the changed
|
|
616
|
+
symbols — the "thematic siblings" blind spot the graph alone cannot
|
|
617
|
+
address. Default query_weight=0.0 = no effect (backwards compatible).
|
|
618
|
+
When query_weight > 0, symbols matching the problem description get a
|
|
619
|
+
boost even with no call-graph or same-file connection.
|
|
608
620
|
"""
|
|
609
621
|
from .lexical import get_lexical_index
|
|
610
622
|
|
|
@@ -630,6 +642,18 @@ def _blend_hybrid(
|
|
|
630
642
|
lex_raw[sid] = sc
|
|
631
643
|
lex_norm = _normalize_scores(lex_raw)
|
|
632
644
|
|
|
645
|
+
# Query-text BM25: rank symbols by relevance to the described problem.
|
|
646
|
+
# This is the one signal that doesn't derive from the changed symbols
|
|
647
|
+
# at all — it reaches symbols semantically related to the bug report
|
|
648
|
+
# even with no structural or lexical connection to the changed code.
|
|
649
|
+
query_norm: Dict[str, float] = {}
|
|
650
|
+
if query_text and query_weight > 0:
|
|
651
|
+
query_raw: Dict[str, float] = {}
|
|
652
|
+
for sid, sc in lexical_index.scores_for(query_text).items():
|
|
653
|
+
if sid not in changed_set and sc > query_raw.get(sid, 0.0):
|
|
654
|
+
query_raw[sid] = sc
|
|
655
|
+
query_norm = _normalize_scores(query_raw)
|
|
656
|
+
|
|
633
657
|
changed_files = {s.split(":")[0] for s in changed_in_index}
|
|
634
658
|
|
|
635
659
|
history_files = {
|
|
@@ -647,6 +671,8 @@ def _blend_hybrid(
|
|
|
647
671
|
sid for sid in index.symbols
|
|
648
672
|
if sid.split(":")[0] in history_files and sid not in changed_set
|
|
649
673
|
)
|
|
674
|
+
if query_norm:
|
|
675
|
+
candidates.update(query_norm.keys())
|
|
650
676
|
for sid in candidates:
|
|
651
677
|
score = w_graph * graph_norm.get(sid, 0.0) + w_lex * lex_norm.get(sid, 0.0)
|
|
652
678
|
sid_file = sid.split(":")[0]
|
|
@@ -654,6 +680,8 @@ def _blend_hybrid(
|
|
|
654
680
|
score += w_file
|
|
655
681
|
if history_scores:
|
|
656
682
|
score += history_weight * history_scores.get(sid_file, 0.0)
|
|
683
|
+
if query_norm:
|
|
684
|
+
score += query_weight * query_norm.get(sid, 0.0)
|
|
657
685
|
blended[sid] = 100.0 * score
|
|
658
686
|
|
|
659
687
|
# Changed symbols keep their unblended score so they stay ranked on top.
|
|
@@ -712,6 +740,8 @@ def analyze_impact(
|
|
|
712
740
|
hybrid: bool = True,
|
|
713
741
|
adaptive: bool = True,
|
|
714
742
|
history: Optional[object] = None,
|
|
743
|
+
query_text: Optional[str] = None,
|
|
744
|
+
query_weight: float = 0.0,
|
|
715
745
|
) -> ImpactResult:
|
|
716
746
|
"""
|
|
717
747
|
Phase 2: Given changed symbols, compute blast radius and impact scores.
|
|
@@ -729,6 +759,16 @@ def analyze_impact(
|
|
|
729
759
|
co-change association is blended as a fourth signal — the only
|
|
730
760
|
signal that can reach co-change partners with no structural or
|
|
731
761
|
lexical connection (the measured cross-subsystem ceiling).
|
|
762
|
+
query_text: the bug report / issue text / task description. When given
|
|
763
|
+
with query_weight > 0, BM25 scores the query text against every
|
|
764
|
+
symbol's source code and adds query_weight * normalized_score to
|
|
765
|
+
each candidate. This is the one signal that ranks by relevance to
|
|
766
|
+
the *described problem* rather than structural nearness — the
|
|
767
|
+
"thematic siblings" blind spot. Default query_weight=0.0 = no
|
|
768
|
+
effect (backwards compatible).
|
|
769
|
+
query_weight: weight for the query_text signal (default 0.0 = off).
|
|
770
|
+
When > 0, symbols matching the problem description get a boost
|
|
771
|
+
even with no call-graph or same-file connection.
|
|
732
772
|
|
|
733
773
|
Fix: expanded_deps is now passed into compute_impact_scores so those
|
|
734
774
|
nodes are actually scored. Previously they were computed and discarded.
|
|
@@ -774,6 +814,7 @@ def analyze_impact(
|
|
|
774
814
|
scores = _blend_hybrid(
|
|
775
815
|
index, changed_symbols, scores,
|
|
776
816
|
adaptive=adaptive, history_scores=history_scores,
|
|
817
|
+
query_text=query_text, query_weight=query_weight,
|
|
777
818
|
)
|
|
778
819
|
|
|
779
820
|
return ImpactResult(
|
|
@@ -836,9 +877,7 @@ def compile(
|
|
|
836
877
|
manifest, graph confidence, warnings), "compact"
|
|
837
878
|
(counts + dropped top-3 only — ~60% smaller, keeps
|
|
838
879
|
warnings), "off" (no meta-header, just code). The
|
|
839
|
-
|
|
840
|
-
tokens by displacing code; compact is the measured
|
|
841
|
-
middle ground.
|
|
880
|
+
pass@1 effect of meta level is UNMEASURED.
|
|
842
881
|
"""
|
|
843
882
|
# Apply dependency-type boost BEFORE selection (the key experiment).
|
|
844
883
|
# Boosts direct callees, callers, and siblings of changed symbols so
|
|
@@ -210,8 +210,7 @@ class TestDocstringCache:
|
|
|
210
210
|
|
|
211
211
|
class TestMetaLevels:
|
|
212
212
|
"""The --meta full|compact|off flag controls the disclosure header's
|
|
213
|
-
budget cost. The
|
|
214
|
-
tokens by displacing code; compact is the measured middle ground."""
|
|
213
|
+
budget cost. The pass@1 effect of meta level is UNMEASURED."""
|
|
215
214
|
|
|
216
215
|
def _make_pkg(self, meta=None):
|
|
217
216
|
symbols = _make_symbols()
|
|
@@ -305,5 +305,60 @@ class TestPipeline:
|
|
|
305
305
|
assert ctx.token_estimate > 0
|
|
306
306
|
|
|
307
307
|
|
|
308
|
+
class TestQueryText:
|
|
309
|
+
"""The query_text signal: BM25 scores the bug report / issue text
|
|
310
|
+
against every symbol's source code, adding a relevance-to-the-problem
|
|
311
|
+
signal the graph alone can't provide."""
|
|
312
|
+
|
|
313
|
+
def test_query_text_adds_candidates(self):
|
|
314
|
+
_require_fixture(MEDIUM, "medium_repo")
|
|
315
|
+
idx = index_repository(MEDIUM)
|
|
316
|
+
sym_id = "./service.py:onboard_user"
|
|
317
|
+
assert sym_id in idx.graph
|
|
318
|
+
|
|
319
|
+
# Without query_text: baseline scores
|
|
320
|
+
impact_base = analyze_impact(idx, [sym_id])
|
|
321
|
+
base_sids = set(impact_base.scores)
|
|
322
|
+
|
|
323
|
+
# With query_text: should surface additional candidates whose code
|
|
324
|
+
# matches the described problem but may have no call-graph connection.
|
|
325
|
+
# Use a query that matches symbols in the medium repo.
|
|
326
|
+
impact_q = analyze_impact(
|
|
327
|
+
idx, [sym_id], query_text="create user order onboard", query_weight=0.3,
|
|
328
|
+
)
|
|
329
|
+
query_sids = set(impact_q.scores)
|
|
330
|
+
|
|
331
|
+
# Query-text candidates can add symbols the graph didn't reach.
|
|
332
|
+
# At minimum, the query should not REMOVE any baseline candidates.
|
|
333
|
+
assert base_sids.issubset(query_sids), (
|
|
334
|
+
"query_text must not remove baseline candidates, only add them"
|
|
335
|
+
)
|
|
336
|
+
|
|
337
|
+
def test_query_weight_zero_is_noop(self):
|
|
338
|
+
_require_fixture(MEDIUM, "medium_repo")
|
|
339
|
+
idx = index_repository(MEDIUM)
|
|
340
|
+
sym_id = "./service.py:onboard_user"
|
|
341
|
+
|
|
342
|
+
# query_weight=0.0 = no effect (backwards compatible)
|
|
343
|
+
impact_off = analyze_impact(idx, [sym_id], query_text="anything", query_weight=0.0)
|
|
344
|
+
impact_none = analyze_impact(idx, [sym_id])
|
|
345
|
+
# Same scores (query_text with weight 0 is a no-op)
|
|
346
|
+
assert impact_off.scores == impact_none.scores, (
|
|
347
|
+
"query_weight=0.0 must be a no-op"
|
|
348
|
+
)
|
|
349
|
+
|
|
350
|
+
def test_no_query_text_is_noop(self):
|
|
351
|
+
_require_fixture(MEDIUM, "medium_repo")
|
|
352
|
+
idx = index_repository(MEDIUM)
|
|
353
|
+
sym_id = "./service.py:onboard_user"
|
|
354
|
+
|
|
355
|
+
# No query_text = no effect even if query_weight > 0
|
|
356
|
+
impact_off = analyze_impact(idx, [sym_id], query_weight=0.3)
|
|
357
|
+
impact_none = analyze_impact(idx, [sym_id])
|
|
358
|
+
assert impact_off.scores == impact_none.scores, (
|
|
359
|
+
"no query_text with query_weight > 0 must be a no-op"
|
|
360
|
+
)
|
|
361
|
+
|
|
362
|
+
|
|
308
363
|
if __name__ == "__main__":
|
|
309
364
|
pytest.main([__file__, "-v"])
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|