descry-codegraph 0.1.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 (50) hide show
  1. descry_codegraph-0.1.0/.github/workflows/ci.yml +76 -0
  2. descry_codegraph-0.1.0/.github/workflows/release.yml +123 -0
  3. descry_codegraph-0.1.0/.gitignore +23 -0
  4. descry_codegraph-0.1.0/CHANGELOG.md +30 -0
  5. descry_codegraph-0.1.0/CLAUDE.md +69 -0
  6. descry_codegraph-0.1.0/LICENSE +21 -0
  7. descry_codegraph-0.1.0/PKG-INFO +345 -0
  8. descry_codegraph-0.1.0/README.md +280 -0
  9. descry_codegraph-0.1.0/SECURITY.md +27 -0
  10. descry_codegraph-0.1.0/justfile +47 -0
  11. descry_codegraph-0.1.0/pi-extension/README.md +55 -0
  12. descry_codegraph-0.1.0/pi-extension/descry-cli.py +250 -0
  13. descry_codegraph-0.1.0/pi-extension/descry-tools.ts +441 -0
  14. descry_codegraph-0.1.0/pyproject.toml +65 -0
  15. descry_codegraph-0.1.0/src/descry/__init__.py +4 -0
  16. descry_codegraph-0.1.0/src/descry/_env.py +30 -0
  17. descry_codegraph-0.1.0/src/descry/_graph.py +36 -0
  18. descry_codegraph-0.1.0/src/descry/ast_grep.py +382 -0
  19. descry_codegraph-0.1.0/src/descry/cli.py +457 -0
  20. descry_codegraph-0.1.0/src/descry/cross_lang.py +404 -0
  21. descry_codegraph-0.1.0/src/descry/embeddings.py +593 -0
  22. descry_codegraph-0.1.0/src/descry/generate.py +3579 -0
  23. descry_codegraph-0.1.0/src/descry/git_history.py +1631 -0
  24. descry_codegraph-0.1.0/src/descry/handlers.py +1907 -0
  25. descry_codegraph-0.1.0/src/descry/mcp_server.py +355 -0
  26. descry_codegraph-0.1.0/src/descry/query.py +2345 -0
  27. descry_codegraph-0.1.0/src/descry/scip/__init__.py +0 -0
  28. descry_codegraph-0.1.0/src/descry/scip/cache.py +1047 -0
  29. descry_codegraph-0.1.0/src/descry/scip/parser.py +531 -0
  30. descry_codegraph-0.1.0/src/descry/scip/pb2.py +92 -0
  31. descry_codegraph-0.1.0/src/descry/scip/support.py +174 -0
  32. descry_codegraph-0.1.0/src/descry/tree_sitter_parser.py +356 -0
  33. descry_codegraph-0.1.0/src/descry/web/__init__.py +0 -0
  34. descry_codegraph-0.1.0/src/descry/web/server.py +1457 -0
  35. descry_codegraph-0.1.0/src/descry/web/web/index.html +2444 -0
  36. descry_codegraph-0.1.0/src/descry/web/web/style.css +1791 -0
  37. descry_codegraph-0.1.0/tests/__init__.py +0 -0
  38. descry_codegraph-0.1.0/tests/conftest.py +29 -0
  39. descry_codegraph-0.1.0/tests/fixtures/small_graph.json +131 -0
  40. descry_codegraph-0.1.0/tests/test_cli.py +66 -0
  41. descry_codegraph-0.1.0/tests/test_config.py +393 -0
  42. descry_codegraph-0.1.0/tests/test_generate.py +859 -0
  43. descry_codegraph-0.1.0/tests/test_git_history.py +223 -0
  44. descry_codegraph-0.1.0/tests/test_handlers.py +253 -0
  45. descry_codegraph-0.1.0/tests/test_hardening.py +346 -0
  46. descry_codegraph-0.1.0/tests/test_mcp.py +96 -0
  47. descry_codegraph-0.1.0/tests/test_query.py +809 -0
  48. descry_codegraph-0.1.0/tests/test_scip.py +319 -0
  49. descry_codegraph-0.1.0/tests/test_tree_sitter_parser.py +125 -0
  50. descry_codegraph-0.1.0/tests/test_web_server.py +210 -0
