rag-your-code 1.2.0__tar.gz → 1.3.0__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 (51) hide show
  1. {rag_your_code-1.2.0/src/rag_your_code.egg-info → rag_your_code-1.3.0}/PKG-INFO +87 -35
  2. {rag_your_code-1.2.0 → rag_your_code-1.3.0}/README.md +86 -34
  3. {rag_your_code-1.2.0 → rag_your_code-1.3.0}/pyproject.toml +1 -1
  4. {rag_your_code-1.2.0 → rag_your_code-1.3.0/src/rag_your_code.egg-info}/PKG-INFO +87 -35
  5. {rag_your_code-1.2.0 → rag_your_code-1.3.0}/src/ragyourcode/__init__.py +1 -1
  6. {rag_your_code-1.2.0 → rag_your_code-1.3.0}/src/ragyourcode/config.py +14 -6
  7. {rag_your_code-1.2.0 → rag_your_code-1.3.0}/src/ragyourcode/search.py +1 -1
  8. {rag_your_code-1.2.0 → rag_your_code-1.3.0}/tests/test_evidence.py +4 -3
  9. {rag_your_code-1.2.0 → rag_your_code-1.3.0}/tests/test_metadata.py +32 -0
  10. {rag_your_code-1.2.0 → rag_your_code-1.3.0}/LICENSE +0 -0
  11. {rag_your_code-1.2.0 → rag_your_code-1.3.0}/setup.cfg +0 -0
  12. {rag_your_code-1.2.0 → rag_your_code-1.3.0}/src/rag_your_code.egg-info/SOURCES.txt +0 -0
  13. {rag_your_code-1.2.0 → rag_your_code-1.3.0}/src/rag_your_code.egg-info/dependency_links.txt +0 -0
  14. {rag_your_code-1.2.0 → rag_your_code-1.3.0}/src/rag_your_code.egg-info/entry_points.txt +0 -0
  15. {rag_your_code-1.2.0 → rag_your_code-1.3.0}/src/rag_your_code.egg-info/requires.txt +0 -0
  16. {rag_your_code-1.2.0 → rag_your_code-1.3.0}/src/rag_your_code.egg-info/top_level.txt +0 -0
  17. {rag_your_code-1.2.0 → rag_your_code-1.3.0}/src/ragyourcode/agentic.py +0 -0
  18. {rag_your_code-1.2.0 → rag_your_code-1.3.0}/src/ragyourcode/annotate.py +0 -0
  19. {rag_your_code-1.2.0 → rag_your_code-1.3.0}/src/ragyourcode/cli.py +0 -0
  20. {rag_your_code-1.2.0 → rag_your_code-1.3.0}/src/ragyourcode/descriptions.py +0 -0
  21. {rag_your_code-1.2.0 → rag_your_code-1.3.0}/src/ragyourcode/document.py +0 -0
  22. {rag_your_code-1.2.0 → rag_your_code-1.3.0}/src/ragyourcode/embeddings.py +0 -0
  23. {rag_your_code-1.2.0 → rag_your_code-1.3.0}/src/ragyourcode/graph.py +0 -0
  24. {rag_your_code-1.2.0 → rag_your_code-1.3.0}/src/ragyourcode/indexer.py +0 -0
  25. {rag_your_code-1.2.0 → rag_your_code-1.3.0}/src/ragyourcode/models.py +0 -0
  26. {rag_your_code-1.2.0 → rag_your_code-1.3.0}/src/ragyourcode/parser.py +0 -0
  27. {rag_your_code-1.2.0 → rag_your_code-1.3.0}/src/ragyourcode/providers.py +0 -0
  28. {rag_your_code-1.2.0 → rag_your_code-1.3.0}/src/ragyourcode/py.typed +0 -0
  29. {rag_your_code-1.2.0 → rag_your_code-1.3.0}/src/ragyourcode/workflow.py +0 -0
  30. {rag_your_code-1.2.0 → rag_your_code-1.3.0}/tests/test_absent_queries.py +0 -0
  31. {rag_your_code-1.2.0 → rag_your_code-1.3.0}/tests/test_agent_protocol.py +0 -0
  32. {rag_your_code-1.2.0 → rag_your_code-1.3.0}/tests/test_agentic.py +0 -0
  33. {rag_your_code-1.2.0 → rag_your_code-1.3.0}/tests/test_config.py +0 -0
  34. {rag_your_code-1.2.0 → rag_your_code-1.3.0}/tests/test_descriptions.py +0 -0
  35. {rag_your_code-1.2.0 → rag_your_code-1.3.0}/tests/test_doc_comments.py +0 -0
  36. {rag_your_code-1.2.0 → rag_your_code-1.3.0}/tests/test_document.py +0 -0
  37. {rag_your_code-1.2.0 → rag_your_code-1.3.0}/tests/test_e2e_cli.py +0 -0
  38. {rag_your_code-1.2.0 → rag_your_code-1.3.0}/tests/test_golden.py +0 -0
  39. {rag_your_code-1.2.0 → rag_your_code-1.3.0}/tests/test_graph_incremental.py +0 -0
  40. {rag_your_code-1.2.0 → rag_your_code-1.3.0}/tests/test_language_fixtures.py +0 -0
  41. {rag_your_code-1.2.0 → rag_your_code-1.3.0}/tests/test_large_repo.py +0 -0
  42. {rag_your_code-1.2.0 → rag_your_code-1.3.0}/tests/test_local_model.py +0 -0
  43. {rag_your_code-1.2.0 → rag_your_code-1.3.0}/tests/test_multilanguage.py +0 -0
  44. {rag_your_code-1.2.0 → rag_your_code-1.3.0}/tests/test_parser_edges.py +0 -0
  45. {rag_your_code-1.2.0 → rag_your_code-1.3.0}/tests/test_providers.py +0 -0
  46. {rag_your_code-1.2.0 → rag_your_code-1.3.0}/tests/test_ragyourcode.py +0 -0
  47. {rag_your_code-1.2.0 → rag_your_code-1.3.0}/tests/test_ranking.py +0 -0
  48. {rag_your_code-1.2.0 → rag_your_code-1.3.0}/tests/test_repo_queries.py +0 -0
  49. {rag_your_code-1.2.0 → rag_your_code-1.3.0}/tests/test_resilience.py +0 -0
  50. {rag_your_code-1.2.0 → rag_your_code-1.3.0}/tests/test_retrieval_correctness.py +0 -0
  51. {rag_your_code-1.2.0 → rag_your_code-1.3.0}/tests/test_workflow.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: rag-your-code
