rag-your-code 1.4.2__tar.gz → 1.4.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.
Files changed (51) hide show
  1. {rag_your_code-1.4.2/src/rag_your_code.egg-info → rag_your_code-1.4.3}/PKG-INFO +136 -136
  2. {rag_your_code-1.4.2 → rag_your_code-1.4.3}/README.md +135 -135
  3. {rag_your_code-1.4.2 → rag_your_code-1.4.3}/pyproject.toml +1 -1
  4. {rag_your_code-1.4.2 → rag_your_code-1.4.3/src/rag_your_code.egg-info}/PKG-INFO +136 -136
  5. {rag_your_code-1.4.2 → rag_your_code-1.4.3}/src/ragyourcode/__init__.py +1 -1
  6. {rag_your_code-1.4.2 → rag_your_code-1.4.3}/LICENSE +0 -0
  7. {rag_your_code-1.4.2 → rag_your_code-1.4.3}/setup.cfg +0 -0
  8. {rag_your_code-1.4.2 → rag_your_code-1.4.3}/src/rag_your_code.egg-info/SOURCES.txt +0 -0
  9. {rag_your_code-1.4.2 → rag_your_code-1.4.3}/src/rag_your_code.egg-info/dependency_links.txt +0 -0
  10. {rag_your_code-1.4.2 → rag_your_code-1.4.3}/src/rag_your_code.egg-info/entry_points.txt +0 -0
  11. {rag_your_code-1.4.2 → rag_your_code-1.4.3}/src/rag_your_code.egg-info/requires.txt +0 -0
  12. {rag_your_code-1.4.2 → rag_your_code-1.4.3}/src/rag_your_code.egg-info/top_level.txt +0 -0
  13. {rag_your_code-1.4.2 → rag_your_code-1.4.3}/src/ragyourcode/agentic.py +0 -0
  14. {rag_your_code-1.4.2 → rag_your_code-1.4.3}/src/ragyourcode/annotate.py +0 -0
  15. {rag_your_code-1.4.2 → rag_your_code-1.4.3}/src/ragyourcode/cli.py +0 -0
  16. {rag_your_code-1.4.2 → rag_your_code-1.4.3}/src/ragyourcode/config.py +0 -0
  17. {rag_your_code-1.4.2 → rag_your_code-1.4.3}/src/ragyourcode/descriptions.py +0 -0
  18. {rag_your_code-1.4.2 → rag_your_code-1.4.3}/src/ragyourcode/document.py +0 -0
  19. {rag_your_code-1.4.2 → rag_your_code-1.4.3}/src/ragyourcode/embeddings.py +0 -0
  20. {rag_your_code-1.4.2 → rag_your_code-1.4.3}/src/ragyourcode/graph.py +0 -0
  21. {rag_your_code-1.4.2 → rag_your_code-1.4.3}/src/ragyourcode/indexer.py +0 -0
  22. {rag_your_code-1.4.2 → rag_your_code-1.4.3}/src/ragyourcode/models.py +0 -0
  23. {rag_your_code-1.4.2 → rag_your_code-1.4.3}/src/ragyourcode/parser.py +0 -0
  24. {rag_your_code-1.4.2 → rag_your_code-1.4.3}/src/ragyourcode/providers.py +0 -0
  25. {rag_your_code-1.4.2 → rag_your_code-1.4.3}/src/ragyourcode/py.typed +0 -0
  26. {rag_your_code-1.4.2 → rag_your_code-1.4.3}/src/ragyourcode/search.py +0 -0
  27. {rag_your_code-1.4.2 → rag_your_code-1.4.3}/src/ragyourcode/workflow.py +0 -0
  28. {rag_your_code-1.4.2 → rag_your_code-1.4.3}/tests/test_absent_queries.py +0 -0
  29. {rag_your_code-1.4.2 → rag_your_code-1.4.3}/tests/test_agent_protocol.py +0 -0
  30. {rag_your_code-1.4.2 → rag_your_code-1.4.3}/tests/test_agentic.py +0 -0
  31. {rag_your_code-1.4.2 → rag_your_code-1.4.3}/tests/test_config.py +0 -0
  32. {rag_your_code-1.4.2 → rag_your_code-1.4.3}/tests/test_descriptions.py +0 -0
  33. {rag_your_code-1.4.2 → rag_your_code-1.4.3}/tests/test_doc_comments.py +0 -0
  34. {rag_your_code-1.4.2 → rag_your_code-1.4.3}/tests/test_document.py +0 -0
  35. {rag_your_code-1.4.2 → rag_your_code-1.4.3}/tests/test_e2e_cli.py +0 -0
  36. {rag_your_code-1.4.2 → rag_your_code-1.4.3}/tests/test_evidence.py +0 -0
  37. {rag_your_code-1.4.2 → rag_your_code-1.4.3}/tests/test_golden.py +0 -0
  38. {rag_your_code-1.4.2 → rag_your_code-1.4.3}/tests/test_graph_incremental.py +0 -0
  39. {rag_your_code-1.4.2 → rag_your_code-1.4.3}/tests/test_language_fixtures.py +0 -0
  40. {rag_your_code-1.4.2 → rag_your_code-1.4.3}/tests/test_large_repo.py +0 -0
  41. {rag_your_code-1.4.2 → rag_your_code-1.4.3}/tests/test_local_model.py +0 -0
  42. {rag_your_code-1.4.2 → rag_your_code-1.4.3}/tests/test_metadata.py +0 -0
  43. {rag_your_code-1.4.2 → rag_your_code-1.4.3}/tests/test_multilanguage.py +0 -0
  44. {rag_your_code-1.4.2 → rag_your_code-1.4.3}/tests/test_parser_edges.py +0 -0
  45. {rag_your_code-1.4.2 → rag_your_code-1.4.3}/tests/test_providers.py +0 -0
  46. {rag_your_code-1.4.2 → rag_your_code-1.4.3}/tests/test_ragyourcode.py +0 -0
  47. {rag_your_code-1.4.2 → rag_your_code-1.4.3}/tests/test_ranking.py +0 -0
  48. {rag_your_code-1.4.2 → rag_your_code-1.4.3}/tests/test_repo_queries.py +0 -0
  49. {rag_your_code-1.4.2 → rag_your_code-1.4.3}/tests/test_resilience.py +0 -0
  50. {rag_your_code-1.4.2 → rag_your_code-1.4.3}/tests/test_retrieval_correctness.py +0 -0
  51. {rag_your_code-1.4.2 → rag_your_code-1.4.3}/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.4.2
