coderag-ai 0.3.2__tar.gz → 0.3.4__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.
- {coderag_ai-0.3.2 → coderag_ai-0.3.4}/PKG-INFO +1 -1
- {coderag_ai-0.3.2 → coderag_ai-0.3.4}/pyproject.toml +1 -1
- {coderag_ai-0.3.2 → coderag_ai-0.3.4}/src/coderag/api/app.py +8 -0
- {coderag_ai-0.3.2 → coderag_ai-0.3.4}/src/coderag/api/dashboard.py +5 -0
- {coderag_ai-0.3.2 → coderag_ai-0.3.4}/src/coderag/api/schemas.py +6 -0
- {coderag_ai-0.3.2 → coderag_ai-0.3.4}/src/coderag/cli/main.py +33 -12
- {coderag_ai-0.3.2 → coderag_ai-0.3.4}/src/coderag/core/config.py +7 -0
- coderag_ai-0.3.4/src/coderag/db/migrate.py +51 -0
- {coderag_ai-0.3.2 → coderag_ai-0.3.4}/tests/test_phase13_measure.py +25 -0
- coderag_ai-0.3.4/tests/test_phase15_migrate.py +43 -0
- {coderag_ai-0.3.2 → coderag_ai-0.3.4}/.env.example +0 -0
- {coderag_ai-0.3.2 → coderag_ai-0.3.4}/.gitignore +0 -0
- {coderag_ai-0.3.2 → coderag_ai-0.3.4}/CODE_OF_CONDUCT.md +0 -0
- {coderag_ai-0.3.2 → coderag_ai-0.3.4}/CONTRIBUTING.md +0 -0
- {coderag_ai-0.3.2 → coderag_ai-0.3.4}/Dockerfile +0 -0
- {coderag_ai-0.3.2 → coderag_ai-0.3.4}/LICENSE +0 -0
- {coderag_ai-0.3.2 → coderag_ai-0.3.4}/Makefile +0 -0
- {coderag_ai-0.3.2 → coderag_ai-0.3.4}/NOTICE +0 -0
- {coderag_ai-0.3.2 → coderag_ai-0.3.4}/README.md +0 -0
- {coderag_ai-0.3.2 → coderag_ai-0.3.4}/SECURITY.md +0 -0
- {coderag_ai-0.3.2 → coderag_ai-0.3.4}/alembic.ini +0 -0
- {coderag_ai-0.3.2 → coderag_ai-0.3.4}/docker/entrypoint.sh +0 -0
- {coderag_ai-0.3.2 → coderag_ai-0.3.4}/docker-compose.yml +0 -0
- {coderag_ai-0.3.2 → coderag_ai-0.3.4}/docs/adding-language.md +0 -0
- {coderag_ai-0.3.2 → coderag_ai-0.3.4}/docs/adr/001-postgres-pgvector.md +0 -0
- {coderag_ai-0.3.2 → coderag_ai-0.3.4}/docs/adr/002-structural-chunks.md +0 -0
- {coderag_ai-0.3.2 → coderag_ai-0.3.4}/docs/adr/003-hybrid-search.md +0 -0
- {coderag_ai-0.3.2 → coderag_ai-0.3.4}/docs/adr/004-tree-sitter.md +0 -0
- {coderag_ai-0.3.2 → coderag_ai-0.3.4}/docs/adr/005-llm-provider-independence.md +0 -0
- {coderag_ai-0.3.2 → coderag_ai-0.3.4}/docs/adr/006-repository-authorization.md +0 -0
- {coderag_ai-0.3.2 → coderag_ai-0.3.4}/docs/adr/007-token-budgeting.md +0 -0
- {coderag_ai-0.3.2 → coderag_ai-0.3.4}/docs/architecture.md +0 -0
- {coderag_ai-0.3.2 → coderag_ai-0.3.4}/docs/claude-code.md +0 -0
- {coderag_ai-0.3.2 → coderag_ai-0.3.4}/docs/evaluation.md +0 -0
- {coderag_ai-0.3.2 → coderag_ai-0.3.4}/docs/install-macos.md +0 -0
- {coderag_ai-0.3.2 → coderag_ai-0.3.4}/docs/licenses.md +0 -0
- {coderag_ai-0.3.2 → coderag_ai-0.3.4}/docs/llm-providers.md +0 -0
- {coderag_ai-0.3.2 → coderag_ai-0.3.4}/docs/retrieval.md +0 -0
- {coderag_ai-0.3.2 → coderag_ai-0.3.4}/docs/security.md +0 -0
- {coderag_ai-0.3.2 → coderag_ai-0.3.4}/docs/vscode-extension.md +0 -0
- {coderag_ai-0.3.2 → coderag_ai-0.3.4}/examples/demo-repository/payments/__init__.py +0 -0
- {coderag_ai-0.3.2 → coderag_ai-0.3.4}/examples/demo-repository/payments/api.py +0 -0
- {coderag_ai-0.3.2 → coderag_ai-0.3.4}/examples/demo-repository/payments/auth.py +0 -0
- {coderag_ai-0.3.2 → coderag_ai-0.3.4}/examples/demo-repository/payments/checkout_service.py +0 -0
- {coderag_ai-0.3.2 → coderag_ai-0.3.4}/examples/demo-repository/payments/models.py +0 -0
- {coderag_ai-0.3.2 → coderag_ai-0.3.4}/examples/demo-repository/payments/payment_repository.py +0 -0
- {coderag_ai-0.3.2 → coderag_ai-0.3.4}/examples/demo-repository/payments/payment_service.py +0 -0
- {coderag_ai-0.3.2 → coderag_ai-0.3.4}/examples/demo-repository/payments/retry_policy.py +0 -0
- {coderag_ai-0.3.2 → coderag_ai-0.3.4}/examples/demo-repository/payments/users.py +0 -0
- {coderag_ai-0.3.2 → coderag_ai-0.3.4}/examples/demo-repository/tests/test_payment_service.py +0 -0
- {coderag_ai-0.3.2 → coderag_ai-0.3.4}/examples/demo-repository/tests/test_retry_policy.py +0 -0
- {coderag_ai-0.3.2 → coderag_ai-0.3.4}/examples/eval/eval_dataset.json +0 -0
- {coderag_ai-0.3.2 → coderag_ai-0.3.4}/scripts/gen_initial_migration.py +0 -0
- {coderag_ai-0.3.2 → coderag_ai-0.3.4}/src/coderag/__init__.py +0 -0
- {coderag_ai-0.3.2 → coderag_ai-0.3.4}/src/coderag/analyzers/__init__.py +0 -0
- {coderag_ai-0.3.2 → coderag_ai-0.3.4}/src/coderag/analyzers/base.py +0 -0
- {coderag_ai-0.3.2 → coderag_ai-0.3.4}/src/coderag/analyzers/flake8_adapter.py +0 -0
- {coderag_ai-0.3.2 → coderag_ai-0.3.4}/src/coderag/analyzers/pylint_adapter.py +0 -0
- {coderag_ai-0.3.2 → coderag_ai-0.3.4}/src/coderag/analyzers/workflow.py +0 -0
- {coderag_ai-0.3.2 → coderag_ai-0.3.4}/src/coderag/api/__init__.py +0 -0
- {coderag_ai-0.3.2 → coderag_ai-0.3.4}/src/coderag/cli/__init__.py +0 -0
- {coderag_ai-0.3.2 → coderag_ai-0.3.4}/src/coderag/context/__init__.py +0 -0
- {coderag_ai-0.3.2 → coderag_ai-0.3.4}/src/coderag/context/builder.py +0 -0
- {coderag_ai-0.3.2 → coderag_ai-0.3.4}/src/coderag/context/package.py +0 -0
- {coderag_ai-0.3.2 → coderag_ai-0.3.4}/src/coderag/core/__init__.py +0 -0
- {coderag_ai-0.3.2 → coderag_ai-0.3.4}/src/coderag/core/logging.py +0 -0
- {coderag_ai-0.3.2 → coderag_ai-0.3.4}/src/coderag/core/text.py +0 -0
- {coderag_ai-0.3.2 → coderag_ai-0.3.4}/src/coderag/core/tokens.py +0 -0
- {coderag_ai-0.3.2 → coderag_ai-0.3.4}/src/coderag/db/__init__.py +0 -0
- {coderag_ai-0.3.2 → coderag_ai-0.3.4}/src/coderag/db/base.py +0 -0
- {coderag_ai-0.3.2 → coderag_ai-0.3.4}/src/coderag/db/migrations/env.py +0 -0
- {coderag_ai-0.3.2 → coderag_ai-0.3.4}/src/coderag/db/migrations/script.py.mako +0 -0
- {coderag_ai-0.3.2 → coderag_ai-0.3.4}/src/coderag/db/migrations/versions/.gitkeep +0 -0
- {coderag_ai-0.3.2 → coderag_ai-0.3.4}/src/coderag/db/migrations/versions/9a973932b94d_measure_baseline_tokens.py +0 -0
- {coderag_ai-0.3.2 → coderag_ai-0.3.4}/src/coderag/db/migrations/versions/c8a40feeda41_initial_schema.py +0 -0
- {coderag_ai-0.3.2 → coderag_ai-0.3.4}/src/coderag/db/models.py +0 -0
- {coderag_ai-0.3.2 → coderag_ai-0.3.4}/src/coderag/embeddings/__init__.py +0 -0
- {coderag_ai-0.3.2 → coderag_ai-0.3.4}/src/coderag/embeddings/base.py +0 -0
- {coderag_ai-0.3.2 → coderag_ai-0.3.4}/src/coderag/embeddings/hashing.py +0 -0
- {coderag_ai-0.3.2 → coderag_ai-0.3.4}/src/coderag/embeddings/pipeline.py +0 -0
- {coderag_ai-0.3.2 → coderag_ai-0.3.4}/src/coderag/embeddings/registry.py +0 -0
- {coderag_ai-0.3.2 → coderag_ai-0.3.4}/src/coderag/embeddings/sentence_transformer.py +0 -0
- {coderag_ai-0.3.2 → coderag_ai-0.3.4}/src/coderag/evaluation/__init__.py +0 -0
- {coderag_ai-0.3.2 → coderag_ai-0.3.4}/src/coderag/evaluation/datasets.py +0 -0
- {coderag_ai-0.3.2 → coderag_ai-0.3.4}/src/coderag/evaluation/harness.py +0 -0
- {coderag_ai-0.3.2 → coderag_ai-0.3.4}/src/coderag/git/__init__.py +0 -0
- {coderag_ai-0.3.2 → coderag_ai-0.3.4}/src/coderag/git/repo.py +0 -0
- {coderag_ai-0.3.2 → coderag_ai-0.3.4}/src/coderag/indexing/__init__.py +0 -0
- {coderag_ai-0.3.2 → coderag_ai-0.3.4}/src/coderag/indexing/ignore.py +0 -0
- {coderag_ai-0.3.2 → coderag_ai-0.3.4}/src/coderag/indexing/indexer.py +0 -0
- {coderag_ai-0.3.2 → coderag_ai-0.3.4}/src/coderag/llm/__init__.py +0 -0
- {coderag_ai-0.3.2 → coderag_ai-0.3.4}/src/coderag/llm/anthropic.py +0 -0
- {coderag_ai-0.3.2 → coderag_ai-0.3.4}/src/coderag/llm/base.py +0 -0
- {coderag_ai-0.3.2 → coderag_ai-0.3.4}/src/coderag/llm/null.py +0 -0
- {coderag_ai-0.3.2 → coderag_ai-0.3.4}/src/coderag/llm/registry.py +0 -0
- {coderag_ai-0.3.2 → coderag_ai-0.3.4}/src/coderag/localdb.py +0 -0
- {coderag_ai-0.3.2 → coderag_ai-0.3.4}/src/coderag/mcp_server.py +0 -0
- {coderag_ai-0.3.2 → coderag_ai-0.3.4}/src/coderag/parsing/__init__.py +0 -0
- {coderag_ai-0.3.2 → coderag_ai-0.3.4}/src/coderag/parsing/base.py +0 -0
- {coderag_ai-0.3.2 → coderag_ai-0.3.4}/src/coderag/parsing/python.py +0 -0
- {coderag_ai-0.3.2 → coderag_ai-0.3.4}/src/coderag/parsing/registry.py +0 -0
- {coderag_ai-0.3.2 → coderag_ai-0.3.4}/src/coderag/retrieval/__init__.py +0 -0
- {coderag_ai-0.3.2 → coderag_ai-0.3.4}/src/coderag/retrieval/base.py +0 -0
- {coderag_ai-0.3.2 → coderag_ai-0.3.4}/src/coderag/retrieval/engine.py +0 -0
- {coderag_ai-0.3.2 → coderag_ai-0.3.4}/src/coderag/retrieval/fusion.py +0 -0
- {coderag_ai-0.3.2 → coderag_ai-0.3.4}/src/coderag/retrieval/graph.py +0 -0
- {coderag_ai-0.3.2 → coderag_ai-0.3.4}/src/coderag/retrieval/lexical.py +0 -0
- {coderag_ai-0.3.2 → coderag_ai-0.3.4}/src/coderag/retrieval/reranking.py +0 -0
- {coderag_ai-0.3.2 → coderag_ai-0.3.4}/src/coderag/retrieval/semantic.py +0 -0
- {coderag_ai-0.3.2 → coderag_ai-0.3.4}/src/coderag/retrieval/symbol.py +0 -0
- {coderag_ai-0.3.2 → coderag_ai-0.3.4}/src/coderag/security/__init__.py +0 -0
- {coderag_ai-0.3.2 → coderag_ai-0.3.4}/src/coderag/security/authz.py +0 -0
- {coderag_ai-0.3.2 → coderag_ai-0.3.4}/src/coderag/security/secrets.py +0 -0
- {coderag_ai-0.3.2 → coderag_ai-0.3.4}/src/coderag/service.py +0 -0
- {coderag_ai-0.3.2 → coderag_ai-0.3.4}/src/coderag/telemetry.py +0 -0
- {coderag_ai-0.3.2 → coderag_ai-0.3.4}/tests/__init__.py +0 -0
- {coderag_ai-0.3.2 → coderag_ai-0.3.4}/tests/conftest.py +0 -0
- {coderag_ai-0.3.2 → coderag_ai-0.3.4}/tests/test_phase0_db.py +0 -0
- {coderag_ai-0.3.2 → coderag_ai-0.3.4}/tests/test_phase0_migrations.py +0 -0
- {coderag_ai-0.3.2 → coderag_ai-0.3.4}/tests/test_phase10_analyzers.py +0 -0
- {coderag_ai-0.3.2 → coderag_ai-0.3.4}/tests/test_phase11_dashboard.py +0 -0
- {coderag_ai-0.3.2 → coderag_ai-0.3.4}/tests/test_phase12_mcp.py +0 -0
- {coderag_ai-0.3.2 → coderag_ai-0.3.4}/tests/test_phase14_localdb.py +0 -0
- {coderag_ai-0.3.2 → coderag_ai-0.3.4}/tests/test_phase1_indexer.py +0 -0
- {coderag_ai-0.3.2 → coderag_ai-0.3.4}/tests/test_phase1_parser.py +0 -0
- {coderag_ai-0.3.2 → coderag_ai-0.3.4}/tests/test_phase2_cli.py +0 -0
- {coderag_ai-0.3.2 → coderag_ai-0.3.4}/tests/test_phase2_retrieval.py +0 -0
- {coderag_ai-0.3.2 → coderag_ai-0.3.4}/tests/test_phase3_embeddings.py +0 -0
- {coderag_ai-0.3.2 → coderag_ai-0.3.4}/tests/test_phase3_semantic.py +0 -0
- {coderag_ai-0.3.2 → coderag_ai-0.3.4}/tests/test_phase3_sentence_transformer.py +0 -0
- {coderag_ai-0.3.2 → coderag_ai-0.3.4}/tests/test_phase4_graph.py +0 -0
- {coderag_ai-0.3.2 → coderag_ai-0.3.4}/tests/test_phase5_context_db.py +0 -0
- {coderag_ai-0.3.2 → coderag_ai-0.3.4}/tests/test_phase5_context_unit.py +0 -0
- {coderag_ai-0.3.2 → coderag_ai-0.3.4}/tests/test_phase6_api.py +0 -0
- {coderag_ai-0.3.2 → coderag_ai-0.3.4}/tests/test_phase6_ask.py +0 -0
- {coderag_ai-0.3.2 → coderag_ai-0.3.4}/tests/test_phase6_llm.py +0 -0
- {coderag_ai-0.3.2 → coderag_ai-0.3.4}/tests/test_phase7_eval.py +0 -0
- {coderag_ai-0.3.2 → coderag_ai-0.3.4}/tests/test_phase8_incremental.py +0 -0
- {coderag_ai-0.3.2 → coderag_ai-0.3.4}/tests/test_phase9_rerank.py +0 -0
- {coderag_ai-0.3.2 → coderag_ai-0.3.4}/tests/test_security_secrets.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: coderag-ai
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.4
|
|
4
4
|
Summary: Token-efficient, structure-aware Code Intelligence + RAG for private source repositories.
|
|
5
5
|
Project-URL: Homepage, https://github.com/galacticsurfer/coderag
|
|
6
6
|
Project-URL: Repository, https://github.com/galacticsurfer/coderag
|
|
@@ -7,7 +7,7 @@ build-backend = "hatchling.build"
|
|
|
7
7
|
# command are both `coderag`. Plain "coderag" is taken on PyPI by an unrelated project,
|
|
8
8
|
# and "code-rag" is rejected as too similar (PyPI collapses -/_/. when comparing names).
|
|
9
9
|
name = "coderag-ai"
|
|
10
|
-
version = "0.3.
|
|
10
|
+
version = "0.3.4"
|
|
11
11
|
description = "Token-efficient, structure-aware Code Intelligence + RAG for private source repositories."
|
|
12
12
|
readme = "README.md"
|
|
13
13
|
requires-python = ">=3.12"
|
|
@@ -286,6 +286,10 @@ def metrics(session: Session = Depends(get_session)) -> s.MetricsOut:
|
|
|
286
286
|
) or 0)
|
|
287
287
|
saved_vs_files = base_sum - base_ctx
|
|
288
288
|
reduction_vs_files = round(100.0 * saved_vs_files / base_sum, 1) if base_sum else 0.0
|
|
289
|
+
from coderag.core.config import get_settings
|
|
290
|
+
|
|
291
|
+
_s = get_settings()
|
|
292
|
+
pin, pout = _s.price_input_per_mtok, _s.price_output_per_mtok
|
|
289
293
|
return s.MetricsOut(
|
|
290
294
|
repositories=count(Repository), symbols=count(Symbol),
|
|
291
295
|
embeddings=count(SymbolEmbedding), relationships=count(SymbolRelationship),
|
|
@@ -297,6 +301,10 @@ def metrics(session: Session = Depends(get_session)) -> s.MetricsOut:
|
|
|
297
301
|
total_tokens_saved=saved, avg_token_reduction_percent=reduction,
|
|
298
302
|
total_baseline_tokens=base_sum, total_saved_vs_files=saved_vs_files,
|
|
299
303
|
reduction_vs_files_percent=reduction_vs_files,
|
|
304
|
+
price_input_per_mtok=pin, price_output_per_mtok=pout,
|
|
305
|
+
cost_saved_vs_files_usd=round(saved_vs_files / 1e6 * pin, 4),
|
|
306
|
+
cost_context_sent_usd=round(int(ctx_sum) / 1e6 * pin, 4),
|
|
307
|
+
cost_llm_usd=round(int(in_tok) / 1e6 * pin + int(out_tok) / 1e6 * pout, 4),
|
|
300
308
|
)
|
|
301
309
|
|
|
302
310
|
|
|
@@ -109,6 +109,7 @@ DASHBOARD_HTML = r"""<!doctype html>
|
|
|
109
109
|
</header>
|
|
110
110
|
|
|
111
111
|
<section class="kpis" id="kpis"></section>
|
|
112
|
+
<p style="font-size:12px;color:var(--muted);margin:-14px 0 22px">Dollar figures are estimates at the configured per-million prices (CODERAG_PRICE_INPUT_PER_MTOK / _OUTPUT_PER_MTOK) — not billing data. On a flat-rate Claude plan, savings show up as headroom rather than a refund.</p>
|
|
112
113
|
|
|
113
114
|
<section class="card">
|
|
114
115
|
<h2>Context vs. saved — recent queries</h2>
|
|
@@ -143,6 +144,7 @@ DASHBOARD_HTML = r"""<!doctype html>
|
|
|
143
144
|
<script>
|
|
144
145
|
const n = x => (x==null? "—" : Number(x).toLocaleString());
|
|
145
146
|
const pct = x => (x==null? "—" : Number(x).toFixed(1) + "%");
|
|
147
|
+
const usd = x => (x==null? "—" : "$" + Number(x).toLocaleString(undefined,{minimumFractionDigits:2, maximumFractionDigits:2}));
|
|
146
148
|
const esc = s => (s||"").replace(/[&<>"]/g, c => ({"&":"&","<":"<",">":">",'"':"""}[c]));
|
|
147
149
|
|
|
148
150
|
function tile(label, val, unit, hero){
|
|
@@ -153,7 +155,10 @@ function tile(label, val, unit, hero){
|
|
|
153
155
|
function renderKpis(m){
|
|
154
156
|
document.getElementById('kpis').innerHTML =
|
|
155
157
|
tile('Saved vs reading files', n(m.total_saved_vs_files), 'tok', true) +
|
|
158
|
+
tile('Est. $ saved', usd(m.cost_saved_vs_files_usd), '', true) +
|
|
156
159
|
tile('Reduction vs files', pct(m.reduction_vs_files_percent), '', true) +
|
|
160
|
+
tile('Est. $ context sent', usd(m.cost_context_sent_usd)) +
|
|
161
|
+
tile('Est. $ LLM spend', usd(m.cost_llm_usd)) +
|
|
157
162
|
tile('Whole-file baseline', n(m.total_baseline_tokens), 'tok') +
|
|
158
163
|
tile('Tokens saved (budgeting)', n(m.total_tokens_saved)) +
|
|
159
164
|
tile('Overall reduction', pct(m.avg_token_reduction_percent)) +
|
|
@@ -158,6 +158,12 @@ class MetricsOut(BaseModel):
|
|
|
158
158
|
total_baseline_tokens: int
|
|
159
159
|
total_saved_vs_files: int
|
|
160
160
|
reduction_vs_files_percent: float
|
|
161
|
+
# Indicative USD at the configured per-million prices (estimates, not billing)
|
|
162
|
+
price_input_per_mtok: float
|
|
163
|
+
price_output_per_mtok: float
|
|
164
|
+
cost_saved_vs_files_usd: float
|
|
165
|
+
cost_context_sent_usd: float
|
|
166
|
+
cost_llm_usd: float
|
|
161
167
|
|
|
162
168
|
|
|
163
169
|
class QueryRow(BaseModel):
|
|
@@ -45,19 +45,17 @@ def localdb_start(
|
|
|
45
45
|
raise typer.Exit(1) from None
|
|
46
46
|
console.print(f"[green]PostgreSQL running.[/]\n\n export CODERAG_DATABASE_URL='{url}'\n")
|
|
47
47
|
if migrate:
|
|
48
|
-
|
|
49
|
-
|
|
48
|
+
# Uses Alembic's Python API against the packaged migrations — an installed
|
|
49
|
+
# wheel has neither the `alembic` console script on PATH nor alembic.ini.
|
|
50
|
+
from coderag.db.migrate import upgrade_to_head
|
|
50
51
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
capture_output=True, text=True)
|
|
54
|
-
if r.returncode == 0:
|
|
52
|
+
try:
|
|
53
|
+
upgrade_to_head(url)
|
|
55
54
|
console.print("[green]Migrations applied.[/]")
|
|
56
|
-
|
|
57
|
-
console.print(
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
)
|
|
55
|
+
except Exception as exc:
|
|
56
|
+
console.print(f"[red]Migrations failed:[/] {exc}")
|
|
57
|
+
console.print("[yellow]Retry with:[/] coderag migrate")
|
|
58
|
+
raise typer.Exit(1) from None
|
|
61
59
|
|
|
62
60
|
|
|
63
61
|
@localdb_app.command("stop")
|
|
@@ -82,6 +80,23 @@ def localdb_status(
|
|
|
82
80
|
console.print(f"[green]running (pid {pid})[/]" if pid else "[yellow]not running[/]")
|
|
83
81
|
|
|
84
82
|
|
|
83
|
+
@app.command()
|
|
84
|
+
def migrate(
|
|
85
|
+
database_url: str | None = typer.Option(
|
|
86
|
+
None, "--database-url", help="Defaults to CODERAG_DATABASE_URL."
|
|
87
|
+
),
|
|
88
|
+
) -> None:
|
|
89
|
+
"""Create/upgrade the database schema (works from a pip/pipx install)."""
|
|
90
|
+
from coderag.db.migrate import current_revision, upgrade_to_head
|
|
91
|
+
|
|
92
|
+
try:
|
|
93
|
+
url = upgrade_to_head(database_url)
|
|
94
|
+
except Exception as exc:
|
|
95
|
+
console.print(f"[red]Migration failed:[/] {exc}")
|
|
96
|
+
raise typer.Exit(1) from None
|
|
97
|
+
console.print(f"[green]Database is up to date[/] (revision {current_revision(url)}).")
|
|
98
|
+
|
|
99
|
+
|
|
85
100
|
@app.command()
|
|
86
101
|
def index(
|
|
87
102
|
path: str = typer.Argument(..., help="Path to the repository to index."),
|
|
@@ -180,9 +195,15 @@ def context(
|
|
|
180
195
|
f"{acct.final_prompt_tokens:,}")
|
|
181
196
|
console.print(t)
|
|
182
197
|
if acct.baseline_tokens:
|
|
198
|
+
price = get_settings().price_input_per_mtok
|
|
199
|
+
saved_usd = acct.tokens_saved_vs_files / 1e6 * price
|
|
183
200
|
console.print(
|
|
184
201
|
f"[green]saved {acct.tokens_saved_vs_files:,} tokens "
|
|
185
|
-
f"({acct.reduction_vs_files}%) vs opening those files[/]"
|
|
202
|
+
f"({acct.reduction_vs_files}%) vs opening those files[/] "
|
|
203
|
+
f"[dim]≈ ${saved_usd:.4f} at ${price:.2f}/M input tokens; "
|
|
204
|
+
f"× 1,000 similar queries ≈ ${saved_usd * 1000:.2f}[/]\n"
|
|
205
|
+
f"[dim]Estimate at configured prices, not billing data. On a "
|
|
206
|
+
f"flat-rate plan this is headroom, not a refund.[/]"
|
|
186
207
|
)
|
|
187
208
|
else:
|
|
188
209
|
console.print("[yellow]No baseline recorded (nothing selected).[/]")
|
|
@@ -85,6 +85,13 @@ class Settings(BaseSettings):
|
|
|
85
85
|
llm_max_output_tokens: int = 1024
|
|
86
86
|
llm_timeout_seconds: float = 60.0
|
|
87
87
|
|
|
88
|
+
# ---- Cost estimation -------------------------------------------------
|
|
89
|
+
# USD per million tokens, used only to turn token counts into indicative
|
|
90
|
+
# dollar figures. Defaults are Claude Opus 4.8 list prices; override for your
|
|
91
|
+
# model/plan. These are ESTIMATES at configured prices — not billing data.
|
|
92
|
+
price_input_per_mtok: float = 5.00
|
|
93
|
+
price_output_per_mtok: float = 25.00
|
|
94
|
+
|
|
88
95
|
# ---- Analyzers (Phase 10) ------------------------------------------
|
|
89
96
|
max_fix_attempts: int = 2
|
|
90
97
|
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"""Run database migrations from an installed package.
|
|
2
|
+
|
|
3
|
+
The `alembic` console script and `alembic.ini` are only available in a source
|
|
4
|
+
checkout. When CodeRAG is installed as a wheel (pipx/pip), neither is on PATH, so
|
|
5
|
+
migrations are driven through Alembic's Python API with the migration directory
|
|
6
|
+
resolved from the installed package.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
|
|
13
|
+
from coderag.core.config import get_settings
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def migrations_path() -> Path:
|
|
17
|
+
"""Filesystem path of the packaged Alembic migration directory."""
|
|
18
|
+
return Path(__file__).resolve().parent / "migrations"
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _config(url: str):
|
|
22
|
+
from alembic.config import Config
|
|
23
|
+
|
|
24
|
+
cfg = Config()
|
|
25
|
+
cfg.set_main_option("script_location", str(migrations_path()))
|
|
26
|
+
# Escape '%' so ConfigParser interpolation doesn't choke on URL-encoded values.
|
|
27
|
+
cfg.set_main_option("sqlalchemy.url", url.replace("%", "%%"))
|
|
28
|
+
return cfg
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def upgrade_to_head(url: str | None = None) -> str:
|
|
32
|
+
"""Upgrade the database to the latest revision. Returns the URL used."""
|
|
33
|
+
from alembic import command
|
|
34
|
+
|
|
35
|
+
target = url or get_settings().database_url
|
|
36
|
+
command.upgrade(_config(target), "head")
|
|
37
|
+
return target
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def current_revision(url: str | None = None) -> str | None:
|
|
41
|
+
"""Return the revision the database is currently stamped at (None if empty)."""
|
|
42
|
+
from alembic.migration import MigrationContext
|
|
43
|
+
from sqlalchemy import create_engine
|
|
44
|
+
|
|
45
|
+
target = url or get_settings().database_url
|
|
46
|
+
engine = create_engine(target)
|
|
47
|
+
try:
|
|
48
|
+
with engine.connect() as conn:
|
|
49
|
+
return MigrationContext.configure(conn).get_current_revision()
|
|
50
|
+
finally:
|
|
51
|
+
engine.dispose()
|
|
@@ -77,3 +77,28 @@ def test_mcp_context_includes_measurement(db_session, demo_repo):
|
|
|
77
77
|
out = coderag_context("retry failed payment", repository="payments")
|
|
78
78
|
assert "fewer input tokens" in out["measurement"]
|
|
79
79
|
assert out["token_accounting"]["baseline_tokens"] > 0
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def test_metrics_exposes_dollar_estimates(db_session, demo_repo):
|
|
83
|
+
"""Token savings are convertible to indicative USD at configured prices."""
|
|
84
|
+
from coderag.core.config import get_settings
|
|
85
|
+
|
|
86
|
+
run_context(db_session, "retry failed payment", "payments")
|
|
87
|
+
db_session.commit()
|
|
88
|
+
|
|
89
|
+
client = TestClient(app)
|
|
90
|
+
m = client.get("/metrics").json()
|
|
91
|
+
s = get_settings()
|
|
92
|
+
assert m["price_input_per_mtok"] == s.price_input_per_mtok
|
|
93
|
+
# dollars must be a faithful function of tokens and the configured price
|
|
94
|
+
expected = round(m["total_saved_vs_files"] / 1e6 * s.price_input_per_mtok, 4)
|
|
95
|
+
assert m["cost_saved_vs_files_usd"] == expected
|
|
96
|
+
assert m["cost_context_sent_usd"] >= 0.0
|
|
97
|
+
assert m["cost_llm_usd"] >= 0.0
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def test_price_settings_are_configurable():
|
|
101
|
+
from coderag.core.config import Settings
|
|
102
|
+
|
|
103
|
+
s = Settings(price_input_per_mtok=3.0, price_output_per_mtok=15.0)
|
|
104
|
+
assert s.price_input_per_mtok == 3.0 and s.price_output_per_mtok == 15.0
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"""Migrations must work from an installed wheel (no alembic.ini, no CLI on PATH)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import uuid
|
|
6
|
+
|
|
7
|
+
import pytest
|
|
8
|
+
from sqlalchemy import create_engine, text
|
|
9
|
+
|
|
10
|
+
from coderag.db.migrate import current_revision, migrations_path, upgrade_to_head
|
|
11
|
+
|
|
12
|
+
pytestmark = pytest.mark.db
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def test_migrations_are_packaged():
|
|
16
|
+
"""The migration scripts ship inside the package, not just the source tree."""
|
|
17
|
+
path = migrations_path()
|
|
18
|
+
assert (path / "env.py").is_file()
|
|
19
|
+
versions = list((path / "versions").glob("*.py"))
|
|
20
|
+
assert versions, "no migration revisions packaged"
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def test_upgrade_to_head_without_alembic_ini(pg_uri: str):
|
|
24
|
+
"""Regression: `localdb start` used to shell out to an `alembic` binary that
|
|
25
|
+
isn't on PATH in a pipx install. Migrations now run via the Python API."""
|
|
26
|
+
dbname = "migapi_" + uuid.uuid4().hex[:8]
|
|
27
|
+
admin = create_engine(pg_uri, isolation_level="AUTOCOMMIT")
|
|
28
|
+
with admin.connect() as conn:
|
|
29
|
+
conn.execute(text(f'CREATE DATABASE "{dbname}"'))
|
|
30
|
+
admin.dispose()
|
|
31
|
+
|
|
32
|
+
url = pg_uri.replace("/postgres?", f"/{dbname}?")
|
|
33
|
+
assert current_revision(url) is None # fresh database
|
|
34
|
+
upgrade_to_head(url)
|
|
35
|
+
assert current_revision(url) is not None # stamped after upgrade
|
|
36
|
+
|
|
37
|
+
eng = create_engine(url)
|
|
38
|
+
with eng.connect() as conn:
|
|
39
|
+
tables = set(conn.execute(text(
|
|
40
|
+
"SELECT table_name FROM information_schema.tables WHERE table_schema='public'"
|
|
41
|
+
)).scalars())
|
|
42
|
+
eng.dispose()
|
|
43
|
+
assert {"repositories", "symbols", "symbol_embeddings"} <= tables
|
|
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
|
{coderag_ai-0.3.2 → coderag_ai-0.3.4}/examples/demo-repository/payments/payment_repository.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{coderag_ai-0.3.2 → coderag_ai-0.3.4}/examples/demo-repository/tests/test_payment_service.py
RENAMED
|
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
|
|
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
|
|
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
|