3
- Version: 1.2.0
3
+ Version: 1.3.0
4
4
  Summary: A local, explainable RAG index for codebases and coding agents
5
5
  Author: rag-your-code contributors
6
6
  License-Expression: MIT
@@ -293,10 +293,18 @@ repository had grown by ninety units.
293
293
 
294
294
  | ruler | what it represents | n | hit@1 | hit@3 | MRR |
295
295
  |---|---|---|---|---|---|
296
- | **A** foreign repo, no descriptions | what a first-time user gets | 35 | 0.229 | 0.400 | 0.300 |
296
+ | **A** foreign repo, no descriptions | what a first-time user gets | 35 | 0.229 | 0.371 | 0.286 |
297
297
  | **B** this repo, generated descriptions only | a cold index of familiar code | 70 | 0.314 | 0.471 | 0.383 |
298
298
  | **C** this repo, agent-written descriptions | the warmest case supported | 70 | 0.443 | 0.614 | 0.507 |
299
299
 
300
+ The corpora, without which none of the above is reproducible — **A**
301
+ cc-enforcer, 1,345 units, `471b78f9f806`; **B** 579 units, `dca2a4656659`;
302
+ **C** 579 units, `0a2f050dbafd`. Ruler A's repository is one this project does
303
+ not control. Between the previous release and this table it grew by 78 units
304
+ and renamed a declaration two questions pointed at, which stopped the ruler
305
+ running at all until they were repointed — hit@3 0.400 → 0.371 and MRR 0.300 →
306
+ 0.286 are that, not a change in retrieval.
307
+
300
308
  **Refusal — the fourth ruler, 30 questions with no answer anywhere**
301
309
 
302
310
  | | this repo | foreign repo |
@@ -310,25 +318,39 @@ repository had grown by ninety units.
310
318
 
311
319
  | gate | A hit@1/3/MRR | B hit@1/3/MRR | C hit@1/3/MRR | silence own / foreign |
312
320
  |---|---|---|---|---|
313
- | neither (pre-1.0.0) | 0.229/0.400/0.300 | 0.314/0.486/0.391 | 0.471/0.686/0.552 | 0.000 / 0.000 |
314
- | coverage only (1.0.0) | 0.229/0.400/0.300 | 0.314/0.471/0.383 | 0.471/0.671/0.548 | 0.700 / 0.767 |
315
- | **both (1.1.0)** | **0.229/0.400/0.300** | **0.314/0.471/0.383** | 0.443/0.614/0.507 | **0.967 / 0.933** |
321
+ | neither (pre-1.0.0) | 0.229/0.371/0.286 | 0.314/0.486/0.391 | 0.486/0.700/0.567 | 0.000 / 0.000 |
322
+ | coverage only (1.0.0) | 0.229/0.371/0.286 | 0.314/0.471/0.383 | 0.486/0.686/0.562 | 0.600 / 0.767 |
323
+ | **both (1.1.0)** | **0.229/0.371/0.286** | **0.314/0.471/0.383** | 0.443/0.614/0.507 | **0.967 / 0.933** |
316
324
 
317
- Rulers A and B are **identical to three decimals**. The entire cost is four
318
- questions of seventy on the warmest ruler. On these four rulers concentration
325
+ Rulers A and B are **identical to three decimals**. The entire cost is three
326
+ questions of seventy at hit@1 on the warmest ruler, and six at hit@3. On these
327
+ four rulers concentration
319
328
  subsumes coverage — stated plainly because it is true; coverage is kept because
320
329
  it answers a different question and names a different diagnosis.
321
330
 
322
- **Latency** — warm corpus, 557 units, 420 samples after warm-up:
323
-
324
- | | |
325
- |---|---|
326
- | query, median | **0.83 ms** |
327
- | query, p95 | 1.68 ms |
328
- | refusing an unanswerable query | **0.03 ms** |
331
+ **Latency** — warm corpus, 579 units `0a2f050dbafd`, five consecutive
332
+ invocations of `python -m benchmarks.query_latency --repeats 10` (420 samples
333
+ each):
329
334
 
330
- Refusal is cheaper than answering by a factor of forty: an unanswerable query
331
- touches only the posting lists of its own distinctive words, never the corpus.
335
+ | | | across the five |
336
+ |---|---|---|
337
+ | query, median | **0.65 ms** | 0.62 – 0.92 |
338
+ | query, p95 | 1.13 ms | 1.09 – 1.48 |
339
+ | refusing an unanswerable query | **0.022 ms** | 0.019 – 0.024 |
340
+ | refusal cheaper than answering by | **~35×** | 31 – 39 |
341
+
342
+ Two significant figures and a spread, because that is the precision the
343
+ measurement has. A second set of five taken earlier the same hour, while the
344
+ machine was busy, put the median at 1.0 ms and p95 at 3.0 ms; the gap between
345
+ the two sets is the machine, and it is wider than any change the code has ever
346
+ made to this number. Earlier releases published `0.83 ms / p95 1.68 ms` to
347
+ three figures from a script that was never committed, on a corpus that no
348
+ longer exists — both values sit inside today's range, which is the point: they
349
+ were unfalsifiable rather than wrong.
350
+
351
+ Refusal is cheap for a structural reason, not a tuned one: an unanswerable
352
+ query touches only the posting lists of its own distinctive words, and never
353
+ reaches ranking at all.
332
354
 
333
355
  **Scale**, synthetic 10,000-unit repository (500 files):
334
356
 