3
+ Version: 1.4.3
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
@@ -61,11 +61,10 @@ grep for. **Reading whole files** is thorough and blows the context budget:
61
61
  five files of a real repository is tens of thousands of tokens, most of them
62
62
  irrelevant.
63
63
 
64
- Retrieval sits in between and brings a third problem that the first two do
65
- not have. Grep can tell you it found nothing. **A ranking cannot.** It always
64
+ Retrieval sits in between, and brings a third problem the first two do not
65
+ have. Grep can tell you it found nothing. **A ranking cannot.** It always
66
66
  produces a least-bad candidate and returns it with a score and a rank that read
67
- exactly like an answer, whether or not the repository contains anything
68
- relevant. That is the failure this project spent its last two releases on.
67
+ exactly like an answer, whether or not the repository holds anything relevant.
69
68
 
70
69
  ## 2 · What it does
71
70
 
@@ -79,9 +78,9 @@ relevant. That is the failure this project spent its last two releases on.
79
78
  | **Serve** | A CLI, and a JSON-lines protocol for a long-lived agent subprocess. |
80
79
 
81
80
  **Scope.** Retrieval over source declarations. Not a code-understanding model,
82
- not a generation step, not an IDE index. Questions are answered in the
83
- vocabulary somebody wrote down in the code, in its documentation, or in a
84
- description an agent added.
81
+ not a generation step, not an IDE index. Questions are answered in vocabulary
82
+ somebody wrote down: in the code, its documentation, or a description an agent
83
+ added.
85
84
 
86
85
  ## 3 · What is actually hard here
87
86
 
@@ -89,11 +88,11 @@ Three things, and all three are measured rather than argued.
89
88
 
90
89
  ### 3.1 · Ranking cannot say "no answer"
91
90
 
92
- Eight releases measured how well retrieval *finds* the answer. None could
93
- measure what it does when there is no answer, because every question graded had
94
- one. A fourth ruler — thirty questions about subjects neither graded repository
95
- implements — settled it in a single run: **all thirty answered**, in both
96
- languages, on both repositories.
91
+ Eight releases measured how well retrieval *finds* the answer. None could see
92
+ what it does when there is none, because every question graded had one. A
93
+ fourth ruler — thirty questions about subjects neither graded repository
94
+ implements — settled it in one run: **all thirty answered**, both languages,
95
+ both repositories.
97
96
 
