codepulse-engine 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. codepulse/__init__.py +5 -0
  2. codepulse/api/app.py +1336 -0
  3. codepulse/api/errors.py +172 -0
  4. codepulse/api/security.py +189 -0
  5. codepulse/auth.py +406 -0
  6. codepulse/cli.py +1052 -0
  7. codepulse/config.py +475 -0
  8. codepulse/data_location.py +78 -0
  9. codepulse/embeddings/chunker.py +138 -0
  10. codepulse/embeddings/embedder.py +120 -0
  11. codepulse/git_diff.py +201 -0
  12. codepulse/indexer/engine.py +2655 -0
  13. codepulse/indexer/ignore.py +120 -0
  14. codepulse/ingestion_safety.py +169 -0
  15. codepulse/jobs.py +257 -0
  16. codepulse/mcp_server.py +269 -0
  17. codepulse/models.py +140 -0
  18. codepulse/observability.py +130 -0
  19. codepulse/parser/__init__.py +8 -0
  20. codepulse/parser/base.py +58 -0
  21. codepulse/parser/factory.py +37 -0
  22. codepulse/parser/graph.py +502 -0
  23. codepulse/parser/javascript.py +428 -0
  24. codepulse/parser/module_graph.py +232 -0
  25. codepulse/parser/python.py +331 -0
  26. codepulse/rate_limit.py +142 -0
  27. codepulse/repository_identity.py +83 -0
  28. codepulse/search/bm25.py +342 -0
  29. codepulse/search/ranker.py +382 -0
  30. codepulse/search/tokenizer.py +80 -0
  31. codepulse/search/vector.py +357 -0
  32. codepulse/storage/db.py +526 -0
  33. codepulse/storage/migrations.py +347 -0
  34. codepulse/storage/schema_migrations/README +1 -0
  35. codepulse/storage/schema_migrations/env.py +70 -0
  36. codepulse/storage/schema_migrations/script.py.mako +28 -0
  37. codepulse/storage/schema_migrations/versions/135a7b064ea7_add_base_classes_and_relationship_type.py +82 -0
  38. codepulse/storage/schema_migrations/versions/20fc2d0607e2_add_file_content_hash_and_natural_key_.py +106 -0
  39. codepulse/storage/schema_migrations/versions/21a7cb7f1a85_initial_schema.py +105 -0
  40. codepulse/storage/schema_migrations/versions/2cd38eb12a7e_add_call_edge_repo_id_isolation_fix.py +112 -0
  41. codepulse/storage/schema_migrations/versions/3ba396e9c78f_add_symbol_natural_key_uniqueness.py +80 -0
  42. codepulse/storage/schema_migrations/versions/49572f834366_add_embedding_persistence_fields.py +102 -0
  43. codepulse/storage/schema_migrations/versions/7c3e5a91d2f4_add_module_edges.py +61 -0
  44. codepulse/storage/schema_migrations/versions/a41f0d2c6b8e_canonicalize_repository_urls.py +94 -0
  45. codepulse/storage/schema_migrations/versions/b0bdd63949f4_add_api_key_model.py +71 -0
  46. codepulse/storage/schema_migrations/versions/b9014c049e33_add_api_key_repository_role.py +65 -0
  47. codepulse/storage/schema_migrations/versions/c286d277ea21_add_repository_snapshot_model.py +62 -0
  48. codepulse/storage/schema_migrations/versions/de22df2fc3ce_add_index_job_model.py +59 -0
  49. codepulse/storage/schema_migrations/versions/f6e481353553_rename_dependency_to_call_edge_add_confidence.py +80 -0
  50. codepulse/symbol_identity.py +29 -0
  51. codepulse_engine-0.1.0.dist-info/METADATA +191 -0
  52. codepulse_engine-0.1.0.dist-info/RECORD +56 -0
  53. codepulse_engine-0.1.0.dist-info/WHEEL +5 -0
  54. codepulse_engine-0.1.0.dist-info/entry_points.txt +2 -0
  55. codepulse_engine-0.1.0.dist-info/licenses/LICENSE +21 -0
  56. codepulse_engine-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,65 @@