@@ -350,8 +372,11 @@ touches only the posting lists of its own distinctive words, never the corpus.
350
372
  | with a usable signature | **91 / 91** |
351
373
  | units invented that do not exist | **0** |
352
374
 
353
- Directional local measurements, not service levels. Reproduce with
354
- `python benchmarks/repo_queries.py` and `python benchmarks/large_repo.py`.
375
+ Directional local measurements, not service levels but every one of them is
376
+ now a command rather than a memory, which two of them were not before. Each
377
+ prints the corpus fingerprint beside its score; quote both or neither.
378
+ [`benchmarks/README.md`](benchmarks/README.md) lists the five scripts and what
379
+ each is for.
355
380
 
356
381
  ## 7 · `rag-your-code search` vs a Grep loop
357
382
 
@@ -364,23 +389,23 @@ declaration spans.
364
389
  **On a repository nobody has described, Grep wins.** That is the measured
365
390
  result and it is not softened here.
366
391
 
367
- | foreign repository · 35 questions · 1,267 units · no descriptions | Grep loop | rag-your-code |
392
+ | foreign repository · 35 questions · 1,345 units `471b78f9f806` · no descriptions | Grep loop | rag-your-code |
368
393
  |---|---|---|
369
- | right file first | **34.3%** | 31.4% |
370
- | right file in top 3 | **60.0%** | 48.6% |
371
- | lines matched across the repo, all questions | 33,213 | — |
372
- | characters returned, all questions | | **163,294** |
373
- | questions it answers | 35 | 28 |
394
+ | right file first | **40.0%** | 28.6% |
395
+ | right file in top 3 | **62.9%** | 48.6% |
396
+ | lines it hands back, all questions | 8,937 | — |
397
+ | characters returned, all questions | 841,484 | **282,102** |
398
+ | questions it answers | **35** | 28 |
374
399
 
375
400
  **Once the vocabulary exists, it is not close.**
376
401
 
377
- | this repository · 70 questions · 569 units · 304 described | Grep loop | rag-your-code |
402
+ | this repository · 70 questions · 579 units `0a2f050dbafd` · 304 described | Grep loop | rag-your-code |
378
403
  |---|---|---|
379
- | right file first | 25.7% | **58.6%** |
380
- | right file in top 3 | 64.3% | **75.7%** |
381
- | lines matched across the repo, all questions | 40,150 | — |
382
- | characters returned, all questions | | **277,327** |
383
- | questions it answers | 70 | 60 |
404
+ | right file first | 22.9% | **58.6%** |
405
+ | right file in top 3 | 55.7% | **75.7%** |
406
+ | lines it hands back, all questions | 11,636 | — |
407
+ | characters returned, all questions | 1,101,618 | **621,837** |
408
+ | questions it answers | **61** | 60 |
384
409
 
385
410
  Those two tables are the whole argument of section 3.3, measured against a real
386
411
  baseline instead of asserted. A cold index retrieves against a sentence the
@@ -390,15 +415,32 @@ to guess which of the matching files is the definition. Descriptions put words
390
415
  in the index that the source never contained, and first-place accuracy goes
391
416
  from below Grep's to **more than double** it.
392
417
 
393
- Three qualifications, because the table would otherwise flatter both sides:
418
+ **Both tables come from `python -m benchmarks.grep_baseline`**, which is what
419
+ changed in 1.3.0. Until then this section — the strongest claim the project
420
+ makes — was published from a script that had never been committed, so nothing
421
+ here could be checked and the word "Grep loop" had no precise meaning. The
422
+ committed version defines it: take the query's words, drop the ones the corpus
423
+ itself shows are everywhere, run one substring search per remaining word over
424
+ exactly the files the index was built from, rank each file by how many distinct
425
+ words hit it, break ties on path. Reconstructing it reproduced this side's
426
+ figures exactly and moved Grep's, which is the expected shape — the ranked arm
427
+ was always a call into shipped code, and the baseline never was.
428
+
429
+ Four qualifications, because the table would otherwise flatter both sides:
394
430
 
395
431
  - **Scored at file granularity**, which understates this side. A Grep hit is a
396
432
  file; a hit here is a declaration with an exact span, a score, and the words
397
433
  it matched on. The agent that reads the result opens 40 lines, not a file.
398
- - **Grep answers everything.** It never declines, which is why it hands back
399
- 33,213 matching lines for 35 questions about 950 lines per question, no
400
- ranking, no spans, no indication which match is the definition. This returns
401
- roughly 5,800 characters per question, ranked. Seven of 35 and ten of 70
434
+ - **Dropping the corpus-common words is generous to Grep**, and it is what
435
+ makes the baseline a fair one rather than a straw man: an agent that greps
436
+ `the` gets every file back in no order. It is also why Grep declines nine of
437
+ the seventy questions here those had no word left that this corpus does not
438
+ use everywhere.
439
+ - **Payload is counted in characters on both sides.** Grep hands back 18,000
440
+ characters per question it answers, unranked and without spans; this returns
441
+ 10,400, ranked, capped by `search.max_chars`. That is a factor of 1.7, not
442
+ the order of magnitude an earlier version of this table implied by counting
443
+ one side in lines and the other in characters. Seven of 35 and ten of 70
402
444
  questions come back empty here instead, with a reason.
403
445
  - **Grep wins outright when you know the string.** `grep -rn "COMMON_TERM"` is
404
446
  exact, instant and complete, and nothing here replaces it.
@@ -651,6 +693,16 @@ need a dependency, and the policy for those is settled: they follow the
651
693
  embedding provider's pattern — optional, user-selected, never in the default
652
694
  install. Full reasoning in [docs/ROADMAP.md](docs/ROADMAP.md).
653
695
 
696
+ **Whether the skill fires unprompted is not measured**, and until 1.2.1 this
697
+ project claimed no command could measure it. That was wrong: `claude plugin
698
+ eval` grades exactly this, with `tool_used: Skill` as a plugin-fired indicator
699
+ and a no-plugin baseline arm. It is unmeasured because the command is in early
700
+ access on the account here and its case schema is undocumented, so a suite
701
+ written from `--help` fragments could not be run even once to see whether it
702
+ loads — and a suite that silently fails to load reads as a gate while checking
703
+ nothing. Since 1.2.0 the four commands give an entry path that does not depend
704
+ on it.
705
+
654
706
  ## 12 · Development