@@ -0,0 +1,76 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ branches: [main]
8
+
9
+ # Default to least-privilege; jobs that need more must opt in explicitly.
10
+ permissions:
11
+ contents: read
12
+
13
+ jobs:
14
+ lint:
15
+ runs-on: ubuntu-latest
16
+ steps:
17
+ - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
18
+ - uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
19
+ - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
20
+ with:
21
+ python-version: "3.12"
22
+ - run: uv pip install --system ruff
23
+ - run: ruff check src/ tests/
24
+ - run: ruff format --check src/ tests/
25
+
26
+ test:
27
+ runs-on: ubuntu-latest
28
+ strategy:
29
+ matrix:
30
+ python-version: ["3.11", "3.12", "3.13"]
31
+ steps:
32
+ - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
33
+ - uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
34
+ - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
35
+ with:
36
+ python-version: ${{ matrix.python-version }}
37
+ - run: uv venv .venv
38
+ - run: . .venv/bin/activate && uv pip install -e ".[dev]"
39
+ env:
40
+ DESCRY_NO_EMBEDDINGS: "1"
41
+ - run: . .venv/bin/activate && python -m pytest tests/ -v
42
+ env:
43
+ DESCRY_NO_EMBEDDINGS: "1"
44
+ DESCRY_NO_SCIP: "1"
45
+
46
+ build:
47
+ runs-on: ubuntu-latest
48
+ steps:
49
+ - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
50
+ - uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
51
+ - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
52
+ with:
53
+ python-version: "3.12"
54
+ - run: uv build
55
+ - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
56
+ with:
57
+ name: dist
58
+ path: dist/
59
+
60
+ audit:
61
+ runs-on: ubuntu-latest
62
+ steps:
63
+ - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
64
+ - uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
65
+ - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
66
+ with:
67
+ python-version: "3.12"
68
+ - name: Install descry[all] + pip-audit
69
+ run: |
70
+ uv venv .venv
71
+ . .venv/bin/activate
72
+ uv pip install -e ".[all]" pip-audit
73
+ - name: Audit resolved environment
74
+ run: |
75
+ . .venv/bin/activate
76
+ pip-audit --skip-editable
@@ -0,0 +1,123 @@
1
+ name: Release
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - "v*"
7
+ workflow_dispatch:
8
+ inputs:
9
+ target:
10
+ description: "Publish target"
11
+ type: choice
12
+ required: true
13
+ default: testpypi
14
+ options:
15
+ - testpypi
16
+ - pypi
17
+
18
+ # Default to least-privilege. Only the publish jobs opt into id-token: write
19
+ # for OIDC trusted publishing.
20
+ permissions:
21
+ contents: read
22
+
23
+ jobs:
24
+ build:
25
+ name: Build distributions
26
+ runs-on: ubuntu-latest
27
+ steps:
28
+ - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
29
+ - uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
30
+ - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
31
+ with:
32
+ python-version: "3.12"
33
+ - name: Verify tag matches pyproject version
34
+ if: github.event_name == 'push'
35
+ env:
36
+ GIT_TAG: ${{ github.ref_name }}
37
+ run: |
38
+ PKG_VERSION=$(python -c "import tomllib; print(tomllib.loads(open('pyproject.toml','rb').read().decode())['project']['version'])")
39
+ TAG_VERSION="${GIT_TAG#v}"
40
+ if [ "$PKG_VERSION" != "$TAG_VERSION" ]; then
41
+ echo "::error::tag $GIT_TAG does not match pyproject version $PKG_VERSION"
42
+ exit 1
43
+ fi
44
+ - name: Build sdist + wheel
45
+ run: uv build
46
+ - name: Check distributions with twine
47
+ run: |
48
+ uv pip install --system twine
49
+ twine check --strict dist/*
50
+ - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
51
+ with:
52
+ name: dist
53
+ path: dist/
54
+
55
+ audit:
56
+ name: Security audit
57
+ needs: build
58
+ runs-on: ubuntu-latest
59
+ steps:
60
+ - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
61
+ - uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
62
+ - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
63
+ with:
64
+ python-version: "3.12"
65
+ - name: Install descry[all] + pip-audit
66
+ run: |
67
+ uv venv .venv
68
+ . .venv/bin/activate
69
+ uv pip install -e ".[all]" pip-audit
70
+ - name: Audit resolved environment
71
+ run: |
72
+ . .venv/bin/activate
73
+ pip-audit --skip-editable
74
+
75
+ publish-testpypi:
76
+ name: Publish to TestPyPI
77
+ needs: [build, audit]
78
+ if: github.event_name == 'workflow_dispatch' && github.event.inputs.target == 'testpypi'
79
+ runs-on: ubuntu-latest
80
+ environment:
81
+ name: testpypi
82
+ url: https://test.pypi.org/p/descry-codegraph
83
+ permissions:
84
+ id-token: write
85
+ steps:
86
+ - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
87
+ with:
88
+ name: dist
89
+ path: dist/
90
+ - name: Publish to TestPyPI via OIDC
91
+ # NOTE: this is a Docker container action — its GHCR image is only
92
+ # tagged with release refs, never commit SHAs. Pinning to a commit
93
+ # SHA here fails with "manifest unknown" because no image at that
94
+ # digest exists. Pinning to the version tag is the documented
95
+ # approach; PyPA controls this tag and tag mutation would be a
96
+ # notable event we'd catch via the trusted-publisher audit log.
97
+ uses: pypa/gh-action-pypi-publish@v1.14.0
98
+ with:
99
+ repository-url: https://test.pypi.org/legacy/
100
+
101
+ publish-pypi:
102
+ name: Publish to PyPI
103
+ needs: [build, audit]
104
+ if: github.event_name == 'push' || (github.event_name == 'workflow_dispatch' && github.event.inputs.target == 'pypi')
105
+ runs-on: ubuntu-latest
106
+ environment:
107
+ name: pypi
108
+ url: https://pypi.org/p/descry-codegraph
109
+ permissions:
110
+ id-token: write
111
+ steps:
112
+ - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
113
+ with:
114
+ name: dist
115
+ path: dist/
116
+ - name: Publish to PyPI via OIDC
117
+ # NOTE: this is a Docker container action — its GHCR image is only
118
+ # tagged with release refs, never commit SHAs. Pinning to a commit
119
+ # SHA here fails with "manifest unknown" because no image at that
120
+ # digest exists. Pinning to the version tag is the documented
121
+ # approach; PyPA controls this tag and tag mutation would be a
122
+ # notable event we'd catch via the trusted-publisher audit log.
123
+ uses: pypa/gh-action-pypi-publish@v1.14.0
@@ -0,0 +1,23 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ *$py.class
4
+ *.so
5
+ *.egg-info/
6
+ dist/
7
+ build/
8
+ .eggs/
9
+ *.egg
10
+ .venv/
11
+ venv/
12
+ env/
13
+ .env
14
+ .descry_cache/
15
+ .codegraph_cache/
16
+ *.scip
17
+ target/
18
+ node_modules/
19
+ .pytest_cache/
20
+ .ruff_cache/
21
+ .mypy_cache/
22
+ uv.lock
23
+ .playwright-mcp/
@@ -0,0 +1,30 @@
1
+ # Changelog
2
+
3
+ All notable changes to Descry will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to pre-1.0 semver (minor-version bumps may include breaking changes; see README versioning stance).
6
+
7
+ ## [0.1.0] — 2026-04-16
8
+
9
+ Initial public PyPI release. Descry is a polyglot codebase knowledge graph toolkit with three interfaces: CLI (`descry`), MCP server (`descry-mcp`), and local web UI (`descry-web`).
10
+
11
+ ### Features
12
+
13
+ - **Indexer**: Parses Rust, Python, TypeScript, JavaScript, and Svelte into a cached knowledge graph of symbols (functions, classes, constants) and edges (calls, imports, defines).
14
+ - **SCIP integration**: Optional type-aware call resolution via `rust-analyzer` and `scip-typescript`; regex fallback otherwise.
15
+ - **Semantic search**: Optional embeddings via sentence-transformers (Jina code embeddings by default; model pinned by revision).
16
+ - **18 MCP tools**: search, callers, callees, context, flow, path, impls, structure, flatten, cross-lang (preview), churn, evolution, changes, semantic, quick, index, status, ensure, health.
17
+ - **Web UI**: Starlette + Alpine.js; 20+ UI panels for browsing the graph visually.
18
+ - **Configuration**: `.descry.toml` + env-var overrides for cache dir, timeouts, embedding model, SCIP toolchain, excluded dirs.
19
+
20
+ ### Security
21
+
22
+ - Git argument injection hardening on all user-controlled inputs (commit ranges, symbol names, file paths, pathspecs).
23
+ - Safe embedding-cache storage (JSON sidecar + safe-mode numpy load) with atomic writes and content-addressed cache keys.
24
+ - Default embedding model revision pinned for supply-chain integrity; `trust_remote_code` defaults to `False` for user-supplied models.
25
+ - TOML-sourced subprocess args (scip toolchain, extra args, embedding model path) are validated.
26
+ - Subprocess env sanitized against known credential patterns.
27
+ - Web UI path traversal containment on `/api/source` + regular-file / size / text-file checks.
28
+ - MCP `descry_index(path=...)` restricted to project root.
29
+ - Graph JSON carries `schema_version`; mismatched graphs are rejected with an actionable error.
30
+
@@ -0,0 +1,69 @@
1
+ # CLAUDE.md
2
+
3
+ This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4
+
5
+ ## Commands
6
+
7
+ Development uses `uv` + `just`. The `justfile` wraps all common tasks — prefer it over raw commands:
8
+
9
+ ```bash
10
+ just install # uv venv .venv + uv pip install -e ".[dev]"
11
+ just test # pytest tests/
12
+ just test-v # pytest tests/ -v
13
+ just lint # ruff check src/ tests/
14
+ just fmt # ruff format src/ tests/
15
+ just clean # remove .descry_cache, dist, caches, __pycache__
16
+ ```
17
+
18
+ Run a single test or filter:
19
+
20
+ ```bash
21
+ just test tests/test_handlers.py # one file
22
+ just test tests/test_handlers.py::test_index # one test
23
+ just test -k "cross_lang and not slow" # by expression
24
+ ```
25
+
26
+ Python 3.11+ required (enforced by `pyproject.toml`). Pre-commit gate: `just lint && just fmt && just test` all pass (195 tests currently — a drop is a regression signal).
27
+
28
+ Invoke the tool while developing:
29
+
30
+ ```bash
31
+ just index # descry index on current project
32
+ just health # descry health
33
+ just web # descry-web, http://127.0.0.1:8787
34
+ just mcp # descry-mcp (stdio transport)
35
+ just search <query>
36
+ ```
37
+
38
+ Packaging: `pyproject.toml` defines four console scripts — `descry`, `descry-mcp`, `descry-generate`, `descry-web` — each a thin entry point into `src/descry/`.
39
+
40
+ ## Architecture
41
+
42
+ **One service, three interfaces.** `DescryService` in `src/descry/handlers.py` owns all business logic. `cli.py`, `mcp_server.py`, and `web/server.py` are thin wrappers that parse their protocol's input, call a service method, and format the output. When adding a feature, implement it as a service method first — the interfaces then expose it with minimal glue.
43
+
44
+ **Two graph phases:**
45
+
46
+ 1. **Generate** (`generate.py`, invoked via `descry index` or `descry-generate`) — walks the project, parses source files with regex + AST + optional ast-grep, optionally runs SCIP indexers (rust-analyzer, scip-typescript) for type-aware call resolution, optionally builds embeddings. Writes `.descry_cache/codebase_graph.json`.
47
+ 2. **Query** (`query.py` → `GraphQuerier`) — loads the cached graph and answers callers/callees/flow/path/context queries. All service methods lazy-load the querier; freshness is tracked by graph file mtime.
48
+
49
+ **Graph schema is versioned.** `src/descry/_graph.py` exports `CURRENT_SCHEMA` (currently `1`) and `load_graph_with_schema()`. Every graph-consuming code path must load via this helper — it raises `GraphSchemaError` on mismatch so stale graphs are rejected rather than silently producing wrong results. If you change the graph shape in a breaking way, bump `CURRENT_SCHEMA`.
50
+
51
+ **Config is layered.** `DescryConfig.from_env()` builds config in three ordered steps: `auto_detect()` walks up from cwd looking for project markers (`.git`, `Cargo.toml`, `package.json`, `pyproject.toml`, `.descry.toml`) → `_apply_toml()` merges `.descry.toml` → env vars (`DESCRY_CACHE_DIR`, `DESCRY_NO_SCIP`, `DESCRY_NO_EMBEDDINGS`, `DESCRY_LOG_LEVEL`) override. All four entry points (CLI, MCP, web, `descry-generate`) construct config this way — don't add an alternate path.
52
+
53
+ **Optional modules degrade gracefully.** `scip/`, `embeddings.py`, `cross_lang.py`, and `git_history.py` are all imported via `_try_import_*` helpers in `handlers.py`. Missing optional deps (e.g. `sentence-transformers`, `scip-typescript` binary) must never crash the service — the feature becomes unavailable instead. Preserve this when touching imports.
54
+
55
+ **MCP transport uses stdout.** `mcp_server.py` logs to `stderr` only; never `print()` or log to stdout from anything reachable by the MCP server, or you will corrupt the protocol stream. The `_fenced()` helper wraps verbatim repo content in a nonce-tagged XML envelope to defend against prompt-injection via indexed docstrings — preserve this behavior when returning source content from new tools.
56
+
57
+ ## Security invariants
58
+
59
+ These are load-bearing — see `SECURITY.md` and the `## Security` section of `CHANGELOG.md`. Do not weaken without a review:
60
+
61
+ - **Subprocess env sanitization.** Every subprocess call (git, rust-analyzer, scip-typescript, ast-grep, `descry-generate`) must pass `env=safe_env()` from `src/descry/_env.py`, which filters credential-shaped env vars by regex.
62
+ - **TOML-sourced subprocess args are validated.** `_validate_toolchain`, `_validate_scip_extra_arg`, `_validate_embedding_model` in `handlers.py` reject short flags, shell metacharacters, and out-of-root local paths. New TOML → subprocess plumbing must validate.
63
+ - **Web UI is local-only by design.** `descry-web` binds `127.0.0.1`, has no auth, and allows CORS `*`. `/api/source` enforces project-root containment, rejects non-regular files / non-text content / >10 MiB, and uses `O_NOFOLLOW` on the final open. `/api/index` (and `/api/index/stream`) take no path parameter (always indexes the configured root). Don't add a `--host 0.0.0.0` flag or expose network listeners.
64
+ - **MCP `index(path=...)`** is restricted to the configured project root.
65
+ - **Embedding cache** uses numpy safe-mode load + JSON sidecar + atomic writes + content-addressed keys. Default embedding model revision is pinned for supply-chain integrity; `trust_remote_code` defaults to `False` for user-supplied models.
66
+
67
+ ## Versioning
68
+
69
+ Pre-1.0. The only stable public API is `descry.__version__`. Submodules (`descry.handlers`, `descry.query`, etc.) are **not** stable — refactor freely. Minor bumps (`0.1.x` → `0.2.x`) may break the library API, graph schema, CLI, or MCP tool signatures; patch bumps won't.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Christian Haynes
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.