coderag-ai 0.3.4__tar.gz → 0.4.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.
Files changed (144) hide show
  1. {coderag_ai-0.3.4 → coderag_ai-0.4.0}/PKG-INFO +46 -8
  2. {coderag_ai-0.3.4 → coderag_ai-0.4.0}/README.md +45 -7
  3. coderag_ai-0.4.0/examples/mcp/.mcp.json +23 -0
  4. coderag_ai-0.4.0/examples/mcp/CLAUDE.md +23 -0
  5. coderag_ai-0.4.0/examples/mcp/README.md +152 -0
  6. {coderag_ai-0.3.4 → coderag_ai-0.4.0}/pyproject.toml +1 -1
  7. {coderag_ai-0.3.4 → coderag_ai-0.4.0}/src/coderag/cli/main.py +91 -0
  8. {coderag_ai-0.3.4 → coderag_ai-0.4.0}/src/coderag/core/config.py +16 -3
  9. {coderag_ai-0.3.4 → coderag_ai-0.4.0}/src/coderag/git/repo.py +15 -0
  10. {coderag_ai-0.3.4 → coderag_ai-0.4.0}/src/coderag/indexing/indexer.py +21 -1
  11. {coderag_ai-0.3.4 → coderag_ai-0.4.0}/src/coderag/localdb.py +19 -0
  12. coderag_ai-0.4.0/src/coderag/setup_flow.py +101 -0
  13. {coderag_ai-0.3.4 → coderag_ai-0.4.0}/tests/test_phase14_localdb.py +27 -0
  14. {coderag_ai-0.3.4 → coderag_ai-0.4.0}/tests/test_phase8_incremental.py +51 -0
  15. {coderag_ai-0.3.4 → coderag_ai-0.4.0}/.env.example +0 -0
  16. {coderag_ai-0.3.4 → coderag_ai-0.4.0}/.gitignore +0 -0
  17. {coderag_ai-0.3.4 → coderag_ai-0.4.0}/CODE_OF_CONDUCT.md +0 -0
  18. {coderag_ai-0.3.4 → coderag_ai-0.4.0}/CONTRIBUTING.md +0 -0
  19. {coderag_ai-0.3.4 → coderag_ai-0.4.0}/Dockerfile +0 -0
  20. {coderag_ai-0.3.4 → coderag_ai-0.4.0}/LICENSE +0 -0
  21. {coderag_ai-0.3.4 → coderag_ai-0.4.0}/Makefile +0 -0
  22. {coderag_ai-0.3.4 → coderag_ai-0.4.0}/NOTICE +0 -0
  23. {coderag_ai-0.3.4 → coderag_ai-0.4.0}/SECURITY.md +0 -0
  24. {coderag_ai-0.3.4 → coderag_ai-0.4.0}/alembic.ini +0 -0
  25. {coderag_ai-0.3.4 → coderag_ai-0.4.0}/docker/entrypoint.sh +0 -0
  26. {coderag_ai-0.3.4 → coderag_ai-0.4.0}/docker-compose.yml +0 -0
  27. {coderag_ai-0.3.4 → coderag_ai-0.4.0}/docs/adding-language.md +0 -0
  28. {coderag_ai-0.3.4 → coderag_ai-0.4.0}/docs/adr/001-postgres-pgvector.md +0 -0
  29. {coderag_ai-0.3.4 → coderag_ai-0.4.0}/docs/adr/002-structural-chunks.md +0 -0
  30. {coderag_ai-0.3.4 → coderag_ai-0.4.0}/docs/adr/003-hybrid-search.md +0 -0
  31. {coderag_ai-0.3.4 → coderag_ai-0.4.0}/docs/adr/004-tree-sitter.md +0 -0
  32. {coderag_ai-0.3.4 → coderag_ai-0.4.0}/docs/adr/005-llm-provider-independence.md +0 -0
  33. {coderag_ai-0.3.4 → coderag_ai-0.4.0}/docs/adr/006-repository-authorization.md +0 -0
  34. {coderag_ai-0.3.4 → coderag_ai-0.4.0}/docs/adr/007-token-budgeting.md +0 -0
  35. {coderag_ai-0.3.4 → coderag_ai-0.4.0}/docs/architecture.md +0 -0
  36. {coderag_ai-0.3.4 → coderag_ai-0.4.0}/docs/claude-code.md +0 -0
  37. {coderag_ai-0.3.4 → coderag_ai-0.4.0}/docs/evaluation.md +0 -0
  38. {coderag_ai-0.3.4 → coderag_ai-0.4.0}/docs/install-macos.md +0 -0
  39. {coderag_ai-0.3.4 → coderag_ai-0.4.0}/docs/licenses.md +0 -0
  40. {coderag_ai-0.3.4 → coderag_ai-0.4.0}/docs/llm-providers.md +0 -0
  41. {coderag_ai-0.3.4 → coderag_ai-0.4.0}/docs/retrieval.md +0 -0
  42. {coderag_ai-0.3.4 → coderag_ai-0.4.0}/docs/security.md +0 -0
  43. {coderag_ai-0.3.4 → coderag_ai-0.4.0}/docs/vscode-extension.md +0 -0
  44. {coderag_ai-0.3.4 → coderag_ai-0.4.0}/examples/demo-repository/payments/__init__.py +0 -0
  45. {coderag_ai-0.3.4 → coderag_ai-0.4.0}/examples/demo-repository/payments/api.py +0 -0
  46. {coderag_ai-0.3.4 → coderag_ai-0.4.0}/examples/demo-repository/payments/auth.py +0 -0
  47. {coderag_ai-0.3.4 → coderag_ai-0.4.0}/examples/demo-repository/payments/checkout_service.py +0 -0
  48. {coderag_ai-0.3.4 → coderag_ai-0.4.0}/examples/demo-repository/payments/models.py +0 -0
  49. {coderag_ai-0.3.4 → coderag_ai-0.4.0}/examples/demo-repository/payments/payment_repository.py +0 -0
  50. {coderag_ai-0.3.4 → coderag_ai-0.4.0}/examples/demo-repository/payments/payment_service.py +0 -0
  51. {coderag_ai-0.3.4 → coderag_ai-0.4.0}/examples/demo-repository/payments/retry_policy.py +0 -0
  52. {coderag_ai-0.3.4 → coderag_ai-0.4.0}/examples/demo-repository/payments/users.py +0 -0
  53. {coderag_ai-0.3.4 → coderag_ai-0.4.0}/examples/demo-repository/tests/test_payment_service.py +0 -0
  54. {coderag_ai-0.3.4 → coderag_ai-0.4.0}/examples/demo-repository/tests/test_retry_policy.py +0 -0
  55. {coderag_ai-0.3.4 → coderag_ai-0.4.0}/examples/eval/eval_dataset.json +0 -0
  56. {coderag_ai-0.3.4 → coderag_ai-0.4.0}/scripts/gen_initial_migration.py +0 -0
  57. {coderag_ai-0.3.4 → coderag_ai-0.4.0}/src/coderag/__init__.py +0 -0
  58. {coderag_ai-0.3.4 → coderag_ai-0.4.0}/src/coderag/analyzers/__init__.py +0 -0
  59. {coderag_ai-0.3.4 → coderag_ai-0.4.0}/src/coderag/analyzers/base.py +0 -0
  60. {coderag_ai-0.3.4 → coderag_ai-0.4.0}/src/coderag/analyzers/flake8_adapter.py +0 -0
  61. {coderag_ai-0.3.4 → coderag_ai-0.4.0}/src/coderag/analyzers/pylint_adapter.py +0 -0
  62. {coderag_ai-0.3.4 → coderag_ai-0.4.0}/src/coderag/analyzers/workflow.py +0 -0
  63. {coderag_ai-0.3.4 → coderag_ai-0.4.0}/src/coderag/api/__init__.py +0 -0
  64. {coderag_ai-0.3.4 → coderag_ai-0.4.0}/src/coderag/api/app.py +0 -0
  65. {coderag_ai-0.3.4 → coderag_ai-0.4.0}/src/coderag/api/dashboard.py +0 -0
  66. {coderag_ai-0.3.4 → coderag_ai-0.4.0}/src/coderag/api/schemas.py +0 -0
  67. {coderag_ai-0.3.4 → coderag_ai-0.4.0}/src/coderag/cli/__init__.py +0 -0
  68. {coderag_ai-0.3.4 → coderag_ai-0.4.0}/src/coderag/context/__init__.py +0 -0
  69. {coderag_ai-0.3.4 → coderag_ai-0.4.0}/src/coderag/context/builder.py +0 -0
  70. {coderag_ai-0.3.4 → coderag_ai-0.4.0}/src/coderag/context/package.py +0 -0
  71. {coderag_ai-0.3.4 → coderag_ai-0.4.0}/src/coderag/core/__init__.py +0 -0
  72. {coderag_ai-0.3.4 → coderag_ai-0.4.0}/src/coderag/core/logging.py +0 -0
  73. {coderag_ai-0.3.4 → coderag_ai-0.4.0}/src/coderag/core/text.py +0 -0
  74. {coderag_ai-0.3.4 → coderag_ai-0.4.0}/src/coderag/core/tokens.py +0 -0
  75. {coderag_ai-0.3.4 → coderag_ai-0.4.0}/src/coderag/db/__init__.py +0 -0
  76. {coderag_ai-0.3.4 → coderag_ai-0.4.0}/src/coderag/db/base.py +0 -0
  77. {coderag_ai-0.3.4 → coderag_ai-0.4.0}/src/coderag/db/migrate.py +0 -0
  78. {coderag_ai-0.3.4 → coderag_ai-0.4.0}/src/coderag/db/migrations/env.py +0 -0
  79. {coderag_ai-0.3.4 → coderag_ai-0.4.0}/src/coderag/db/migrations/script.py.mako +0 -0
  80. {coderag_ai-0.3.4 → coderag_ai-0.4.0}/src/coderag/db/migrations/versions/.gitkeep +0 -0
  81. {coderag_ai-0.3.4 → coderag_ai-0.4.0}/src/coderag/db/migrations/versions/9a973932b94d_measure_baseline_tokens.py +0 -0
  82. {coderag_ai-0.3.4 → coderag_ai-0.4.0}/src/coderag/db/migrations/versions/c8a40feeda41_initial_schema.py +0 -0
  83. {coderag_ai-0.3.4 → coderag_ai-0.4.0}/src/coderag/db/models.py +0 -0
  84. {coderag_ai-0.3.4 → coderag_ai-0.4.0}/src/coderag/embeddings/__init__.py +0 -0
  85. {coderag_ai-0.3.4 → coderag_ai-0.4.0}/src/coderag/embeddings/base.py +0 -0
  86. {coderag_ai-0.3.4 → coderag_ai-0.4.0}/src/coderag/embeddings/hashing.py +0 -0
  87. {coderag_ai-0.3.4 → coderag_ai-0.4.0}/src/coderag/embeddings/pipeline.py +0 -0
  88. {coderag_ai-0.3.4 → coderag_ai-0.4.0}/src/coderag/embeddings/registry.py +0 -0
  89. {coderag_ai-0.3.4 → coderag_ai-0.4.0}/src/coderag/embeddings/sentence_transformer.py +0 -0
  90. {coderag_ai-0.3.4 → coderag_ai-0.4.0}/src/coderag/evaluation/__init__.py +0 -0
  91. {coderag_ai-0.3.4 → coderag_ai-0.4.0}/src/coderag/evaluation/datasets.py +0 -0
  92. {coderag_ai-0.3.4 → coderag_ai-0.4.0}/src/coderag/evaluation/harness.py +0 -0
  93. {coderag_ai-0.3.4 → coderag_ai-0.4.0}/src/coderag/git/__init__.py +0 -0
  94. {coderag_ai-0.3.4 → coderag_ai-0.4.0}/src/coderag/indexing/__init__.py +0 -0
  95. {coderag_ai-0.3.4 → coderag_ai-0.4.0}/src/coderag/indexing/ignore.py +0 -0
  96. {coderag_ai-0.3.4 → coderag_ai-0.4.0}/src/coderag/llm/__init__.py +0 -0
  97. {coderag_ai-0.3.4 → coderag_ai-0.4.0}/src/coderag/llm/anthropic.py +0 -0
  98. {coderag_ai-0.3.4 → coderag_ai-0.4.0}/src/coderag/llm/base.py +0 -0
  99. {coderag_ai-0.3.4 → coderag_ai-0.4.0}/src/coderag/llm/null.py +0 -0
  100. {coderag_ai-0.3.4 → coderag_ai-0.4.0}/src/coderag/llm/registry.py +0 -0
  101. {coderag_ai-0.3.4 → coderag_ai-0.4.0}/src/coderag/mcp_server.py +0 -0
  102. {coderag_ai-0.3.4 → coderag_ai-0.4.0}/src/coderag/parsing/__init__.py +0 -0
  103. {coderag_ai-0.3.4 → coderag_ai-0.4.0}/src/coderag/parsing/base.py +0 -0
  104. {coderag_ai-0.3.4 → coderag_ai-0.4.0}/src/coderag/parsing/python.py +0 -0
  105. {coderag_ai-0.3.4 → coderag_ai-0.4.0}/src/coderag/parsing/registry.py +0 -0
  106. {coderag_ai-0.3.4 → coderag_ai-0.4.0}/src/coderag/retrieval/__init__.py +0 -0
  107. {coderag_ai-0.3.4 → coderag_ai-0.4.0}/src/coderag/retrieval/base.py +0 -0
  108. {coderag_ai-0.3.4 → coderag_ai-0.4.0}/src/coderag/retrieval/engine.py +0 -0
  109. {coderag_ai-0.3.4 → coderag_ai-0.4.0}/src/coderag/retrieval/fusion.py +0 -0
  110. {coderag_ai-0.3.4 → coderag_ai-0.4.0}/src/coderag/retrieval/graph.py +0 -0
  111. {coderag_ai-0.3.4 → coderag_ai-0.4.0}/src/coderag/retrieval/lexical.py +0 -0
  112. {coderag_ai-0.3.4 → coderag_ai-0.4.0}/src/coderag/retrieval/reranking.py +0 -0
  113. {coderag_ai-0.3.4 → coderag_ai-0.4.0}/src/coderag/retrieval/semantic.py +0 -0
  114. {coderag_ai-0.3.4 → coderag_ai-0.4.0}/src/coderag/retrieval/symbol.py +0 -0
  115. {coderag_ai-0.3.4 → coderag_ai-0.4.0}/src/coderag/security/__init__.py +0 -0
  116. {coderag_ai-0.3.4 → coderag_ai-0.4.0}/src/coderag/security/authz.py +0 -0
  117. {coderag_ai-0.3.4 → coderag_ai-0.4.0}/src/coderag/security/secrets.py +0 -0
  118. {coderag_ai-0.3.4 → coderag_ai-0.4.0}/src/coderag/service.py +0 -0
  119. {coderag_ai-0.3.4 → coderag_ai-0.4.0}/src/coderag/telemetry.py +0 -0
  120. {coderag_ai-0.3.4 → coderag_ai-0.4.0}/tests/__init__.py +0 -0
  121. {coderag_ai-0.3.4 → coderag_ai-0.4.0}/tests/conftest.py +0 -0
  122. {coderag_ai-0.3.4 → coderag_ai-0.4.0}/tests/test_phase0_db.py +0 -0
  123. {coderag_ai-0.3.4 → coderag_ai-0.4.0}/tests/test_phase0_migrations.py +0 -0
  124. {coderag_ai-0.3.4 → coderag_ai-0.4.0}/tests/test_phase10_analyzers.py +0 -0
  125. {coderag_ai-0.3.4 → coderag_ai-0.4.0}/tests/test_phase11_dashboard.py +0 -0
  126. {coderag_ai-0.3.4 → coderag_ai-0.4.0}/tests/test_phase12_mcp.py +0 -0
  127. {coderag_ai-0.3.4 → coderag_ai-0.4.0}/tests/test_phase13_measure.py +0 -0
  128. {coderag_ai-0.3.4 → coderag_ai-0.4.0}/tests/test_phase15_migrate.py +0 -0
  129. {coderag_ai-0.3.4 → coderag_ai-0.4.0}/tests/test_phase1_indexer.py +0 -0
  130. {coderag_ai-0.3.4 → coderag_ai-0.4.0}/tests/test_phase1_parser.py +0 -0
  131. {coderag_ai-0.3.4 → coderag_ai-0.4.0}/tests/test_phase2_cli.py +0 -0
  132. {coderag_ai-0.3.4 → coderag_ai-0.4.0}/tests/test_phase2_retrieval.py +0 -0
  133. {coderag_ai-0.3.4 → coderag_ai-0.4.0}/tests/test_phase3_embeddings.py +0 -0
  134. {coderag_ai-0.3.4 → coderag_ai-0.4.0}/tests/test_phase3_semantic.py +0 -0
  135. {coderag_ai-0.3.4 → coderag_ai-0.4.0}/tests/test_phase3_sentence_transformer.py +0 -0
  136. {coderag_ai-0.3.4 → coderag_ai-0.4.0}/tests/test_phase4_graph.py +0 -0
  137. {coderag_ai-0.3.4 → coderag_ai-0.4.0}/tests/test_phase5_context_db.py +0 -0
  138. {coderag_ai-0.3.4 → coderag_ai-0.4.0}/tests/test_phase5_context_unit.py +0 -0
  139. {coderag_ai-0.3.4 → coderag_ai-0.4.0}/tests/test_phase6_api.py +0 -0
  140. {coderag_ai-0.3.4 → coderag_ai-0.4.0}/tests/test_phase6_ask.py +0 -0
  141. {coderag_ai-0.3.4 → coderag_ai-0.4.0}/tests/test_phase6_llm.py +0 -0
  142. {coderag_ai-0.3.4 → coderag_ai-0.4.0}/tests/test_phase7_eval.py +0 -0
  143. {coderag_ai-0.3.4 → coderag_ai-0.4.0}/tests/test_phase9_rerank.py +0 -0
  144. {coderag_ai-0.3.4 → coderag_ai-0.4.0}/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.4