655
707
 
656
708
  ```bash
@@ -264,10 +264,18 @@ repository had grown by ninety units.
264
264
 
265
265
  | ruler | what it represents | n | hit@1 | hit@3 | MRR |
266
266
  |---|---|---|---|---|---|
267
- | **A** foreign repo, no descriptions | what a first-time user gets | 35 | 0.229 | 0.400 | 0.300 |
267
+ | **A** foreign repo, no descriptions | what a first-time user gets | 35 | 0.229 | 0.371 | 0.286 |
268
268
  | **B** this repo, generated descriptions only | a cold index of familiar code | 70 | 0.314 | 0.471 | 0.383 |
269
269
  | **C** this repo, agent-written descriptions | the warmest case supported | 70 | 0.443 | 0.614 | 0.507 |
270
270
 
271
+ The corpora, without which none of the above is reproducible — **A**
272
+ cc-enforcer, 1,345 units, `471b78f9f806`; **B** 579 units, `dca2a4656659`;
273
+ **C** 579 units, `0a2f050dbafd`. Ruler A's repository is one this project does
274
+ not control. Between the previous release and this table it grew by 78 units
275
+ and renamed a declaration two questions pointed at, which stopped the ruler
276
+ running at all until they were repointed — hit@3 0.400 → 0.371 and MRR 0.300 →
277
+ 0.286 are that, not a change in retrieval.
278
+
271
279
  **Refusal — the fourth ruler, 30 questions with no answer anywhere**
272
280
 
273
281
  | | this repo | foreign repo |
@@ -281,25 +289,39 @@ repository had grown by ninety units.
281
289
 
282
290
  | gate | A hit@1/3/MRR | B hit@1/3/MRR | C hit@1/3/MRR | silence own / foreign |
283
291
  |---|---|---|---|---|
284
- | neither (pre-1.0.0) | 0.229/0.400/0.300 | 0.314/0.486/0.391 | 0.471/0.686/0.552 | 0.000 / 0.000 |
285
- | coverage only (1.0.0) | 0.229/0.400/0.300 | 0.314/0.471/0.383 | 0.471/0.671/0.548 | 0.700 / 0.767 |
286
- | **both (1.1.0)** | **0.229/0.400/0.300** | **0.314/0.471/0.383** | 0.443/0.614/0.507 | **0.967 / 0.933** |
292
+ | neither (pre-1.0.0) | 0.229/0.371/0.286 | 0.314/0.486/0.391 | 0.486/0.700/0.567 | 0.000 / 0.000 |
293
+ | coverage only (1.0.0) | 0.229/0.371/0.286 | 0.314/0.471/0.383 | 0.486/0.686/0.562 | 0.600 / 0.767 |
294
+ | **both (1.1.0)** | **0.229/0.371/0.286** | **0.314/0.471/0.383** | 0.443/0.614/0.507 | **0.967 / 0.933** |
287
295
 
288
- Rulers A and B are **identical to three decimals**. The entire cost is four
289
- questions of seventy on the warmest ruler. On these four rulers concentration
296
+ Rulers A and B are **identical to three decimals**. The entire cost is three
297
+ questions of seventy at hit@1 on the warmest ruler, and six at hit@3. On these
298
+ four rulers concentration
290
299
  subsumes coverage — stated plainly because it is true; coverage is kept because
291
300
  it answers a different question and names a different diagnosis.
292
301
 
293
- **Latency** — warm corpus, 557 units, 420 samples after warm-up:
294
-
295
- | | |
296
- |---|---|
297
- | query, median | **0.83 ms** |
298
- | query, p95 | 1.68 ms |
299
- | refusing an unanswerable query | **0.03 ms** |
302
+ **Latency** — warm corpus, 579 units `0a2f050dbafd`, five consecutive
303
+ invocations of `python -m benchmarks.query_latency --repeats 10` (420 samples
304
+ each):
300
305
 
301
- Refusal is cheaper than answering by a factor of forty: an unanswerable query
302
- touches only the posting lists of its own distinctive words, never the corpus.
306
+ | | | across the five |
307
+ |---|---|---|
308
+ | query, median | **0.65 ms** | 0.62 – 0.92 |
309
+ | query, p95 | 1.13 ms | 1.09 – 1.48 |
310
+ | refusing an unanswerable query | **0.022 ms** | 0.019 – 0.024 |
311
+ | refusal cheaper than answering by | **~35×** | 31 – 39 |
312
+
313
+ Two significant figures and a spread, because that is the precision the
314
+ measurement has. A second set of five taken earlier the same hour, while the
315
+ machine was busy, put the median at 1.0 ms and p95 at 3.0 ms; the gap between
316
+ the two sets is the machine, and it is wider than any change the code has ever
317
+ made to this number. Earlier releases published `0.83 ms / p95 1.68 ms` to
318
+ three figures from a script that was never committed, on a corpus that no
319
+ longer exists — both values sit inside today's range, which is the point: they
320
+ were unfalsifiable rather than wrong.
321
+
322
+ Refusal is cheap for a structural reason, not a tuned one: an unanswerable
323
+ query touches only the posting lists of its own distinctive words, and never
324
+ reaches ranking at all.
303
325
 
304
326
  **Scale**, synthetic 10,000-unit repository (500 files):
305
327
 
@@ -321,8 +343,11 @@ touches only the posting lists of its own distinctive words, never the corpus.
321
343
  | with a usable signature | **91 / 91** |
322
344
  | units invented that do not exist | **0** |
323
345
 
324
- Directional local measurements, not service levels. Reproduce with
325
- `python benchmarks/repo_queries.py` and `python benchmarks/large_repo.py`.
346
+ Directional local measurements, not service levels but every one of them is
347
+ now a command rather than a memory, which two of them were not before. Each
348
+ prints the corpus fingerprint beside its score; quote both or neither.
349
+ [`benchmarks/README.md`](benchmarks/README.md) lists the five scripts and what
350
+ each is for.
326
351
 
327
352
  ## 7 · `rag-your-code search` vs a Grep loop
328
353
 
@@ -335,23 +360,23 @@ declaration spans.
335
360
  **On a repository nobody has described, Grep wins.** That is the measured
336
361
  result and it is not softened here.
337
362
 
338
- | foreign repository · 35 questions · 1,267 units · no descriptions | Grep loop | rag-your-code |
363
+ | foreign repository · 35 questions · 1,345 units `471b78f9f806` · no descriptions | Grep loop | rag-your-code |
339
364
  |---|---|---|
340
- | right file first | **34.3%** | 31.4% |
341
- | right file in top 3 | **60.0%** | 48.6% |
342
- | lines matched across the repo, all questions | 33,213 | — |
343
- | characters returned, all questions | | **163,294** |
344
- | questions it answers | 35 | 28 |
365
+ | right file first | **40.0%** | 28.6% |
366
+ | right file in top 3 | **62.9%** | 48.6% |
367
+ | lines it hands back, all questions | 8,937 | — |
368
+ | characters returned, all questions | 841,484 | **282,102** |
369
+ | questions it answers | **35** | 28 |
345
370
 
346
371
  **Once the vocabulary exists, it is not close.**
347
372
 
348
- | this repository · 70 questions · 569 units · 304 described | Grep loop | rag-your-code |
373
+ | this repository · 70 questions · 579 units `0a2f050dbafd` · 304 described | Grep loop | rag-your-code |
349
374
  |---|---|---|
350
- | right file first | 25.7% | **58.6%** |
351
- | right file in top 3 | 64.3% | **75.7%** |
352
- | lines matched across the repo, all questions | 40,150 | — |
353
- | characters returned, all questions | | **277,327** |
354
- | questions it answers | 70 | 60 |
375
+ | right file first | 22.9% | **58.6%** |
376
+ | right file in top 3 | 55.7% | **75.7%** |
377
+ | lines it hands back, all questions | 11,636 | — |
378
+ | characters returned, all questions | 1,101,618 | **621,837** |
379
+ | questions it answers | **61** | 60 |
355
380
 
356
381
  Those two tables are the whole argument of section 3.3, measured against a real
357
382
  baseline instead of asserted. A cold index retrieves against a sentence the
@@ -361,15 +386,32 @@ to guess which of the matching files is the definition. Descriptions put words
361
386
  in the index that the source never contained, and first-place accuracy goes
362
387
  from below Grep's to **more than double** it.
363
388
 
364
- Three qualifications, because the table would otherwise flatter both sides:
389
+ **Both tables come from `python -m benchmarks.grep_baseline`**, which is what
390
+ changed in 1.3.0. Until then this section — the strongest claim the project
391
+ makes — was published from a script that had never been committed, so nothing
392
+ here could be checked and the word "Grep loop" had no precise meaning. The
393
+ committed version defines it: take the query's words, drop the ones the corpus
394
+ itself shows are everywhere, run one substring search per remaining word over
395
+ exactly the files the index was built from, rank each file by how many distinct
396
+ words hit it, break ties on path. Reconstructing it reproduced this side's
397
+ figures exactly and moved Grep's, which is the expected shape — the ranked arm
398
+ was always a call into shipped code, and the baseline never was.
399
+
400
+ Four qualifications, because the table would otherwise flatter both sides:
365
401
 
366
402
  - **Scored at file granularity**, which understates this side. A Grep hit is a
367
403
  file; a hit here is a declaration with an exact span, a score, and the words
368
404
  it matched on. The agent that reads the result opens 40 lines, not a file.
369
- - **Grep answers everything.** It never declines, which is why it hands back
370
- 33,213 matching lines for 35 questions about 950 lines per question, no
371
- ranking, no spans, no indication which match is the definition. This returns
372
- roughly 5,800 characters per question, ranked. Seven of 35 and ten of 70
405
+ - **Dropping the corpus-common words is generous to Grep**, and it is what
406
+ makes the baseline a fair one rather than a straw man: an agent that greps
407
+ `the` gets every file back in no order. It is also why Grep declines nine of
408
+ the seventy questions here those had no word left that this corpus does not
409
+ use everywhere.
410
+ - **Payload is counted in characters on both sides.** Grep hands back 18,000
411
+ characters per question it answers, unranked and without spans; this returns
412
+ 10,400, ranked, capped by `search.max_chars`. That is a factor of 1.7, not
413
+ the order of magnitude an earlier version of this table implied by counting
414
+ one side in lines and the other in characters. Seven of 35 and ten of 70
373
415
  questions come back empty here instead, with a reason.
374
416
  - **Grep wins outright when you know the string.** `grep -rn "COMMON_TERM"` is
375
417
  exact, instant and complete, and nothing here replaces it.
@@ -622,6 +664,16 @@ need a dependency, and the policy for those is settled: they follow the
622
664
  embedding provider's pattern — optional, user-selected, never in the default
623
665
  install. Full reasoning in [docs/ROADMAP.md](docs/ROADMAP.md).
624
666
 
667
+ **Whether the skill fires unprompted is not measured**, and until 1.2.1 this
668
+ project claimed no command could measure it. That was wrong: `claude plugin
669
+ eval` grades exactly this, with `tool_used: Skill` as a plugin-fired indicator
670
+ and a no-plugin baseline arm. It is unmeasured because the command is in early
671
+ access on the account here and its case schema is undocumented, so a suite
672
+ written from `--help` fragments could not be run even once to see whether it
673
+ loads — and a suite that silently fails to load reads as a gate while checking
674
+ nothing. Since 1.2.0 the four commands give an entry path that does not depend
675
+ on it.
676
+
625
677
  ## 12 · Development
626
678
 
627
679
  ```bash
