rag-your-code 1.4.0__tar.gz → 1.4.2__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.
- {rag_your_code-1.4.0/src/rag_your_code.egg-info → rag_your_code-1.4.2}/PKG-INFO +42 -31
- {rag_your_code-1.4.0 → rag_your_code-1.4.2}/README.md +41 -30
- {rag_your_code-1.4.0 → rag_your_code-1.4.2}/pyproject.toml +1 -1
- {rag_your_code-1.4.0 → rag_your_code-1.4.2/src/rag_your_code.egg-info}/PKG-INFO +42 -31
- {rag_your_code-1.4.0 → rag_your_code-1.4.2}/src/ragyourcode/__init__.py +1 -1
- {rag_your_code-1.4.0 → rag_your_code-1.4.2}/src/ragyourcode/annotate.py +9 -1
- {rag_your_code-1.4.0 → rag_your_code-1.4.2}/src/ragyourcode/document.py +1 -1
- {rag_your_code-1.4.0 → rag_your_code-1.4.2}/src/ragyourcode/parser.py +2 -2
- {rag_your_code-1.4.0 → rag_your_code-1.4.2}/src/ragyourcode/search.py +36 -1
- {rag_your_code-1.4.0 → rag_your_code-1.4.2}/tests/test_ranking.py +54 -0
- {rag_your_code-1.4.0 → rag_your_code-1.4.2}/LICENSE +0 -0
- {rag_your_code-1.4.0 → rag_your_code-1.4.2}/setup.cfg +0 -0
- {rag_your_code-1.4.0 → rag_your_code-1.4.2}/src/rag_your_code.egg-info/SOURCES.txt +0 -0
- {rag_your_code-1.4.0 → rag_your_code-1.4.2}/src/rag_your_code.egg-info/dependency_links.txt +0 -0
- {rag_your_code-1.4.0 → rag_your_code-1.4.2}/src/rag_your_code.egg-info/entry_points.txt +0 -0
- {rag_your_code-1.4.0 → rag_your_code-1.4.2}/src/rag_your_code.egg-info/requires.txt +0 -0
- {rag_your_code-1.4.0 → rag_your_code-1.4.2}/src/rag_your_code.egg-info/top_level.txt +0 -0
- {rag_your_code-1.4.0 → rag_your_code-1.4.2}/src/ragyourcode/agentic.py +0 -0
- {rag_your_code-1.4.0 → rag_your_code-1.4.2}/src/ragyourcode/cli.py +0 -0
- {rag_your_code-1.4.0 → rag_your_code-1.4.2}/src/ragyourcode/config.py +0 -0
- {rag_your_code-1.4.0 → rag_your_code-1.4.2}/src/ragyourcode/descriptions.py +0 -0
- {rag_your_code-1.4.0 → rag_your_code-1.4.2}/src/ragyourcode/embeddings.py +0 -0
- {rag_your_code-1.4.0 → rag_your_code-1.4.2}/src/ragyourcode/graph.py +0 -0
- {rag_your_code-1.4.0 → rag_your_code-1.4.2}/src/ragyourcode/indexer.py +0 -0
- {rag_your_code-1.4.0 → rag_your_code-1.4.2}/src/ragyourcode/models.py +0 -0
- {rag_your_code-1.4.0 → rag_your_code-1.4.2}/src/ragyourcode/providers.py +0 -0
- {rag_your_code-1.4.0 → rag_your_code-1.4.2}/src/ragyourcode/py.typed +0 -0
- {rag_your_code-1.4.0 → rag_your_code-1.4.2}/src/ragyourcode/workflow.py +0 -0
- {rag_your_code-1.4.0 → rag_your_code-1.4.2}/tests/test_absent_queries.py +0 -0
- {rag_your_code-1.4.0 → rag_your_code-1.4.2}/tests/test_agent_protocol.py +0 -0
- {rag_your_code-1.4.0 → rag_your_code-1.4.2}/tests/test_agentic.py +0 -0
- {rag_your_code-1.4.0 → rag_your_code-1.4.2}/tests/test_config.py +0 -0
- {rag_your_code-1.4.0 → rag_your_code-1.4.2}/tests/test_descriptions.py +0 -0
- {rag_your_code-1.4.0 → rag_your_code-1.4.2}/tests/test_doc_comments.py +0 -0
- {rag_your_code-1.4.0 → rag_your_code-1.4.2}/tests/test_document.py +0 -0
- {rag_your_code-1.4.0 → rag_your_code-1.4.2}/tests/test_e2e_cli.py +0 -0
- {rag_your_code-1.4.0 → rag_your_code-1.4.2}/tests/test_evidence.py +0 -0
- {rag_your_code-1.4.0 → rag_your_code-1.4.2}/tests/test_golden.py +0 -0
- {rag_your_code-1.4.0 → rag_your_code-1.4.2}/tests/test_graph_incremental.py +0 -0
- {rag_your_code-1.4.0 → rag_your_code-1.4.2}/tests/test_language_fixtures.py +0 -0
- {rag_your_code-1.4.0 → rag_your_code-1.4.2}/tests/test_large_repo.py +0 -0
- {rag_your_code-1.4.0 → rag_your_code-1.4.2}/tests/test_local_model.py +0 -0
- {rag_your_code-1.4.0 → rag_your_code-1.4.2}/tests/test_metadata.py +0 -0
- {rag_your_code-1.4.0 → rag_your_code-1.4.2}/tests/test_multilanguage.py +0 -0
- {rag_your_code-1.4.0 → rag_your_code-1.4.2}/tests/test_parser_edges.py +0 -0
- {rag_your_code-1.4.0 → rag_your_code-1.4.2}/tests/test_providers.py +0 -0
- {rag_your_code-1.4.0 → rag_your_code-1.4.2}/tests/test_ragyourcode.py +0 -0
- {rag_your_code-1.4.0 → rag_your_code-1.4.2}/tests/test_repo_queries.py +0 -0
- {rag_your_code-1.4.0 → rag_your_code-1.4.2}/tests/test_resilience.py +0 -0
- {rag_your_code-1.4.0 → rag_your_code-1.4.2}/tests/test_retrieval_correctness.py +0 -0
- {rag_your_code-1.4.0 → rag_your_code-1.4.2}/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.
|
|
3
|
+
Version: 1.4.2
|
|
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
|
|
@@ -295,11 +295,11 @@ repository had grown by ninety units.
|
|
|
295
295
|
|---|---|---|---|---|---|
|
|
296
296
|
| **A** Flask 3.1.3, no descriptions | what a first-time user gets | 35 | 0.200 | 0.286 | 0.238 |
|
|
297
297
|
| **B** this repo, generated descriptions only | a cold index of familiar code | 70 | 0.314 | 0.471 | 0.383 |
|
|
298
|
-
| **C** this repo, agent-written descriptions | the warmest case supported | 70 | 0.443 | 0.614 | 0.
|
|
298
|
+
| **C** this repo, agent-written descriptions | the warmest case supported | 70 | 0.443 | 0.614 | 0.509 |
|
|
299
299
|
|
|
300
300
|
The corpora, without which none of the above is reproducible — **A** 1,572
|
|
301
|
-
units, `5fd51169eacc`; **B**
|
|
302
|
-
`
|
|
301
|
+
units, `5fd51169eacc`; **B** 584 units, `fb1f841fa43a`; **C** 584 units,
|
|
302
|
+
`c9df00350cbd`. Ruler A grades **a copy of Flask 3.1.3 carried in this
|
|
303
303
|
repository**, at [`benchmarks/corpus/flask`](benchmarks/corpus/flask), pinned
|
|
304
304
|
to commit `22d9247`. Through 1.3.0 it graded a checkout on one machine, and
|
|
305
305
|
that cost three things: two questions pointed at a declaration the subject had
|
|
@@ -330,10 +330,10 @@ Flask does not implement get through on exactly that.
|
|
|
330
330
|
|
|
331
331
|
| gate | A hit@1/3/MRR | B hit@1/3/MRR | C hit@1/3/MRR | silence own / foreign |
|
|
332
332
|
|---|---|---|---|---|
|
|
333
|
-
| neither (pre-1.0.0) | 0.200/0.286/0.238 | 0.314/0.486/0.391 | 0.486/0.700/0.
|
|
334
|
-
| coverage only (1.0.0) | 0.200/0.286/0.238 | 0.314/0.471/0.383 | 0.486/0.686/0.
|
|
335
|
-
| concentration only | 0.200/0.286/0.238 | 0.314/0.471/0.383 | 0.443/0.614/0.
|
|
336
|
-
| **both (1.1.0)** | **0.200/0.286/0.238** | **0.314/0.471/0.383** | 0.443/0.614/0.
|
|
333
|
+
| neither (pre-1.0.0) | 0.200/0.286/0.238 | 0.314/0.486/0.391 | 0.486/0.700/0.569 | 0.000 / 0.000 |
|
|
334
|
+
| coverage only (1.0.0) | 0.200/0.286/0.238 | 0.314/0.471/0.383 | 0.486/0.686/0.564 | 0.567 / 0.733 |
|
|
335
|
+
| 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
|
+
| **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
337
|
|
|
338
338
|
Ruler A is **unmoved by either bar**, and B by concentration. The whole cost is
|
|
339
339
|
three questions of seventy at hit@1 on the warmest ruler, and six at hit@3.
|
|
@@ -352,20 +352,20 @@ from 0.314 and C to 0.329 from 0.443. **0.28 was chosen before this corpus
|
|
|
352
352
|
existed and survived meeting it**, which is the only kind of evidence a default
|
|
353
353
|
can have.
|
|
354
354
|
|
|
355
|
-
**Latency** — warm corpus,
|
|
355
|
+
**Latency** — warm corpus, 584 units `c9df00350cbd`, five consecutive
|
|
356
356
|
invocations of `python -m benchmarks.query_latency --repeats 10` (420 samples
|
|
357
357
|
each):
|
|
358
358
|
|
|
359
359
|
| | | across the five |
|
|
360
360
|
|---|---|---|
|
|
361
|
-
| query, median | **
|
|
362
|
-
| query, p95 | 1.
|
|
363
|
-
| refusing an unanswerable query | **0.
|
|
364
|
-
| refusal cheaper than answering by | **~36×** |
|
|
361
|
+
| query, median | **0.61 ms** | 0.60 – 0.64 |
|
|
362
|
+
| query, p95 | 1.09 ms | 1.06 – 1.14 |
|
|
363
|
+
| refusing an unanswerable query | **0.017 ms** | 0.015 – 0.019 |
|
|
364
|
+
| refusal cheaper than answering by | **~36×** | 33 – 41 |
|
|
365
365
|
|
|
366
366
|
Two significant figures and a spread, because that is the precision the
|
|
367
|
-
measurement has. Across
|
|
368
|
-
machine the median has landed anywhere from 0.
|
|
367
|
+
measurement has. Across twenty invocations over three releases on the same idle
|
|
368
|
+
machine the median has landed anywhere from 0.51 to 1.44 ms and p95 from 0.85
|
|
369
369
|
to 7.34 ms — a band wider than any change the code has ever made to this
|
|
370
370
|
number. Releases before 1.3.0 published `0.83 ms / p95 1.68 ms` to three
|
|
371
371
|
figures from a script that was never committed; both values sit inside that
|
|
@@ -420,17 +420,17 @@ framework reversed it. The honest claim is narrower than either table alone:
|
|
|
420
420
|
| right file first | 22.9% | **37.1%** |
|
|
421
421
|
| right file in top 3 | 45.7% | **57.1%** |
|
|
422
422
|
| lines it hands back, all questions | 17,641 | — |
|
|
423
|
-
| characters returned, all questions | 1,415,656 | **
|
|
423
|
+
| characters returned, all questions | 1,415,656 | **258,236** |
|
|
424
424
|
| questions it answers | 30 | 30 |
|
|
425
425
|
|
|
426
426
|
**Once the vocabulary exists, it is not close.**
|
|
427
427
|
|
|
428
|
-
| this repository · 70 questions ·
|
|
428
|
+
| this repository · 70 questions · 584 units `c9df00350cbd` · 304 described | Grep loop | rag-your-code |
|
|
429
429
|
|---|---|---|
|
|
430
430
|
| right file first | 22.9% | **58.6%** |
|
|
431
|
-
| right file in top 3 | 54.3% | **
|
|
432
|
-
| lines it hands back, all questions | 11,
|
|
433
|
-
| characters returned, all questions | 1,
|
|
431
|
+
| right file in top 3 | 54.3% | **77.1%** |
|
|
432
|
+
| lines it hands back, all questions | 11,959 | — |
|
|
433
|
+
| characters returned, all questions | 1,135,411 | **615,673** |
|
|
434
434
|
| questions it answers | **61** | 60 |
|
|
435
435
|
|
|
436
436
|
Those two tables are the whole argument of section 3.3, measured against a real
|
|
@@ -467,13 +467,15 @@ Four qualifications, because the table would otherwise flatter both sides:
|
|
|
467
467
|
`the` gets every file back in no order. It is also why Grep declines nine of
|
|
468
468
|
the seventy questions here — those had no word left that this corpus does not
|
|
469
469
|
use everywhere.
|
|
470
|
-
- **Payload is counted in characters on both sides.**
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
470
|
+
- **Payload is counted in characters on both sides.** Grep hands back 18,600
|
|
471
|
+
characters per question it answers here, unranked and without spans, against
|
|
472
|
+
10,300 ranked and capped by `search.max_chars` — a factor of 1.8, and 5.5 on
|
|
473
|
+
Flask, where a framework repeats its vocabulary across many files and Grep
|
|
474
|
+
cannot rank what it finds. 1.4.1 changed what fits in that cap: the block had
|
|
475
|
+
been reprinting the docstring the code below already showed, 2,381 of 3,382
|
|
476
|
+
characters of prose header on Flask, so the same budget now carries **119
|
|
477
|
+
declarations instead of 92** there and 323 instead of 305 here. Both sides
|
|
478
|
+
decline the same five of those 35 — the five Chinese ones, all of them. A Chinese word is
|
|
477
479
|
not a substring of English source and it is not a token in an index built
|
|
478
480
|
from English source, so on a repository written in one language the cold
|
|
479
481
|
cross-language case is not this tool's failure but the corpus's.
|
|
@@ -554,10 +556,10 @@ against a pinned corpus:
|
|
|
554
556
|
| **C** own, described | 581 `978a1d48a82a` | **0.443 / 0.614 / 0.507** | 0.429 / 0.600 / 0.500 |
|
|
555
557
|
| **D** silence, own / foreign | as above | 0.967 / 0.833 | 0.967 / 0.833 |
|
|
556
558
|
|
|
557
|
-
**Worse or identical on every ruler.**
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
559
|
+
**Worse or identical on every ruler.** The 581-unit stamps are the corpus both
|
|
560
|
+
arms shared, kept rather than refreshed — that is what a stamp is for. It ships
|
|
561
|
+
anyway because it does one thing the hash cannot and these rulers cannot see:
|
|
562
|
+
reach a unit sharing no word with the question. The pairs it scores zero on:
|
|
561
563
|
|
|
562
564
|
| pair | signed hash | MiniLM |
|
|
563
565
|
|---|---|---|
|
|
@@ -612,6 +614,14 @@ measured worse, so it stays off there.
|
|
|
612
614
|
/reload-plugins
|
|
613
615
|
```
|
|
614
616
|
|
|
617
|
+
Updating needs the full id and a marketplace refresh first; the bare name is
|
|
618
|
+
refused with `Plugin "rag-your-code" not found`, which reads like it is gone:
|
|
619
|
+
|
|
620
|
+
```bash
|
|
621
|
+
claude plugin marketplace update rag-your-code
|
|
622
|
+
claude plugin update rag-your-code@rag-your-code # then restart
|
|
623
|
+
```
|
|
624
|
+
|
|
615
625
|
Four commands and one skill. No hooks, no agents, no MCP server:
|
|
616
626
|
|
|
617
627
|
| | |
|
|
@@ -759,6 +769,7 @@ Windows, plus a job that installs the built wheel into a clean environment and
|
|
|
759
769
|
runs every documented command, and another that runs the skill's own install
|
|
760
770
|
line verbatim.
|
|
761
771
|
|
|
772
|
+
- [docs/FLOW.md](docs/FLOW.md) — the whole thing in four diagrams
|
|
762
773
|
- [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) — how each stage works and why
|
|
763
774
|
- [docs/TESTING.md](docs/TESTING.md) — what the suites protect
|
|
764
775
|
- [docs/ROADMAP.md](docs/ROADMAP.md) — what shipped, what was rejected and why
|
|
@@ -266,11 +266,11 @@ repository had grown by ninety units.
|
|
|
266
266
|
|---|---|---|---|---|---|
|
|
267
267
|
| **A** Flask 3.1.3, no descriptions | what a first-time user gets | 35 | 0.200 | 0.286 | 0.238 |
|
|
268
268
|
| **B** this repo, generated descriptions only | a cold index of familiar code | 70 | 0.314 | 0.471 | 0.383 |
|
|
269
|
-
| **C** this repo, agent-written descriptions | the warmest case supported | 70 | 0.443 | 0.614 | 0.
|
|
269
|
+
| **C** this repo, agent-written descriptions | the warmest case supported | 70 | 0.443 | 0.614 | 0.509 |
|
|
270
270
|
|
|
271
271
|
The corpora, without which none of the above is reproducible — **A** 1,572
|
|
272
|
-
units, `5fd51169eacc`; **B**
|
|
273
|
-
`
|
|
272
|
+
units, `5fd51169eacc`; **B** 584 units, `fb1f841fa43a`; **C** 584 units,
|
|
273
|
+
`c9df00350cbd`. Ruler A grades **a copy of Flask 3.1.3 carried in this
|
|
274
274
|
repository**, at [`benchmarks/corpus/flask`](benchmarks/corpus/flask), pinned
|
|
275
275
|
to commit `22d9247`. Through 1.3.0 it graded a checkout on one machine, and
|
|
276
276
|
that cost three things: two questions pointed at a declaration the subject had
|
|
@@ -301,10 +301,10 @@ Flask does not implement get through on exactly that.
|
|
|
301
301
|
|
|
302
302
|
| gate | A hit@1/3/MRR | B hit@1/3/MRR | C hit@1/3/MRR | silence own / foreign |
|
|
303
303
|
|---|---|---|---|---|
|
|
304
|
-
| neither (pre-1.0.0) | 0.200/0.286/0.238 | 0.314/0.486/0.391 | 0.486/0.700/0.
|
|
305
|
-
| coverage only (1.0.0) | 0.200/0.286/0.238 | 0.314/0.471/0.383 | 0.486/0.686/0.
|
|
306
|
-
| concentration only | 0.200/0.286/0.238 | 0.314/0.471/0.383 | 0.443/0.614/0.
|
|
307
|
-
| **both (1.1.0)** | **0.200/0.286/0.238** | **0.314/0.471/0.383** | 0.443/0.614/0.
|
|
304
|
+
| neither (pre-1.0.0) | 0.200/0.286/0.238 | 0.314/0.486/0.391 | 0.486/0.700/0.569 | 0.000 / 0.000 |
|
|
305
|
+
| coverage only (1.0.0) | 0.200/0.286/0.238 | 0.314/0.471/0.383 | 0.486/0.686/0.564 | 0.567 / 0.733 |
|
|
306
|
+
| concentration only | 0.200/0.286/0.238 | 0.314/0.471/0.383 | 0.443/0.614/0.509 | 0.967 / 0.800 |
|
|
307
|
+
| **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** |
|
|
308
308
|
|
|
309
309
|
Ruler A is **unmoved by either bar**, and B by concentration. The whole cost is
|
|
310
310
|
three questions of seventy at hit@1 on the warmest ruler, and six at hit@3.
|
|
@@ -323,20 +323,20 @@ from 0.314 and C to 0.329 from 0.443. **0.28 was chosen before this corpus
|
|
|
323
323
|
existed and survived meeting it**, which is the only kind of evidence a default
|
|
324
324
|
can have.
|
|
325
325
|
|
|
326
|
-
**Latency** — warm corpus,
|
|
326
|
+
**Latency** — warm corpus, 584 units `c9df00350cbd`, five consecutive
|
|
327
327
|
invocations of `python -m benchmarks.query_latency --repeats 10` (420 samples
|
|
328
328
|
each):
|
|
329
329
|
|
|
330
330
|
| | | across the five |
|
|
331
331
|
|---|---|---|
|
|
332
|
-
| query, median | **
|
|
333
|
-
| query, p95 | 1.
|
|
334
|
-
| refusing an unanswerable query | **0.
|
|
335
|
-
| refusal cheaper than answering by | **~36×** |
|
|
332
|
+
| query, median | **0.61 ms** | 0.60 – 0.64 |
|
|
333
|
+
| query, p95 | 1.09 ms | 1.06 – 1.14 |
|
|
334
|
+
| refusing an unanswerable query | **0.017 ms** | 0.015 – 0.019 |
|
|
335
|
+
| refusal cheaper than answering by | **~36×** | 33 – 41 |
|
|
336
336
|
|
|
337
337
|
Two significant figures and a spread, because that is the precision the
|
|
338
|
-
measurement has. Across
|
|
339
|
-
machine the median has landed anywhere from 0.
|
|
338
|
+
measurement has. Across twenty invocations over three releases on the same idle
|
|
339
|
+
machine the median has landed anywhere from 0.51 to 1.44 ms and p95 from 0.85
|
|
340
340
|
to 7.34 ms — a band wider than any change the code has ever made to this
|
|
341
341
|
number. Releases before 1.3.0 published `0.83 ms / p95 1.68 ms` to three
|
|
342
342
|
figures from a script that was never committed; both values sit inside that
|
|
@@ -391,17 +391,17 @@ framework reversed it. The honest claim is narrower than either table alone:
|
|
|
391
391
|
| right file first | 22.9% | **37.1%** |
|
|
392
392
|
| right file in top 3 | 45.7% | **57.1%** |
|
|
393
393
|
| lines it hands back, all questions | 17,641 | — |
|
|
394
|
-
| characters returned, all questions | 1,415,656 | **
|
|
394
|
+
| characters returned, all questions | 1,415,656 | **258,236** |
|
|
395
395
|
| questions it answers | 30 | 30 |
|
|
396
396
|
|
|
397
397
|
**Once the vocabulary exists, it is not close.**
|
|
398
398
|
|
|
399
|
-
| this repository · 70 questions ·
|
|
399
|
+
| this repository · 70 questions · 584 units `c9df00350cbd` · 304 described | Grep loop | rag-your-code |
|
|
400
400
|
|---|---|---|
|
|
401
401
|
| right file first | 22.9% | **58.6%** |
|
|
402
|
-
| right file in top 3 | 54.3% | **
|
|
403
|
-
| lines it hands back, all questions | 11,
|
|
404
|
-
| characters returned, all questions | 1,
|
|
402
|
+
| right file in top 3 | 54.3% | **77.1%** |
|
|
403
|
+
| lines it hands back, all questions | 11,959 | — |
|
|
404
|
+
| characters returned, all questions | 1,135,411 | **615,673** |
|
|
405
405
|
| questions it answers | **61** | 60 |
|
|
406
406
|
|
|
407
407
|
Those two tables are the whole argument of section 3.3, measured against a real
|
|
@@ -438,13 +438,15 @@ Four qualifications, because the table would otherwise flatter both sides:
|
|
|
438
438
|
`the` gets every file back in no order. It is also why Grep declines nine of
|
|
439
439
|
the seventy questions here — those had no word left that this corpus does not
|
|
440
440
|
use everywhere.
|
|
441
|
-
- **Payload is counted in characters on both sides.**
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
441
|
+
- **Payload is counted in characters on both sides.** Grep hands back 18,600
|
|
442
|
+
characters per question it answers here, unranked and without spans, against
|
|
443
|
+
10,300 ranked and capped by `search.max_chars` — a factor of 1.8, and 5.5 on
|
|
444
|
+
Flask, where a framework repeats its vocabulary across many files and Grep
|
|
445
|
+
cannot rank what it finds. 1.4.1 changed what fits in that cap: the block had
|
|
446
|
+
been reprinting the docstring the code below already showed, 2,381 of 3,382
|
|
447
|
+
characters of prose header on Flask, so the same budget now carries **119
|
|
448
|
+
declarations instead of 92** there and 323 instead of 305 here. Both sides
|
|
449
|
+
decline the same five of those 35 — the five Chinese ones, all of them. A Chinese word is
|
|
448
450
|
not a substring of English source and it is not a token in an index built
|
|
449
451
|
from English source, so on a repository written in one language the cold
|
|
450
452
|
cross-language case is not this tool's failure but the corpus's.
|
|
@@ -525,10 +527,10 @@ against a pinned corpus:
|
|
|
525
527
|
| **C** own, described | 581 `978a1d48a82a` | **0.443 / 0.614 / 0.507** | 0.429 / 0.600 / 0.500 |
|
|
526
528
|
| **D** silence, own / foreign | as above | 0.967 / 0.833 | 0.967 / 0.833 |
|
|
527
529
|
|
|
528
|
-
**Worse or identical on every ruler.**
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
530
|
+
**Worse or identical on every ruler.** The 581-unit stamps are the corpus both
|
|
531
|
+
arms shared, kept rather than refreshed — that is what a stamp is for. It ships
|
|
532
|
+
anyway because it does one thing the hash cannot and these rulers cannot see:
|
|
533
|
+
reach a unit sharing no word with the question. The pairs it scores zero on:
|
|
532
534
|
|
|
533
535
|
| pair | signed hash | MiniLM |
|
|
534
536
|
|---|---|---|
|
|
@@ -583,6 +585,14 @@ measured worse, so it stays off there.
|
|
|
583
585
|
/reload-plugins
|
|
584
586
|
```
|
|
585
587
|
|
|
588
|
+
Updating needs the full id and a marketplace refresh first; the bare name is
|
|
589
|
+
refused with `Plugin "rag-your-code" not found`, which reads like it is gone:
|
|
590
|
+
|
|
591
|
+
```bash
|
|
592
|
+
claude plugin marketplace update rag-your-code
|
|
593
|
+
claude plugin update rag-your-code@rag-your-code # then restart
|
|
594
|
+
```
|
|
595
|
+
|
|
586
596
|
Four commands and one skill. No hooks, no agents, no MCP server:
|
|
587
597
|
|
|
588
598
|
| | |
|
|
@@ -730,6 +740,7 @@ Windows, plus a job that installs the built wheel into a clean environment and
|
|
|
730
740
|
runs every documented command, and another that runs the skill's own install
|
|
731
741
|
line verbatim.
|
|
732
742
|
|
|
743
|
+
- [docs/FLOW.md](docs/FLOW.md) — the whole thing in four diagrams
|
|
733
744
|
- [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) — how each stage works and why
|
|
734
745
|
- [docs/TESTING.md](docs/TESTING.md) — what the suites protect
|
|
735
746
|
- [docs/ROADMAP.md](docs/ROADMAP.md) — what shipped, what was rejected and why
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: rag-your-code
|
|
3
|
-
Version: 1.4.
|
|
3
|
+
Version: 1.4.2
|
|
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
|
|
@@ -295,11 +295,11 @@ repository had grown by ninety units.
|
|
|
295
295
|
|---|---|---|---|---|---|
|
|
296
296
|
| **A** Flask 3.1.3, no descriptions | what a first-time user gets | 35 | 0.200 | 0.286 | 0.238 |
|
|
297
297
|
| **B** this repo, generated descriptions only | a cold index of familiar code | 70 | 0.314 | 0.471 | 0.383 |
|
|
298
|
-
| **C** this repo, agent-written descriptions | the warmest case supported | 70 | 0.443 | 0.614 | 0.
|
|
298
|
+
| **C** this repo, agent-written descriptions | the warmest case supported | 70 | 0.443 | 0.614 | 0.509 |
|
|
299
299
|
|
|
300
300
|
The corpora, without which none of the above is reproducible — **A** 1,572
|
|
301
|
-
units, `5fd51169eacc`; **B**
|
|
302
|
-
`
|
|
301
|
+
units, `5fd51169eacc`; **B** 584 units, `fb1f841fa43a`; **C** 584 units,
|
|
302
|
+
`c9df00350cbd`. Ruler A grades **a copy of Flask 3.1.3 carried in this
|
|
303
303
|
repository**, at [`benchmarks/corpus/flask`](benchmarks/corpus/flask), pinned
|
|
304
304
|
to commit `22d9247`. Through 1.3.0 it graded a checkout on one machine, and
|
|
305
305
|
that cost three things: two questions pointed at a declaration the subject had
|
|
@@ -330,10 +330,10 @@ Flask does not implement get through on exactly that.
|
|
|
330
330
|
|
|
331
331
|
| gate | A hit@1/3/MRR | B hit@1/3/MRR | C hit@1/3/MRR | silence own / foreign |
|
|
332
332
|
|---|---|---|---|---|
|
|
333
|
-
| neither (pre-1.0.0) | 0.200/0.286/0.238 | 0.314/0.486/0.391 | 0.486/0.700/0.
|
|
334
|
-
| coverage only (1.0.0) | 0.200/0.286/0.238 | 0.314/0.471/0.383 | 0.486/0.686/0.
|
|
335
|
-
| concentration only | 0.200/0.286/0.238 | 0.314/0.471/0.383 | 0.443/0.614/0.
|
|
336
|
-
| **both (1.1.0)** | **0.200/0.286/0.238** | **0.314/0.471/0.383** | 0.443/0.614/0.
|
|
333
|
+
| neither (pre-1.0.0) | 0.200/0.286/0.238 | 0.314/0.486/0.391 | 0.486/0.700/0.569 | 0.000 / 0.000 |
|
|
334
|
+
| coverage only (1.0.0) | 0.200/0.286/0.238 | 0.314/0.471/0.383 | 0.486/0.686/0.564 | 0.567 / 0.733 |
|
|
335
|
+
| 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
|
+
| **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
337
|
|
|
338
338
|
Ruler A is **unmoved by either bar**, and B by concentration. The whole cost is
|
|
339
339
|
three questions of seventy at hit@1 on the warmest ruler, and six at hit@3.
|
|
@@ -352,20 +352,20 @@ from 0.314 and C to 0.329 from 0.443. **0.28 was chosen before this corpus
|
|
|
352
352
|
existed and survived meeting it**, which is the only kind of evidence a default
|
|
353
353
|
can have.
|
|
354
354
|
|
|
355
|
-
**Latency** — warm corpus,
|
|
355
|
+
**Latency** — warm corpus, 584 units `c9df00350cbd`, five consecutive
|
|
356
356
|
invocations of `python -m benchmarks.query_latency --repeats 10` (420 samples
|
|
357
357
|
each):
|
|
358
358
|
|
|
359
359
|
| | | across the five |
|
|
360
360
|
|---|---|---|
|
|
361
|
-
| query, median | **
|
|
362
|
-
| query, p95 | 1.
|
|
363
|
-
| refusing an unanswerable query | **0.
|
|
364
|
-
| refusal cheaper than answering by | **~36×** |
|
|
361
|
+
| query, median | **0.61 ms** | 0.60 – 0.64 |
|
|
362
|
+
| query, p95 | 1.09 ms | 1.06 – 1.14 |
|
|
363
|
+
| refusing an unanswerable query | **0.017 ms** | 0.015 – 0.019 |
|
|
364
|
+
| refusal cheaper than answering by | **~36×** | 33 – 41 |
|
|
365
365
|
|
|
366
366
|
Two significant figures and a spread, because that is the precision the
|
|
367
|
-
measurement has. Across
|
|
368
|
-
machine the median has landed anywhere from 0.
|
|
367
|
+
measurement has. Across twenty invocations over three releases on the same idle
|
|
368
|
+
machine the median has landed anywhere from 0.51 to 1.44 ms and p95 from 0.85
|
|
369
369
|
to 7.34 ms — a band wider than any change the code has ever made to this
|
|
370
370
|
number. Releases before 1.3.0 published `0.83 ms / p95 1.68 ms` to three
|
|
371
371
|
figures from a script that was never committed; both values sit inside that
|
|
@@ -420,17 +420,17 @@ framework reversed it. The honest claim is narrower than either table alone:
|
|
|
420
420
|
| right file first | 22.9% | **37.1%** |
|
|
421
421
|
| right file in top 3 | 45.7% | **57.1%** |
|
|
422
422
|
| lines it hands back, all questions | 17,641 | — |
|
|
423
|
-
| characters returned, all questions | 1,415,656 | **
|
|
423
|
+
| characters returned, all questions | 1,415,656 | **258,236** |
|
|
424
424
|
| questions it answers | 30 | 30 |
|
|
425
425
|
|
|
426
426
|
**Once the vocabulary exists, it is not close.**
|
|
427
427
|
|
|
428
|
-
| this repository · 70 questions ·
|
|
428
|
+
| this repository · 70 questions · 584 units `c9df00350cbd` · 304 described | Grep loop | rag-your-code |
|
|
429
429
|
|---|---|---|
|
|
430
430
|
| right file first | 22.9% | **58.6%** |
|
|
431
|
-
| right file in top 3 | 54.3% | **
|
|
432
|
-
| lines it hands back, all questions | 11,
|
|
433
|
-
| characters returned, all questions | 1,
|
|
431
|
+
| right file in top 3 | 54.3% | **77.1%** |
|
|
432
|
+
| lines it hands back, all questions | 11,959 | — |
|
|
433
|
+
| characters returned, all questions | 1,135,411 | **615,673** |
|
|
434
434
|
| questions it answers | **61** | 60 |
|
|
435
435
|
|
|
436
436
|
Those two tables are the whole argument of section 3.3, measured against a real
|
|
@@ -467,13 +467,15 @@ Four qualifications, because the table would otherwise flatter both sides:
|
|
|
467
467
|
`the` gets every file back in no order. It is also why Grep declines nine of
|
|
468
468
|
the seventy questions here — those had no word left that this corpus does not
|
|
469
469
|
use everywhere.
|
|
470
|
-
- **Payload is counted in characters on both sides.**
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
470
|
+
- **Payload is counted in characters on both sides.** Grep hands back 18,600
|
|
471
|
+
characters per question it answers here, unranked and without spans, against
|
|
472
|
+
10,300 ranked and capped by `search.max_chars` — a factor of 1.8, and 5.5 on
|
|
473
|
+
Flask, where a framework repeats its vocabulary across many files and Grep
|
|
474
|
+
cannot rank what it finds. 1.4.1 changed what fits in that cap: the block had
|
|
475
|
+
been reprinting the docstring the code below already showed, 2,381 of 3,382
|
|
476
|
+
characters of prose header on Flask, so the same budget now carries **119
|
|
477
|
+
declarations instead of 92** there and 323 instead of 305 here. Both sides
|
|
478
|
+
decline the same five of those 35 — the five Chinese ones, all of them. A Chinese word is
|
|
477
479
|
not a substring of English source and it is not a token in an index built
|
|
478
480
|
from English source, so on a repository written in one language the cold
|
|
479
481
|
cross-language case is not this tool's failure but the corpus's.
|
|
@@ -554,10 +556,10 @@ against a pinned corpus:
|
|
|
554
556
|
| **C** own, described | 581 `978a1d48a82a` | **0.443 / 0.614 / 0.507** | 0.429 / 0.600 / 0.500 |
|
|
555
557
|
| **D** silence, own / foreign | as above | 0.967 / 0.833 | 0.967 / 0.833 |
|
|
556
558
|
|
|
557
|
-
**Worse or identical on every ruler.**
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
559
|
+
**Worse or identical on every ruler.** The 581-unit stamps are the corpus both
|
|
560
|
+
arms shared, kept rather than refreshed — that is what a stamp is for. It ships
|
|
561
|
+
anyway because it does one thing the hash cannot and these rulers cannot see:
|
|
562
|
+
reach a unit sharing no word with the question. The pairs it scores zero on:
|
|
561
563
|
|
|
562
564
|
| pair | signed hash | MiniLM |
|
|
563
565
|
|---|---|---|
|
|
@@ -612,6 +614,14 @@ measured worse, so it stays off there.
|
|
|
612
614
|
/reload-plugins
|
|
613
615
|
```
|
|
614
616
|
|
|
617
|
+
Updating needs the full id and a marketplace refresh first; the bare name is
|
|
618
|
+
refused with `Plugin "rag-your-code" not found`, which reads like it is gone:
|
|
619
|
+
|
|
620
|
+
```bash
|
|
621
|
+
claude plugin marketplace update rag-your-code
|
|
622
|
+
claude plugin update rag-your-code@rag-your-code # then restart
|
|
623
|
+
```
|
|
624
|
+
|
|
615
625
|
Four commands and one skill. No hooks, no agents, no MCP server:
|
|
616
626
|
|
|
617
627
|
| | |
|
|
@@ -759,6 +769,7 @@ Windows, plus a job that installs the built wheel into a clean environment and
|
|
|
759
769
|
runs every documented command, and another that runs the skill's own install
|
|
760
770
|
line verbatim.
|
|
761
771
|
|
|
772
|
+
- [docs/FLOW.md](docs/FLOW.md) — the whole thing in four diagrams
|
|
762
773
|
- [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) — how each stage works and why
|
|
763
774
|
- [docs/TESTING.md](docs/TESTING.md) — what the suites protect
|
|
764
775
|
- [docs/ROADMAP.md](docs/ROADMAP.md) — what shipped, what was rejected and why
|
|
@@ -5,6 +5,14 @@ from __future__ import annotations
|
|
|
5
5
|
import ast
|
|
6
6
|
import re
|
|
7
7
|
|
|
8
|
+
# Where a generated description stops describing the signature and starts
|
|
9
|
+
# quoting what the author wrote. Three modules needed to recognise it and all
|
|
10
|
+
# three spelled it out: this one writes it, the parser writes it again for the
|
|
11
|
+
# other fourteen languages, and `document` looked for it. A string literal
|
|
12
|
+
# duplicated across modules that must agree is a rename away from a silent
|
|
13
|
+
# disagreement.
|
|
14
|
+
DOCUMENTED_MARKER = "Documented intent:"
|
|
15
|
+
|
|
8
16
|
|
|
9
17
|
def _humanize(name: str) -> str:
|
|
10
18
|
"""Turns a programmer identifier into ordinary words: splits camelCase
|
|
@@ -37,7 +45,7 @@ def describe_python(node: ast.AST, source: str, calls: list[str], imports: list[
|
|
|
37
45
|
pieces.append("using " + ", ".join(imports[:8]))
|
|
38
46
|
doc = ast.get_docstring(node)
|
|
39
47
|
if doc:
|
|
40
|
-
pieces.append("
|
|
48
|
+
pieces.append(f"{DOCUMENTED_MARKER} " + " ".join(doc.split()))
|
|
41
49
|
return ". ".join(pieces) + "."
|
|
42
50
|
|
|
43
51
|
|
|
@@ -26,6 +26,7 @@ import re
|
|
|
26
26
|
from dataclasses import dataclass
|
|
27
27
|
from pathlib import Path
|
|
28
28
|
|
|
29
|
+
from .annotate import DOCUMENTED_MARKER
|
|
29
30
|
from .descriptions import DescriptionStore
|
|
30
31
|
from .models import CodeUnit
|
|
31
32
|
from .parser import EXTENSIONS, parse_file
|
|
@@ -59,7 +60,6 @@ WRAP_WIDTH = 76
|
|
|
59
60
|
# rewritten read as undocumented. That mistake proposed a hundred and seven
|
|
60
61
|
# insertions against a repository with seventeen genuinely undocumented
|
|
61
62
|
# declarations.
|
|
62
|
-
DOCUMENTED_MARKER = "Documented intent:"
|
|
63
63
|
_CJK = re.compile(r"[一-鿿]")
|
|
64
64
|
|
|
65
65
|
|
|
@@ -26,7 +26,7 @@ import re
|
|
|
26
26
|
from dataclasses import dataclass
|
|
27
27
|
from pathlib import Path
|
|
28
28
|
|
|
29
|
-
from .annotate import describe_python
|
|
29
|
+
from .annotate import DOCUMENTED_MARKER, describe_python
|
|
30
30
|
from .models import CodeUnit
|
|
31
31
|
|
|
32
32
|
PARSER_VERSION = "3"
|
|
@@ -542,7 +542,7 @@ def _generic_units(path: Path, source: str, relative: str, language: str) -> lis
|
|
|
542
542
|
if documented:
|
|
543
543
|
# Phrased exactly as the Python path phrases a docstring, so the
|
|
544
544
|
# two routes produce the same shape of text for the same thing.
|
|
545
|
-
description += f"
|
|
545
|
+
description += f" {DOCUMENTED_MARKER} {documented}"
|
|
546
546
|
parent = dotted.rsplit(".", 1)[0] if "." in dotted else None
|
|
547
547
|
units.append(
|
|
548
548
|
CodeUnit(
|
|
@@ -8,6 +8,7 @@ from bisect import bisect_left
|
|
|
8
8
|
from collections import Counter, defaultdict
|
|
9
9
|
from dataclasses import dataclass
|
|
10
10
|
|
|
11
|
+
from .annotate import DOCUMENTED_MARKER
|
|
11
12
|
from .config import BY_PATH
|
|
12
13
|
from .embeddings import DEFAULT_DIMENSIONS, LocalEmbedder, tokenize
|
|
13
14
|
from .models import CodeUnit, SearchResult
|
|
@@ -549,13 +550,47 @@ def search(
|
|
|
549
550
|
]
|
|
550
551
|
|
|
551
552
|
|
|
553
|
+
def _visible_description(unit: CodeUnit) -> str:
|
|
554
|
+
"""The description minus whatever the code printed under it already says.
|
|
555
|
+
|
|
556
|
+
A generated description ends with the author's own docstring, because that
|
|
557
|
+
is how a docstring becomes searchable -- and the source below the block
|
|
558
|
+
then prints it a second time. Measured on a repository whose author wrote
|
|
559
|
+
them, 2,381 of 3,382 characters of prose header were a verbatim repeat of
|
|
560
|
+
the code beneath it: a fifth of everything a query returned, paid for
|
|
561
|
+
twice. Retrieval is the half of RAG that has to fit in a context window,
|
|
562
|
+
so this is not a cosmetic saving.
|
|
563
|
+
|
|
564
|
+
Only the rendering drops it. ``searchable_text`` keeps the whole thing, so
|
|
565
|
+
no ruler moves and the docstring stays exactly as findable as it was.
|
|
566
|
+
|
|
567
|
+
The quoted docstring is re-flowed onto one line where the source has it
|
|
568
|
+
indented across many, so the test is against whitespace-collapsed source
|
|
569
|
+
rather than against the source itself. An authored description the source
|
|
570
|
+
does not carry is kept: it is the one part a reader cannot get from the
|
|
571
|
+
code.
|
|
572
|
+
"""
|
|
573
|
+
head, marker, documented = unit.description.partition(DOCUMENTED_MARKER)
|
|
574
|
+
if not marker:
|
|
575
|
+
return unit.description
|
|
576
|
+
quoted = " ".join(documented.split())
|
|
577
|
+
# `describe_python` joins its pieces with ". " and closes with ".", so a
|
|
578
|
+
# docstring that already ended in one arrives here with two. Matching on
|
|
579
|
+
# the raw text found nothing at all on the first repository tried.
|
|
580
|
+
trimmed = quoted[:-1] if quoted.endswith(".") else quoted
|
|
581
|
+
body = " ".join(unit.source.split())
|
|
582
|
+
if quoted in body or trimmed in body:
|
|
583
|
+
return head.rstrip()
|
|
584
|
+
return unit.description
|
|
585
|
+
|
|
586
|
+
|
|
552
587
|
def _block(result: SearchResult) -> str:
|
|
553
588
|
"""One result as an agent reads it: identifier, score, why it matched,
|
|
554
589
|
what it is, and the code itself.
|
|
555
590
|
"""
|
|
556
591
|
unit = result.unit
|
|
557
592
|
evidence = "\nEvidence: " + " | ".join(result.evidence) if result.evidence else ""
|
|
558
|
-
return f"[{unit.id}] score={result.score:.3f}{evidence}\n{unit
|
|
593
|
+
return f"[{unit.id}] score={result.score:.3f}{evidence}\n{_visible_description(unit)}\n```{unit.language}\n{unit.source}\n```"
|
|
559
594
|
|
|
560
595
|
|
|
561
596
|
def within_budget(results: list[SearchResult], max_chars: int) -> list[SearchResult]:
|
|
@@ -18,6 +18,7 @@ present anywhere in the unit -- ranks the *wrong* answer first.
|
|
|
18
18
|
|
|
19
19
|
from __future__ import annotations
|
|
20
20
|
|
|
21
|
+
import dataclasses
|
|
21
22
|
from pathlib import Path
|
|
22
23
|
|
|
23
24
|
from ragyourcode.indexer import build_units
|
|
@@ -200,3 +201,56 @@ def test_a_field_every_unit_leaves_empty_does_not_divide_by_zero():
|
|
|
200
201
|
)
|
|
201
202
|
index = build_search_index([unit])
|
|
202
203
|
assert search([unit], "nothing", limit=1, search_index=index)[0].unit.id == unit.id
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
def test_a_block_does_not_reprint_the_docstring_the_code_below_it_shows(tmp_path: Path):
|
|
207
|
+
"""The author's own words reached the block twice: once quoted into the
|
|
208
|
+
generated description so they are searchable, once in the source.
|
|
209
|
+
|
|
210
|
+
Measured on a repository whose author wrote them, 2,381 of 3,382
|
|
211
|
+
characters of prose header were a verbatim repeat of the code beneath it.
|
|
212
|
+
At a fixed budget that is answers crowded out by their own duplicate: the
|
|
213
|
+
same twelve thousand characters carried 92 declarations before this and
|
|
214
|
+
119 after.
|
|
215
|
+
|
|
216
|
+
Both halves are asserted. Dropping the quote must not drop what only the
|
|
217
|
+
header says, and it must not touch what the index can find -- the docstring
|
|
218
|
+
stays in `searchable_text`, which is why no ruler moves.
|
|
219
|
+
"""
|
|
220
|
+
(tmp_path / "billing.py").write_text(
|
|
221
|
+
'def charge(amount):\n'
|
|
222
|
+
' """Charge the amount against the stored card and return a receipt."""\n'
|
|
223
|
+
' return amount\n',
|
|
224
|
+
encoding="utf-8",
|
|
225
|
+
)
|
|
226
|
+
unit = build_units(tmp_path)[0]
|
|
227
|
+
index = build_search_index([unit])
|
|
228
|
+
rendered = _block(search([unit], "charge the amount", search_index=index, limit=1)[0])
|
|
229
|
+
|
|
230
|
+
quoted = "Charge the amount against the stored card and return a receipt."
|
|
231
|
+
assert rendered.count(quoted) == 1, "the docstring is printed once, by the code"
|
|
232
|
+
assert rendered.index(quoted) > rendered.index("```"), "the surviving copy is the one in the source"
|
|
233
|
+
assert "This method charge" in rendered, "the generated half is not in the source and must stay"
|
|
234
|
+
assert quoted in unit.searchable_text, "dropping it from the block must not drop it from the index"
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
def test_a_written_description_the_source_does_not_carry_survives(tmp_path: Path):
|
|
238
|
+
"""The rule is "the code already shows it", not "it came after the marker".
|
|
239
|
+
|
|
240
|
+
An authored description is the one part of a block a reader cannot recover
|
|
241
|
+
by reading the code, so a rule that keyed on the marker alone would delete
|
|
242
|
+
exactly the text this project spends tokens to produce.
|
|
243
|
+
"""
|
|
244
|
+
(tmp_path / "billing.py").write_text(
|
|
245
|
+
'def charge(amount):\n'
|
|
246
|
+
' """Charge the card."""\n'
|
|
247
|
+
' return amount\n',
|
|
248
|
+
encoding="utf-8",
|
|
249
|
+
)
|
|
250
|
+
unit = build_units(tmp_path)[0]
|
|
251
|
+
unit = dataclasses.replace(
|
|
252
|
+
unit, description="This method charge. Documented intent: retries a declined authorisation once."
|
|
253
|
+
)
|
|
254
|
+
index = build_search_index([unit])
|
|
255
|
+
rendered = _block(search([unit], "charge the amount", search_index=index, limit=1)[0])
|
|
256
|
+
assert "retries a declined authorisation once." in rendered
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|