coderag-ai 0.3.0__tar.gz → 0.3.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (139) hide show
  1. {coderag_ai-0.3.0 → coderag_ai-0.3.2}/PKG-INFO +4 -4
  2. {coderag_ai-0.3.0 → coderag_ai-0.3.2}/docs/install-macos.md +8 -0
  3. {coderag_ai-0.3.0 → coderag_ai-0.3.2}/pyproject.toml +7 -4
  4. {coderag_ai-0.3.0 → coderag_ai-0.3.2}/src/coderag/embeddings/sentence_transformer.py +7 -2
  5. {coderag_ai-0.3.0 → coderag_ai-0.3.2}/src/coderag/localdb.py +34 -4
  6. {coderag_ai-0.3.0 → coderag_ai-0.3.2}/src/coderag/parsing/python.py +13 -3
  7. coderag_ai-0.3.2/tests/test_phase14_localdb.py +72 -0
  8. coderag_ai-0.3.0/tests/test_phase14_localdb.py +0 -35
  9. {coderag_ai-0.3.0 → coderag_ai-0.3.2}/.env.example +0 -0
  10. {coderag_ai-0.3.0 → coderag_ai-0.3.2}/.gitignore +0 -0
  11. {coderag_ai-0.3.0 → coderag_ai-0.3.2}/CODE_OF_CONDUCT.md +0 -0
  12. {coderag_ai-0.3.0 → coderag_ai-0.3.2}/CONTRIBUTING.md +0 -0
  13. {coderag_ai-0.3.0 → coderag_ai-0.3.2}/Dockerfile +0 -0
  14. {coderag_ai-0.3.0 → coderag_ai-0.3.2}/LICENSE +0 -0
  15. {coderag_ai-0.3.0 → coderag_ai-0.3.2}/Makefile +0 -0
  16. {coderag_ai-0.3.0 → coderag_ai-0.3.2}/NOTICE +0 -0
  17. {coderag_ai-0.3.0 → coderag_ai-0.3.2}/README.md +0 -0
  18. {coderag_ai-0.3.0 → coderag_ai-0.3.2}/SECURITY.md +0 -0
  19. {coderag_ai-0.3.0 → coderag_ai-0.3.2}/alembic.ini +0 -0
  20. {coderag_ai-0.3.0 → coderag_ai-0.3.2}/docker/entrypoint.sh +0 -0
  21. {coderag_ai-0.3.0 → coderag_ai-0.3.2}/docker-compose.yml +0 -0
  22. {coderag_ai-0.3.0 → coderag_ai-0.3.2}/docs/adding-language.md +0 -0
  23. {coderag_ai-0.3.0 → coderag_ai-0.3.2}/docs/adr/001-postgres-pgvector.md +0 -0
  24. {coderag_ai-0.3.0 → coderag_ai-0.3.2}/docs/adr/002-structural-chunks.md +0 -0
  25. {coderag_ai-0.3.0 → coderag_ai-0.3.2}/docs/adr/003-hybrid-search.md +0 -0
  26. {coderag_ai-0.3.0 → coderag_ai-0.3.2}/docs/adr/004-tree-sitter.md +0 -0
  27. {coderag_ai-0.3.0 → coderag_ai-0.3.2}/docs/adr/005-llm-provider-independence.md +0 -0
  28. {coderag_ai-0.3.0 → coderag_ai-0.3.2}/docs/adr/006-repository-authorization.md +0 -0
  29. {coderag_ai-0.3.0 → coderag_ai-0.3.2}/docs/adr/007-token-budgeting.md +0 -0
  30. {coderag_ai-0.3.0 → coderag_ai-0.3.2}/docs/architecture.md +0 -0
  31. {coderag_ai-0.3.0 → coderag_ai-0.3.2}/docs/claude-code.md +0 -0
  32. {coderag_ai-0.3.0 → coderag_ai-0.3.2}/docs/evaluation.md +0 -0
  33. {coderag_ai-0.3.0 → coderag_ai-0.3.2}/docs/licenses.md +0 -0
  34. {coderag_ai-0.3.0 → coderag_ai-0.3.2}/docs/llm-providers.md +0 -0
  35. {coderag_ai-0.3.0 → coderag_ai-0.3.2}/docs/retrieval.md +0 -0
  36. {coderag_ai-0.3.0 → coderag_ai-0.3.2}/docs/security.md +0 -0
  37. {coderag_ai-0.3.0 → coderag_ai-0.3.2}/docs/vscode-extension.md +0 -0
  38. {coderag_ai-0.3.0 → coderag_ai-0.3.2}/examples/demo-repository/payments/__init__.py +0 -0
  39. {coderag_ai-0.3.0 → coderag_ai-0.3.2}/examples/demo-repository/payments/api.py +0 -0
  40. {coderag_ai-0.3.0 → coderag_ai-0.3.2}/examples/demo-repository/payments/auth.py +0 -0
  41. {coderag_ai-0.3.0 → coderag_ai-0.3.2}/examples/demo-repository/payments/checkout_service.py +0 -0
  42. {coderag_ai-0.3.0 → coderag_ai-0.3.2}/examples/demo-repository/payments/models.py +0 -0
  43. {coderag_ai-0.3.0 → coderag_ai-0.3.2}/examples/demo-repository/payments/payment_repository.py +0 -0
  44. {coderag_ai-0.3.0 → coderag_ai-0.3.2}/examples/demo-repository/payments/payment_service.py +0 -0
  45. {coderag_ai-0.3.0 → coderag_ai-0.3.2}/examples/demo-repository/payments/retry_policy.py +0 -0
  46. {coderag_ai-0.3.0 → coderag_ai-0.3.2}/examples/demo-repository/payments/users.py +0 -0
  47. {coderag_ai-0.3.0 → coderag_ai-0.3.2}/examples/demo-repository/tests/test_payment_service.py +0 -0
  48. {coderag_ai-0.3.0 → coderag_ai-0.3.2}/examples/demo-repository/tests/test_retry_policy.py +0 -0
  49. {coderag_ai-0.3.0 → coderag_ai-0.3.2}/examples/eval/eval_dataset.json +0 -0
  50. {coderag_ai-0.3.0 → coderag_ai-0.3.2}/scripts/gen_initial_migration.py +0 -0
  51. {coderag_ai-0.3.0 → coderag_ai-0.3.2}/src/coderag/__init__.py +0 -0
  52. {coderag_ai-0.3.0 → coderag_ai-0.3.2}/src/coderag/analyzers/__init__.py +0 -0
  53. {coderag_ai-0.3.0 → coderag_ai-0.3.2}/src/coderag/analyzers/base.py +0 -0
  54. {coderag_ai-0.3.0 → coderag_ai-0.3.2}/src/coderag/analyzers/flake8_adapter.py +0 -0
  55. {coderag_ai-0.3.0 → coderag_ai-0.3.2}/src/coderag/analyzers/pylint_adapter.py +0 -0
  56. {coderag_ai-0.3.0 → coderag_ai-0.3.2}/src/coderag/analyzers/workflow.py +0 -0
  57. {coderag_ai-0.3.0 → coderag_ai-0.3.2}/src/coderag/api/__init__.py +0 -0
  58. {coderag_ai-0.3.0 → coderag_ai-0.3.2}/src/coderag/api/app.py +0 -0
  59. {coderag_ai-0.3.0 → coderag_ai-0.3.2}/src/coderag/api/dashboard.py +0 -0
  60. {coderag_ai-0.3.0 → coderag_ai-0.3.2}/src/coderag/api/schemas.py +0 -0
  61. {coderag_ai-0.3.0 → coderag_ai-0.3.2}/src/coderag/cli/__init__.py +0 -0
  62. {coderag_ai-0.3.0 → coderag_ai-0.3.2}/src/coderag/cli/main.py +0 -0
  63. {coderag_ai-0.3.0 → coderag_ai-0.3.2}/src/coderag/context/__init__.py +0 -0
  64. {coderag_ai-0.3.0 → coderag_ai-0.3.2}/src/coderag/context/builder.py +0 -0
  65. {coderag_ai-0.3.0 → coderag_ai-0.3.2}/src/coderag/context/package.py +0 -0
  66. {coderag_ai-0.3.0 → coderag_ai-0.3.2}/src/coderag/core/__init__.py +0 -0
  67. {coderag_ai-0.3.0 → coderag_ai-0.3.2}/src/coderag/core/config.py +0 -0
  68. {coderag_ai-0.3.0 → coderag_ai-0.3.2}/src/coderag/core/logging.py +0 -0
  69. {coderag_ai-0.3.0 → coderag_ai-0.3.2}/src/coderag/core/text.py +0 -0
  70. {coderag_ai-0.3.0 → coderag_ai-0.3.2}/src/coderag/core/tokens.py +0 -0
  71. {coderag_ai-0.3.0 → coderag_ai-0.3.2}/src/coderag/db/__init__.py +0 -0
  72. {coderag_ai-0.3.0 → coderag_ai-0.3.2}/src/coderag/db/base.py +0 -0
  73. {coderag_ai-0.3.0 → coderag_ai-0.3.2}/src/coderag/db/migrations/env.py +0 -0
  74. {coderag_ai-0.3.0 → coderag_ai-0.3.2}/src/coderag/db/migrations/script.py.mako +0 -0
  75. {coderag_ai-0.3.0 → coderag_ai-0.3.2}/src/coderag/db/migrations/versions/.gitkeep +0 -0
  76. {coderag_ai-0.3.0 → coderag_ai-0.3.2}/src/coderag/db/migrations/versions/9a973932b94d_measure_baseline_tokens.py +0 -0
  77. {coderag_ai-0.3.0 → coderag_ai-0.3.2}/src/coderag/db/migrations/versions/c8a40feeda41_initial_schema.py +0 -0
  78. {coderag_ai-0.3.0 → coderag_ai-0.3.2}/src/coderag/db/models.py +0 -0
  79. {coderag_ai-0.3.0 → coderag_ai-0.3.2}/src/coderag/embeddings/__init__.py +0 -0
  80. {coderag_ai-0.3.0 → coderag_ai-0.3.2}/src/coderag/embeddings/base.py +0 -0
  81. {coderag_ai-0.3.0 → coderag_ai-0.3.2}/src/coderag/embeddings/hashing.py +0 -0
  82. {coderag_ai-0.3.0 → coderag_ai-0.3.2}/src/coderag/embeddings/pipeline.py +0 -0
  83. {coderag_ai-0.3.0 → coderag_ai-0.3.2}/src/coderag/embeddings/registry.py +0 -0
  84. {coderag_ai-0.3.0 → coderag_ai-0.3.2}/src/coderag/evaluation/__init__.py +0 -0
  85. {coderag_ai-0.3.0 → coderag_ai-0.3.2}/src/coderag/evaluation/datasets.py +0 -0
  86. {coderag_ai-0.3.0 → coderag_ai-0.3.2}/src/coderag/evaluation/harness.py +0 -0
  87. {coderag_ai-0.3.0 → coderag_ai-0.3.2}/src/coderag/git/__init__.py +0 -0
  88. {coderag_ai-0.3.0 → coderag_ai-0.3.2}/src/coderag/git/repo.py +0 -0
  89. {coderag_ai-0.3.0 → coderag_ai-0.3.2}/src/coderag/indexing/__init__.py +0 -0
  90. {coderag_ai-0.3.0 → coderag_ai-0.3.2}/src/coderag/indexing/ignore.py +0 -0
  91. {coderag_ai-0.3.0 → coderag_ai-0.3.2}/src/coderag/indexing/indexer.py +0 -0
  92. {coderag_ai-0.3.0 → coderag_ai-0.3.2}/src/coderag/llm/__init__.py +0 -0
  93. {coderag_ai-0.3.0 → coderag_ai-0.3.2}/src/coderag/llm/anthropic.py +0 -0
  94. {coderag_ai-0.3.0 → coderag_ai-0.3.2}/src/coderag/llm/base.py +0 -0
  95. {coderag_ai-0.3.0 → coderag_ai-0.3.2}/src/coderag/llm/null.py +0 -0
  96. {coderag_ai-0.3.0 → coderag_ai-0.3.2}/src/coderag/llm/registry.py +0 -0
  97. {coderag_ai-0.3.0 → coderag_ai-0.3.2}/src/coderag/mcp_server.py +0 -0
  98. {coderag_ai-0.3.0 → coderag_ai-0.3.2}/src/coderag/parsing/__init__.py +0 -0
  99. {coderag_ai-0.3.0 → coderag_ai-0.3.2}/src/coderag/parsing/base.py +0 -0
  100. {coderag_ai-0.3.0 → coderag_ai-0.3.2}/src/coderag/parsing/registry.py +0 -0
  101. {coderag_ai-0.3.0 → coderag_ai-0.3.2}/src/coderag/retrieval/__init__.py +0 -0
  102. {coderag_ai-0.3.0 → coderag_ai-0.3.2}/src/coderag/retrieval/base.py +0 -0
  103. {coderag_ai-0.3.0 → coderag_ai-0.3.2}/src/coderag/retrieval/engine.py +0 -0
  104. {coderag_ai-0.3.0 → coderag_ai-0.3.2}/src/coderag/retrieval/fusion.py +0 -0
  105. {coderag_ai-0.3.0 → coderag_ai-0.3.2}/src/coderag/retrieval/graph.py +0 -0
  106. {coderag_ai-0.3.0 → coderag_ai-0.3.2}/src/coderag/retrieval/lexical.py +0 -0
  107. {coderag_ai-0.3.0 → coderag_ai-0.3.2}/src/coderag/retrieval/reranking.py +0 -0
  108. {coderag_ai-0.3.0 → coderag_ai-0.3.2}/src/coderag/retrieval/semantic.py +0 -0
  109. {coderag_ai-0.3.0 → coderag_ai-0.3.2}/src/coderag/retrieval/symbol.py +0 -0
  110. {coderag_ai-0.3.0 → coderag_ai-0.3.2}/src/coderag/security/__init__.py +0 -0
  111. {coderag_ai-0.3.0 → coderag_ai-0.3.2}/src/coderag/security/authz.py +0 -0
  112. {coderag_ai-0.3.0 → coderag_ai-0.3.2}/src/coderag/security/secrets.py +0 -0
  113. {coderag_ai-0.3.0 → coderag_ai-0.3.2}/src/coderag/service.py +0 -0
  114. {coderag_ai-0.3.0 → coderag_ai-0.3.2}/src/coderag/telemetry.py +0 -0
  115. {coderag_ai-0.3.0 → coderag_ai-0.3.2}/tests/__init__.py +0 -0
  116. {coderag_ai-0.3.0 → coderag_ai-0.3.2}/tests/conftest.py +0 -0
  117. {coderag_ai-0.3.0 → coderag_ai-0.3.2}/tests/test_phase0_db.py +0 -0
  118. {coderag_ai-0.3.0 → coderag_ai-0.3.2}/tests/test_phase0_migrations.py +0 -0
  119. {coderag_ai-0.3.0 → coderag_ai-0.3.2}/tests/test_phase10_analyzers.py +0 -0
  120. {coderag_ai-0.3.0 → coderag_ai-0.3.2}/tests/test_phase11_dashboard.py +0 -0
  121. {coderag_ai-0.3.0 → coderag_ai-0.3.2}/tests/test_phase12_mcp.py +0 -0
  122. {coderag_ai-0.3.0 → coderag_ai-0.3.2}/tests/test_phase13_measure.py +0 -0
  123. {coderag_ai-0.3.0 → coderag_ai-0.3.2}/tests/test_phase1_indexer.py +0 -0
  124. {coderag_ai-0.3.0 → coderag_ai-0.3.2}/tests/test_phase1_parser.py +0 -0
  125. {coderag_ai-0.3.0 → coderag_ai-0.3.2}/tests/test_phase2_cli.py +0 -0
  126. {coderag_ai-0.3.0 → coderag_ai-0.3.2}/tests/test_phase2_retrieval.py +0 -0
  127. {coderag_ai-0.3.0 → coderag_ai-0.3.2}/tests/test_phase3_embeddings.py +0 -0
  128. {coderag_ai-0.3.0 → coderag_ai-0.3.2}/tests/test_phase3_semantic.py +0 -0
  129. {coderag_ai-0.3.0 → coderag_ai-0.3.2}/tests/test_phase3_sentence_transformer.py +0 -0
  130. {coderag_ai-0.3.0 → coderag_ai-0.3.2}/tests/test_phase4_graph.py +0 -0
  131. {coderag_ai-0.3.0 → coderag_ai-0.3.2}/tests/test_phase5_context_db.py +0 -0
  132. {coderag_ai-0.3.0 → coderag_ai-0.3.2}/tests/test_phase5_context_unit.py +0 -0
  133. {coderag_ai-0.3.0 → coderag_ai-0.3.2}/tests/test_phase6_api.py +0 -0
  134. {coderag_ai-0.3.0 → coderag_ai-0.3.2}/tests/test_phase6_ask.py +0 -0
  135. {coderag_ai-0.3.0 → coderag_ai-0.3.2}/tests/test_phase6_llm.py +0 -0
  136. {coderag_ai-0.3.0 → coderag_ai-0.3.2}/tests/test_phase7_eval.py +0 -0
  137. {coderag_ai-0.3.0 → coderag_ai-0.3.2}/tests/test_phase8_incremental.py +0 -0
  138. {coderag_ai-0.3.0 → coderag_ai-0.3.2}/tests/test_phase9_rerank.py +0 -0
  139. {coderag_ai-0.3.0 → coderag_ai-0.3.2}/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.0