@@ -6,7 +6,7 @@ build-backend = "setuptools.build_meta"
6
6
 
7
7
  [project]
8
8
  name = "rag-your-code"
9
- version = "1.2.0"
9
+ version = "1.3.0"
10
10
  description = "A local, explainable RAG index for codebases and coding agents"
11
11
  readme = "README.md"
12
12
  requires-python = ">=3.10"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: rag-your-code
3
- Version: 1.2.0
3
+ Version: 1.3.0
4
4
  Summary: A local, explainable RAG index for codebases and coding agents
5
5
  Author: rag-your-code contributors
6
6
  License-Expression: MIT
@@ -293,10 +293,18 @@ repository had grown by ninety units.
293
293
 
294
294
  | ruler | what it represents | n | hit@1 | hit@3 | MRR |
295
295
  |---|---|---|---|---|---|
296
- | **A** foreign repo, no descriptions | what a first-time user gets | 35 | 0.229 | 0.400 | 0.300 |
296
+ | **A** foreign repo, no descriptions | what a first-time user gets | 35 | 0.229 | 0.371 | 0.286 |
297
297
  | **B** this repo, generated descriptions only | a cold index of familiar code | 70 | 0.314 | 0.471 | 0.383 |
298
298
  | **C** this repo, agent-written descriptions | the warmest case supported | 70 | 0.443 | 0.614 | 0.507 |