98
97
  | asked of a repository containing no such code | answered with | on the evidence of |
99
98
  |---|---|---|
@@ -101,8 +100,8 @@ languages, on both repositories.
101
100
  | `准入控制为什么会拒绝没有资源限额的容器组` | the UTF-8 console setup | `拒绝` `控制` `没有` |
102
101
  | `how is the OAuth refresh token rotated` | a description-store method | `before` `is` `refresh` `the` |
103
102
 
104
- Not a Chinese problem and not a ranking problem — a **missing question**.
105
- Nothing in the pipeline ever asked *is any of this evidence*.
103
+ Not a Chinese problem and not a ranking problem — a **missing question**: nothing
104
+ in the pipeline ever asked *is any of this evidence*.
106
105
 
107
106
  Retrieval now asks two questions that ranking cannot:
108
107
 
@@ -115,28 +114,26 @@ until you notice which half.
115
114
  **Concentration** — what share of the query's *rarity* lands inside a single
116
115
  declaration. Coverage alone asks whether each word occurs somewhere, which a
117
116
  question about a subject nothing here implements can satisfy entirely out of
118
- unrelated units: four of six words found in four different declarations, none
119
- of which has anything to do with the question or with one another. Rarity-
120
- weighted rather than counted, because a unit holding two ordinary words is not
121
- better evidence than one holding the rare word the question is about.
117
+ unrelated units: four of six words in four declarations with nothing to do with
118
+ the question or with one another. Rarity-weighted rather than counted, because
119
+ two ordinary words are not better evidence than the rare word asked about.
122
120
 
123
- Both are **ratios inside the query**, never thresholds on a score. A score
124
- threshold is tied to whatever scale the ranking currently produces, and this
125
- project has already had one silently stop existing the moment BM25F changed
126
- the scale.
121
+ Both are **ratios inside the query**, never thresholds on a score: a score
122
+ threshold is tied to whatever scale the ranking produces, and this project has
123
+ already had one silently stop existing the moment BM25F changed that scale.
127
124
 
128
125
  ### 3.2 · The vector was carrying nothing, and here is why
129
126
 
130
127
  The default embedder is a signed feature hash. Ablating it entirely moves the
131
128
  three positive rulers by **±1 question in either direction** while the vectors
132
- occupy **65.3%** of the index. That was known since 0.6.0 and left unexplained.
129
+ occupy **72.1%** of the index. That was known since 0.6.0 and left unexplained.
133
130
  The explanation, measured here:
134
131
 
135
- - **Not saturation.** Median 56 distinct tokens per unit into 384 buckets;
136
- 13.6% expected occupancy; 0.4% of units exceed the width. Widening to 16,384
137
- raises fidelity to true overlap from r=0.40 to r=0.56 and buys no ranking.
138
- - **Not redundancy.** Its cosine correlates only **+0.45** with the BM25F score
139
- over 26,490 scored candidates, so it does carry variance of its own.
132
+ - **Not saturation.** Median 56 distinct tokens per unit into 384 buckets, 0.4%
133
+ of units over the width; widening to 16,384 raises fidelity from r=0.40 to
134
+ r=0.56 and buys no ranking.
135
+ - **Not redundancy.** Its cosine correlates only **+0.45** with BM25F over
136
+ 26,490 scored candidates, so it does carry variance of its own.
140
137
  - **The variance is the wrong variance.** A signed hash counts every token
141
138
  equally. The independent part of what it measures is therefore precisely the
142
139
  contribution of words that are everywhere — the part rarity weighting exists
@@ -145,20 +142,19 @@ The explanation, measured here:
145
142
  vector cannot make anything retrievable. Six of thirty-five foreign-ruler
146
143
  questions have an accepted answer sharing **no token at all** with the query.
147
144
 
148
- Eight replacement schemes were implemented and measured across releases —
149
- character n-grams, random indexing, truncated SVD, posting-list signatures, a
150
- rarity-weighted hash, call-graph diffusion, postings expansion, authored-fields-
151
- only. None beat using no vector. The conclusion is not that the hash needs
152
- tuning; it is that **a vector computed from the same words cannot know anything
153
- the words do not already say.** Making it useful requires a model — which is
154
- now an installable option, and measured below.
145
+ Eight replacement schemes were measured across releases — character n-grams,
146
+ random indexing, truncated SVD, posting-list signatures, a rarity-weighted
147
+ hash, call-graph diffusion, postings expansion, authored-fields-only. None beat
148
+ using no vector: **a vector computed from the same words cannot know anything
149
+ the words do not already say.** Making it useful takes a model, which is an
150
+ installable option and is measured below.
155
151
 