3
+ Version: 0.3.2
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
@@ -32,8 +32,8 @@ Requires-Dist: pydantic>=2.7
32
32
  Requires-Dist: rich>=13.7
33
33
  Requires-Dist: sqlalchemy>=2.0.30
34
34
  Requires-Dist: structlog>=24.1
35
- Requires-Dist: tree-sitter-python<0.22,>=0.21
36
- Requires-Dist: tree-sitter<0.22,>=0.21
35
+ Requires-Dist: tree-sitter-python>=0.23
36
+ Requires-Dist: tree-sitter>=0.23
37
37
  Requires-Dist: typer>=0.12
38
38
  Requires-Dist: uvicorn[standard]>=0.30
39
39
  Provides-Extra: analyzers
@@ -50,7 +50,7 @@ Provides-Extra: embeddings
50
50
  Requires-Dist: sentence-transformers>=3.0; extra == 'embeddings'
51
51
  Provides-Extra: localdb
52
52
  Requires-Dist: alembic>=1.13; extra == 'localdb'
53
- Requires-Dist: pgserver>=0.1.4; extra == 'localdb'
53
+ Requires-Dist: pgserver>=0.1.4; (python_version < '3.13') and extra == 'localdb'
54
54
  Provides-Extra: mcp
55
55
  Requires-Dist: mcp>=2.0; extra == 'mcp'