299
299
 
300
+ The corpora, without which none of the above is reproducible — **A**
301
+ cc-enforcer, 1,345 units, `471b78f9f806`; **B** 579 units, `dca2a4656659`;
302
+ **C** 579 units, `0a2f050dbafd`. Ruler A's repository is one this project does
303
+ not control. Between the previous release and this table it grew by 78 units
304
+ and renamed a declaration two questions pointed at, which stopped the ruler
305
+ running at all until they were repointed — hit@3 0.400 → 0.371 and MRR 0.300 →
306
+ 0.286 are that, not a change in retrieval.
307
+
300
308
  **Refusal — the fourth ruler, 30 questions with no answer anywhere**
301
309
 
302
310
  | | this repo | foreign repo |
@@ -310,25 +318,39 @@ repository had grown by ninety units.
310
318
 
311
319
  | gate | A hit@1/3/MRR | B hit@1/3/MRR | C hit@1/3/MRR | silence own / foreign |
312
320
  |---|---|---|---|---|
313
- | neither (pre-1.0.0) | 0.229/0.400/0.300 | 0.314/0.486/0.391 | 0.471/0.686/0.552 | 0.000 / 0.000 |
314
- | coverage only (1.0.0) | 0.229/0.400/0.300 | 0.314/0.471/0.383 | 0.471/0.671/0.548 | 0.700 / 0.767 |
315
- | **both (1.1.0)** | **0.229/0.400/0.300** | **0.314/0.471/0.383** | 0.443/0.614/0.507 | **0.967 / 0.933** |
321
+ | neither (pre-1.0.0) | 0.229/0.371/0.286 | 0.314/0.486/0.391 | 0.486/0.700/0.567 | 0.000 / 0.000 |
322
+ | coverage only (1.0.0) | 0.229/0.371/0.286 | 0.314/0.471/0.383 | 0.486/0.686/0.562 | 0.600 / 0.767 |
323
+ | **both (1.1.0)** | **0.229/0.371/0.286** | **0.314/0.471/0.383** | 0.443/0.614/0.507 | **0.967 / 0.933** |
316
324
 
317
- Rulers A and B are **identical to three decimals**. The entire cost is four
318
- questions of seventy on the warmest ruler. On these four rulers concentration
325
+ Rulers A and B are **identical to three decimals**. The entire cost is three
326
+ questions of seventy at hit@1 on the warmest ruler, and six at hit@3. On these
327
+ four rulers concentration
319
328
  subsumes coverage — stated plainly because it is true; coverage is kept because
320
329
  it answers a different question and names a different diagnosis.
321
330
 
322
- **Latency** — warm corpus, 557 units, 420 samples after warm-up:
323
-
324
- | | |
325
- |---|---|
326
- | query, median | **0.83 ms** |
327
- | query, p95 | 1.68 ms |
328
- | refusing an unanswerable query | **0.03 ms** |
331
+ **Latency** — warm corpus, 579 units `0a2f050dbafd`, five consecutive
332
+ invocations of `python -m benchmarks.query_latency --repeats 10` (420 samples
333
+ each):
329
334
 
330
- Refusal is cheaper than answering by a factor of forty: an unanswerable query
331
- touches only the posting lists of its own distinctive words, never the corpus.
335
+ | | | across the five |
336
+ |---|---|---|
337
+ | query, median | **0.65 ms** | 0.62 – 0.92 |
338
+ | query, p95 | 1.13 ms | 1.09 – 1.48 |
339
+ | refusing an unanswerable query | **0.022 ms** | 0.019 – 0.024 |
340
+ | refusal cheaper than answering by | **~35×** | 31 – 39 |
341
+
342
+ Two significant figures and a spread, because that is the precision the
343
+ measurement has. A second set of five taken earlier the same hour, while the
344
+ machine was busy, put the median at 1.0 ms and p95 at 3.0 ms; the gap between
345
+ the two sets is the machine, and it is wider than any change the code has ever
346
+ made to this number. Earlier releases published `0.83 ms / p95 1.68 ms` to
347
+ three figures from a script that was never committed, on a corpus that no
348
+ longer exists — both values sit inside today's range, which is the point: they
349
+ were unfalsifiable rather than wrong.
350
+
351
+ Refusal is cheap for a structural reason, not a tuned one: an unanswerable
352
+ query touches only the posting lists of its own distinctive words, and never
353
+ reaches ranking at all.
332
354
 
333
355
  **Scale**, synthetic 10,000-unit repository (500 files):
334
356
 
@@ -350,8 +372,11 @@ touches only the posting lists of its own distinctive words, never the corpus.
350
372
  | with a usable signature | **91 / 91** |
351
373
  | units invented that do not exist | **0** |
352
374
 