156
152
  ### 3.3 · Retrieval reaches only what somebody wrote down
157
153
 
158
154
  `retry_charge` tokenizes to one opaque term, not to *retry* and *charge*.
159
- Splitting identifiers was implemented and measured with query and stored
160
- vectors rebuilt together: equal or worse on every ruler, because the pieces are
161
- `get`, `find`, `check`, `test`, which rarity weighting immediately discounts.
155
+ Splitting identifiers was measured with query and stored vectors rebuilt
156
+ together: equal or worse on every ruler, because the pieces are `get`, `find`,
157
+ `check`, `test`, which rarity weighting discounts.
162
158
 
163
159
  So the vocabulary ladder is the answer, cheapest rung first:
164
160
 
@@ -171,6 +167,9 @@ So the vocabulary ladder is the answer, cheapest rung first:
171
167
 
172
168
  ## 4 · How it works
173
169
 
170
+ The same pipeline drawn out, with the refusal path and the three surfaces:
171
+ **[docs/FLOW.md](docs/FLOW.md)**.
172
+
174
173
  ```
175
174
  your repository
176
175
  → walk source files configurable ignores, suffixes, size cap
@@ -192,19 +191,18 @@ qualified names, call lists and spans are exact. Every other language goes
192
191
  through three separated layers: a scanner reading one line at a time, a rule
193
192
  table per language, and a span closer following brace depth, Ruby's `end`, or
194
193
  the next declaration. Because a pattern never sees a second line, a reported
195
- line number **is** the scanner's loop index and cannot drift, and no
196
- declaration can swallow the ones after it. A 441-byte JavaScript file that once
197
- took 12.6 s to parse now takes 0.36 ms.
194
+ line number **is** the loop index and cannot drift, and no declaration can
195
+ swallow the ones after it. A 530-byte JavaScript file that took 12.6 s to parse
196
+ now takes 0.37 ms.
198
197
 
199
- Qualified names come from the spans the closer already produced a declaration
200
- nested inside another's span is nested in it, whatever the braces did on the
201
- way. One mechanism, so there is no second one to disagree with the first.
198
+ Qualified names come from the spans the closer already produced: nested inside
199
+ another's span *is* nested in it, whatever the braces did on the way. One
200
+ mechanism, so there is no second one to disagree with it.
202
201
 
203
- **Ranking.** BM25F with per-field length normalisation. Normalising per field
204
- is the part that matters: measured against one length for the whole unit, a
205
- body repeating a word forty times still beat the declaration actually named
206
- after it, because a long body's advantage in raw count almost exactly cancelled
207
- its penalty for being long.
202
+ **Ranking.** BM25F with per-field length normalisation, which is the part that
203
+ matters: against one length for the whole unit, a body repeating a word forty
204
+ times still beat the declaration named after it, because its raw-count
205
+ advantage cancelled its length penalty.
208
206
 
209
207
  | field | weight | why |
210
208
  |---|---|---|
@@ -218,26 +216,33 @@ its penalty for being long.
218
216
  their low weight the same way a Chinese bigram does — by being everywhere — so
219
217
  it works in a language nobody anticipated and no list has to be maintained.
220
218
 
221
- **Safety.** A repository being scanned is untrusted input, including any
222
- `.rag-your-code/index.json` it ships. Nothing read out of an index may name a
223
- path to act on: superseded vector sidecars are enumerated from the naming
224
- scheme the writer itself uses. A crafted index used to make the documented
225
- `index` command delete an arbitrary in-tree file and report success.
219
+ **Safety.** A scanned repository is untrusted input, including any
220
+ `.rag-your-code/index.json` it ships, so nothing read out of an index may name
221
+ a path to act on: superseded vector sidecars are enumerated from the writer's
222
+ own naming scheme. A crafted index once made `index` delete an arbitrary
223
+ in-tree file and report success.
226
224
 
227
225
  ## 5 · Before and after
228
226
 
229
227
  A question with no lexical shortcut, asked of this repository:
230
228
 
231
- ```console
229
+ ````console
232
230
  $ rag-your-code search "where does it decide whether to answer at all" --limit 1
