diffcontext 0.5.3__tar.gz → 0.5.4__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.
Files changed (72) hide show
  1. {diffcontext-0.5.3 → diffcontext-0.5.4}/PKG-INFO +1 -1
  2. {diffcontext-0.5.3 → diffcontext-0.5.4}/diffcontext/__init__.py +1 -1
  3. {diffcontext-0.5.3 → diffcontext-0.5.4}/diffcontext/cli/__init__.py +5 -0
  4. {diffcontext-0.5.3 → diffcontext-0.5.4}/diffcontext/mcp_server.py +4 -2
  5. {diffcontext-0.5.3 → diffcontext-0.5.4}/diffcontext.egg-info/PKG-INFO +1 -1
  6. {diffcontext-0.5.3 → diffcontext-0.5.4}/tests/test_core.py +59 -34
  7. {diffcontext-0.5.3 → diffcontext-0.5.4}/LICENSE +0 -0
  8. {diffcontext-0.5.3 → diffcontext-0.5.4}/README.md +0 -0
  9. {diffcontext-0.5.3 → diffcontext-0.5.4}/diffcontext/_warn_once.py +0 -0
  10. {diffcontext-0.5.3 → diffcontext-0.5.4}/diffcontext/cache.py +0 -0
  11. {diffcontext-0.5.3 → diffcontext-0.5.4}/diffcontext/context/__init__.py +0 -0
  12. {diffcontext-0.5.3 → diffcontext-0.5.4}/diffcontext/context/compiler.py +0 -0
  13. {diffcontext-0.5.3 → diffcontext-0.5.4}/diffcontext/context/selector.py +0 -0
  14. {diffcontext-0.5.3 → diffcontext-0.5.4}/diffcontext/diff/__init__.py +0 -0
  15. {diffcontext-0.5.3 → diffcontext-0.5.4}/diffcontext/diff/git_diff.py +0 -0
  16. {diffcontext-0.5.3 → diffcontext-0.5.4}/diffcontext/diff/state_manager.py +0 -0
  17. {diffcontext-0.5.3 → diffcontext-0.5.4}/diffcontext/graph_builder.py +0 -0
  18. {diffcontext-0.5.3 → diffcontext-0.5.4}/diffcontext/history.py +0 -0
  19. {diffcontext-0.5.3 → diffcontext-0.5.4}/diffcontext/impact/__init__.py +0 -0
  20. {diffcontext-0.5.3 → diffcontext-0.5.4}/diffcontext/impact/blast_radius.py +0 -0
  21. {diffcontext-0.5.3 → diffcontext-0.5.4}/diffcontext/impact/scoring.py +0 -0
  22. {diffcontext-0.5.3 → diffcontext-0.5.4}/diffcontext/impact/traversal.py +0 -0
  23. {diffcontext-0.5.3 → diffcontext-0.5.4}/diffcontext/impact/visualizer.py +0 -0
  24. {diffcontext-0.5.3 → diffcontext-0.5.4}/diffcontext/languages/__init__.py +0 -0
  25. {diffcontext-0.5.3 → diffcontext-0.5.4}/diffcontext/languages/typescript.py +0 -0
  26. {diffcontext-0.5.3 → diffcontext-0.5.4}/diffcontext/lexical.py +0 -0
  27. {diffcontext-0.5.3 → diffcontext-0.5.4}/diffcontext/models.py +0 -0
  28. {diffcontext-0.5.3 → diffcontext-0.5.4}/diffcontext/parser.py +0 -0
  29. {diffcontext-0.5.3 → diffcontext-0.5.4}/diffcontext/pipeline.py +0 -0
  30. {diffcontext-0.5.3 → diffcontext-0.5.4}/diffcontext/py.typed +0 -0
  31. {diffcontext-0.5.3 → diffcontext-0.5.4}/diffcontext/rerank/__init__.py +0 -0
  32. {diffcontext-0.5.3 → diffcontext-0.5.4}/diffcontext/rerank/features.py +0 -0
  33. {diffcontext-0.5.3 → diffcontext-0.5.4}/diffcontext/rerank/model.py +0 -0
  34. {diffcontext-0.5.3 → diffcontext-0.5.4}/diffcontext/resolver.py +0 -0
  35. {diffcontext-0.5.3 → diffcontext-0.5.4}/diffcontext/scanner.py +0 -0
  36. {diffcontext-0.5.3 → diffcontext-0.5.4}/diffcontext/symbols.py +0 -0
  37. {diffcontext-0.5.3 → diffcontext-0.5.4}/diffcontext/verify/__init__.py +0 -0
  38. {diffcontext-0.5.3 → diffcontext-0.5.4}/diffcontext/verify/cases.py +0 -0
  39. {diffcontext-0.5.3 → diffcontext-0.5.4}/diffcontext/verify/history.py +0 -0
  40. {diffcontext-0.5.3 → diffcontext-0.5.4}/diffcontext/verify/sufficiency.py +0 -0
  41. {diffcontext-0.5.3 → diffcontext-0.5.4}/diffcontext.egg-info/SOURCES.txt +0 -0
  42. {diffcontext-0.5.3 → diffcontext-0.5.4}/diffcontext.egg-info/dependency_links.txt +0 -0
  43. {diffcontext-0.5.3 → diffcontext-0.5.4}/diffcontext.egg-info/entry_points.txt +0 -0
  44. {diffcontext-0.5.3 → diffcontext-0.5.4}/diffcontext.egg-info/requires.txt +0 -0
  45. {diffcontext-0.5.3 → diffcontext-0.5.4}/diffcontext.egg-info/top_level.txt +0 -0
  46. {diffcontext-0.5.3 → diffcontext-0.5.4}/pyproject.toml +0 -0
  47. {diffcontext-0.5.3 → diffcontext-0.5.4}/setup.cfg +0 -0
  48. {diffcontext-0.5.3 → diffcontext-0.5.4}/tests/test_cache.py +0 -0
  49. {diffcontext-0.5.3 → diffcontext-0.5.4}/tests/test_compiler_meta.py +0 -0
  50. {diffcontext-0.5.3 → diffcontext-0.5.4}/tests/test_cutoff.py +0 -0
  51. {diffcontext-0.5.3 → diffcontext-0.5.4}/tests/test_determinism.py +0 -0
  52. {diffcontext-0.5.3 → diffcontext-0.5.4}/tests/test_downstream_eval.py +0 -0
  53. {diffcontext-0.5.3 → diffcontext-0.5.4}/tests/test_git_diff.py +0 -0
  54. {diffcontext-0.5.3 → diffcontext-0.5.4}/tests/test_harness_api.py +0 -0
  55. {diffcontext-0.5.3 → diffcontext-0.5.4}/tests/test_history.py +0 -0
  56. {diffcontext-0.5.3 → diffcontext-0.5.4}/tests/test_incremental.py +0 -0
  57. {diffcontext-0.5.3 → diffcontext-0.5.4}/tests/test_index_scoping.py +0 -0
  58. {diffcontext-0.5.3 → diffcontext-0.5.4}/tests/test_lexical.py +0 -0
  59. {diffcontext-0.5.3 → diffcontext-0.5.4}/tests/test_mcp_server.py +0 -0
  60. {diffcontext-0.5.3 → diffcontext-0.5.4}/tests/test_rerank.py +0 -0
  61. {diffcontext-0.5.3 → diffcontext-0.5.4}/tests/test_semantic_ablation.py +0 -0
  62. {diffcontext-0.5.3 → diffcontext-0.5.4}/tests/test_semantic_audit.py +0 -0
  63. {diffcontext-0.5.3 → diffcontext-0.5.4}/tests/test_semantic_gap.py +0 -0
  64. {diffcontext-0.5.3 → diffcontext-0.5.4}/tests/test_semantic_metrics.py +0 -0
  65. {diffcontext-0.5.3 → diffcontext-0.5.4}/tests/test_semantic_mine.py +0 -0
  66. {diffcontext-0.5.3 → diffcontext-0.5.4}/tests/test_service_clone.py +0 -0
  67. {diffcontext-0.5.3 → diffcontext-0.5.4}/tests/test_src_layout.py +0 -0
  68. {diffcontext-0.5.3 → diffcontext-0.5.4}/tests/test_stats.py +0 -0
  69. {diffcontext-0.5.3 → diffcontext-0.5.4}/tests/test_token_budget.py +0 -0
  70. {diffcontext-0.5.3 → diffcontext-0.5.4}/tests/test_typescript_adapter.py +0 -0
  71. {diffcontext-0.5.3 → diffcontext-0.5.4}/tests/test_verify.py +0 -0
  72. {diffcontext-0.5.3 → diffcontext-0.5.4}/tests/test_version.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: diffcontext