3
+ Version: 0.4.0
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
@@ -109,15 +109,21 @@ Full details: [`docs/architecture.md`](docs/architecture.md), plus ADRs in
109
109
 
110
110
  ## Install
111
111
 
112
- **macOS / no Docker (easiest):** ships a rootless bundled PostgreSQL+pgvector — no Docker,
113
- Homebrew, or `sudo`:
112
+ **Two commands.** Ships a rootless bundled PostgreSQL+pgvector — no Docker, Homebrew, or `sudo`:
114
113
 
115
114
  ```bash
116
115
  pipx install "coderag-ai[mcp,localdb]"
117
- coderag localdb start # starts Postgres + applies migrations, prints the URL
118
- export CODERAG_DATABASE_URL='…' # the URL it prints
119
- coderag index /path/to/your-project --name myproject
120
- coderag search "where is authentication handled?"
116
+ cd /path/to/your-project && coderag setup
117
+ ```
118
+
119
+ `coderag setup` starts the database, applies migrations, indexes the repo, registers the MCP
120
+ server with Claude Code, and appends the retrieval nudge to your `CLAUDE.md` — the step that
121
+ makes Claude Code actually call the tools. It records the database URL, so **no
122
+ `export CODERAG_DATABASE_URL` is needed**; later commands find it automatically. Re-running is
123
+ safe (it won't duplicate the nudge). Opt out with `--no-mcp` / `--no-claude-md`.
124
+
125
+ ```bash
126
+ coderag search "where is authentication handled?" # works in any new shell
121
127
  ```
122
128
 
123
129
  Full walkthrough incl. Claude Code wiring: [`docs/install-macos.md`](docs/install-macos.md).
@@ -345,7 +351,39 @@ claude mcp add coderag \
345
351
  Tools: `coderag_context`, `coderag_search`, `coderag_symbol`, `coderag_repositories`,
346
352
  `coderag_index`. Every call is recorded, so `/dashboard` shows the savings live. No API key
347
353
  needed — Claude Code is the LLM. **Full step-by-step:**
348
- [`docs/claude-code.md`](docs/claude-code.md).
354
+ [`docs/claude-code.md`](docs/claude-code.md). Copy-pasteable `.mcp.json` + `CLAUDE.md` snippet,
355
+ with **real** captured tool output: [`examples/mcp/`](examples/mcp/).
356
+
357
+ ## Composes with other token-efficiency tools
358
+
359
+ CodeRAG attacks **one slice** of token spend: the code you'd otherwise read whole files to get.
360
+ It does nothing about output tokens, conversation history, cache placement, or model routing.
361
+ Tools that target those slices stack with it, because the slices are disjoint:
362
+
363
+ | Slice | Billed at | CodeRAG | Tools like [Caveman](https://caveman.so) |
364
+ |---|---|:--:|:--:|
365
+ | File-reading input | 1× input | ✅ retrieval instead of whole files | — |
366
+ | Output tokens | **5× input** | ❌ | ✅ output compression |
367
+ | Conversation history | ~0.1× (cached) | ❌ | ✅ context compression |
368
+ | Cache placement | — | ❌ | ✅ automatic breakpoints |
369
+ | Model routing | varies | ❌ | ✅ cheapest model passing evals |
370
+
371
+ Savings on disjoint slices are near-additive. On a typical mid-session turn, CodeRAG's measured
372
+ file-slice reduction works out to ~26% of cost; adding an output-compression layer takes the
373
+ combined figure to roughly ~38%. **Both halves of that are models, not measurements** — verify
374
+ on your own workload before believing either.
375
+
376
+ **Wiring:** they use different mechanisms and don't conflict — CodeRAG is an MCP server, a
377
+ compression skill is a Skill, a proxy is an `ANTHROPIC_BASE_URL`. CodeRAG's own `ask` can also
378
+ route through such a proxy, since the LLM base URL is configurable:
379
+
380
+ ```bash
381
+ export CODERAG_ANTHROPIC_BASE_URL=http://localhost:<proxy-port>
382
+ ```
383
+
384
+ Enable one at a time and compare `/cost` between them — a proxy that compresses context may be
385
+ compressing context CodeRAG already minimised, so the marginal gain can be smaller than the two
386
+ vendors' claims added together.
349
387
 
350
388
  ## Editor integration (VS Code)
351
389
 
@@ -48,15 +48,21 @@ Full details: [`docs/architecture.md`](docs/architecture.md), plus ADRs in
48
48
 
49
49
  ## Install
50
50
 
51
- **macOS / no Docker (easiest):** ships a rootless bundled PostgreSQL+pgvector — no Docker,
52
- Homebrew, or `sudo`:
51
+ **Two commands.** Ships a rootless bundled PostgreSQL+pgvector — no Docker, Homebrew, or `sudo`:
53
52
 
54
53
  ```bash
55
54
  pipx install "coderag-ai[mcp,localdb]"
56
- coderag localdb start # starts Postgres + applies migrations, prints the URL
57
- export CODERAG_DATABASE_URL='…' # the URL it prints
58
- coderag index /path/to/your-project --name myproject
59
- coderag search "where is authentication handled?"
55
+ cd /path/to/your-project && coderag setup
56
+ ```
57
+
58
+ `coderag setup` starts the database, applies migrations, indexes the repo, registers the MCP
59
+ server with Claude Code, and appends the retrieval nudge to your `CLAUDE.md` — the step that
60
+ makes Claude Code actually call the tools. It records the database URL, so **no
61
+ `export CODERAG_DATABASE_URL` is needed**; later commands find it automatically. Re-running is
62
+ safe (it won't duplicate the nudge). Opt out with `--no-mcp` / `--no-claude-md`.
63
+
64
+ ```bash
65
+ coderag search "where is authentication handled?" # works in any new shell
60
66
  ```
61
67
 
62
68
  Full walkthrough incl. Claude Code wiring: [`docs/install-macos.md`](docs/install-macos.md).
@@ -284,7 +290,39 @@ claude mcp add coderag \
284
290
  Tools: `coderag_context`, `coderag_search`, `coderag_symbol`, `coderag_repositories`,
285
291
  `coderag_index`. Every call is recorded, so `/dashboard` shows the savings live. No API key
286
292
  needed — Claude Code is the LLM. **Full step-by-step:**
287
- [`docs/claude-code.md`](docs/claude-code.md).
293
+ [`docs/claude-code.md`](docs/claude-code.md). Copy-pasteable `.mcp.json` + `CLAUDE.md` snippet,
294
+ with **real** captured tool output: [`examples/mcp/`](examples/mcp/).
295
+
296
+ ## Composes with other token-efficiency tools
297
+
298
+ CodeRAG attacks **one slice** of token spend: the code you'd otherwise read whole files to get.
299
+ It does nothing about output tokens, conversation history, cache placement, or model routing.
300
+ Tools that target those slices stack with it, because the slices are disjoint:
301
+
302
+ | Slice | Billed at | CodeRAG | Tools like [Caveman](https://caveman.so) |
303
+ |---|---|:--:|:--:|
304
+ | File-reading input | 1× input | ✅ retrieval instead of whole files | — |
305
+ | Output tokens | **5× input** | ❌ | ✅ output compression |
306
+ | Conversation history | ~0.1× (cached) | ❌ | ✅ context compression |
307
+ | Cache placement | — | ❌ | ✅ automatic breakpoints |
308
+ | Model routing | varies | ❌ | ✅ cheapest model passing evals |
309
+
310
+ Savings on disjoint slices are near-additive. On a typical mid-session turn, CodeRAG's measured
311
+ file-slice reduction works out to ~26% of cost; adding an output-compression layer takes the
312
+ combined figure to roughly ~38%. **Both halves of that are models, not measurements** — verify
313
+ on your own workload before believing either.
314
+
315
+ **Wiring:** they use different mechanisms and don't conflict — CodeRAG is an MCP server, a
316
+ compression skill is a Skill, a proxy is an `ANTHROPIC_BASE_URL`. CodeRAG's own `ask` can also
317
+ route through such a proxy, since the LLM base URL is configurable:
318
+
319
+ ```bash
320
+ export CODERAG_ANTHROPIC_BASE_URL=http://localhost:<proxy-port>
321
+ ```
322
+
323
+ Enable one at a time and compare `/cost` between them — a proxy that compresses context may be
324
+ compressing context CodeRAG already minimised, so the marginal gain can be smaller than the two
325
+ vendors' claims added together.
288
326
 
289
327
  ## Editor integration (VS Code)
290
328
 
@@ -0,0 +1,23 @@
1
+ {
2
+ "$comment": [
3
+ "Copy this file to the ROOT of the project you want Claude Code to search,",
4
+ "rename it to '.mcp.json', and edit the two values below.",
5
+ " command -> absolute path to coderag-mcp ('which coderag-mcp').",
6
+ " An absolute path matters: Claude Code spawns the process",
7
+ " without your shell's PATH.",
8
+ " CODERAG_DATABASE_URL -> the URL 'coderag localdb start' printed",
9
+ " ('echo $CODERAG_DATABASE_URL'). JSON has no shell",
10
+ " expansion, so paste the literal value.",
11
+ " CODERAG_DEFAULT_REPOSITORY -> the --name you indexed with.",
12
+ "Then reload VS Code / restart Claude Code and run /mcp to confirm."
13
+ ],
14
+ "mcpServers": {
15
+ "coderag": {
16
+ "command": "/Users/you/.local/bin/coderag-mcp",
17
+ "env": {
18
+ "CODERAG_DATABASE_URL": "postgresql+psycopg://postgres:@/postgres?host=/Users/you/.coderag/pgdata",
19
+ "CODERAG_DEFAULT_REPOSITORY": "myproject"
20
+ }
21
+ }
22
+ }
23
+ }
@@ -0,0 +1,23 @@
1
+ # Append this to your project's CLAUDE.md
2
+
3
+ Registering the MCP server is not enough on its own — Claude Code will keep
4
+ globbing, grepping, and reading whole files unless you tell it not to. This
5
+ snippet is what actually produces the token saving.
6
+
7
+ ---
8
+
9
+ ## Code search
10
+
11
+ This repo is indexed in CodeRAG. To find or understand code, call the
12
+ `coderag_context` or `coderag_search` MCP tools **before** globbing/grepping or
13
+ reading whole files — they return only the relevant symbols and cost far fewer
14
+ input tokens.
15
+
16
+ - `coderag_search` — locate symbols ("where is X defined?"). Returns ranked
17
+ symbols with file, line range, and why each matched.
18
+ - `coderag_context` — understand behaviour ("why does X happen?"). Returns the
19
+ target symbol plus its dependencies, callers, and tests, under a token budget.
20
+ - `coderag_symbol` — fetch one symbol's full source by qualified name.
21
+
22
+ Read whole files only when the retrieved context is genuinely insufficient. If
23
+ the index looks stale, call `coderag_index` with `incremental: true`.
@@ -0,0 +1,152 @@
1
+ # MCP setup example (Claude Code)
2
+
3
+ A complete, copy-pasteable setup, plus **real** tool output captured from the
4
+ bundled demo repository — not illustrative JSON.
5
+
6
+ Files here:
7
+
8
+ | File | Use |
9
+ |---|---|
10
+ | [`.mcp.json`](.mcp.json) | Drop in your project root (project-scoped server, committable) |
11
+ | [`CLAUDE.md`](CLAUDE.md) | Append to your project's `CLAUDE.md` — **this is what makes Claude Code actually use the tools** |
12
+
13
+ ---
14
+
15
+ ## 1. Install and start the database
16
+
17
+ ```bash
18
+ pipx install "coderag-ai[mcp,localdb]"
19
+ coderag localdb start # prints the DB URL; applies migrations
20
+ export CODERAG_DATABASE_URL='…' # paste what it printed; add to ~/.zshrc
21
+ ```
22
+
23
+ ## 2. Index the repo you want to search
24
+
25
+ ```bash
26
+ cd /path/to/your-project
27
+ coderag index . --name myproject
28
+ coderag search "where is authentication handled?" # sanity check, no LLM needed
29
+ ```
30
+
31
+ ## 3. Register the server — pick one
32
+
33
+ **A. One command, all projects:**
34
+
35
+ ```bash
36
+ claude mcp add coderag \
37
+ -e CODERAG_DATABASE_URL="$CODERAG_DATABASE_URL" \
38
+ -e CODERAG_DEFAULT_REPOSITORY=myproject \
39
+ -s user \
40
+ -- "$(which coderag-mcp)"
41
+ ```
42
+
43
+ **B. Committed `.mcp.json`** (see the file in this directory) so your team gets it
44
+ automatically. Note JSON has no shell expansion — paste literal values.
45
+
46
+ Either way: use the **absolute path** to `coderag-mcp`. Claude Code spawns it
47
+ without your shell's PATH, so a bare command name fails with "command not found".
48
+
49
+ ## 4. Add the `CLAUDE.md` nudge
50
+
51
+ Append [`CLAUDE.md`](CLAUDE.md) to your project's `CLAUDE.md`. **Without it the
52
+ saving is zero** — the tools are registered but unused.
53
+
54
+ ## 5. Verify
55
+
56
+ Run `/mcp` in Claude Code. You should see `coderag` **connected** with five tools.
57
+ Then just ask a question normally — MCP tools are not slash commands, so there is
58
+ no `/coderag`:
59
+
60
+ > Where are CPQ filter options defined across this repo?
61
+
62
+ To force a tool on the first try, name it: *"Use the coderag_search tool to …"*.
63
+
64
+ ---
65
+
66
+ ## What the tools actually return
67
+
68
+ Captured by running the tools against `examples/demo-repository`.
69
+
70
+ ### `coderag_search("where are failed payments retried?", limit=3)`
71
+
72
+ ```json
73
+ [
74
+ {
75
+ "symbol": "payments.payment_service.PaymentService.retry_payment",
76
+ "type": "method",
77
+ "file": "payments/payment_service.py",
78
+ "lines": "33-54",
79
+ "score": 0.04672,
80
+ "why": ["graph_callee", "graph_caller", "graph_child", "lexical", "semantic"],
81
+ "tokens": 215
82
+ }
83
+ ]
84
+ ```
85
+
86
+ Every hit explains **why** it was retrieved — `lexical` (full-text), `semantic`
87
+ (vector), `exact_symbol`, or a graph reason such as `graph_caller` / `graph_test`.
88
+
89
+ ### `coderag_context("why can retry_payment leave an invoice pending?")`
90
+
91
+ Returns `context` (the code text), `selected_symbols`, `token_accounting`, and a
92
+ plain-language `measurement`:
93
+
94
+ ```
95
+ Returned 1449 tokens of context instead of the 1654 tokens it would take to read
96
+ the 8 file(s) containing this code (12.4% fewer input tokens).
97
+ ```
98
+
99
+ ```json
100
+ {
101
+ "candidates_found": 38,
102
+ "candidates_selected": 27,
103
+ "candidate_tokens": 2240,
104
+ "context_tokens": 1449,
105
+ "token_reduction_from_candidates": 35.3,
106
+ "baseline_tokens": 1654,
107
+ "baseline_files": 8,
108
+ "tokens_saved_vs_files": 205,
109
+ "reduction_vs_files": 12.4
110
+ }
111
+ ```
112
+
113
+ Symbols come back grouped by priority — target first, then dependencies, callers,
114
+ tests:
115
+
116
+ ```json
117
+ [
118
+ {"category": "target", "symbol": "…PaymentService.retry_payment", "lines": "33-54", "tokens": 215},
119
+ {"category": "implementation", "symbol": "…PaymentService.__init__", "lines": "13-21", "tokens": 51},
120
+ {"category": "dependencies", "symbol": "…PaymentService.process_payment", "lines": "23-31", "tokens": 75}
121
+ ]
122
+ ```
123
+
124
+ > **Note the 12.4%.** That's the *demo* repo — 11 tiny files, where retrieval
125
+ > reaches most of the codebase and the saving is small. On a real backend the
126
+ > same measurement runs far higher (75% has been observed). Run
127
+ > `coderag context "…" --measure` on your own repo for your number; don't trust
128
+ > either figure as representative.
129
+
130
+ ---
131
+
132
+ ## The five tools
133
+
134
+ | Tool | Use |
135
+ |---|---|
136
+ | `coderag_context` | Budgeted context for a question (target + deps + callers + tests) |
137
+ | `coderag_search` | Ranked symbols with file/lines/score/why |
138
+ | `coderag_symbol` | Full source of one symbol by qualified name |
139
+ | `coderag_repositories` | Which repos are indexed, and at which commit |
140
+ | `coderag_index` | Index / re-index (`incremental: true` for a fast update) |
141
+
142
+ ## Troubleshooting
143
+
144
+ | Symptom | Cause / fix |
145
+ |---|---|
146
+ | `/mcp` doesn't list `coderag` | Server not registered, or `.mcp.json` is not in the project root. Reload the window. |
147
+ | Listed but **failed** | Run the command by hand: `CODERAG_DATABASE_URL=… /abs/path/coderag-mcp`. A healthy server sits silently on stdin (Ctrl-C to exit); a traceback tells you what's wrong. |
148
+ | "command not found" | Use the absolute path from `which coderag-mcp`. |
149
+ | "no repositories indexed yet" | Run `coderag index . --name myproject`. |
150
+ | "multiple repositories; pass --repo" | Set `CODERAG_DEFAULT_REPOSITORY`. |
151
+ | Tools connected but never called | The `CLAUDE.md` nudge is missing — see step 4. |
152
+ | Results reference code that moved | Re-index: `coderag index . --name myproject --incremental`. |
@@ -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.4"
10
+ version = "0.4.0"
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"
@@ -80,6 +80,97 @@ def localdb_status(
80
80
  console.print(f"[green]running (pid {pid})[/]" if pid else "[yellow]not running[/]")
81
81
 
82
82
 
83
+ @app.command()
84
+ def setup(
85
+ path: str = typer.Argument(".", help="Repository to index (default: current dir)."),
86
+ name: str | None = typer.Option(None, "--name", help="Repository name."),
87
+ mcp: bool = typer.Option(True, "--mcp/--no-mcp", help="Register with Claude Code."),
88
+ claude_md: bool = typer.Option(True, "--claude-md/--no-claude-md",
89
+ help="Append the retrieval nudge to CLAUDE.md."),
90
+ ) -> None:
91
+ """One command: start the database, migrate, index, and wire up Claude Code."""
92
+ from pathlib import Path
93
+
94
+ from coderag import localdb
95
+ from coderag.core.config import get_settings
96
+ from coderag.db.migrate import upgrade_to_head
97
+ from coderag.setup_flow import (
98
+ SetupResult,
99
+ append_nudge,
100
+ claude_md_needs_nudge,
101
+ register_mcp,
102
+ )
103
+
104
+ repo_path = Path(path).expanduser().resolve()
105
+ repo_name = name or repo_path.name
106
+ res = SetupResult(repository=repo_name)
107
+
108
+ # 1. database — reuse an already-configured one, else start the local server
109
+ import os
110
+
111
+ if os.environ.get("CODERAG_DATABASE_URL"):
112
+ res.database_url = get_settings().database_url
113
+ res.skip("using CODERAG_DATABASE_URL from the environment")
114
+ else:
115
+ try:
116
+ res.database_url = localdb.start()
117
+ res.ok("local PostgreSQL running (URL recorded; no export needed)")
118
+ except RuntimeError as exc:
119
+ res.warn(str(exc).splitlines()[0])
120
+ console.print(f"[red]{exc}[/]")
121
+ raise typer.Exit(1) from None
122
+
123
+ # 2. schema
124
+ try:
125
+ upgrade_to_head(res.database_url)
126
+ res.ok("database schema up to date")
127
+ except Exception as exc:
128
+ res.warn(f"migrations failed: {str(exc)[:120]}")
129
+ console.print(f"[red]Migrations failed:[/] {exc}")
130
+ raise typer.Exit(1) from None
131
+
132
+ # 3. index
133
+ get_settings.cache_clear()
134
+ os.environ["CODERAG_DATABASE_URL"] = res.database_url or ""
135
+ with session_scope() as session:
136
+ from coderag.service import run_index
137
+
138
+ repo, stats = run_index(session, str(repo_path), repo_name)
139
+ res.symbols = stats.symbols_indexed
140
+ res.ok(f"indexed {repo_name}: {stats.files_indexed} files, "
141
+ f"{stats.symbols_indexed} symbols, {stats.relationships_created} relationships")
142
+
143
+ # 4. Claude Code MCP registration
144
+ if mcp:
145
+ done, msg = register_mcp("coderag", res.database_url or "", repo_name)
146
+ (res.ok if done else res.warn)(msg)
147
+ else:
148
+ res.skip("MCP registration skipped (--no-mcp)")
149
+
150
+ # 5. the nudge — without it Claude Code never calls the tools
151
+ if claude_md:
152
+ target = repo_path / "CLAUDE.md"
153
+ if claude_md_needs_nudge(target):
154
+ append_nudge(target)
155
+ res.ok(f"added the retrieval nudge to {target}")
156
+ else:
157
+ res.skip("CLAUDE.md already contains the nudge")
158
+ else:
159
+ res.skip("CLAUDE.md untouched (--no-claude-md)")
160
+
161
+ icons = {"ok": "[green]✓[/]", "skip": "[dim]·[/]", "warn": "[yellow]![/]"}
162
+ console.print("\n[bold]CodeRAG setup[/]")
163
+ for status, msg in res.steps:
164
+ console.print(f" {icons[status]} {msg}")
165
+ console.print(
166
+ "\nTry it: [bold]coderag search \"where is X handled?\"[/]\n"
167
+ "In Claude Code: run [bold]/mcp[/] (expect 'coderag' connected), then ask a "
168
+ "question normally — MCP tools are not slash commands.\n"
169
+ "Dashboard: [bold]uvicorn coderag.api.app:app --port 8000[/] "
170
+ "-> http://localhost:8000/dashboard"
171
+ )
172
+
173
+
83
174
  @app.command()
84
175
  def migrate(
85
176
  database_url: str | None = typer.Option(
@@ -12,6 +12,19 @@ from functools import lru_cache
12
12
  from pydantic import Field
13
13
  from pydantic_settings import BaseSettings, SettingsConfigDict
14
14
 
15
+ COMPOSE_DATABASE_URL = "postgresql+psycopg://coderag:coderag@localhost:5432/coderag"
16
+
17
+
18
+ def _default_database_url() -> str:
19
+ """Fall back to the URL written by `coderag localdb start`, else compose's."""
20
+ from pathlib import Path
21
+
22
+ try:
23
+ value = (Path.home() / ".coderag" / "database_url").read_text().strip()
24
+ except OSError:
25
+ return COMPOSE_DATABASE_URL
26
+ return value or COMPOSE_DATABASE_URL
27
+
15
28
 
16
29
  class Settings(BaseSettings):
17
30
  model_config = SettingsConfigDict(
@@ -23,9 +36,9 @@ class Settings(BaseSettings):
23
36
 
24
37
  # ---- Database -------------------------------------------------------
25
38
  # A libpq URL. In dev/test the pgserver fixture overrides this.
26
- database_url: str = Field(
27
- default="postgresql+psycopg://coderag:coderag@localhost:5432/coderag"
28
- )
39
+ # Resolution order: CODERAG_DATABASE_URL env/.env -> the URL recorded by
40
+ # `coderag localdb start` -> the docker-compose default.
41
+ database_url: str = Field(default_factory=lambda: _default_database_url())
29
42
  db_echo: bool = False
30
43
 
31
44
  # Default repository name used when a request doesn't name one (handy for the
@@ -44,6 +44,21 @@ class GitRepo:
44
44
  except (subprocess.CalledProcessError, FileNotFoundError):
45
45
  return False
46
46
 
47
+ def commit_exists(self, sha: str) -> bool:
48
+ """True if ``sha`` resolves to a commit object in this repository.
49
+
50
+ A rebase, amend, or force-push can orphan a previously-indexed commit;
51
+ diffing against it would fail, so callers check first and fall back to a
52
+ full index.
53
+ """
54
+ if not sha:
55
+ return False
56
+ try:
57
+ self._git("cat-file", "-e", f"{sha}^{{commit}}")
58
+ return True
59
+ except (subprocess.CalledProcessError, FileNotFoundError):
60
+ return False
61
+
47
62
  def current_commit(self) -> str | None:
48
63
  try:
49
64
  return self._git("rev-parse", "HEAD").strip()
@@ -9,6 +9,7 @@ Phase 8.
9
9
  from __future__ import annotations
10
10
 
11
11
  import hashlib
12
+ import subprocess
12
13
  import time
13
14
  from dataclasses import dataclass
14
15
 
@@ -155,6 +156,14 @@ class Indexer:
155
156
  head = git.current_commit()
156
157
  if not git.is_git() or not base:
157
158
  return self.full_index(repo)
159
+ if not git.commit_exists(base):
160
+ # The previously-indexed commit is gone (rebase / amend / force-push),
161
+ # so there is no valid diff base. Rebuild instead of failing.
162
+ log.warning(
163
+ "incremental_index.base_missing", repository=repo.name,
164
+ base=base[:12], action="falling back to full index",
165
+ )
166
+ return self.full_index(repo)
158
167
 
159
168
  started = time.perf_counter()
160
169
  run = IndexingRun(repository_id=repo.id, mode="incremental", status="running",
@@ -166,7 +175,18 @@ class Indexer:
166
175
  self._pending_rels = []
167
176
  stats = IndexStats(commit_sha=head)
168
177
  try:
169
- diff = git.diff(base, head or "HEAD")
178
+ try:
179
+ diff = git.diff(base, head or "HEAD")
180
+ except subprocess.CalledProcessError:
181
+ # Unexpected diff failure (shallow clone, corrupt object, …):
182
+ # a full rebuild is always correct, so prefer it over erroring.
183
+ log.warning(
184
+ "incremental_index.diff_failed", repository=repo.name,
185
+ action="falling back to full index",
186
+ )
187
+ run.status = "superseded"
188
+ self.session.flush()
189
+ return self.full_index(repo)
170
190
  changed = [p for p in diff.changed if should_index(p, self.extra_ignore)]
171
191
  gone = set(changed) | set(diff.deleted)
172
192
  for path in gone:
@@ -13,6 +13,18 @@ from __future__ import annotations
13
13
  from pathlib import Path
14
14
 
15
15
  DEFAULT_PGDATA = Path.home() / ".coderag" / "pgdata"
16
+ # `localdb start` records its URL here so other commands (and the MCP server)
17
+ # find the database automatically, without the user exporting anything.
18
+ URL_FILE = Path.home() / ".coderag" / "database_url"
19
+
20
+
21
+ def saved_url() -> str | None:
22
+ """The URL recorded by the most recent `localdb start`, if any."""
23
+ try:
24
+ value = URL_FILE.read_text().strip()
25
+ except OSError:
26
+ return None
27
+ return value or None
16
28
 
17
29
 
18
30
  def _require_pgserver():
@@ -51,6 +63,13 @@ def start(pgdata: Path | str | None = None) -> str:
51
63
  server = pgserver.get_server(str(path), cleanup_mode=None)
52
64
  url = sqlalchemy_url(server.get_uri())
53
65
  _ensure_vector_extension(url)
66
+ # Record it (default location only) so later commands need no env var.
67
+ if Path(pgdata or DEFAULT_PGDATA).expanduser() == DEFAULT_PGDATA:
68
+ try:
69
+ URL_FILE.parent.mkdir(parents=True, exist_ok=True)
70
+ URL_FILE.write_text(url + "\n")
71
+ except OSError: # pragma: no cover - convenience only, never fatal
72
+ pass
54
73
  return url
55
74
 
56
75