233
- [src/ragyourcode/search.py:116:Evidence] score=0.449
234
- This class evidence. and calls dataclass. ... Documented intent: Whether a query
235
- reached this index at all, kept apart from how its results rank. ...
231
+ [src/ragyourcode/search.py:117:Evidence] score=0.446
232
+ The verdict on whether a question reached this index at all, kept separate from
233
+ how results rank. ... 中文:判定一个提问究竟有没有够到索引的结论。...
234
+ ```python
235
+ class Evidence:
236
+ """Whether a query reached this index at all, kept apart from ..."""
236
237
  ```
238
+ ````
237
239
 
238
- The same question through Grep is not askable there is no string to search
239
- for. The nearest guess, `grep -rn "decide"`, returns matches scattered across
240
- the repository that a reader must then triage by hand.
240
+ There is no string here to grep for: *decide* occurs nowhere in that
241
+ declaration and matched nothing. What ranked it first is ordinary words —
242
+ *answer*, *whether*, *where* weighted against this corpus, where they are
243
+ rare enough to tell declarations apart. What the agent-written description adds
244
+ is the other language: 「在哪里判定一个提问有没有答案」 returns the same
245
+ declaration first, at 0.392, sharing not one character with its source.
241
246
 
242
247
  Now the case that motivated 1.0.0 and 1.1.0 — a question this repository has no
243
248
  answer to at all:
@@ -261,15 +266,15 @@ it happens to use elsewhere.
261
266
  "matched_terms": ["job","leave","print"],
262
267
  "ubiquitous_terms": ["a","does","the","why"],
263
268
  "coverage": 0.5, "min_coverage": 0.4,
264
- "concentration": 0.1682, "min_concentration": 0.28,
269
+ "concentration": 0.1691, "min_concentration": 0.28,
265
270
  "applied_min_coverage": 0.4, "applied_min_concentration": 0.28,
266
271
  "hint": "..."}}