3
- Version: 0.5.3
3
+ Version: 0.5.4
4
4
  Summary: Static-analysis-powered repository context compiler for LLMs
5
5
  Author-email: Trakshan Mishra <trakshanmishra477@gmail.com>
6
6
  License: MIT
@@ -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.3"
35
+ __version__ = "0.5.4"
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
@@ -392,6 +392,11 @@ def _cmd_compile(args):
392
392
  from ..history import CoChangeIndex
393
393
  history = CoChangeIndex(args.repo)
394
394
 
395
+ # Untuned default; not derived from any sweep. The query_weight controls
396
+ # how much the problem-description signal moves candidates relative to
397
+ # the graph + BM25 + same-file blend. 0.3 is a guess that "matters but
398
+ # doesn't dominate" — it has NOT been benchmarked or optimized. Do not
399
+ # cite this as a measured value; tune it with evidence when data exists.
395
400
  query_weight = 0.3 if args.query_text else 0.0
396
401
  impact = analyze_impact(
397
402
  idx, changed, max_depth=args.depth, hybrid=not args.graph_only,
@@ -131,8 +131,10 @@ def main():
131
131
  if not changed and not task_description:
132
132
  return "No changed symbols found. Pass changed_symbols, git_ref, or task_description."
133
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).
134
+ # Untuned default; not derived from any sweep. The query_weight controls
135
+ # how much the problem-description signal moves candidates relative to
136
+ # the graph + BM25 + same-file blend. 0.3 is a guess that "matters but
137
+ # doesn't dominate" — it has NOT been benchmarked or optimized.
136
138
  query_weight = 0.3 if task_description else 0.0
