rag-your-code 0.4.2__tar.gz → 0.5.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {rag_your_code-0.4.2/src/rag_your_code.egg-info → rag_your_code-0.5.0}/PKG-INFO +43 -15
- {rag_your_code-0.4.2 → rag_your_code-0.5.0}/README.md +42 -14
- {rag_your_code-0.4.2 → rag_your_code-0.5.0}/pyproject.toml +1 -1
- {rag_your_code-0.4.2 → rag_your_code-0.5.0/src/rag_your_code.egg-info}/PKG-INFO +43 -15
- {rag_your_code-0.4.2 → rag_your_code-0.5.0}/src/rag_your_code.egg-info/SOURCES.txt +4 -0
- {rag_your_code-0.4.2 → rag_your_code-0.5.0}/src/ragyourcode/__init__.py +1 -1
- {rag_your_code-0.4.2 → rag_your_code-0.5.0}/src/ragyourcode/agentic.py +7 -0
- {rag_your_code-0.4.2 → rag_your_code-0.5.0}/src/ragyourcode/annotate.py +11 -0
- {rag_your_code-0.4.2 → rag_your_code-0.5.0}/src/ragyourcode/cli.py +100 -9
- {rag_your_code-0.4.2 → rag_your_code-0.5.0}/src/ragyourcode/config.py +51 -0
- {rag_your_code-0.4.2 → rag_your_code-0.5.0}/src/ragyourcode/descriptions.py +125 -11
- rag_your_code-0.5.0/src/ragyourcode/document.py +261 -0
- {rag_your_code-0.4.2 → rag_your_code-0.5.0}/src/ragyourcode/embeddings.py +22 -0
- {rag_your_code-0.4.2 → rag_your_code-0.5.0}/src/ragyourcode/graph.py +40 -0
- {rag_your_code-0.4.2 → rag_your_code-0.5.0}/src/ragyourcode/indexer.py +96 -17
- {rag_your_code-0.4.2 → rag_your_code-0.5.0}/src/ragyourcode/models.py +23 -0
- {rag_your_code-0.4.2 → rag_your_code-0.5.0}/src/ragyourcode/parser.py +148 -0
- {rag_your_code-0.4.2 → rag_your_code-0.5.0}/src/ragyourcode/search.py +20 -0
- {rag_your_code-0.4.2 → rag_your_code-0.5.0}/tests/test_config.py +1 -1
- {rag_your_code-0.4.2 → rag_your_code-0.5.0}/tests/test_descriptions.py +5 -1
- rag_your_code-0.5.0/tests/test_doc_comments.py +215 -0
- rag_your_code-0.5.0/tests/test_document.py +211 -0
- rag_your_code-0.5.0/tests/test_repo_queries.py +72 -0
- {rag_your_code-0.4.2 → rag_your_code-0.5.0}/LICENSE +0 -0
- {rag_your_code-0.4.2 → rag_your_code-0.5.0}/setup.cfg +0 -0
- {rag_your_code-0.4.2 → rag_your_code-0.5.0}/src/rag_your_code.egg-info/dependency_links.txt +0 -0
- {rag_your_code-0.4.2 → rag_your_code-0.5.0}/src/rag_your_code.egg-info/entry_points.txt +0 -0
- {rag_your_code-0.4.2 → rag_your_code-0.5.0}/src/rag_your_code.egg-info/requires.txt +0 -0
- {rag_your_code-0.4.2 → rag_your_code-0.5.0}/src/rag_your_code.egg-info/top_level.txt +0 -0
- {rag_your_code-0.4.2 → rag_your_code-0.5.0}/src/ragyourcode/py.typed +0 -0
- {rag_your_code-0.4.2 → rag_your_code-0.5.0}/tests/test_agent_protocol.py +0 -0
- {rag_your_code-0.4.2 → rag_your_code-0.5.0}/tests/test_agentic.py +0 -0
- {rag_your_code-0.4.2 → rag_your_code-0.5.0}/tests/test_e2e_cli.py +0 -0
- {rag_your_code-0.4.2 → rag_your_code-0.5.0}/tests/test_golden.py +0 -0
- {rag_your_code-0.4.2 → rag_your_code-0.5.0}/tests/test_graph_incremental.py +0 -0
- {rag_your_code-0.4.2 → rag_your_code-0.5.0}/tests/test_language_fixtures.py +0 -0
- {rag_your_code-0.4.2 → rag_your_code-0.5.0}/tests/test_large_repo.py +0 -0
- {rag_your_code-0.4.2 → rag_your_code-0.5.0}/tests/test_metadata.py +0 -0
- {rag_your_code-0.4.2 → rag_your_code-0.5.0}/tests/test_multilanguage.py +0 -0
- {rag_your_code-0.4.2 → rag_your_code-0.5.0}/tests/test_parser_edges.py +0 -0
- {rag_your_code-0.4.2 → rag_your_code-0.5.0}/tests/test_ragyourcode.py +0 -0
- {rag_your_code-0.4.2 → rag_your_code-0.5.0}/tests/test_resilience.py +0 -0
- {rag_your_code-0.4.2 → rag_your_code-0.5.0}/tests/test_retrieval_correctness.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: rag-your-code
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.5.0
|
|
4
4
|
Summary: A local, explainable RAG index for codebases and coding agents
|
|
5
5
|
Author: rag-your-code contributors
|
|
6
6
|
License-Expression: MIT
|
|
@@ -153,7 +153,15 @@ callees listed, the docstring appended. It introduces no vocabulary the source
|
|
|
153
153
|
did not already have — which is exactly why retrieval cannot reach a concept
|
|
154
154
|
nobody wrote down.
|
|
155
155
|
|
|
156
|
-
|
|
156
|
+
**First, the documentation you already wrote is indexed.** Fourteen of the
|
|
157
|
+
fifteen supported languages put documentation immediately above a declaration
|
|
158
|
+
— JSDoc, Javadoc, KDoc, rustdoc, Go doc comments, XML doc comments, PHPDoc —
|
|
159
|
+
and a unit's span begins at the declaration, so all of it used to sit outside
|
|
160
|
+
the index. The same sentence reached thirteen searchable words as a Python
|
|
161
|
+
docstring and two as a JavaScript comment. Now both reach thirteen. Commented-
|
|
162
|
+
out code, separator rules and licence headers are deliberately left out.
|
|
163
|
+
|
|
164
|
+
**Where there is none, the agent can write it:**
|
|
157
165
|
|
|
158
166
|
```bash
|
|
159
167
|
rag-your-code describe status # coverage, and what is pending
|
|
@@ -165,23 +173,43 @@ rag-your-code index . # apply it
|
|
|
165
173
|
or, in the protocol, `describe_pending` and `describe_put` — which take effect
|
|
166
174
|
in the same session, with no refresh.
|
|
167
175
|
|
|
176
|
+
**And you can move it into the code**, where it needs no bookkeeping at all:
|
|
177
|
+
|
|
178
|
+
```bash
|
|
179
|
+
rag-your-code describe promote | git apply # review it first
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
That emits a unified diff adding a doc comment in each language's own
|
|
183
|
+
convention, for declarations that have none. The tool still never writes your
|
|
184
|
+
source. Only the half meant for a reader is promoted, so a bilingual
|
|
185
|
+
description leaves its second language in the store where retrieval still uses
|
|
186
|
+
it — measured, promoting all 68 on this repository discarded no description
|
|
187
|
+
and left Chinese retrieval unchanged.
|
|
188
|
+
|
|
168
189
|
### Measured on this repository
|
|
169
190
|
|
|
170
|
-
This project describes its own implementation:
|
|
171
|
-
|
|
172
|
-
|
|
191
|
+
This project describes its own implementation: every unit under `src/` carries
|
|
192
|
+
an agent-written bilingual description, committed to the repo, and 68 of them
|
|
193
|
+
have been promoted into the source as doc comments.
|
|
173
194
|
|
|
174
|
-
|
|
195
|
+
Seventy natural-language questions about this codebase, in English and
|
|
196
|
+
Chinese, each listing every unit that genuinely answers it
|
|
197
|
+
([`benchmarks/repo_queries.json`](benchmarks/repo_queries.json)):
|
|
198
|
+
|
|
199
|
+
| | generated descriptions | agent-written |
|
|
175
200
|
|---|---|---|
|
|
176
|
-
|
|
|
177
|
-
|
|
|
178
|
-
|
|
|
201
|
+
| hit@1 | 0.171 | **0.500** |
|
|
202
|
+
| hit@3 | 0.314 | **0.729** |
|
|
203
|
+
| MRR | 0.240 | **0.605** |
|
|
204
|
+
| answered with no shared word at all | 15.7% | **0%** |
|
|
205
|
+
|
|
206
|
+
Roughly a threefold improvement in first-place accuracy. Nineteen questions
|
|
207
|
+
still fail, which is what makes the set usable for measuring the next change;
|
|
208
|
+
`tests/test_repo_queries.py` asserts that some question always does.
|
|
179
209
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
The other returned a unit that answers the question from a different file than
|
|
184
|
-
predicted, so the expectation was wrong rather than the retrieval.
|
|
210
|
+
One failure is worth naming: a query saying `catastrophic backtracking` does
|
|
211
|
+
not reach a description saying `backtracks catastrophically`. There is no
|
|
212
|
+
stemming — exactly the limit documented above.
|
|
185
213
|
|
|
186
214
|
**What this is:** it moves the semantic work from query time to index time.
|
|
187
215
|
Matching stays lexical. It is LLM-authored keyword expansion, and its reach is
|
|
@@ -230,7 +258,7 @@ Directional local measurements, not service levels; the archived run is
|
|
|
230
258
|
**Suite:** Python 3.10 – 3.13 on Linux and Windows, plus a job that installs
|
|
231
259
|
the built wheel into a clean environment and runs every command the
|
|
232
260
|
documentation prescribes, and another that runs the skill's own install line
|
|
233
|
-
verbatim.
|
|
261
|
+
verbatim. 248 tests as of 0.5.0 — the count is version-stamped rather than
|
|
234
262
|
maintained, because a bare figure in a living document is a claim that rots;
|
|
235
263
|
per-release counts are in [CHANGELOG.md](CHANGELOG.md).
|
|
236
264
|
|
|
@@ -126,7 +126,15 @@ callees listed, the docstring appended. It introduces no vocabulary the source
|
|
|
126
126
|
did not already have — which is exactly why retrieval cannot reach a concept
|
|
127
127
|
nobody wrote down.
|
|
128
128
|
|
|
129
|
-
|
|
129
|
+
**First, the documentation you already wrote is indexed.** Fourteen of the
|
|
130
|
+
fifteen supported languages put documentation immediately above a declaration
|
|
131
|
+
— JSDoc, Javadoc, KDoc, rustdoc, Go doc comments, XML doc comments, PHPDoc —
|
|
132
|
+
and a unit's span begins at the declaration, so all of it used to sit outside
|
|
133
|
+
the index. The same sentence reached thirteen searchable words as a Python
|
|
134
|
+
docstring and two as a JavaScript comment. Now both reach thirteen. Commented-
|
|
135
|
+
out code, separator rules and licence headers are deliberately left out.
|
|
136
|
+
|
|
137
|
+
**Where there is none, the agent can write it:**
|
|
130
138
|
|
|
131
139
|
```bash
|
|
132
140
|
rag-your-code describe status # coverage, and what is pending
|
|
@@ -138,23 +146,43 @@ rag-your-code index . # apply it
|
|
|
138
146
|
or, in the protocol, `describe_pending` and `describe_put` — which take effect
|
|
139
147
|
in the same session, with no refresh.
|
|
140
148
|
|
|
149
|
+
**And you can move it into the code**, where it needs no bookkeeping at all:
|
|
150
|
+
|
|
151
|
+
```bash
|
|
152
|
+
rag-your-code describe promote | git apply # review it first
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
That emits a unified diff adding a doc comment in each language's own
|
|
156
|
+
convention, for declarations that have none. The tool still never writes your
|
|
157
|
+
source. Only the half meant for a reader is promoted, so a bilingual
|
|
158
|
+
description leaves its second language in the store where retrieval still uses
|
|
159
|
+
it — measured, promoting all 68 on this repository discarded no description
|
|
160
|
+
and left Chinese retrieval unchanged.
|
|
161
|
+
|
|
141
162
|
### Measured on this repository
|
|
142
163
|
|
|
143
|
-
This project describes its own implementation:
|
|
144
|
-
|
|
145
|
-
|
|
164
|
+
This project describes its own implementation: every unit under `src/` carries
|
|
165
|
+
an agent-written bilingual description, committed to the repo, and 68 of them
|
|
166
|
+
have been promoted into the source as doc comments.
|
|
146
167
|
|
|
147
|
-
|
|
168
|
+
Seventy natural-language questions about this codebase, in English and
|
|
169
|
+
Chinese, each listing every unit that genuinely answers it
|
|
170
|
+
([`benchmarks/repo_queries.json`](benchmarks/repo_queries.json)):
|
|
171
|
+
|
|
172
|
+
| | generated descriptions | agent-written |
|
|
148
173
|
|---|---|---|
|
|
149
|
-
|
|
|
150
|
-
|
|
|
151
|
-
|
|
|
174
|
+
| hit@1 | 0.171 | **0.500** |
|
|
175
|
+
| hit@3 | 0.314 | **0.729** |
|
|
176
|
+
| MRR | 0.240 | **0.605** |
|
|
177
|
+
| answered with no shared word at all | 15.7% | **0%** |
|
|
178
|
+
|
|
179
|
+
Roughly a threefold improvement in first-place accuracy. Nineteen questions
|
|
180
|
+
still fail, which is what makes the set usable for measuring the next change;
|
|
181
|
+
`tests/test_repo_queries.py` asserts that some question always does.
|
|
152
182
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
The other returned a unit that answers the question from a different file than
|
|
157
|
-
predicted, so the expectation was wrong rather than the retrieval.
|
|
183
|
+
One failure is worth naming: a query saying `catastrophic backtracking` does
|
|
184
|
+
not reach a description saying `backtracks catastrophically`. There is no
|
|
185
|
+
stemming — exactly the limit documented above.
|
|
158
186
|
|
|
159
187
|
**What this is:** it moves the semantic work from query time to index time.
|
|
160
188
|
Matching stays lexical. It is LLM-authored keyword expansion, and its reach is
|
|
@@ -203,7 +231,7 @@ Directional local measurements, not service levels; the archived run is
|
|
|
203
231
|
**Suite:** Python 3.10 – 3.13 on Linux and Windows, plus a job that installs
|
|
204
232
|
the built wheel into a clean environment and runs every command the
|
|
205
233
|
documentation prescribes, and another that runs the skill's own install line
|
|
206
|
-
verbatim.
|
|
234
|
+
verbatim. 248 tests as of 0.5.0 — the count is version-stamped rather than
|
|
207
235
|
maintained, because a bare figure in a living document is a claim that rots;
|
|
208
236
|
per-release counts are in [CHANGELOG.md](CHANGELOG.md).
|
|
209
237
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: rag-your-code
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.5.0
|
|
4
4
|
Summary: A local, explainable RAG index for codebases and coding agents
|
|
5
5
|
Author: rag-your-code contributors
|
|
6
6
|
License-Expression: MIT
|
|
@@ -153,7 +153,15 @@ callees listed, the docstring appended. It introduces no vocabulary the source
|
|
|
153
153
|
did not already have — which is exactly why retrieval cannot reach a concept
|
|
154
154
|
nobody wrote down.
|
|
155
155
|
|
|
156
|
-
|
|
156
|
+
**First, the documentation you already wrote is indexed.** Fourteen of the
|
|
157
|
+
fifteen supported languages put documentation immediately above a declaration
|
|
158
|
+
— JSDoc, Javadoc, KDoc, rustdoc, Go doc comments, XML doc comments, PHPDoc —
|
|
159
|
+
and a unit's span begins at the declaration, so all of it used to sit outside
|
|
160
|
+
the index. The same sentence reached thirteen searchable words as a Python
|
|
161
|
+
docstring and two as a JavaScript comment. Now both reach thirteen. Commented-
|
|
162
|
+
out code, separator rules and licence headers are deliberately left out.
|
|
163
|
+
|
|
164
|
+
**Where there is none, the agent can write it:**
|
|
157
165
|
|
|
158
166
|
```bash
|
|
159
167
|
rag-your-code describe status # coverage, and what is pending
|
|
@@ -165,23 +173,43 @@ rag-your-code index . # apply it
|
|
|
165
173
|
or, in the protocol, `describe_pending` and `describe_put` — which take effect
|
|
166
174
|
in the same session, with no refresh.
|
|
167
175
|
|
|
176
|
+
**And you can move it into the code**, where it needs no bookkeeping at all:
|
|
177
|
+
|
|
178
|
+
```bash
|
|
179
|
+
rag-your-code describe promote | git apply # review it first
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
That emits a unified diff adding a doc comment in each language's own
|
|
183
|
+
convention, for declarations that have none. The tool still never writes your
|
|
184
|
+
source. Only the half meant for a reader is promoted, so a bilingual
|
|
185
|
+
description leaves its second language in the store where retrieval still uses
|
|
186
|
+
it — measured, promoting all 68 on this repository discarded no description
|
|
187
|
+
and left Chinese retrieval unchanged.
|
|
188
|
+
|
|
168
189
|
### Measured on this repository
|
|
169
190
|
|
|
170
|
-
This project describes its own implementation:
|
|
171
|
-
|
|
172
|
-
|
|
191
|
+
This project describes its own implementation: every unit under `src/` carries
|
|
192
|
+
an agent-written bilingual description, committed to the repo, and 68 of them
|
|
193
|
+
have been promoted into the source as doc comments.
|
|
173
194
|
|
|
174
|
-
|
|
195
|
+
Seventy natural-language questions about this codebase, in English and
|
|
196
|
+
Chinese, each listing every unit that genuinely answers it
|
|
197
|
+
([`benchmarks/repo_queries.json`](benchmarks/repo_queries.json)):
|
|
198
|
+
|
|
199
|
+
| | generated descriptions | agent-written |
|
|
175
200
|
|---|---|---|
|
|
176
|
-
|
|
|
177
|
-
|
|
|
178
|
-
|
|
|
201
|
+
| hit@1 | 0.171 | **0.500** |
|
|
202
|
+
| hit@3 | 0.314 | **0.729** |
|
|
203
|
+
| MRR | 0.240 | **0.605** |
|
|
204
|
+
| answered with no shared word at all | 15.7% | **0%** |
|
|
205
|
+
|
|
206
|
+
Roughly a threefold improvement in first-place accuracy. Nineteen questions
|
|
207
|
+
still fail, which is what makes the set usable for measuring the next change;
|
|
208
|
+
`tests/test_repo_queries.py` asserts that some question always does.
|
|
179
209
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
The other returned a unit that answers the question from a different file than
|
|
184
|
-
predicted, so the expectation was wrong rather than the retrieval.
|
|
210
|
+
One failure is worth naming: a query saying `catastrophic backtracking` does
|
|
211
|
+
not reach a description saying `backtracks catastrophically`. There is no
|
|
212
|
+
stemming — exactly the limit documented above.
|
|
185
213
|
|
|
186
214
|
**What this is:** it moves the semantic work from query time to index time.
|
|
187
215
|
Matching stays lexical. It is LLM-authored keyword expansion, and its reach is
|
|
@@ -230,7 +258,7 @@ Directional local measurements, not service levels; the archived run is
|
|
|
230
258
|
**Suite:** Python 3.10 – 3.13 on Linux and Windows, plus a job that installs
|
|
231
259
|
the built wheel into a clean environment and runs every command the
|
|
232
260
|
documentation prescribes, and another that runs the skill's own install line
|
|
233
|
-
verbatim.
|
|
261
|
+
verbatim. 248 tests as of 0.5.0 — the count is version-stamped rather than
|
|
234
262
|
maintained, because a bare figure in a living document is a claim that rots;
|
|
235
263
|
per-release counts are in [CHANGELOG.md](CHANGELOG.md).
|
|
236
264
|
|
|
@@ -13,6 +13,7 @@ src/ragyourcode/annotate.py
|
|
|
13
13
|
src/ragyourcode/cli.py
|
|
14
14
|
src/ragyourcode/config.py
|
|
15
15
|
src/ragyourcode/descriptions.py
|
|
16
|
+
src/ragyourcode/document.py
|
|
16
17
|
src/ragyourcode/embeddings.py
|
|
17
18
|
src/ragyourcode/graph.py
|
|
18
19
|
src/ragyourcode/indexer.py
|
|
@@ -24,6 +25,8 @@ tests/test_agent_protocol.py
|
|
|
24
25
|
tests/test_agentic.py
|
|
25
26
|
tests/test_config.py
|
|
26
27
|
tests/test_descriptions.py
|
|
28
|
+
tests/test_doc_comments.py
|
|
29
|
+
tests/test_document.py
|
|
27
30
|
tests/test_e2e_cli.py
|
|
28
31
|
tests/test_golden.py
|
|
29
32
|
tests/test_graph_incremental.py
|
|
@@ -33,5 +36,6 @@ tests/test_metadata.py
|
|
|
33
36
|
tests/test_multilanguage.py
|
|
34
37
|
tests/test_parser_edges.py
|
|
35
38
|
tests/test_ragyourcode.py
|
|
39
|
+
tests/test_repo_queries.py
|
|
36
40
|
tests/test_resilience.py
|
|
37
41
|
tests/test_retrieval_correctness.py
|
|
@@ -8,10 +8,17 @@ from .search import DEFAULT_VECTOR_WEIGHT, SearchIndex, search
|
|
|
8
8
|
|
|
9
9
|
|
|
10
10
|
def _result_ids(results: list[SearchResult]) -> set[str]:
|
|
11
|
+
"""Collects the unit identifiers out of a list of search results, so two
|
|
12
|
+
result sets can be compared for overlap or novelty. Used to decide
|
|
13
|
+
whether a second retrieval round actually surfaced anything new.
|
|
14
|
+
"""
|
|
11
15
|
return {result.unit.id for result in results}
|
|
12
16
|
|
|
13
17
|
|
|
14
18
|
def _serialize(results: list[SearchResult]) -> list[dict]:
|
|
19
|
+
"""Converts search results into plain JSON-ready dictionaries for the agent
|
|
20
|
+
protocol reply.
|
|
21
|
+
"""
|
|
15
22
|
return [result.to_dict() for result in results]
|
|
16
23
|
|
|
17
24
|
|
|
@@ -7,11 +7,22 @@ import re
|
|
|
7
7
|
|
|
8
8
|
|
|
9
9
|
def _humanize(name: str) -> str:
|
|
10
|
+
"""Turns a programmer identifier into ordinary words: splits camelCase
|
|
11
|
+
apart, replaces underscores with spaces, lowercases the result. Empty
|
|
12
|
+
input becomes a placeholder rather than an empty string.
|
|
13
|
+
"""
|
|
10
14
|
words = re.sub(r"([a-z0-9])([A-Z])", r"\1 \2", name).replace("_", " ").split()
|
|
11
15
|
return " ".join(words).strip().lower() or "anonymous unit"
|
|
12
16
|
|
|
13
17
|
|
|
14
18
|
def describe_python(node: ast.AST, source: str, calls: list[str], imports: list[str]) -> str:
|
|
19
|
+
"""Builds a readable sentence about a Python function or class without
|
|
20
|
+
using a language model: the humanised name, the arguments it accepts,
|
|
21
|
+
the functions it calls, the modules it uses, and the docstring appended
|
|
22
|
+
verbatim as stated intent. Because it only rearranges words already in
|
|
23
|
+
the source, it adds no vocabulary the code did not have, which is why
|
|
24
|
+
retrieval cannot reach a concept nobody wrote down.
|
|
25
|
+
"""
|
|
15
26
|
name = getattr(node, "name", "anonymous")
|
|
16
27
|
kind = "method" if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)) else "class"
|
|
17
28
|
args = []
|
|
@@ -14,9 +14,10 @@ from .annotate import comment_for
|
|
|
14
14
|
from .agentic import research
|
|
15
15
|
from .config import BY_PATH, SETTINGS, Config, ConfigError
|
|
16
16
|
from .descriptions import DescriptionStore, guidance, index_descriptions_fingerprint
|
|
17
|
+
from .document import plan as plan_documentation, render_patch, summarise as summarise_documentation
|
|
17
18
|
from .embeddings import embed, embedding_metadata
|
|
18
19
|
from .graph import build_graph, graph_from_dict, graph_search
|
|
19
|
-
from .indexer import StaleMonitor, build_units, fingerprint,
|
|
20
|
+
from .indexer import StaleMonitor, build_fingerprint, build_units, fingerprint, index_build_fingerprint, read_index, snapshot_repository, write_index
|
|
20
21
|
from .search import build_search_index, context, search
|
|
21
22
|
|
|
22
23
|
# Derived from the settings table so the default is written down once.
|
|
@@ -26,10 +27,24 @@ MAX_OPEN_CHARS = BY_PATH["agent.max_open_chars"].default
|
|
|
26
27
|
|
|
27
28
|
|
|
28
29
|
def _default_index(root: Path) -> Path:
|
|
30
|
+
"""Where a repository index file is kept when the caller names no other
|
|
31
|
+
location.
|
|
32
|
+
"""
|
|
29
33
|
return root / ".rag-your-code" / "index.json"
|
|
30
34
|
|
|
31
35
|
|
|
32
36
|
def _refresh_index(root: Path, output: Path, full: bool = False, compact: bool | None = None, cfg: Config | None = None) -> dict:
|
|
37
|
+
"""Builds or rebuilds a repository index and publishes it, reusing the
|
|
38
|
+
previous one where it can. It works out up front whether the rules that
|
|
39
|
+
decide what a unit is have changed — the indexing settings, the vector
|
|
40
|
+
width, or the parser itself — and when they have it discards the
|
|
41
|
+
previous work rather than reusing it, so the report can honestly say
|
|
42
|
+
whether reuse happened. That report previously claimed reuse on exactly
|
|
43
|
+
the runs that had rebuilt everything, because it was computed from
|
|
44
|
+
whether a previous index existed rather than from whether its units were
|
|
45
|
+
kept. It also applies the written descriptions and reports how many
|
|
46
|
+
units still have none.
|
|
47
|
+
"""
|
|
33
48
|
cfg = cfg if cfg is not None else config_module.load(root)
|
|
34
49
|
previous_payload: dict = {}
|
|
35
50
|
previous_units = []
|
|
@@ -48,9 +63,9 @@ def _refresh_index(root: Path, output: Path, full: bool = False, compact: bool |
|
|
|
48
63
|
# rather than only inside build_units is what lets the reported
|
|
49
64
|
# `incremental` describe what the run actually did: it claimed reuse on
|
|
50
65
|
# exactly the runs where the configuration change had forbidden it.
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
if
|
|
66
|
+
previous_build = index_build_fingerprint(previous_payload) if previous_payload else None
|
|
67
|
+
inputs_changed = previous_build is not None and previous_build != build_fingerprint(cfg)
|
|
68
|
+
if inputs_changed:
|
|
54
69
|
previous_payload, previous_units = {}, []
|
|
55
70
|
diagnostics: list[dict] = []
|
|
56
71
|
# One snapshot for both halves: parsing from one walk and publishing hashes
|
|
@@ -64,7 +79,7 @@ def _refresh_index(root: Path, output: Path, full: bool = False, compact: bool |
|
|
|
64
79
|
diagnostics=diagnostics,
|
|
65
80
|
snapshot=snapshot,
|
|
66
81
|
cfg=cfg,
|
|
67
|
-
|
|
82
|
+
previous_build=None if inputs_changed else previous_build,
|
|
68
83
|
descriptions=store,
|
|
69
84
|
)
|
|
70
85
|
graph = build_graph(units)
|
|
@@ -75,7 +90,7 @@ def _refresh_index(root: Path, output: Path, full: bool = False, compact: bool |
|
|
|
75
90
|
"graph_edges": len(graph.edges),
|
|
76
91
|
"warnings": len(diagnostics),
|
|
77
92
|
"incremental": bool(previous_units) and not full,
|
|
78
|
-
"
|
|
93
|
+
"rebuilt_for_inputs": inputs_changed,
|
|
79
94
|
"compact": bool(compact),
|
|
80
95
|
"described": len(groups["described"]),
|
|
81
96
|
"pending_descriptions": len(groups["missing"]) + len(groups["superseded"]),
|
|
@@ -86,6 +101,10 @@ def _refresh_index(root: Path, output: Path, full: bool = False, compact: bool |
|
|
|
86
101
|
|
|
87
102
|
|
|
88
103
|
def _cmd_index(args: argparse.Namespace) -> int:
|
|
104
|
+
"""The index command: scans a repository and writes its index, reporting
|
|
105
|
+
how many units and relationships were found and how many descriptions
|
|
106
|
+
are still pending.
|
|
107
|
+
"""
|
|
89
108
|
root = Path(args.root).resolve()
|
|
90
109
|
cfg = config_module.load(root)
|
|
91
110
|
output = Path(args.output) if args.output else _default_index(root)
|
|
@@ -94,6 +113,13 @@ def _cmd_index(args: argparse.Namespace) -> int:
|
|
|
94
113
|
|
|
95
114
|
|
|
96
115
|
def _load(args: argparse.Namespace):
|
|
116
|
+
"""Opens a published index for a read-only command and settles whether it
|
|
117
|
+
is still current. Three things can make it out of date and only one of
|
|
118
|
+
them is a file edit: the repository content can have moved, the rules
|
|
119
|
+
that decide what a unit is can have changed, or the written descriptions
|
|
120
|
+
can have changed. Neither authored input nor the parser is an indexed
|
|
121
|
+
file, so each has to report itself.
|
|
122
|
+
"""
|
|
97
123
|
root = Path(args.root).resolve()
|
|
98
124
|
cfg = config_module.load(root)
|
|
99
125
|
store = descriptions_module.load(root)
|
|
@@ -105,7 +131,7 @@ def _load(args: argparse.Namespace):
|
|
|
105
131
|
# descriptions mean it serves text nobody wrote any more. Neither moves
|
|
106
132
|
# a tracked file, so each has to report itself.
|
|
107
133
|
stale = payload.get("fingerprint") != fingerprint(root, cfg)
|
|
108
|
-
stale = stale or
|
|
134
|
+
stale = stale or index_build_fingerprint(payload) != build_fingerprint(cfg)
|
|
109
135
|
payload["stale"] = stale or index_descriptions_fingerprint(payload) != store.fingerprint
|
|
110
136
|
except OSError:
|
|
111
137
|
payload["stale"] = True
|
|
@@ -113,6 +139,13 @@ def _load(args: argparse.Namespace):
|
|
|
113
139
|
|
|
114
140
|
|
|
115
141
|
def _cmd_search(args: argparse.Namespace) -> int:
|
|
142
|
+
"""The search command: retrieves the code units most relevant to a
|
|
143
|
+
question, optionally following relationships outward, and prints either
|
|
144
|
+
a readable context block or machine-readable output for an agent. Result
|
|
145
|
+
count, context budget and the balance between word overlap and vector
|
|
146
|
+
similarity all fall back to the repository settings when no flag
|
|
147
|
+
overrides them. Warns when the index no longer describes the repository.
|
|
148
|
+
"""
|
|
116
149
|
payload, units, graph, cfg, _ = _load(args)
|
|
117
150
|
limit = args.limit if args.limit is not None else cfg["search.limit"]
|
|
118
151
|
max_chars = args.max_chars if args.max_chars is not None else cfg["search.max_chars"]
|
|
@@ -133,6 +166,12 @@ def _cmd_search(args: argparse.Namespace) -> int:
|
|
|
133
166
|
|
|
134
167
|
|
|
135
168
|
def _cmd_annotate(args: argparse.Namespace) -> int:
|
|
169
|
+
"""The annotate command: writes a numbered inventory of every indexed unit
|
|
170
|
+
to a separate document, with its location, kind and description. Source
|
|
171
|
+
files are never touched. Refuses to run against an index that no longer
|
|
172
|
+
describes the repository, since a numbered inventory of stale code is
|
|
173
|
+
worse than none.
|
|
174
|
+
"""
|
|
136
175
|
payload, units, _, _, _ = _load(args)
|
|
137
176
|
if payload.get("stale"):
|
|
138
177
|
print("Index is stale; run `rag-your-code index` before annotating.", file=sys.stderr)
|
|
@@ -148,6 +187,14 @@ def _cmd_annotate(args: argparse.Namespace) -> int:
|
|
|
148
187
|
|
|
149
188
|
|
|
150
189
|
def _cmd_config(args: argparse.Namespace) -> int:
|
|
190
|
+
"""The config command: creates a commented settings file, lists every
|
|
191
|
+
setting with its effective value and whether it was customised, reads
|
|
192
|
+
one value, changes one value in place, or reports where the file lives.
|
|
193
|
+
Changing a value refuses anything out of range before writing, so a
|
|
194
|
+
rejected change leaves the file exactly as it was, and the reply says
|
|
195
|
+
whether the change forces a full rebuild. Creating refuses to overwrite
|
|
196
|
+
an existing file unless told to.
|
|
197
|
+
"""
|
|
151
198
|
root = Path(args.root).resolve()
|
|
152
199
|
path = config_module.config_path(root)
|
|
153
200
|
if args.action == "path":
|
|
@@ -293,7 +340,38 @@ def _apply_descriptions(units: list, store: DescriptionStore, cfg: Config) -> in
|
|
|
293
340
|
|
|
294
341
|
|
|
295
342
|
def _cmd_describe(args: argparse.Namespace) -> int:
|
|
343
|
+
"""The describe command: reports how many units have a usable description,
|
|
344
|
+
how many have one the code has since outgrown and how many have none;
|
|
345
|
+
exports a batch of pending work with source and brief; imports written
|
|
346
|
+
descriptions back; or emits a patch that moves a description into the
|
|
347
|
+
source as a doc comment. Importing says explicitly that a rebuild is
|
|
348
|
+
needed, because the published index still holds the previous wording and
|
|
349
|
+
no source file moved to signal it. The patch owns standard output so it
|
|
350
|
+
can be piped straight into git apply.
|
|
351
|
+
"""
|
|
296
352
|
payload, units, _, cfg, store = _load(args)
|
|
353
|
+
if args.action == "promote":
|
|
354
|
+
# A stored description exists so text about a unit can be written
|
|
355
|
+
# without touching the file, and that independence costs a digest, a
|
|
356
|
+
# relocation lookup, a fingerprint and a pruning rule -- all of them
|
|
357
|
+
# simulating a property a docstring has for free. This offers the
|
|
358
|
+
# promotion as a patch rather than performing it: the tool still never
|
|
359
|
+
# writes source, and a person stays between an agent's prose and the
|
|
360
|
+
# repository.
|
|
361
|
+
root = Path(args.root).resolve()
|
|
362
|
+
insertions = plan_documentation(units, store, root)
|
|
363
|
+
report = summarise_documentation(units, store, insertions, root)
|
|
364
|
+
patch = render_patch(root, insertions)
|
|
365
|
+
if args.output:
|
|
366
|
+
Path(args.output).write_text(patch, encoding="utf-8", newline="")
|
|
367
|
+
report["output"] = args.output
|
|
368
|
+
print(json.dumps(report, ensure_ascii=False, indent=2))
|
|
369
|
+
else:
|
|
370
|
+
# The patch owns stdout so it can be piped straight into `git
|
|
371
|
+
# apply`; the summary goes to stderr.
|
|
372
|
+
sys.stdout.write(patch)
|
|
373
|
+
print(json.dumps(report, ensure_ascii=False), file=sys.stderr)
|
|
374
|
+
return 0
|
|
297
375
|
if args.action == "status":
|
|
298
376
|
groups = store.classify(units)
|
|
299
377
|
print(json.dumps({
|
|
@@ -484,6 +562,13 @@ def _cmd_agent(args: argparse.Namespace) -> int:
|
|
|
484
562
|
|
|
485
563
|
|
|
486
564
|
def build_parser() -> argparse.ArgumentParser:
|
|
565
|
+
"""Declares the whole command-line surface: indexing, retrieval,
|
|
566
|
+
annotation, the long-running agent, settings, and descriptions including
|
|
567
|
+
the promotion patch, with their options and help text. Options that have
|
|
568
|
+
a configurable counterpart default to nothing rather than to a literal,
|
|
569
|
+
so an unset flag means whatever the repository configured instead of a
|
|
570
|
+
number frozen into the program.
|
|
571
|
+
"""
|
|
487
572
|
parser = argparse.ArgumentParser(prog="rag-your-code", description="Index and retrieve explainable code units locally.")
|
|
488
573
|
sub = parser.add_subparsers(dest="command", required=True)
|
|
489
574
|
index = sub.add_parser("index", help="scan a repository and build its local index")
|
|
@@ -522,12 +607,12 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
522
607
|
config_parser.add_argument("--force", action="store_true", help="with init, overwrite an existing file")
|
|
523
608
|
config_parser.set_defaults(func=_cmd_config)
|
|
524
609
|
describe = sub.add_parser("describe", help="inspect or supply agent-authored unit descriptions")
|
|
525
|
-
describe.add_argument("action", choices=("status", "export", "import"))
|
|
610
|
+
describe.add_argument("action", choices=("status", "export", "import", "promote"))
|
|
526
611
|
describe.add_argument("file", nargs="?", help="with import, a JSON file of {id, text} objects")
|
|
527
612
|
describe.add_argument("--root", default=".")
|
|
528
613
|
describe.add_argument("--index")
|
|
529
614
|
describe.add_argument("--limit", type=int, default=None, help=f"with export, units per batch (config describe.batch, default {BY_PATH['describe.batch'].default})")
|
|
530
|
-
describe.add_argument("--output", help="with export, write
|
|
615
|
+
describe.add_argument("--output", help="with export or promote, write here instead of stdout")
|
|
531
616
|
describe.set_defaults(func=_cmd_describe)
|
|
532
617
|
return parser
|
|
533
618
|
|
|
@@ -557,6 +642,12 @@ def _use_utf8_streams() -> None:
|
|
|
557
642
|
|
|
558
643
|
|
|
559
644
|
def main(argv: list[str] | None = None) -> int:
|
|
645
|
+
"""The program entry point: pins the streams, parses the command line, runs
|
|
646
|
+
the chosen command, and turns an expected failure into a message and a
|
|
647
|
+
non-zero exit code instead of a stack trace. A settings problem is
|
|
648
|
+
reported separately and names the file, because that fix is always in
|
|
649
|
+
one known place.
|
|
650
|
+
"""
|
|
560
651
|
_use_utf8_streams()
|
|
561
652
|
args = build_parser().parse_args(argv)
|
|
562
653
|
try:
|