353
- Directional local measurements, not service levels. Reproduce with
354
- `python benchmarks/repo_queries.py` and `python benchmarks/large_repo.py`.
375
+ Directional local measurements, not service levels but every one of them is
376
+ now a command rather than a memory, which two of them were not before. Each
377
+ prints the corpus fingerprint beside its score; quote both or neither.
378
+ [`benchmarks/README.md`](benchmarks/README.md) lists the five scripts and what
379
+ each is for.
355
380
 
356
381
  ## 7 · `rag-your-code search` vs a Grep loop
357
382
 
@@ -364,23 +389,23 @@ declaration spans.
364
389
  **On a repository nobody has described, Grep wins.** That is the measured
365
390
  result and it is not softened here.
366
391
 
367
- | foreign repository · 35 questions · 1,267 units · no descriptions | Grep loop | rag-your-code |
392
+ | foreign repository · 35 questions · 1,345 units `471b78f9f806` · no descriptions | Grep loop | rag-your-code |
368
393
  |---|---|---|
369
- | right file first | **34.3%** | 31.4% |
370
- | right file in top 3 | **60.0%** | 48.6% |
371
- | lines matched across the repo, all questions | 33,213 | — |
372
- | characters returned, all questions | | **163,294** |
373
- | questions it answers | 35 | 28 |
394
+ | right file first | **40.0%** | 28.6% |
395
+ | right file in top 3 | **62.9%** | 48.6% |
396
+ | lines it hands back, all questions | 8,937 | — |
397
+ | characters returned, all questions | 841,484 | **282,102** |
398
+ | questions it answers | **35** | 28 |
374
399
 
375
400
  **Once the vocabulary exists, it is not close.**
376
401
 
377
- | this repository · 70 questions · 569 units · 304 described | Grep loop | rag-your-code |
402
+ | this repository · 70 questions · 579 units `0a2f050dbafd` · 304 described | Grep loop | rag-your-code |
378
403
  |---|---|---|
379
- | right file first | 25.7% | **58.6%** |
380
- | right file in top 3 | 64.3% | **75.7%** |
381
- | lines matched across the repo, all questions | 40,150 | — |
382
- | characters returned, all questions | | **277,327** |
383
- | questions it answers | 70 | 60 |
404
+ | right file first | 22.9% | **58.6%** |
405
+ | right file in top 3 | 55.7% | **75.7%** |
406
+ | lines it hands back, all questions | 11,636 | — |
407
+ | characters returned, all questions | 1,101,618 | **621,837** |
408
+ | questions it answers | **61** | 60 |
384
409
 
385
410
  Those two tables are the whole argument of section 3.3, measured against a real
386
411
  baseline instead of asserted. A cold index retrieves against a sentence the
@@ -390,15 +415,32 @@ to guess which of the matching files is the definition. Descriptions put words
390
415
  in the index that the source never contained, and first-place accuracy goes
391
416
  from below Grep's to **more than double** it.
392
417
 
393
- Three qualifications, because the table would otherwise flatter both sides:
418
+ **Both tables come from `python -m benchmarks.grep_baseline`**, which is what
419
+ changed in 1.3.0. Until then this section — the strongest claim the project
420
+ makes — was published from a script that had never been committed, so nothing
421
+ here could be checked and the word "Grep loop" had no precise meaning. The
422
+ committed version defines it: take the query's words, drop the ones the corpus
423
+ itself shows are everywhere, run one substring search per remaining word over
424
+ exactly the files the index was built from, rank each file by how many distinct
425
+ words hit it, break ties on path. Reconstructing it reproduced this side's
426
+ figures exactly and moved Grep's, which is the expected shape — the ranked arm
427
+ was always a call into shipped code, and the baseline never was.
428
+
429
+ Four qualifications, because the table would otherwise flatter both sides:
394
430
 
395
431
  - **Scored at file granularity**, which understates this side. A Grep hit is a
396
432
  file; a hit here is a declaration with an exact span, a score, and the words
397
433
  it matched on. The agent that reads the result opens 40 lines, not a file.
398
- - **Grep answers everything.** It never declines, which is why it hands back
399
- 33,213 matching lines for 35 questions about 950 lines per question, no
400
- ranking, no spans, no indication which match is the definition. This returns
401
- roughly 5,800 characters per question, ranked. Seven of 35 and ten of 70
434
+ - **Dropping the corpus-common words is generous to Grep**, and it is what
435
+ makes the baseline a fair one rather than a straw man: an agent that greps
436
+ `the` gets every file back in no order. It is also why Grep declines nine of
437
+ the seventy questions here those had no word left that this corpus does not
438
+ use everywhere.
439
+ - **Payload is counted in characters on both sides.** Grep hands back 18,000
440
+ characters per question it answers, unranked and without spans; this returns
441
+ 10,400, ranked, capped by `search.max_chars`. That is a factor of 1.7, not
442
+ the order of magnitude an earlier version of this table implied by counting
443
+ one side in lines and the other in characters. Seven of 35 and ten of 70
402
444
  questions come back empty here instead, with a reason.
403
445
  - **Grep wins outright when you know the string.** `grep -rn "COMMON_TERM"` is
404
446
  exact, instant and complete, and nothing here replaces it.
@@ -651,6 +693,16 @@ need a dependency, and the policy for those is settled: they follow the
651
693
  embedding provider's pattern — optional, user-selected, never in the default
652
694
  install. Full reasoning in [docs/ROADMAP.md](docs/ROADMAP.md).
653
695
 
696
+ **Whether the skill fires unprompted is not measured**, and until 1.2.1 this
697
+ project claimed no command could measure it. That was wrong: `claude plugin
698
+ eval` grades exactly this, with `tool_used: Skill` as a plugin-fired indicator
699
+ and a no-plugin baseline arm. It is unmeasured because the command is in early
700
+ access on the account here and its case schema is undocumented, so a suite
701
+ written from `--help` fragments could not be run even once to see whether it
702
+ loads — and a suite that silently fails to load reads as a gate while checking
703
+ nothing. Since 1.2.0 the four commands give an entry path that does not depend
704
+ on it.
705
+
654
706
  ## 12 · Development