137
139
  impact = analyze_impact(
138
140
  idx, changed, query_text=task_description, query_weight=query_weight,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: diffcontext
3
- Version: 0.5.3
3
+ Version: 0.5.4
4
4
  Summary: Static-analysis-powered repository context compiler for LLMs
5
5
  Author-email: Trakshan Mishra <trakshanmishra477@gmail.com>
6
6
  License: MIT
@@ -308,55 +308,80 @@ class TestPipeline:
308
308
  class TestQueryText:
309
309
  """The query_text signal: BM25 scores the bug report / issue text
310
310
  against every symbol's source code, adding a relevance-to-the-problem
311
- signal the graph alone can't provide."""
311
+ signal the graph alone can't provide.
312
312
 
313
- def test_query_text_adds_candidates(self):
313
+ Two requirements the user asked for:
314
+ 1. query_text=None reproduces current ranking byte-identically
315
+ (regression guard — no behavior change when the feature is off).
316
+ 2. A non-None value measurably reorders results (the signal actually
317
+ changes the ranking, not just the scores).
318
+ """
319
+
320
+ def test_none_reproduces_baseline_byte_identically(self):
321
+ """The regression guard: query_text=None must not change ANY score,
322
+ not even by a floating-point epsilon. This is what keeps the feature
323
+ truly opt-in — existing users see zero behavior change."""
314
324
  _require_fixture(MEDIUM, "medium_repo")
315
325
  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)
326
+ seed = "./service.py:onboard_user"
322
327
 
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
+ baseline = analyze_impact(idx, [seed])
329
+ with_query_kwarg = analyze_impact(
330
+ idx, [seed], query_text=None, query_weight=0.0,
328
331
  )
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"
332
+ # Every score must be identical — not just the set of candidates,
333
+ # but the actual float values. query_text=None is a true no-op.
334
+ assert baseline.scores == with_query_kwarg.scores, (
335
+ "query_text=None must reproduce baseline scores exactly; "
336
+ "the feature must be a true no-op when disabled."
335
337
  )
336
338
 
337
- def test_query_weight_zero_is_noop(self):
339
+ def test_query_text_reorders_results(self):
340
+ """A non-None query_text must measurably change the ranking — not
341
+ just add candidates, but move at least one existing symbol to a
342
+ different rank position. This proves the signal is doing work."""
338
343
  _require_fixture(MEDIUM, "medium_repo")
339
344
  idx = index_repository(MEDIUM)
340
- sym_id = "./service.py:onboard_user"
345
+ seed = "./service.py:onboard_user"
341
346
 
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"
347
+ baseline = analyze_impact(idx, [seed])
348
+ base_ranked = sorted(baseline.scores.items(), key=lambda x: -x[1])
349
+ base_order = [sid for sid, _ in base_ranked]
350
+
351
+ # "email validation check" should boost is_valid_email (whose code
352
+ # contains "email" and "valid") above User.__init__ (which doesn't).
353
+ impacted = analyze_impact(
354
+ idx, [seed], query_text="email validation check", query_weight=0.3,
355
+ )
356
+ q_ranked = sorted(impacted.scores.items(), key=lambda x: -x[1])
357
+ q_order = [sid for sid, _ in q_ranked]
358
+
359
+ assert base_order != q_order, (
360
+ "query_text must reorder at least one symbol; if the ranking "
361
+ "is identical the signal has no effect."
362
+ )
363
+ # Specifically: is_valid_email should rank higher with the query
364
+ # "email validation check" than without it.
365
+ base_email_rank = base_order.index("./validators.py:is_valid_email")
366
+ q_email_rank = q_order.index("./validators.py:is_valid_email")
367
+ assert q_email_rank < base_email_rank, (
368
+ f"is_valid_email should rank HIGHER with query_text='email "
369
+ f"validation check' (baseline rank {base_email_rank} -> query "
370
+ f"rank {q_email_rank}), not lower or unchanged."
348
371
  )
349
372
 
350
- def test_no_query_text_is_noop(self):
373
+ def test_query_weight_zero_is_noop(self):
374
+ """query_weight=0.0 must be a no-op even with non-None query_text."""
351
375
  _require_fixture(MEDIUM, "medium_repo")
352
376
  idx = index_repository(MEDIUM)
353
- sym_id = "./service.py:onboard_user"
377
+ seed = "./service.py:onboard_user"
354
378
 
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"
379
+ baseline = analyze_impact(idx, [seed])
380
+ with_weight_zero = analyze_impact(
381
+ idx, [seed], query_text="email validation", query_weight=0.0,
382
+ )
383
+ assert baseline.scores == with_weight_zero.scores, (
384
+ "query_weight=0.0 must be a no-op even with non-None query_text."
360
385
  )
361
386
 
362
387
 
File without changes
File without changes
File without changes
File without changes