267
272
  ```
268
273
 
269
- Read `coverage: 0.5` against `concentration: 0.1682`. Half the distinctive
270
- words are here — `job`, `leave`, `print` — and they are spread thin enough that
271
- no declaration holds a sixth of what was asked. Before 1.1.0 that question came
272
- back with a confident-looking result.
274
+ Read `coverage: 0.5` against `concentration: 0.1691`. Half the distinctive
275
+ words are here — `job`, `leave`, `print` — and spread thin enough that no
276
+ declaration holds a fifth of what was asked, against a bar of 0.28. Before
277
+ 1.1.0 that question came back with a confident-looking result.
273
278
 
274
279
  Four reasons, because each is recovered by a different move:
275
280
 
@@ -283,7 +288,7 @@ Four reasons, because each is recovered by a different move:
283
288
  ## 6 · Benchmark dashboard
284
289
 
285
290
  Four rulers, 135 distinct questions in English and Chinese, graded 235 times —
286
- two of them run against both repositories. Three grade whether the answer is
291
+ one of them runs against both repositories. Three grade whether the answer is
287
292
  **found**; the fourth grades whether silence is **kept**. Every report
288
293
  carries a fingerprint of the corpus it graded, because between two runs of an
289
294
  unchanged `search.py` the foreign ruler moved 0.257 → 0.229 purely because that
@@ -321,7 +326,7 @@ Foreign silence fell from 0.933 to 0.833 when the subject changed, and the
321
326
  cause is a limit of the design rather than a defect. A word counts as evidence
322
327
  unless it occurs in more than 5% of units — a stopword list derived from the
323
328
  corpus, so that it needs no list and works in any language. Here `how`, `when`,
324
- `does` and `are` are everywhere, because 304 units carry written English prose.
329
+ `does` and `are` are everywhere, because 301 units carry written English prose.
325
330
  Across 1,572 units of mostly short, undocumented methods they occur in 1–5% of
326
331
  them and start counting as evidence. Five English questions about subjects
327
332
  Flask does not implement get through on exactly that.
@@ -335,8 +340,9 @@ Flask does not implement get through on exactly that.
335
340
  | concentration only | 0.200/0.286/0.238 | 0.314/0.471/0.383 | 0.443/0.614/0.509 | 0.967 / 0.800 |
336
341
  | **both (1.1.0)** | **0.200/0.286/0.238** | **0.314/0.471/0.383** | 0.443/0.614/0.509 | **0.967 / 0.833** |
337
342
 
338
- Ruler A is **unmoved by either bar**, and B by concentration. The whole cost is
339
- three questions of seventy at hit@1 on the warmest ruler, and six at hit@3.
343
+ Ruler A is **unmoved by either bar**; B loses one hit@3 question to either bar
344
+ alone and nothing further when both apply. The rest of the cost is three of
345
+ seventy at hit@1 on the warmest ruler, and six at hit@3.
340
346
 
341
347
  Through 1.3.0 this section said concentration subsumes coverage. **On a corpus
342
348
  this project did not choose, it does not.** Both bars together silence 0.833 of
@@ -425,7 +431,7 @@ framework reversed it. The honest claim is narrower than either table alone:
425
431
 
426
432
  **Once the vocabulary exists, it is not close.**
427
433
 
428
- | this repository · 70 questions · 584 units `c9df00350cbd` · 304 described | Grep loop | rag-your-code |
434
+ | this repository · 70 questions · 584 units `c9df00350cbd` · 301 described | Grep loop | rag-your-code |
429
435
  |---|---|---|
430
436
  | right file first | 22.9% | **58.6%** |
431
437
  | right file in top 3 | 54.3% | **77.1%** |
@@ -434,28 +440,26 @@ framework reversed it. The honest claim is narrower than either table alone:
434
440
  | questions it answers | **61** | 60 |
435
441
 
436
442
  Those two tables are the whole argument of section 3.3, measured against a real
437
- baseline instead of asserted. A cold index retrieves against a sentence the
438
- parser generated from identifiers the author already chose, plus whatever
439
- docstrings the author wrote so how it fares against Grep is decided by how
440
- much prose the repository already contains. Flask has a written docstring on
441
- most public methods, and the cold index beats Grep there without a single
442
- description being added. On the previous subject, a tool with terse comments
443
- and long identifiers, the same cold index lost to Grep by the same margin.
443
+ baseline instead of asserted. A cold index retrieves against a generated
444
+ sentence plus whatever docstrings the author wrote, so how it fares against Grep
445
+ is decided by how much prose the repository already has. Flask documents most
446
+ public methods and the cold index beats Grep there with no description added; on
447
+ the previous subject, terse comments and long identifiers, it lost by the same
448
+ margin.
444
449
 
445
450
  What does not depend on the subject is what descriptions buy: on this
446
451
  repository first-place accuracy goes to **more than double** Grep's, and the
447
452
  payload comes back ranked, spanned, and roughly half the size.
448
453
 
449
- **Both tables come from `python -m benchmarks.grep_baseline`**, which is what
450
- changed in 1.3.0. Until then this section — the strongest claim the project
451
- makes was published from a script that had never been committed, so nothing
452
- here could be checked and the word "Grep loop" had no precise meaning. The
453
- committed version defines it: take the query's words, drop the ones the corpus
454
- itself shows are everywhere, run one substring search per remaining word over
455
- exactly the files the index was built from, rank each file by how many distinct
456
- words hit it, break ties on path. Reconstructing it reproduced this side's
457
- figures exactly and moved Grep's, which is the expected shape the ranked arm
458
- was always a call into shipped code, and the baseline never was.
454
+ **Both tables come from `python -m benchmarks.grep_baseline`**, new in 1.3.0.
455
+ Until then this section — the strongest claim the project makes — came from a
456
+ script that was never committed, so nothing here could be checked and "Grep
457
+ loop" had no precise meaning. The committed version defines it: take the
458
+ query's words, drop the ones the corpus itself shows are everywhere, run one
459
+ substring search per remaining word over exactly the files the index was built
460
+ from, rank each file by how many distinct words hit it, break ties on path.
461
+ Reconstructing it reproduced this side's figures exactly and moved Grep's
462
+ the expected shape, since the ranked arm was always a call into shipped code.
459
463
 
460
464
  Four qualifications, because the table would otherwise flatter both sides:
461
465
 
@@ -490,16 +494,14 @@ the time to describe.
490
494
 
491
495
  **Build the ruler before reshaping the thing measured.** Four candidate scoring
492
496
  changes once landed between five and six correct over an eight-question set —
493
- that is the resolution limit of the instrument, not a ranking of options. There
494
- are 135 now across four rulers, and every claim in this README is a number from
495
- one of them.
497
+ the instrument's resolution limit, not a ranking. There are 135 questions now
498
+ across four rulers, and every claim here is a number from one of them.
496
499
 
497
500
  **Measure somewhere it can fail.** Every ruler this project had once graded a
498
- repository its own authors wrote. Indexed cold against a foreign repository the
499
- same code scored 0.086 hit@1 against a self-reported 0.457. Ruler A exists so
500
- that can never be comfortable again — and stemming, which helps both own-repo
501
- rulers, was rejected on exactly this evidence: it costs the foreign ruler 3 of
502
- 35 hit@3.
501
+ repository its own authors wrote; cold against a foreign one the same code
502
+ scored 0.086 hit@1 against a self-reported 0.457. Ruler A exists so that can
503
+ never be comfortable again — and stemming, which helps both own-repo rulers,
504
+ was rejected on it: 3 of 35 hit@3 on the foreign one.
503
505
 
504
506
  **Make the error structurally impossible rather than checking for it.** A line
505
507
  number that *is* the loop index cannot drift. A description keyed by a digest
@@ -509,18 +511,16 @@ of its own code cannot outlive it.
509
511
  ratios do not.
510
512
 
511
513
  **Derive figures from data; a hand-maintained number is a claim nobody checks.**
512
- The parser fingerprints its own source. The settings table in this README is
513
- asserted against `config.py` in both directions — it had drifted nine settings
514
- behind before that test existed.
514
+ The settings table in this README is asserted against `config.py` in both
515
+ directions — it had drifted nine settings behind before that test existed.
515
516
 
516
- **The contract does not move.** `CodeUnit`, index schema 2, and the JSON-lines
517
+ **The contract does not move.** `CodeUnit`, index schema 2 and the JSON-lines
517
518
  protocol are unchanged across every release; new information arrives in new
518
- fields beside the old ones, never by widening an enumeration callers branch on.
519
+ fields, never by widening an enumeration callers branch on.
519
520
 
520
- **Publish what was measured and rejected.** Twelve changes have been
521
- implemented, measured and dropped. They are recorded in
522
- [docs/ROADMAP.md](docs/ROADMAP.md) with their numbers, because "we tried that
523
- and it cost 3 of 35" is worth more than an unexplored idea.
521
+ **Publish what was measured and rejected.** Twelve changes were implemented,
522
+ measured and dropped, with their numbers, in [docs/ROADMAP.md](docs/ROADMAP.md)
523
+ "we tried that and it cost 3 of 35" beats an unexplored idea.
524
524
 
525
525
  ## 9 · Bringing your own model
526
526
 
@@ -633,10 +633,9 @@ Four commands and one skill. No hooks, no agents, no MCP server:
633
633
 
634
634
  Measured with `claude plugin details` on an installed copy: **~249 tokens added
635
635
  to every session** (skill ~30, each command ~50–60), and 590–2,400 only when
636
- one of them fires. That is up from ~39 in 1.1.0, and the increase is the price
637
- of being findable a skill fires only when a model decides it should, which
638
- left the whole plugin with no entry point a person could discover. The commands
639
- install the Python package on first use.
636
+ one fires. Up from ~39 in 1.1.0, and the increase is the price of being
637
+ findable: a skill fires only when a model decides it should, which left the
638
+ plugin with no entry point a person could discover.
640
639
 
641
640
  **As a CLI:**
642
641
 
@@ -648,14 +647,13 @@ rag-your-code describe status # description coverage
648
647
  rag-your-code describe promote | git apply # move descriptions into the code
649
648
  ```