56
56
  Provides-Extra: metrics
@@ -23,6 +23,14 @@ Open a new terminal afterwards so your `PATH` updates.
23
23
 
24
24
  ### 2. Install CodeRAG
25
25
 
26
+ > **Python version:** the `localdb` extra needs **Python 3.9–3.12** (its bundled
27
+ > PostgreSQL has no wheels for 3.13+). On a newer default Python use
28
+ > `pipx install --python python3.12 "coderag-ai[mcp,localdb]"` (`brew install python@3.12`),
29
+ > or install without `localdb` and supply your own PostgreSQL.
30
+
31
+ > **Corporate network?** If install fails with `invalid peer certificate: UnknownIssuer`,
32
+ > your proxy re-signs TLS. Fix with `export UV_NATIVE_TLS=true` (uses the macOS keychain).
33
+
26
34
  ```bash
27
35
  pipx install "coderag-ai[mcp,localdb]"
28
36
  ```
@@ -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.0"
10
+ version = "0.3.2"
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"
@@ -40,8 +40,8 @@ dependencies = [
40
40
  "alembic>=1.13",
41
41
  "psycopg[binary]>=3.1", # LGPL-3.0 — see docs/licenses.md
42
42
  "pgvector>=0.3.0",
43
- "tree-sitter>=0.21,<0.22",
44
- "tree-sitter-python>=0.21,<0.22",
43
+ "tree-sitter>=0.23",
44
+ "tree-sitter-python>=0.23",
45
45
  "typer>=0.12",
46
46
  "rich>=13.7",
47
47
  "httpx>=0.27",
@@ -60,7 +60,10 @@ analyzers = ["pylint>=3.2", "flake8>=7.1"]
60
60
  # MCP server: lets Claude Code (and other MCP clients) call CodeRAG retrieval.
61
61
  mcp = ["mcp>=2.0"]
62
62
  # Rootless local PostgreSQL+pgvector — no Docker, Homebrew, or sudo required.
63
- localdb = ["pgserver>=0.1.4", "alembic>=1.13"]
63
+ # pgserver only ships wheels for CPython 3.9–3.12, so the marker keeps the extra
64
+ # installable on newer interpreters (the `coderag localdb` command then explains
65
+ # what to do instead of the install failing outright).
66
+ localdb = ["pgserver>=0.1.4; python_version < '3.13'", "alembic>=1.13"]
64
67
  # Development / testing.
65
68
  dev = [
66
69
  "pytest>=8.2",
@@ -21,13 +21,18 @@ class SentenceTransformerEmbeddingProvider(EmbeddingProvider):
21
21
  except ImportError as exc: # pragma: no cover - optional dependency
22
22
  raise RuntimeError(
23
23
  "sentence-transformers is not installed. Install the 'embeddings' "
24
- "extra: pip install 'coderag[embeddings]'"
24
+ "extra: pip install 'coderag-ai[embeddings]'"
25
25
  ) from exc
26
26
  self._model = SentenceTransformer(model_name, device=device)
27
27
  self.model_name = model_name
28
28
  self.model_version = "st1"
29
29
  self.batch_size = batch_size
30
- self.dimension = int(self._model.get_sentence_embedding_dimension())
30
+ dim = self._model.get_sentence_embedding_dimension()
31
+ if dim is None: # pragma: no cover - model without a fixed dimension
32
+ raise RuntimeError(
33
+ f"model {model_name!r} does not report an embedding dimension"
34
+ )
35
+ self.dimension = int(dim)
31
36
 
32
37
  def embed_documents(self, texts: list[str]) -> list[list[float]]:
33
38
  vectors = self._model.encode(
@@ -19,10 +19,21 @@ def _require_pgserver():
19
19
  try:
20
20
  import pgserver # noqa: PLC0415
21
21
  except ImportError as exc: # pragma: no cover - optional dependency
22
+ import sys
23
+
24
+ if sys.version_info >= (3, 13):
25
+ raise RuntimeError(
26
+ f"`coderag localdb` needs pgserver, which has no wheels for Python "
27
+ f"{sys.version_info.major}.{sys.version_info.minor} (it supports 3.9–3.12).\n"
28
+ "Either install CodeRAG on Python 3.12:\n"
29
+ " pipx install --python python3.12 'coderag-ai[mcp,localdb]'\n"
30
+ "or run PostgreSQL yourself (Docker: `docker compose up -d db`, or "
31
+ "Homebrew: `brew install postgresql@16 pgvector`) and set "
32
+ "CODERAG_DATABASE_URL."
33
+ ) from exc
22
34
  raise RuntimeError(
23
35
  "pgserver is not installed. Install the 'localdb' extra:\n"
24
- " pipx install 'coderag[mcp,localdb] @ "
25
- "git+https://github.com/galacticsurfer/coderag'"
36
+ " pipx install 'coderag-ai[mcp,localdb]'"
26
37
  ) from exc
27
38
  return pgserver
28
39
 
@@ -38,8 +49,27 @@ def start(pgdata: Path | str | None = None) -> str:
38
49
  path.parent.mkdir(parents=True, exist_ok=True)
39
50
  # cleanup_mode=None -> the postmaster keeps running after we exit.
40
51
  server = pgserver.get_server(str(path), cleanup_mode=None)
41
- server.psql("CREATE EXTENSION IF NOT EXISTS vector;")
42
- return sqlalchemy_url(server.get_uri())
52
+ url = sqlalchemy_url(server.get_uri())
53
+ _ensure_vector_extension(url)
54
+ return url
55
+
56
+
57
+ def _ensure_vector_extension(url: str) -> None:
58
+ """Create the pgvector extension over a normal connection.
59
+
60
+ Deliberately does NOT use pgserver's bundled ``psql`` binary: on macOS that
61
+ executable can fail to run (exit 127), which would break `localdb start` even
62
+ though the server itself is healthy. psycopg is already a dependency, so we
63
+ talk to the server the same way the rest of the application does.
64
+ """
65
+ from sqlalchemy import create_engine, text
66
+
67
+ engine = create_engine(url)
68
+ try:
69
+ with engine.begin() as conn:
70
+ conn.execute(text("CREATE EXTENSION IF NOT EXISTS vector"))
71
+ finally:
72
+ engine.dispose()
43
73
 
44
74
 
45
75
  def stop(pgdata: Path | str | None = None, timeout: float = 15.0) -> None:
@@ -34,9 +34,19 @@ class PythonParser(LanguageParser):
34
34
  extensions = (".py",)
35
35
 
36
36
  def __init__(self) -> None:
37
- self._lang = Language(tspython.language(), "python")
38
- self._parser = Parser()
39
- self._parser.set_language(self._lang)
37
+ # The tree-sitter Python API changed at 0.22: `Language(ptr)` lost its
38
+ # `name` argument and `Parser.set_language()` was replaced by passing the
39
+ # language to the constructor. Support both so the package installs on
40
+ # any platform/version combination (0.21 has no arm64 macOS wheel).
41
+ try:
42
+ self._lang = Language(tspython.language()) # tree-sitter >= 0.22
43
+ except TypeError: # pragma: no cover - legacy tree-sitter 0.21
44
+ self._lang = Language(tspython.language(), "python") # type: ignore[call-overload]
45
+ try:
46
+ self._parser = Parser(self._lang) # tree-sitter >= 0.22
47
+ except TypeError: # pragma: no cover - legacy tree-sitter 0.21
48
+ self._parser = Parser()
49
+ self._parser.set_language(self._lang) # type: ignore[attr-defined]
40
50
 
41
51
  def parse(self, module_qualified_name: str, source: str) -> ParseResult:
42
52
  src = source.encode("utf-8")
@@ -0,0 +1,72 @@
1
+ """Rootless local Postgres helper (no Docker/sudo) — lifecycle correctness."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import pytest
6
+
7
+ pytest.importorskip("pgserver")
8
+
9
+ from coderag import localdb # noqa: E402
10
+
11
+
12
+ def test_status_does_not_start_the_server(tmp_path):
13
+ """A status check must never start a server (regression: it used to)."""
14
+ pgdata = tmp_path / "pgdata"
15
+ assert localdb.status(pgdata) is None # not running, nothing created
16
+ assert not (pgdata / "postmaster.pid").exists()
17
+
18
+
19
+ def test_start_status_stop_lifecycle(tmp_path):
20
+ pgdata = tmp_path / "pgdata"
21
+ url = localdb.start(pgdata)
22
+ try:
23
+ assert url.startswith("postgresql+psycopg://")
24
+ pid = localdb.status(pgdata)
25
+ assert pid and pid > 0
26
+ finally:
27
+ localdb.stop(pgdata)
28
+ assert localdb.status(pgdata) is None # stays stopped
29
+
30
+
31
+ def test_stale_pidfile_reports_not_running(tmp_path):
32
+ pgdata = tmp_path / "pgdata"
33
+ pgdata.mkdir()
34
+ (pgdata / "postmaster.pid").write_text("999999999\n") # impossible pid
35
+ assert localdb.status(pgdata) is None
36
+
37
+
38
+ def test_start_does_not_shell_out_to_psql(tmp_path, monkeypatch):
39
+ """Regression: the bundled `psql` binary can fail on macOS (exit 127).
40
+
41
+ `start()` must create the pgvector extension over a normal DB connection, so
42
+ the command works even when that executable is unusable.
43
+ """
44
+ import pgserver
45
+
46
+ real_get_server = pgserver.get_server
47
+
48
+ def guarded(pgdata, cleanup_mode=None):
49
+ server = real_get_server(pgdata, cleanup_mode=cleanup_mode)
50
+
51
+ def boom(*a, **k):
52
+ raise AssertionError("start() must not invoke the bundled psql binary")
53
+
54
+ monkeypatch.setattr(server, "psql", boom, raising=False)
55
+ return server
56
+
57
+ monkeypatch.setattr(pgserver, "get_server", guarded)
58
+
59
+ pgdata = tmp_path / "pgdata"
60
+ url = localdb.start(pgdata)
61
+ try:
62
+ from sqlalchemy import create_engine, text
63
+
64
+ engine = create_engine(url)
65
+ with engine.connect() as conn:
66
+ got = conn.execute(
67
+ text("SELECT extversion FROM pg_extension WHERE extname='vector'")
68
+ ).scalar()
69
+ engine.dispose()
70
+ assert got, "pgvector extension should have been created"
71
+ finally:
72
+ localdb.stop(pgdata)
@@ -1,35 +0,0 @@
1
- """Rootless local Postgres helper (no Docker/sudo) — lifecycle correctness."""
2
-
3
- from __future__ import annotations
4
-
5
- import pytest
6
-
7
- pytest.importorskip("pgserver")
8
-
9
- from coderag import localdb # noqa: E402
10
-
11
-
12
- def test_status_does_not_start_the_server(tmp_path):
13
- """A status check must never start a server (regression: it used to)."""
14
- pgdata = tmp_path / "pgdata"
15
- assert localdb.status(pgdata) is None # not running, nothing created
16
- assert not (pgdata / "postmaster.pid").exists()
17
-
18
-
19
- def test_start_status_stop_lifecycle(tmp_path):
20
- pgdata = tmp_path / "pgdata"
21
- url = localdb.start(pgdata)
22
- try:
23
- assert url.startswith("postgresql+psycopg://")
24
- pid = localdb.status(pgdata)
25
- assert pid and pid > 0
26
- finally:
27
- localdb.stop(pgdata)
28
- assert localdb.status(pgdata) is None # stays stopped
29
-
30
-
31
- def test_stale_pidfile_reports_not_running(tmp_path):
32
- pgdata = tmp_path / "pgdata"
33
- pgdata.mkdir()
34
- (pgdata / "postmaster.pid").write_text("999999999\n") # impossible pid
35
- assert localdb.status(pgdata) is None
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