1
+ """add_api_key_repository_role
2
+
3
+ Revision ID: b9014c049e33
4
+ Revises: 135a7b064ea7
5
+ Create Date: 2026-09-23 00:00:00.000000
6
+
7
+ Phase 7.2 (roadmap §37 item 7.2: "RBAC roles + repo permission model";
8
+ §15's literal minimal-viable model: "roles `viewer` (search/read),
9
+ `indexer` (viewer + trigger indexing), `admin` (all + manage keys/
10
+ repos). Enforced at the repository-permission level, not globally.").
11
+ Architecture: `role` lives on `api_key_repositories`
12
+ (`APIKeyRepositoryModel`), the per-(key, repository) grant table --
13
+ not on `api_keys` itself -- matching "not globally" literally. See
14
+ `APIKeyRepositoryModel`'s own docstring for the full reasoning.
15
+
16
+ This migration is purely additive and non-destructive:
17
+
18
+ Adds `api_key_repositories.role` (`VARCHAR(20)`, `NOT NULL`,
19
+ `DEFAULT 'admin'`). Every existing grant row, before this migration,
20
+ had UNRESTRICTED access to every endpoint that checked repository
21
+ membership alone (no role concept existed to restrict it) --
22
+ backfilling every existing row to `'admin'` reflects that row's
23
+ actual historical capability honestly, not a guess (there is only
24
+ one historically-correct answer here, unlike an ambiguous backfill
25
+ elsewhere in this project's history). `codepulse.auth.
26
+ create_api_key`'s own default for grants created AFTER this
27
+ migration is intentionally the safer `'viewer'` (least privilege) --
28
+ a forward-looking application-level default, not a migration
29
+ concern. SQLite applies the column default to every existing row
30
+ automatically when a `NOT NULL DEFAULT` column is added.
31
+
32
+ Uses Alembic "batch" mode (SQLite table-rebuild-under-the-hood),
33
+ matching every prior column-addition migration in this project's
34
+ history.
35
+ """
36
+ from typing import Sequence, Union
37
+
38
+ from alembic import op
39
+ import sqlalchemy as sa
40
+
41
+
42
+ # revision identifiers, used by Alembic.
43
+ revision: str = 'b9014c049e33'
44
+ down_revision: Union[str, Sequence[str], None] = '135a7b064ea7'
45
+ branch_labels: Union[str, Sequence[str], None] = None
46
+ depends_on: Union[str, Sequence[str], None] = None
47
+
48
+
49
+ def upgrade() -> None:
50
+ """Upgrade schema."""
51
+ with op.batch_alter_table('api_key_repositories', schema=None) as batch_op:
52
+ batch_op.add_column(
53
+ sa.Column(
54
+ 'role',
55
+ sa.String(length=20),
56
+ nullable=False,
57
+ server_default='admin',
58
+ )
59
+ )
60
+
61
+
62
+ def downgrade() -> None:
63
+ """Downgrade schema."""
64
+ with op.batch_alter_table('api_key_repositories', schema=None) as batch_op:
65
+ batch_op.drop_column('role')
@@ -0,0 +1,62 @@
1
+ """add_repository_snapshot_model
2
+
3
+ Revision ID: c286d277ea21
4
+ Revises: de22df2fc3ce
5
+ Create Date: 2026-09-14 20:00:00.000000
6
+
7
+ Phase 3.1 (roadmap §37 item 3.1: "Git-diff change detection
8
+ (added/modified/deleted)"; §29 Data Model, which explicitly names
9
+ `RepositorySnapshot` as required now: "Required for incremental indexing
10
+ (§9) and crash-safe reads (§17)").
11
+
12
+ This migration is purely additive and non-destructive: it creates one new
13
+ table (`repository_snapshots`) and touches no existing table, column, or
14
+ row. No `codepulse.db` data is read, transformed, or migrated.
15
+
16
+ `repository_snapshots`: one row per successful indexing run (a
17
+ run-history table, like `index_jobs` -- no natural-key `UniqueConstraint`)
18
+ -- `commit_sha` records the git commit the repository's content was at
19
+ (`NULL` for a non-git/local-path repository), `embedding_model`/
20
+ `embedding_dim` record what embedding configuration was used. See
21
+ codepulse.storage.db.RepositorySnapshotModel's own docstring for the full
22
+ design rationale, including what this milestone deliberately does NOT
23
+ implement (§17's full crash-safe status-gated read semantics).
24
+
25
+ Since this is a brand new table, no pre-flight duplicate check is needed
26
+ (unlike prior phases' migrations that added constraints to existing,
27
+ possibly-already-populated tables) -- there is no pre-existing data that
28
+ could violate anything here.
29
+ """
30
+ from typing import Sequence, Union
31
+
32
+ from alembic import op
33
+ import sqlalchemy as sa
34
+
35
+
36
+ # revision identifiers, used by Alembic.
37
+ revision: str = 'c286d277ea21'
38
+ down_revision: Union[str, Sequence[str], None] = 'de22df2fc3ce'
39
+ branch_labels: Union[str, Sequence[str], None] = None
40
+ depends_on: Union[str, Sequence[str], None] = None
41
+
42
+
43
+ def upgrade() -> None:
44
+ """Upgrade schema."""
45
+ op.create_table(
46
+ 'repository_snapshots',
47
+ sa.Column('id', sa.Integer(), autoincrement=True, nullable=False),
48
+ sa.Column('repo_id', sa.Integer(), nullable=False),
49
+ sa.Column('commit_sha', sa.String(length=64), nullable=True),
50
+ sa.Column('embedding_model', sa.String(length=255), nullable=True),
51
+ sa.Column('embedding_dim', sa.Integer(), nullable=True),
52
+ sa.Column('indexed_at', sa.DateTime(), nullable=False),
53
+ sa.ForeignKeyConstraint(['repo_id'], ['repositories.id'], ondelete='CASCADE'),
54
+ sa.PrimaryKeyConstraint('id'),
55
+ )
56
+ op.create_index(op.f('ix_repository_snapshots_repo_id'), 'repository_snapshots', ['repo_id'], unique=False)
57
+
58
+
59
+ def downgrade() -> None:
60
+ """Downgrade schema."""
61
+ op.drop_index(op.f('ix_repository_snapshots_repo_id'), table_name='repository_snapshots')
62
+ op.drop_table('repository_snapshots')
@@ -0,0 +1,59 @@
1
+ """add_index_job_model
2
+
3
+ Revision ID: de22df2fc3ce
4
+ Revises: b0bdd63949f4
5
+ Create Date: 2026-09-08 23:46:00.576010
6
+
7
+ Phase 1.3 (roadmap §37 item 1.3: "IndexJob state machine + /v1/jobs/{id}").
8
+
9
+ This migration is purely additive and non-destructive: it creates one new
10
+ table (`index_jobs`) and touches no existing table, column, or row. No
11
+ `codepulse.db` data is read, transformed, or migrated.
12
+
13
+ `index_jobs`: one row per indexing *run* (an event, not an idempotent
14
+ upsert-by-natural-key entity like every other table in this schema) --
15
+ `status` cycles through `queued -> running -> succeeded|failed|cancelled`
16
+ (see codepulse.storage.db.IndexJobModel's docstring). `result_summary`
17
+ holds a JSON-serialized copy of the real, measured result of
18
+ `IndexingEngine.index_directory()` once a job succeeds.
19
+
20
+ Since this is a brand new table, no pre-flight duplicate check is needed
21
+ (unlike prior phases' migrations that added constraints to existing,
22
+ possibly-already-populated tables) -- there is no pre-existing data that
23
+ could violate anything here.
24
+ """
25
+ from typing import Sequence, Union
26
+
27
+ from alembic import op
28
+ import sqlalchemy as sa
29
+
30
+
31
+ # revision identifiers, used by Alembic.
32
+ revision: str = 'de22df2fc3ce'
33
+ down_revision: Union[str, Sequence[str], None] = 'b0bdd63949f4'
34
+ branch_labels: Union[str, Sequence[str], None] = None
35
+ depends_on: Union[str, Sequence[str], None] = None
36
+
37
+
38
+ def upgrade() -> None:
39
+ """Upgrade schema."""
40
+ op.create_table(
41
+ 'index_jobs',
42
+ sa.Column('id', sa.Integer(), autoincrement=True, nullable=False),
43
+ sa.Column('repo_id', sa.Integer(), nullable=False),
44
+ sa.Column('status', sa.String(length=20), nullable=False),
45
+ sa.Column('created_at', sa.DateTime(), nullable=False),
46
+ sa.Column('started_at', sa.DateTime(), nullable=True),
47
+ sa.Column('finished_at', sa.DateTime(), nullable=True),
48
+ sa.Column('error_message', sa.Text(), nullable=True),
49
+ sa.Column('result_summary', sa.Text(), nullable=True),
50
+ sa.ForeignKeyConstraint(['repo_id'], ['repositories.id'], ondelete='CASCADE'),
51
+ sa.PrimaryKeyConstraint('id'),
52
+ )
53
+ op.create_index(op.f('ix_index_jobs_repo_id'), 'index_jobs', ['repo_id'], unique=False)
54
+
55
+
56
+ def downgrade() -> None:
57
+ """Downgrade schema."""
58
+ op.drop_index(op.f('ix_index_jobs_repo_id'), table_name='index_jobs')
59
+ op.drop_table('index_jobs')
@@ -0,0 +1,80 @@
1
+ """rename_dependency_to_call_edge_add_confidence
2
+
3
+ Revision ID: f6e481353553
4
+ Revises: c286d277ea21
5
+ Create Date: 2026-09-15 00:00:00.000000
6
+
7
+ Phase 3.4 (roadmap §37 item 3.4: "CallEdge.confidence scoring"; §11:
8
+ "every `CallEdge` should carry a `confidence` field — `high` when
9
+ resolved through an explicit import mapping to a known file+symbol,
10
+ `low` when resolved only by bare-name match against same-file symbols,
11
+ `unresolved` when the callee couldn't be matched to any known symbol at
12
+ all"; §29 Data Model: "`CallEdge` | Yes (rename from `DependencyModel`,
13
+ add `confidence`) | §11").
14
+
15
+ This migration:
16
+
17
+ 1. Renames the `dependencies` table to `call_edges` (matching the
18
+ roadmap's own target entity name), and renames its two existing
19
+ indexes to match (`ix_dependencies_*` -> `ix_call_edges_*`, the
20
+ exact names SQLAlchemy's `index=True` would auto-generate for a
21
+ model actually named `call_edges` -- kept in sync with what a
22
+ brand-new database created from today's models would produce).
23
+ 2. Adds a new, nullable `confidence` column.
24
+
25
+ Non-destructive: every existing row's `id`, `source_symbol_name`,
26
+ `source_file`, `target_symbol_name`, `target_file`, and `call_count`
27
+ value is preserved exactly (a table rename changes no row data at all).
28
+
29
+ `confidence` is deliberately left NULL for pre-existing rows, not
30
+ backfilled with a guessed value -- CLAUDE.md's "no fabricated metrics"
31
+ rule applies here: this migration has no way to honestly reconstruct
32
+ which resolution path (import-mapped, same-file bare-name, or
33
+ unresolved) produced a historical row, since that information was
34
+ never persisted before this migration existed. Each such row gets a
35
+ real, accurate `confidence` automatically the next time its repository
36
+ is re-indexed (see `codepulse.parser.graph.CallGraphBuilder._build_graph`
37
+ and `IndexingEngine._persist_to_db`), at which point the resolution
38
+ decision is freshly recomputed.
39
+ """
40
+ from typing import Sequence, Union
41
+
42
+ from alembic import op
43
+ import sqlalchemy as sa
44
+
45
+
46
+ # revision identifiers, used by Alembic.
47
+ revision: str = 'f6e481353553'
48
+ down_revision: Union[str, Sequence[str], None] = 'c286d277ea21'
49
+ branch_labels: Union[str, Sequence[str], None] = None
50
+ depends_on: Union[str, Sequence[str], None] = None
51
+
52
+
53
+ def upgrade() -> None:
54
+ """Upgrade schema."""
55
+ op.rename_table('dependencies', 'call_edges')
56
+ op.drop_index('ix_dependencies_source_symbol_name', table_name='call_edges')
57
+ op.drop_index('ix_dependencies_target_symbol_name', table_name='call_edges')
58
+ op.create_index(
59
+ op.f('ix_call_edges_source_symbol_name'), 'call_edges', ['source_symbol_name'], unique=False
60
+ )
61
+ op.create_index(
62
+ op.f('ix_call_edges_target_symbol_name'), 'call_edges', ['target_symbol_name'], unique=False
63
+ )
64
+ with op.batch_alter_table('call_edges', schema=None) as batch_op:
65
+ batch_op.add_column(sa.Column('confidence', sa.String(length=20), nullable=True))
66
+
67
+
68
+ def downgrade() -> None:
69
+ """Downgrade schema."""
70
+ with op.batch_alter_table('call_edges', schema=None) as batch_op:
71
+ batch_op.drop_column('confidence')
72
+ op.drop_index(op.f('ix_call_edges_source_symbol_name'), table_name='call_edges')
73
+ op.drop_index(op.f('ix_call_edges_target_symbol_name'), table_name='call_edges')
74
+ op.create_index(
75
+ 'ix_dependencies_source_symbol_name', 'call_edges', ['source_symbol_name'], unique=False
76
+ )
77
+ op.create_index(
78
+ 'ix_dependencies_target_symbol_name', 'call_edges', ['target_symbol_name'], unique=False
79
+ )
80
+ op.rename_table('call_edges', 'dependencies')
@@ -0,0 +1,29 @@
1
+ """
2
+ The one place that decides what a symbol's in-memory id looks like.
3
+
4
+ FUTURE_CHANGES item 31: the ids used to be `repo::file::name`, so same-named
5
+ symbols in one file (every class's `__init__`, `run`, `get`, property setters,
6
+ overloads) shared one id. The roadmap (section 29, idempotency rule) defines a
7
+ symbol's natural key as `(file_id, name, start_line)` and the database enforces
8
+ it; these ids now carry the same three parts, plus the repository.
9
+
10
+ An id is a pure function of the symbol, so it can be rebuilt from a database row
11
+ alone (after a restart, or when purging a file's old documents) with no knowledge
12
+ of the symbol's siblings. Ids exist only in memory (the lexical and vector indexes
13
+ are rebuilt from the database at startup); nothing persists them.
14
+
15
+ A symbol whose lines shift gets a new id, which matches how incremental indexing
16
+ already treats it (the same `(name, start_line)` key: the old row is replaced and
17
+ the symbol is re-chunked and re-embedded).
18
+ """
19
+ from __future__ import annotations
20
+
21
+
22
+ def symbol_doc_id(repo_id: int, file_path: str, name: str, start_line: int) -> str:
23
+ """Id of a symbol in the lexical index, the in-memory symbol maps and result lists."""
24
+ return f"{repo_id}::{file_path}::{name}@{start_line}"
25
+
26
+
27
+ def chunk_doc_id(repo_id: int, file_path: str, name: str, start_line: int, chunk_index: int) -> str:
28
+ """Id of one embedded chunk of a symbol in the vector index."""
29
+ return f"{symbol_doc_id(repo_id, file_path, name, start_line)}::{chunk_index}"
@@ -0,0 +1,191 @@
1
+ Metadata-Version: 2.4
2
+ Name: codepulse-engine
3
+ Version: 0.1.0
4
+ Summary: Local-first code intelligence engine: hybrid keyword, semantic and call-graph search and impact analysis for developers and AI agents, over a CLI, a REST API and an MCP server.
5
+ Author: Dhairya Chhabra
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/Chh-Dhairya/CodePulse
8
+ Project-URL: Repository, https://github.com/Chh-Dhairya/CodePulse
9
+ Project-URL: Issues, https://github.com/Chh-Dhairya/CodePulse/issues
10
+ Project-URL: Documentation, https://github.com/Chh-Dhairya/CodePulse/tree/HEAD/docs
11
+ Keywords: code search,code intelligence,semantic search,call graph,impact analysis,mcp,model context protocol,tree-sitter,local-first,developer tools
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Topic :: Software Development
19
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
20
+ Classifier: Topic :: Text Processing :: Indexing
21
+ Requires-Python: <3.13,>=3.11
22
+ Description-Content-Type: text/markdown
23
+ License-File: LICENSE
24
+ Requires-Dist: fastapi<0.112.0,>=0.111.0
25
+ Requires-Dist: uvicorn[standard]<0.31.0,>=0.30.0
26
+ Requires-Dist: pydantic<2.8.0,>=2.7.0
27
+ Requires-Dist: sqlalchemy<2.1.0,>=2.0.30
28
+ Requires-Dist: alembic<1.14.0,>=1.13.1
29
+ Requires-Dist: tantivy<0.23.0,>=0.22.0
30
+ Requires-Dist: sqlite-vec<0.2.0,>=0.1.9
31
+ Requires-Dist: tree-sitter<0.22.0,>=0.21.0
32
+ Requires-Dist: tree-sitter-languages>=1.10.2
33
+ Requires-Dist: pathspec<1.2.0,>=1.1.0
34
+ Requires-Dist: networkx<4.0,>=3.0
35
+ Requires-Dist: mcp<2.0.0,>=1.9.0
36
+ Requires-Dist: sentence-transformers<3.1.0,>=3.0.0
37
+ Requires-Dist: torch<2.4.0,>=2.3.0
38
+ Requires-Dist: numpy<2
39
+ Requires-Dist: rich<16.0.0,>=15.0.0
40
+ Requires-Dist: prometheus-client<0.21.0,>=0.20.0
41
+ Requires-Dist: httpx<0.28.0,>=0.27.0
42
+ Requires-Dist: structlog<24.2.0,>=24.1.0
43
+ Provides-Extra: dev
44
+ Requires-Dist: pytest>=8.2.0; extra == "dev"
45
+ Requires-Dist: pytest-asyncio>=0.23.7; extra == "dev"
46
+ Requires-Dist: pytest-cov>=5.0.0; extra == "dev"
47
+ Requires-Dist: ruff>=0.4.8; extra == "dev"
48
+ Requires-Dist: mypy>=1.10.0; extra == "dev"
49
+ Requires-Dist: setuptools>=68.0; extra == "dev"
50
+ Dynamic: license-file
51
+
52
+ # CodePulse
53
+
54
+ **A local-first code intelligence engine for developers and AI agents.**
55
+
56
+ CodePulse indexes a repository once, then answers questions about it through one shared core, reachable from a CLI, a REST API, or an MCP server:
57
+
58
+ ```
59
+ Index → Retrieve → Explain (structure) → Navigate → Decide (impact) → Act
60
+ ```
61
+
62
+ - **Hybrid retrieval.** Keyword (BM25 via Tantivy), semantic (sentence-transformers embeddings in sqlite-vec) and structural (call graph) signals are fused into one ranking.
63
+ - **Agent-native.** Responses are structured and token-efficient, and they say explicitly when results were truncated or omitted.
64
+ - **Local-first.** Everything runs on your machine: SQLite, embedded indexes, a local embedding model. No external service or API key is needed to index and search.
65
+
66
+ Python and JavaScript/TypeScript sources are parsed (tree-sitter) into symbols, a call graph, and inheritance edges.
67
+
68
+ ## Install
69
+
70
+ Requires **Python 3.11 or 3.12** (3.13+ is not supported yet).
71
+
72
+ ```bash
73
+ python -m venv .venv
74
+ source .venv/bin/activate # Windows: .venv\Scripts\activate
75
+ pip install -e ".[dev]"
76
+ ```
77
+
78
+ This installs a `codepulse` command (`python -m codepulse.cli` works too). The first run downloads the `all-MiniLM-L6-v2` embedding model from Hugging Face (about 90 MB) and caches it. After that CodePulse works offline.
79
+
80
+ ## Quick start
81
+
82
+ ```bash
83
+ codepulse init . # writes codepulse.toml here
84
+ codepulse index path/to/repo # full index
85
+ codepulse search path/to/repo -q "parse config" # hybrid search (also: -t keyword|semantic)
86
+ codepulse symbol path/to/repo load_config # definition lookup
87
+ codepulse callers path/to/repo load_config # who calls it (direct + transitive)
88
+ codepulse impact path/to/repo load_config # blast radius if it changes
89
+ codepulse status path/to/repo # index freshness
90
+ codepulse doctor # environment diagnostics
91
+ ```
92
+
93
+ Every query command accepts `--json`. Run `codepulse --help` for the full list: `parse`, `search`, `impact`, `init`, `index`, `update`, `symbol`, `refs`, `callers`, `callees`, `similar`, `status`, `modules`, `doctor`, `migrate`, `key`, `serve`.
94
+
95
+ ### Where the database lives
96
+
97
+ By default there is **one database per user**, in your operating system's per-user data directory (`%LOCALAPPDATA%\CodePulse` on Windows, `~/Library/Application Support/CodePulse` on macOS, `$XDG_DATA_HOME/codepulse` or `~/.local/share/codepulse` on Linux), so it does not matter which directory you run a command from and nothing is written into your projects. Set `CODEPULSE_DATA_DIR` to move that directory.
98
+
99
+ Two things override the default:
100
+
101
+ - An explicit setting always wins: `--db-url`, `CODEPULSE_DB_URL`, or `db_url` in `codepulse.toml` (`codepulse init` writes a project-local one).
102
+ - If the directory you run from already contains a `codepulse.db`, that file is used (it was the default before), so existing setups keep working unchanged.
103
+
104
+ Every command prints which database it is using on stderr, and says so when it is about to create a new one. A `codepulse.toml` is picked up only from the directory you run from.
105
+
106
+ ### Incremental indexing
107
+
108
+ `codepulse update <repo>` re-indexes using `git diff` when the repository has git history and a prior snapshot exists. Otherwise it falls back to a full index. The call graph is held in memory, so the incremental path only applies inside a long-lived process such as `codepulse serve`. A one-shot CLI run always does a full index and reports `incremental: false`.
109
+
110
+ ## REST API
111
+
112
+ ```bash
113
+ codepulse serve # http://127.0.0.1:8000
114
+ ```
115
+
116
+ Interactive docs are at `/docs`. Health checks are `/health/live` and `/health/ready`; `/metrics` exposes Prometheus metrics. All other endpoints need an `X-API-Key` header.
117
+
118
+ Keys are scoped to repositories and carry a role: `viewer` (search and read) < `indexer` (plus indexing) < `admin` (plus managing keys). Creating a key over REST needs an admin key, so make your first one on the command line:
119
+
120
+ ```bash
121
+ codepulse key create --repo path/to/project # role indexer (the default)
122
+ codepulse key create --repo path/to/project --role admin --name ci
123
+ codepulse key create --repo a --repo b --json # several folders; machine-readable
124
+ ```
125
+
126
+ It prints the key once (only its hash is stored) and registers the folder if it is not in the database yet, so you can index it with that key right away. At least one `--repo` is required, since a key with no repository can do nothing. Further keys can come from `POST /v1/admin/keys`. A key can index a new repository only if it already holds the `indexer` role (or higher) on some existing repository; the key that creates a repository becomes its `admin`, so it can follow the indexing job and see the result.
127
+
128
+ Main endpoints: `POST /v1/search`, `POST /v1/repositories/index`, `GET /v1/repositories/{id}/index-status`, `GET /v1/symbols/{id}`, `POST /v1/symbols/{id}/impact-analysis`, `POST /v1/symbols/{id}/similar`, `GET /v1/symbols/{id}/references`, `POST /v1/agent/context`. Errors always use one shape: `{"error": {"code", "message", "details"}}`. Requests are rate-limited per key (5 req/s, burst 20, configurable).
129
+
130
+ A local repository is identified by its canonical path (absolute, symlinks and `..` resolved, true case and an uppercase drive letter on Windows, no trailing separator), so `C:\\proj`, `c:/proj/` and `C:\\proj\\sub\\..` are one repository, not three.
131
+
132
+ Local-path ingestion can be restricted to an allow-list with `ingestion_allowed_roots` (or `CODEPULSE_INGESTION_ALLOWED_ROOTS`). Cloning from git URLs is not implemented: the URL is validated (https/ssh, SSRF check) and the request is then rejected with 501.
133
+
134
+ ## MCP server
135
+
136
+ ```bash
137
+ export CODEPULSE_MCP_API_KEY=cpk_... # or pass --api-key
138
+ codepulse serve --mcp # stdio transport
139
+ ```
140
+
141
+ The connection is scoped to the repositories the key can access. The MCP tools call the same functions as the CLI and REST API; there is no separate implementation.
142
+
143
+ ## VS Code extension
144
+
145
+ A separate extension (CodeLens, hovers, impact tree, MCP registration) lives in its own repository, [`codepulse-vscode`](https://github.com/Chh-Dhairya/codepulse-vscode). It talks to a running `codepulse serve` using a scoped API key.
146
+
147
+ ## Configuration
148
+
149
+ Precedence: CLI flags > `CODEPULSE_*` environment variables > `codepulse.toml` > built-in defaults. Common settings are `db_url`, `embedding_model`, `embedding_device`, `ingestion_allowed_roots`, and the `rate_limit_*` options. See `codepulse/config.py` for the full list.
150
+
151
+ ## Upgrading an existing database
152
+
153
+ After updating CodePulse, bring the database up to date with one command:
154
+
155
+ ```bash
156
+ codepulse migrate # apply (backs the SQLite file up first)
157
+ codepulse migrate --check # only report: exit 0 up to date, 1 needs migrating, 2 cannot be migrated safely
158
+ ```
159
+
160
+ It works on whatever database CodePulse is configured for (`--db-url`, `CODEPULSE_DB_URL`, a `codepulse.toml` in the current directory, or the default `sqlite:///codepulse.db`) and does the safe thing for each case:
161
+
162
+ | The database is | `migrate` |
163
+ |---|---|
164
+ | new (no tables yet) | creates it at the current schema |
165
+ | behind | copies it to `<file>.pre-<revision>-<time>.bak`, then upgrades it |
166
+ | from an older CodePulse and was never tracked | checks its schema against the models; if it matches (tables it does not have yet are simply created, empty), records it as up to date, after a backup |
167
+ | not tracked and its schema differs in any other way (a missing or extra column, an extra table) | refuses, lists what differs, changes nothing |
168
+ | from a newer CodePulse | refuses, changes nothing |
169
+
170
+ Every CodePulse command prints a warning on stderr when the database is behind, and `codepulse doctor` reports the schema state. Until you migrate, CodePulse keeps working; features that need a newer table (for example module structure after a restart) are simply unavailable and a warning is logged. A database CodePulse creates from scratch is tracked from the start, so it can always be upgraded this way. The migration scripts ship inside the package, so this works from an installed copy as well as from a checkout.
171
+
172
+ ## Development
173
+
174
+ ```bash
175
+ pytest tests/ -q
176
+ ```
177
+
178
+ Schema changes go through Alembic (`codepulse/storage/schema_migrations/`; `alembic.ini` in the repository root points at it for `alembic revision`/`alembic upgrade` during development). PostgreSQL, Redis, and distributed workers are intentionally out of scope for now.
179
+
180
+ ## Project documents
181
+
182
+ - [`docs/CodePulse_Strategy_and_Roadmap.md`](https://github.com/Chh-Dhairya/CodePulse/blob/HEAD/docs/CodePulse_Strategy_and_Roadmap.md): the authoritative strategy, architecture, and milestone plan.
183
+ - [`docs/CODEPULSE_PRODUCT_AND_ARCHITECTURE.md`](https://github.com/Chh-Dhairya/CodePulse/blob/HEAD/docs/CODEPULSE_PRODUCT_AND_ARCHITECTURE.md): what CodePulse is and how it is built, as shipped.
184
+ - [`docs/CODEPULSE_CLIENT_STRATEGY.md`](https://github.com/Chh-Dhairya/CodePulse/blob/HEAD/docs/CODEPULSE_CLIENT_STRATEGY.md): the plan for the VS Code extension and other clients.
185
+ - [`docs/BACKUP_RESTORE.md`](https://github.com/Chh-Dhairya/CodePulse/blob/HEAD/docs/BACKUP_RESTORE.md): backing up and restoring a database.
186
+ - [`docs/ORIGINAL_DESIGN.md`](https://github.com/Chh-Dhairya/CodePulse/blob/HEAD/docs/ORIGINAL_DESIGN.md): the original design, kept for history (it describes infrastructure that was never built).
187
+ - [`docs/internal/`](https://github.com/Chh-Dhairya/CodePulse/tree/HEAD/docs/internal/): the working log (`CODEPULSE_PROGRESS.md`: what is built and verified, milestone by milestone) and `CODEPULSE_FUTURE_CHANGES.md` (known limitations and planned improvements).
188
+
189
+ ## License
190
+
191
+ MIT. See [LICENSE](https://github.com/Chh-Dhairya/CodePulse/blob/HEAD/LICENSE).
@@ -0,0 +1,56 @@
1
+ codepulse/__init__.py,sha256=NMjXlDqrrDuzz4lqokxvda3NJwT1t6kdUTawPCxfTOk,80
2
+ codepulse/auth.py,sha256=jCJtUrk53Dhhh1a5UxJQLWbYEl6Mz1oeU-_2I2s_qzQ,16232
3
+ codepulse/cli.py,sha256=1jZYFQLYO55YwR4km9qx7YXrIWIZ9-d-Kl8xNsTSikc,48178
4
+ codepulse/config.py,sha256=18WGtd05kpxbc4XGuVtkAbOvReVxKyJLG-NkMCY9ZhQ,19268
5
+ codepulse/data_location.py,sha256=p_4dMFHaqw93DT_dgaB4x9fB4JF_Q5kalm4p0xos-n4,3399
6
+ codepulse/git_diff.py,sha256=QmH32PSQoZ7GhDO-rgpnMUDBCD1etthznq330k2ha0w,8760
7
+ codepulse/ingestion_safety.py,sha256=BpyzfXCx8eGueLf9o-Ipy1lQ1hqzVkrrrCpGeZgYxBI,7879
8
+ codepulse/jobs.py,sha256=YdQeZ7nVERCi4ZMdcu3lY5Sf0QJ5_yDu6pjjNCaMlvo,11258
9
+ codepulse/mcp_server.py,sha256=6Lp97C88SW4U0bmi0Q2KrZ7Xf5s55r3igNF8gouaJKQ,12750
10
+ codepulse/models.py,sha256=Zes0jKnV2yA-ZeeWIIta3uJ3JIbKA36izZh9MfQNMGA,5943
11
+ codepulse/observability.py,sha256=AXqAvGUxZENwu2ygA3l8ZJDvS4XcLzZBhW3Shhot7GU,5806
12
+ codepulse/rate_limit.py,sha256=64ayl8JWeUzC8gQJlA0NQ_pHYNI7komOjj8OiPxBEBE,6173
13
+ codepulse/repository_identity.py,sha256=ttkSsAmW9_2XqwWhJpJEYLsbruavxfeJHc1A_1YULrs,3817
14
+ codepulse/symbol_identity.py,sha256=WRqeneAUc-nsiLkocl_sNvyutxe49XVxCv58VaVi8xU,1503
15
+ codepulse/api/app.py,sha256=FHQ_7nVOX6Var1X52QiBh-icWL8-pWdIBS_RMt5_bIU,59980
16
+ codepulse/api/errors.py,sha256=TeGXFfAaY_tcUIzYuDW5Ck5HA2c10Q3IT55OvfqvD_Y,8522
17
+ codepulse/api/security.py,sha256=wp44y5knQLGA7qugVi8YXvSFBMttaDwvElCF-qVtQP0,9381
18
+ codepulse/embeddings/chunker.py,sha256=nf64CnP3_IZjHWylcJCtyHp06J4BWS48Paz2bnjg0_g,4875
19
+ codepulse/embeddings/embedder.py,sha256=EhPLncT3Rk_bmbQ4sNEqSO0mZiFVOX6efUo-85RCLM0,4690
20
+ codepulse/indexer/engine.py,sha256=GDqoblE9Q2yisug_w76zKXpTyQoGR_JbH7MqPjoJDUY,135158
21
+ codepulse/indexer/ignore.py,sha256=_sm-0AqgwuzbNSQ2ne36UqsSLng5H3pnaZJWBiK0nrs,5499
22
+ codepulse/parser/__init__.py,sha256=tUqFIUZye-zpOXdRpefLVW0goGj_lcf2z3mfGfe3jWU,253
23
+ codepulse/parser/base.py,sha256=cvYxyfkwxLTgwQTBa3Nm89fRgGAjmvsMw-vmm9aVVGQ,2027
24
+ codepulse/parser/factory.py,sha256=arQlbleElFZXUvWV24HFfUXgCFuSWM_34EpHTbK2dww,1420
25
+ codepulse/parser/graph.py,sha256=mXBQFYmWwUoUuD41h9B_5xB9M5W6admUGC1Z7dlO2nw,24342
26
+ codepulse/parser/javascript.py,sha256=CYgt1Avx3iFXynF6jzXl0B-Q0cU_rGmNYNG2wzQoZho,19870
27
+ codepulse/parser/module_graph.py,sha256=lfD3RvefsZIoMbe040zo8ISs-vdRAD0R5_ME0EDK6_s,11947
28
+ codepulse/parser/python.py,sha256=bvNJklZvVh1mwFz7MBUFRLj9nvV0ihFNKAqt9Ubkhdo,14513
29
+ codepulse/search/bm25.py,sha256=Ov9JXeMu6XLM1N_g7Z97DC-czPx77jnBZUN-hvOknOg,17406
30
+ codepulse/search/ranker.py,sha256=NAgC69GTdt9BKwnpQz3Ec3BkmbYGzicFdgbrAN3QFDY,16526
31
+ codepulse/search/tokenizer.py,sha256=uvItiFxDiU72KCbva1sW_ZHcYI3RZBr9wpjOGc-YyoA,2389
32
+ codepulse/search/vector.py,sha256=rLpn4VfvCoa2Qbb_f_Tzlvhy4cgUPlciBh5dTzxWEzg,19320
33
+ codepulse/storage/db.py,sha256=0YowdvEOGiN-DQ_OF91gN5nF7xfu6bsaimRlChkg4EI,26970
34
+ codepulse/storage/migrations.py,sha256=4T9OL8RpQ2JlSvpCCTCbPJs8bGq0pZ664pwR3Ki5l8I,16121
35
+ codepulse/storage/schema_migrations/README,sha256=MVlc9TYmr57RbhXET6QxgyCcwWP7w-vLkEsirENqiIQ,38
36
+ codepulse/storage/schema_migrations/env.py,sha256=oDPiMYwF7-h3Dl10AK3K0lWKbkpaHvPQIHfhxy7nGbA,1947
37
+ codepulse/storage/schema_migrations/script.py.mako,sha256=04kgeBtNMa4cCnG8CfQcKt6P6rnloIfj8wy0u_DBydM,704
38
+ codepulse/storage/schema_migrations/versions/135a7b064ea7_add_base_classes_and_relationship_type.py,sha256=un3HGj9vFiDfNIhYYHpYRJeQKYCP1rcjHSx_lVUJ0sU,3512
39
+ codepulse/storage/schema_migrations/versions/20fc2d0607e2_add_file_content_hash_and_natural_key_.py,sha256=b6QKhYFIrwtK3L6SujplmW7xvkz0-Ub-ru1SJ2cJtEs,4320
40
+ codepulse/storage/schema_migrations/versions/21a7cb7f1a85_initial_schema.py,sha256=E6cJ8kQKPMylIex1sKMRc77_h_iQpDfn1My_-oFL82g,4686
41
+ codepulse/storage/schema_migrations/versions/2cd38eb12a7e_add_call_edge_repo_id_isolation_fix.py,sha256=omKUXc9BagP2vpVX5fwscJ-5PFyh0tZiCZuMBw-Zy7Q,5130
42
+ codepulse/storage/schema_migrations/versions/3ba396e9c78f_add_symbol_natural_key_uniqueness.py,sha256=qBuuUIByouP-n21sUzBOdQi3yJynoskdvAgHH0E9W5U,2980
43
+ codepulse/storage/schema_migrations/versions/49572f834366_add_embedding_persistence_fields.py,sha256=Wv0CFE8LvtzwSEpMyxFZq-H9Izjy98P9aVH4iR7UQrk,4531
44
+ codepulse/storage/schema_migrations/versions/7c3e5a91d2f4_add_module_edges.py,sha256=8MuGbFC_4xC5D504X2i0H-P0q6GK77SVs5Jj5WzKyRQ,2682
45
+ codepulse/storage/schema_migrations/versions/a41f0d2c6b8e_canonicalize_repository_urls.py,sha256=s98BmtNmi6ycc21Z0KKfdj_ElDNo_BrCosvozSODwyo,3902
46
+ codepulse/storage/schema_migrations/versions/b0bdd63949f4_add_api_key_model.py,sha256=wx3rnk8anXU7KNUQagIbNmQFZPR_awtafti6UcLfmXY,2993
47
+ codepulse/storage/schema_migrations/versions/b9014c049e33_add_api_key_repository_role.py,sha256=JSZDTj_DnJumMlm4cGpul2HBkZzS8cCvBNpH-9IETQY,2537
48
+ codepulse/storage/schema_migrations/versions/c286d277ea21_add_repository_snapshot_model.py,sha256=E4jKc4TFRVxg-Z8Ns_zCb5O1M-8bxOu-kb-FTjBc-p0,2645
49
+ codepulse/storage/schema_migrations/versions/de22df2fc3ce_add_index_job_model.py,sha256=tmMEzU6o5Igv8u3YELN4B4GtNxoJw2em9p7CTG4bjuk,2367
50
+ codepulse/storage/schema_migrations/versions/f6e481353553_rename_dependency_to_call_edge_add_confidence.py,sha256=7pr9GYNf_FcvlrKc_qeoOMrW2mAkzgSobfIMvzXKDvM,3566
51
+ codepulse_engine-0.1.0.dist-info/licenses/LICENSE,sha256=hMHnUvamOVPVjz9uh9RTcDdnx5jkz_JTs579jfpU8aU,1072
52
+ codepulse_engine-0.1.0.dist-info/METADATA,sha256=3Ewu7tnYtqxMjQWlajJzufUVGoOe6hsEaIgK889sjk0,12389
53
+ codepulse_engine-0.1.0.dist-info/WHEEL,sha256=mOdXR9C3wMVov7cSY8PY-XCzo3SmmWCHvGQr5csJoao,91
54
+ codepulse_engine-0.1.0.dist-info/entry_points.txt,sha256=xRsekBS_9RQ0fx-UagKqWflKPqnxwpJSS2AtWCPIoK0,49
55
+ codepulse_engine-0.1.0.dist-info/top_level.txt,sha256=mKhxHbH2S88BxfgihA0ZatGxffQORlWftBSZp71ZJuc,10
56
+ codepulse_engine-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (84.2.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ codepulse = codepulse.cli:main
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Dhairya Chhabra
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1 @@
1
+ codepulse