650
649
 
651
- `bootstrap` exists because indexing is not the same as being searchable. A
652
- fresh index retrieves against the sentence the parser generated, which adds no
653
- word the source did not have. It reports which rung the repository is on and
654
- hands over that rung's work; run it again after each round.
650
+ `bootstrap` exists because indexing is not the same as being searchable: a
651
+ fresh index retrieves against a generated sentence that adds no word the source
652
+ did not have. It reports which rung the repository is on and hands over that
653
+ rung's work; run it again after each round.
655
654
 
656
655
  The index is written under `.rag-your-code/`; **your source files are never
657
- modified**. `describe promote` emits a diff for you to review — the tool never
658
- writes source itself.
656
+ modified**. `describe promote` emits a diff to review — it never writes source.
659
657
 
660
658
  ### Configuration
661
659
 
@@ -711,35 +709,37 @@ than following the console codepage.
711
709
 
712
710
  Named because they are measured, not because they are excuses.
713
711
 
714
- **One English question in fifteen still gets answered when it should not.**
715
- `how is a hostname resolved when the nameserver times out` finds `hostname`,
716
- `resolved` and `times` genuinely co-occurring in one unrelated declaration, on
717
- both repositories. No lexical rule separates a real vocabulary collision from a
718
- real answer. Chinese sits at 1.000 silence on both.
712
+ **One English question in fifteen is answered when it should not be** on this
713
+ repository, and five in fifteen on Flask. `how is a hostname resolved when the
714
+ nameserver times out` finds `hostname`, `resolved` and `times` genuinely
715
+ co-occurring in one unrelated declaration. No lexical rule separates a real
716
+ vocabulary collision from a real answer. Chinese sits at 1.000 silence on both.
719
717
 
