rag-your-code 0.4.1__py3-none-any.whl

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.
@@ -0,0 +1,237 @@
1
+ Metadata-Version: 2.4
2
+ Name: rag-your-code
3
+ Version: 0.4.1
4
+ Summary: A local, explainable RAG index for codebases and coding agents
5
+ Author: rag-your-code contributors
6
+ License-Expression: MIT
7
+ Keywords: rag,code-search,retrieval,indexing,graphrag,offline,explainable,agent
8
+ Classifier: Development Status :: 4 - Beta
9
+ Classifier: Environment :: Console
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: Operating System :: OS Independent
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3.10
14
+ Classifier: Programming Language :: Python :: 3.11
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Programming Language :: Python :: 3.13
17
+ Classifier: Topic :: Software Development :: Libraries
18
+ Classifier: Topic :: Text Processing :: Indexing
19
+ Classifier: Typing :: Typed
20
+ Requires-Python: >=3.10
21
+ Description-Content-Type: text/markdown
22
+ License-File: LICENSE
23
+ Provides-Extra: dev
24
+ Requires-Dist: pytest>=7; extra == "dev"
25
+ Requires-Dist: tomli>=2.0; python_version < "3.11" and extra == "dev"
26
+ Dynamic: license-file
27
+
28
+ # RAG Your Code
29
+
30
+ `rag-your-code` is the **R** in RAG: a local, explainable retrieval index over a
31
+ codebase. The generation half stays in your agent. It scans functions, methods
32
+ and classes across Python and fourteen other languages, assigns stable serial
33
+ numbers, and answers queries with a file, a line range, the terms that matched,
34
+ and the source itself.
35
+
36
+ No network calls and no runtime dependencies, by design rather than by
37
+ omission — it is meant to run over a private repository on a machine with the
38
+ network turned off, and to produce an index you can read.
39
+
40
+ ## What the embedding does, and what it does not
41
+
42
+ This matters more than any feature list, so it is stated up front rather than
43
+ in a footnote.
44
+
45
+ The embedder is a **signed feature hash**: it hashes tokens into 384 buckets.
46
+ Cosine similarity over those vectors is therefore a normalised measure of
47
+ *token overlap*, and it carries no semantics whatever:
48
+
49
+ | pair | cosine |
50
+ |---|---|
51
+ | `retry failed card charge` vs itself | 1.0000 |
52
+ | `sum two numbers` vs `add a pair of integers` | **0.0000** |
53
+ | `计算两个数的和` vs `sum two numbers` | **0.0000** |
54
+ | `sum two numbers` vs `delete the user database table` | 0.0000 |
55
+
56
+ A trained embedding model scores row 2 around 0.8. Here a synonym pair and an
57
+ unrelated pair are indistinguishable, because zero shared tokens is zero either
58
+ way.
59
+
60
+ Retrieval works anyway, because **identifiers and docstrings are already
61
+ natural language**: `retry_charge` contains the words *retry* and *charge*. But
62
+ it reaches only concepts someone wrote down. Two mechanisms close the rest of
63
+ the gap, and neither of them is a model:
64
+
65
+ - **Your agent rewrites the query.** It has the conversation; turning "重试扣款"
66
+ into `retry charge payment gateway` costs it nothing.
67
+ - **Your agent writes the descriptions** (see below), which puts the missing
68
+ vocabulary into the index once instead of into every query.
69
+
70
+ ## Quick start
71
+
72
+ ```bash
73
+ # Not on PyPI. Take the wheel from the latest release, or install the source:
74
+ # https://github.com/skymanbp/rag-your-code/releases
75
+ pip install ./rag_your_code-0.4.0-py3-none-any.whl
76
+ # ... or, from a clone: python -m pip install -e .
77
+
78
+ rag-your-code index .
79
+ rag-your-code search "where are HTTP retries handled" --json
80
+ rag-your-code search "what calls the retry handler" --graph --hops 1 --json
81
+ rag-your-code annotate
82
+ ```
83
+
84
+ The index and annotations are written under `.rag-your-code/`; source files are
85
+ never modified. Use `--json` when feeding results to an agent.
86
+
87
+ For a large repository prefer `rag-your-code index . --compact`. Later
88
+ `index` runs and the agent's `refresh` reuse unchanged files and preserve
89
+ global serials; `--full` discards the cache.
90
+
91
+ ## Agent-authored descriptions
92
+
93
+ Every unit carries a description, and that description is indexed. By default
94
+ it is generated without a model: the identifier humanised, the parameter and
95
+ callee names listed, the docstring appended. That introduces no vocabulary the
96
+ source did not already contain, which is exactly why retrieval cannot reach a
97
+ concept nobody wrote down.
98
+
99
+ The agent already reading this index can supply those words:
100
+
101
+ ```bash
102
+ rag-your-code describe status # coverage, and what is pending
103
+ rag-your-code describe export --limit 20 # a batch, with source and a brief
104
+ rag-your-code describe import written.json # store what the agent wrote
105
+ rag-your-code index . # apply it
106
+ ```
107
+
108
+ or, in the JSON-lines protocol, `describe_pending` and `describe_put` — which
109
+ take effect in the same session, without a refresh.
110
+
111
+ Measured on the fixture repository, replacing one generated sentence with an
112
+ agent-written bilingual one:
113
+
114
+ | query | generated description | agent description |
115
+ |---|---|---|
116
+ | `exponential backoff` | no lexical evidence | **#1**, 1.0172 |
117
+ | `double billing safety` | no lexical evidence | **#1**, 0.3404 |
118
+ | `支付网关超时` | no lexical evidence | **#1**, 0.8632 |
119
+
120
+ **What this is:** it moves the semantic work from query time to index time.
121
+ Matching stays lexical — a description saying `retry` still cannot answer a
122
+ query saying `resend` unless the description also says so. It is LLM-authored
123
+ keyword expansion, and its reach is bounded by how many ways of saying the
124
+ thing the agent thought to write down.
125
+
126
+ Descriptions live in `rag-your-code.descriptions.json` at the repository root
127
+ and are meant to be committed, so one person's pass benefits everyone who
128
+ clones. Each is keyed by unit id **and a digest of the unit's source**: when
129
+ the code changes the description is not applied, the unit returns to the
130
+ pending queue, and retrieval falls back to the generated sentence. A
131
+ description that outlived its code would be a confident wrong answer, which is
132
+ the one thing this index is built not to give.
133
+
134
+ ## Configuration
135
+
136
+ Twelve settings live in `rag-your-code.toml` at the repository root:
137
+
138
+ ```bash
139
+ rag-your-code config init # a commented file, all defaults
140
+ rag-your-code config list # effective values and their source
141
+ rag-your-code config set index.ignore '["vendor", "generated"]'
142
+ rag-your-code config set search.vector_weight 0.25
143
+ ```
144
+
145
+ Resolution is CLI flag > file > built-in default. There is no environment
146
+ layer: an index is an artifact of a repository, not of a shell.
147
+
148
+ | section | settings |
149
+ |---|---|
150
+ | `[index]` | `ignore`, `suffixes`, `max_file_bytes` |
151
+ | `[embedding]` | `dimensions` |
152
+ | `[search]` | `vector_weight`, `limit`, `max_chars` |
153
+ | `[agent]` | `max_open_bytes`, `max_open_chars` |
154
+ | `[describe]` | `languages`, `batch`, `max_chars` |
155
+
156
+ An unknown key or an out-of-range value is an error, not a shrug — a setting
157
+ that is silently dropped is indistinguishable from one that had no effect.
158
+ `index.suffixes` may only name suffixes the parser has rules for, because a
159
+ suffix it cannot read is walked, parsed to nothing, and reported as a clean
160
+ index of zero units.
161
+
162
+ The four settings under `[index]` and `[embedding]` determine what an index
163
+ *contains*, so a digest of them is recorded in the index and a change forces a
164
+ full rebuild. The rest take effect immediately and never invalidate anything.
165
+
166
+ ## Agent protocol
167
+
168
+ `rag-your-code agent --root PATH` reads JSON lines from stdin and writes JSON
169
+ lines to stdout:
170
+
171
+ ```json
172
+ {"action":"search","query":"database transaction rollback","limit":5}
173
+ {"action":"research","query":"trace payment retry behavior","max_steps":2}
174
+ {"action":"neighbors","id":"payments.py:4:retry_charge","hops":1}
175
+ {"action":"open","path":"payments.py","start_line":1,"end_line":80}
176
+ {"action":"describe_pending","limit":20}
177
+ {"action":"describe_put","descriptions":[{"id":"payments.py:4:retry_charge","text":"..."}]}
178
+ {"action":"refresh"}
179
+ {"action":"stats"}
180
+ ```
181
+
182
+ No single request can end the session: numeric fields saturate at their bounds,
183
+ `open` is bounded in both lines and bytes, and anything unanticipated is
184
+ reported in-band with its exception type. Streams are pinned to UTF-8 rather
185
+ than following the OS codepage.
186
+
187
+ The bundled Claude plugin skill documents the recommended workflow: index at
188
+ session start, retrieve narrowly, inspect returned source, describe what is
189
+ pending, and re-index after substantial changes.
190
+
191
+ ## Design notes
192
+
193
+ - Python uses the standard-library AST, so nested functions, methods, calls,
194
+ imports, signatures and source line ranges are precise.
195
+ - Other languages use a line-oriented declaration scanner: a per-language rule
196
+ table matched one line at a time, then a span closer (brace balance, Ruby's
197
+ `end`, or the next declaration). Because a pattern never sees a second line,
198
+ the reported line number is the scanner's own loop index and cannot drift,
199
+ and a declaration cannot swallow the ones after it. Fourteen languages are
200
+ covered and graded against source-controlled fixtures in
201
+ `tests/fixtures/languages/`; `SPEC.md` there states what counts as a unit.
202
+ - Retrieval combines lexical overlap and cosine similarity. Every result is
203
+ explainable: you can read why it matched.
204
+ - Schema 2 supports incremental per-file reuse, repository-global serials,
205
+ graph edges, and optional compact float32 vector storage (`index --compact`).
206
+ - GRAG expands bounded `calls`/`imports`/`contains` neighbours with edge-path
207
+ evidence, and omits an edge it cannot resolve rather than guessing one. ARAG
208
+ exposes bounded, observable `research`, `neighbors`, `open` and `refresh`.
209
+ - The generated `RAG[00001] ...` comments live in a sidecar Markdown file,
210
+ which avoids rewriting your code while preserving the numbered layer that
211
+ gets embedded.
212
+
213
+ ## What lives where
214
+
215
+ | path | authored or generated | committed |
216
+ |---|---|---|
217
+ | `rag-your-code.toml` | authored | yes |
218
+ | `rag-your-code.descriptions.json` | authored by your agent | yes |
219
+ | `.rag-your-code/` (index, vectors, annotations) | generated | no |
220
+
221
+ Nothing authored lives under `.rag-your-code/`: that directory is what people
222
+ delete to clear the cache.
223
+
224
+ ## Development
225
+
226
+ ```bash
227
+ python -m pip install -e ".[dev]"
228
+ pytest -q
229
+ ```
230
+
231
+ No runtime dependencies; `pytest` and, below Python 3.11, `tomli` come from the
232
+ `dev` extra. CI covers Python 3.10 through 3.13 on Linux and Windows and
233
+ installs the built wheel into a clean environment to check that the workflow
234
+ the bundled skill documents actually runs from a published artifact. See
235
+ [CONTRIBUTING.md](CONTRIBUTING.md) for what the golden set and the language
236
+ fixtures are protecting, and [docs/ROADMAP.md](docs/ROADMAP.md) for what is
237
+ deliberately not here yet.
@@ -0,0 +1,19 @@
1
+ rag_your_code-0.4.1.dist-info/licenses/LICENSE,sha256=LtQrRkCo2Rbvy8DWxjJGOgaa9VcP3jK4EjqbaGUkJgE,1083
2
+ ragyourcode/__init__.py,sha256=6k5MyVoot5TtRPrCDmPDExED9TRURV8Gv4mUsViEQOo,150
3
+ ragyourcode/agentic.py,sha256=5kucZi36a125_TRfQ9LoMIXNBFPhYmQ7ptpUAJ2Bh0c,2487
4
+ ragyourcode/annotate.py,sha256=IGWCwp886dSXIjZt-o45dNkBJdITLnb9dYteVOdr7Hg,1291
5
+ ragyourcode/cli.py,sha256=oiuqPtfOVoG3JYAMzT7Bvvi6Ophq9SNr8B9d2khmFSQ,29249
6
+ ragyourcode/config.py,sha256=6dJt729dOPo_7Qxb2W6DNe_Jr7wDXf4xDffNuHTG1Eo,23099
7
+ ragyourcode/descriptions.py,sha256=8QEUHgZL-0loNYjRxX0ALPF5i57uy6sAA_CGdiZfFMQ,11133
8
+ ragyourcode/embeddings.py,sha256=rEoi6YhLV_GIYXBaLVDT5mHogdfrAHKzbRwhFoaNuNc,2041
9
+ ragyourcode/graph.py,sha256=GA4fDiGhyphkAmQqdjzg4PoJOf_U6v58CtO-1d2Cn0E,8758
10
+ ragyourcode/indexer.py,sha256=8kqIbtqPdPpcjzi3T2_0zbmEiu-Fbuk-_bNNUgYxoGg,17939
11
+ ragyourcode/models.py,sha256=GnhgHE7X2I-75ZuPYhb2W8drzzUgo1sHAeUq1vLnBcU,2564
12
+ ragyourcode/parser.py,sha256=1JX6j-UvN9qj3Ygxq6NjNIsQntCs_n48ayi32XPjDxo,22305
13
+ ragyourcode/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
14
+ ragyourcode/search.py,sha256=hXEnC8BgPChXQQMsk-LepY5R4WVAuPNo5dSXX3AU4eU,6225
15
+ rag_your_code-0.4.1.dist-info/METADATA,sha256=pp2-MT3ccYRNBtetlsbt_cRki2GmMZusJ_zi4541SOg,10531
16
+ rag_your_code-0.4.1.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
17
+ rag_your_code-0.4.1.dist-info/entry_points.txt,sha256=r9SkpQf07eEcLSKcKS0uP1pAec-EbwCBsJ9Fgt9gh14,55
18
+ rag_your_code-0.4.1.dist-info/top_level.txt,sha256=Q7HCsNIpxL4UsLM8ZjfPvkCmBbYb2SuVTKD1u0FYKEw,12
19
+ rag_your_code-0.4.1.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (84.0.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ rag-your-code = ragyourcode.cli:main
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 rag-your-code contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1 @@
1
+ ragyourcode
@@ -0,0 +1,6 @@
1
+ """Local codebase RAG for coding agents."""
2
+
3
+ from .models import CodeUnit, SearchResult
4
+
5
+ __all__ = ["CodeUnit", "SearchResult"]
6
+ __version__ = "0.4.1"
ragyourcode/agentic.py ADDED
@@ -0,0 +1,55 @@
1
+ """Bounded, observable agentic retrieval (ARAG) orchestration."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from .graph import CodeGraph, graph_search
6
+ from .models import CodeUnit, SearchResult
7
+ from .search import DEFAULT_VECTOR_WEIGHT, SearchIndex, search
8
+
9
+
10
+ def _result_ids(results: list[SearchResult]) -> set[str]:
11
+ return {result.unit.id for result in results}
12
+
13
+
14
+ def _serialize(results: list[SearchResult]) -> list[dict]:
15
+ return [result.to_dict() for result in results]
16
+
17
+
18
+ def research(
19
+ units: list[CodeUnit],
20
+ query: str,
21
+ limit: int = 8,
22
+ hops: int = 1,
23
+ max_steps: int = 2,
24
+ confidence_threshold: float = 0.8,
25
+ graph: CodeGraph | None = None,
26
+ search_index: SearchIndex | None = None,
27
+ vector_weight: float = DEFAULT_VECTOR_WEIGHT,
28
+ ) -> dict:
29
+ """Run at most two deterministic retrieval steps and explain the stop.
30
+
31
+ This is deliberately bounded. A future LLM planner can replace the query
32
+ proposal, but the budget, evidence format, and no-progress stop remain
33
+ stable safety contracts.
34
+ """
35
+ max_steps = min(2, max(1, max_steps))
36
+ steps: list[dict] = []
37
+ initial = search(units, query, max(limit, 1), search_index=search_index, vector_weight=vector_weight)
38
+ steps.append({"action": "search", "query": query, "results": _serialize(initial)})
39
+ best_score = initial[0].score if initial else 0.0
40
+ if not initial:
41
+ return {"query": query, "results": [], "steps": steps, "stop_reason": "no_results"}
42
+ if max_steps == 1 or (best_score >= confidence_threshold and initial[0].matched_terms):
43
+ return {"query": query, "results": _serialize(initial[:limit]), "steps": steps, "stop_reason": "high_confidence"}
44
+
45
+ expanded = graph_search(units, query, limit=max(limit * 2, 8), hops=hops, graph=graph, search_index=search_index, vector_weight=vector_weight)
46
+ steps.append({"action": "graph_expand", "hops": hops, "results": _serialize(expanded[:limit])})
47
+ merged = {result.unit.id: result for result in initial}
48
+ for result in expanded:
49
+ current = merged.get(result.unit.id)
50
+ if current is None or result.score > current.score:
51
+ merged[result.unit.id] = result
52
+ final = sorted(merged.values(), key=lambda result: (-result.score, result.unit.id))[:limit]
53
+ new_ids = _result_ids(final) - _result_ids(initial)
54
+ reason = "new_graph_evidence" if new_ids else "no_new_evidence"
55
+ return {"query": query, "results": _serialize(final), "steps": steps, "stop_reason": reason}
@@ -0,0 +1,35 @@
1
+ """Generate stable, descriptive comments for code units without an LLM."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import ast
6
+ import re
7
+
8
+
9
+ def _humanize(name: str) -> str:
10
+ words = re.sub(r"([a-z0-9])([A-Z])", r"\1 \2", name).replace("_", " ").split()
11
+ return " ".join(words).strip().lower() or "anonymous unit"
12
+
13
+
14
+ def describe_python(node: ast.AST, source: str, calls: list[str], imports: list[str]) -> str:
15
+ name = getattr(node, "name", "anonymous")
16
+ kind = "method" if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)) else "class"
17
+ args = []
18
+ if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
19
+ args = [arg.arg for arg in node.args.args]
20
+ pieces = [f"This {kind} {_humanize(name)}"]
21
+ if args:
22
+ pieces.append("accepts " + ", ".join(args))
23
+ if calls:
24
+ pieces.append("and calls " + ", ".join(calls[:8]))
25
+ if imports:
26
+ pieces.append("using " + ", ".join(imports[:8]))
27
+ doc = ast.get_docstring(node)
28
+ if doc:
29
+ pieces.append("Documented intent: " + " ".join(doc.split()))
30
+ return ". ".join(pieces) + "."
31
+
32
+
33
+ def comment_for(description: str, serial: int, unit_id: str) -> str:
34
+ """A language-neutral comment payload suitable for sidecar files."""
35
+ return f"RAG[{serial:05d}] {unit_id}: {description}"