655
707
 
656
708
  ```bash
@@ -3,4 +3,4 @@
3
3
  from .models import CodeUnit, SearchResult
4
4
 
5
5
  __all__ = ["CodeUnit", "SearchResult"]
6
- __version__ = "1.2.0"
6
+ __version__ = "1.3.0"
@@ -201,10 +201,15 @@ SETTINGS: tuple[Setting, ...] = (
201
201
  ),
202
202
  # How much of a question has to reach the index before an answer counts as
203
203
  # evidence rather than as a guess. Measured, not chosen: across two
204
- # repositories, two languages and 126 questions, 0.40 is the largest value
205
- # at which every question that was being answered correctly still is, and
206
- # it silences 47% of questions whose answer is not in the repository at
207
- # all. It is a ratio inside the query, so unlike a score threshold it does
204
+ # repositories, two languages and every question set under `benchmarks/`,
205
+ # 0.40 is the largest value at which every question that was being answered
206
+ # correctly still is. On its own it silences three fifths of the questions
207
+ # whose answer is not in the repository at all; the rest is what
208
+ # `search.min_concentration` below adds. The command is the claim --
209
+ # `repo_queries --questions benchmarks/absent_queries.json
210
+ # --min-concentration 0` -- because a count typed into a comment is a figure
211
+ # nothing checks, and both numbers this sentence used to carry had rotted.
212
+ # It is a ratio inside the query, so unlike a score threshold it does
208
213
  # not move when the corpus or the scale of the ranking does -- the defect
209
214
  # that made `confidence_threshold = 0.8` stop meaning anything.
210
215
  Setting(
@@ -221,8 +226,11 @@ SETTINGS: tuple[Setting, ...] = (
221
226
  # unrelated declarations -- four of six words found in four places with
222
227
  # nothing to do with one another or with what was asked. Measured across
223
228
  # four rulers, requiring a quarter of a query's rarity to land inside one
224
- # unit leaves the two rulers over undescribed code unchanged and roughly
225
- # halves the questions that get an answer they should not have. Rarity-
229
+ # unit leaves the two rulers over undescribed code unchanged and removes
230
+ # most of what the coverage bar alone still answers; the ablation table
231
+ # carries the numbers, in docs/ROADMAP.md, rather than this line, which
232
+ # said "roughly halves" while that table said an order of magnitude.
233
+ # Rarity-
226
234
  # weighted rather than counted, because a unit holding two ordinary words is
227
235
  # not better evidence than one holding the rare word the question is about.
228
236
  Setting(
@@ -119,7 +119,7 @@ class Evidence:
119
119
 
120
120
  Ranking answers "which of these is best". It cannot answer "is any of this
121
121
  an answer", and reading the first as the second is what let a repository
122
- reply to all 32 questions in `benchmarks/absent_queries.json` -- every one
122
+ reply to every question in `benchmarks/absent_queries.json` -- each one
123
123
  about a subject neither repository implements. `where are CUDA kernels
124
124
  dispatched to the device` came back with a test about word counting, on the
125
125
  evidence of `are`, `the`, `to` and `where`. That is not a Chinese problem
@@ -1,9 +1,10 @@
1
1
  """Retrieval must be able to say it has no answer.
2
2
 
3
3
  Ranking always produces a least-bad unit and returns it with a score and a
4
- rank, which read exactly like an answer. Graded against 32 questions about
5
- subjects neither this repository nor `benchmarks/cold_queries.json`'s
6
- repository implements, every single one came back answered -- `where are CUDA
4
+ rank, which read exactly like an answer. Graded against every question in
5
+ `benchmarks/absent_queries.json` -- subjects neither this repository nor
6
+ `benchmarks/cold_queries.json`'s repository implements -- every single one came
7
+ back answered, on both repositories. `where are CUDA
7
8
  kernels dispatched to the device` on the evidence of `are`, `the`, `to` and
8
9
  `where`. These assert the second question retrieval now asks: not which unit
9
10
  ranks highest, but whether any of this is evidence at all.
@@ -321,3 +321,35 @@ def test_every_documented_provider_block_actually_configures_that_provider(tmp_p
321
321
  # arrangement exists to prevent.
322
322
  assert "sk-" not in settings
323
323
  assert {"sentence-transformers", "openai-compatible"} <= seen, f"undocumented providers; README shows {sorted(seen)}"
324
+
325
+
326
+ # --- a measurement nobody can re-run is a claim, not a measurement ----------
327
+
328
+ BENCHMARKS = tuple(
329
+ sorted(
330
+ path.name
331
+ for path in (ROOT / "benchmarks").glob("*.py")
332
+ if path.name != "__init__.py"
333
+ )
334
+ )
335
+
336
+
337
+ def test_every_benchmark_script_is_listed_in_its_own_index():
338
+ """The index of the rulers is discovered, not maintained by hand.
339
+
340
+ 1.3.0 found two figures in the README -- query latency, and the entire Grep
341
+ head-to-head -- produced by scripts that had never been committed. Nobody
342
+ could re-derive either, and the second one meant the phrase "a Grep loop"
343
+ had no definition a reader could argue with.
344
+
345
+ Both directions matter. A script absent from the index is one nobody knows
346
+ to run; a command in the index naming a script that does not exist is the
347
+ install-line defect this repository shipped twice. Discovery by glob is
348
+ what keeps the seventh script from being the one nothing checks.
349
+ """
350
+ index = (ROOT / "benchmarks" / "README.md").read_text(encoding="utf-8")
351
+ assert BENCHMARKS, "benchmarks/ holds no scripts; this guard would pass vacuously"
352
+ for name in BENCHMARKS:
353
+ assert f"benchmarks.{Path(name).stem}" in index, f"{name} is in benchmarks/ and not in its README"
354
+ for module in re.findall(r"benchmarks\.([a-z_]+)", index):
355
+ assert (ROOT / "benchmarks" / f"{module}.py").is_file(), f"the README runs benchmarks.{module}, which does not exist"
File without changes
File without changes