720
718
  **Chinese cold-start hit@1 is 0.000** on rulers A and B. Chinese reaches a
721
719
  repository through descriptions or not at all: the code contains no Chinese, so
722
720
  a cold index has no Chinese vocabulary to match. `describe` is the fix and it
723
- works — ruler C is 0.333 — but there is no free rung of the ladder for it.
721
+ works — ruler C is 0.250 on its twelve Chinese questions — but there is no free
722
+ rung of the ladder for it. A Grep loop scores 0.000 there too, on the same
723
+ questions: it is the corpus's limit, not this tool's.
724
724
 
725
725
  **There is no stemming.** `catastrophic backtracking` does not reach
726
726
  `backtracks catastrophically`. A light suffix stripper was implemented and
727
727
  measured on all four rulers: it improves both own-repository rulers and costs
728
728
  the foreign one 3 of 35 hit@3, so it was rejected.
729
729
 
730
- **A test declaration sometimes outranks real code** — 10 of 175 questions across
731
- three rulers, where a test at rank 1 displaced an accepted answer at rank 2–3.
732
- The long-standing explanation, that a test outranks the code it *tests*, is
733
- wrong: of the eight inspected, seven are unrelated tests winning on
734
- prose. A callee-before-caller rerank fires on zero questions and the `name`
735
- field weight moves nothing, because an underscored test name is a single token.
730
+ **A test declaration sometimes outranks real code** — 9 of 175 questions across
731
+ three rulers, a test at rank 1 displacing an accepted answer at rank 2–3, and
732
+ none of them on Flask. The long-standing explanation, that a test outranks the
733
+ code it *tests*, is wrong: five of the nine are unrelated tests winning on
734
+ prose. A callee-before-caller rerank fires on zero questions, and the `name`
735
+ field weight moves nothing because an underscored test name is one token.
736
736
 
737
- **The vectors are 65.3% of the index and earn ±1 question** under the default
738
- embedder. Not removed: the same storage is what makes an optional model work,
739
- and the schema stays one shape.
737
+ **The vectors are 72.1% of the index and earn ±1 question** under the default
738
+ embedder. Kept: the same storage is what makes an optional model work.
740
739
 
741
- **`search.vector_recall` scans every vector per query.** Affordable at the
742
- measured envelope, and exactly the work an ANN index would replace.
740
+ **`search.vector_recall` scans every vector per query** under a semantic
741
+ embedder. The default hash never widens at all. Affordable at the measured
742
+ envelope, and exactly the work an ANN index would replace.
743
743
 
744
744
  **Tree-sitter parsing and a SQLite/ANN storage layer are not here.** Both would
745
745
  need a dependency, and the policy for those is settled: they follow the
@@ -765,9 +765,9 @@ pytest -q
765
765
 
766
766
  Per-release test counts are in [CHANGELOG.md](CHANGELOG.md); a bare figure in a
767
767
  living document is a claim that rots. CI runs Python 3.10–3.13 on Linux and
768
- Windows, plus a job that installs the built wheel into a clean environment and
769
- runs every documented command, and another that runs the skill's own install
770
- line verbatim.
768
+ Windows, installs the built wheel into a clean environment and runs the
769
+ documented CLI end to end `bootstrap` through `describe promote` plus the
770
+ skill's own install line verbatim.
771
771
 
772
772
  - [docs/FLOW.md](docs/FLOW.md) — the whole thing in four diagrams
773
773
  - [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) — how each stage works and why