descry-codegraph 1.1.0__tar.gz → 1.2.1__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.
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/.github/workflows/release.yml +3 -3
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/AGENTS.md +3 -3
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/CHANGELOG.md +53 -0
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/PKG-INFO +10 -8
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/README.md +9 -7
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/claude-skill/README.md +1 -1
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/claude-skill/descry/SKILL.md +7 -7
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/claude-skill/descry/references/cli-reference.md +2 -1
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/claude-skill/descry/references/mcp-tools.md +1 -1
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/claude-skill/descry/references/recipes.md +3 -3
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/docs/CERTAINTY.md +5 -4
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/docs/TOOLS.md +1 -1
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/pi-extension/descry-tools.ts +1 -1
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/pyproject.toml +4 -1
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/src/descry/__init__.py +1 -1
- descry_codegraph-1.2.1/src/descry/_files.py +158 -0
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/src/descry/cli.py +2 -0
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/src/descry/embeddings.py +2 -79
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/src/descry/generate.py +60 -33
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/src/descry/handlers.py +43 -15
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/src/descry/mcp_server.py +1 -1
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/src/descry/resolver.py +12 -2
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/tests/test_cli.py +14 -1
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/tests/test_embeddings.py +82 -10
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/tests/test_generate.py +56 -1
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/tests/test_handlers.py +49 -2
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/tests/test_parse_cache.py +29 -0
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/tests/test_web_server.py +4 -5
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/tests/test_written_through.py +39 -5
- descry_codegraph-1.1.0/src/descry/_files.py +0 -68
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/.github/assets/01-landing.png +0 -0
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/.github/workflows/ci.yml +0 -0
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/.gitignore +0 -0
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/CONTRACT.md +0 -0
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/LICENSE +0 -0
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/SECURITY.md +0 -0
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/THIRD-PARTY-NOTICES.md +0 -0
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/docs/UPGRADING.md +0 -0
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/justfile +0 -0
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/pi-extension/README.md +0 -0
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/pi-extension/descry-cli.py +0 -0
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/src/descry/_answers.py +0 -0
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/src/descry/_comments.py +0 -0
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/src/descry/_env.py +0 -0
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/src/descry/_graph.py +0 -0
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/src/descry/_parse_cache.py +0 -0
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/src/descry/_parsing.py +0 -0
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/src/descry/_reads.py +0 -0
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/src/descry/_stdlib.py +0 -0
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/src/descry/_streams.py +0 -0
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/src/descry/clang_parser.py +0 -0
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/src/descry/cross_lang.py +0 -0
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/src/descry/dart_parser.py +0 -0
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/src/descry/dotnet_parser.py +0 -0
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/src/descry/git_history.py +0 -0
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/src/descry/go_parser.py +0 -0
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/src/descry/java_parser.py +0 -0
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/src/descry/php_parser.py +0 -0
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/src/descry/proto_parser.py +0 -0
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/src/descry/python_parser.py +0 -0
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/src/descry/query.py +0 -0
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/src/descry/ruby_parser.py +0 -0
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/src/descry/rust_parser.py +0 -0
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/src/descry/scip/__init__.py +0 -0
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/src/descry/scip/adapter.py +0 -0
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/src/descry/scip/adapters/__init__.py +0 -0
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/src/descry/scip/adapters/_empty_environment.json +0 -0
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/src/descry/scip/adapters/_java_compat.init.gradle +0 -0
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/src/descry/scip/adapters/clang.py +0 -0
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/src/descry/scip/adapters/dart.py +0 -0
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/src/descry/scip/adapters/dotnet.py +0 -0
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/src/descry/scip/adapters/go.py +0 -0
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/src/descry/scip/adapters/java.py +0 -0
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/src/descry/scip/adapters/php.py +0 -0
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/src/descry/scip/adapters/python.py +0 -0
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/src/descry/scip/adapters/ruby.py +0 -0
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/src/descry/scip/adapters/rust.py +0 -0
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/src/descry/scip/adapters/typescript.py +0 -0
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/src/descry/scip/cache.py +0 -0
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/src/descry/scip/parser.py +0 -0
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/src/descry/scip/pb2.py +0 -0
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/src/descry/scip/support.py +0 -0
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/src/descry/stdlib/c.json +0 -0
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/src/descry/stdlib/cpp.json +0 -0
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/src/descry/stdlib/dart.json +0 -0
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/src/descry/stdlib/dotnet.json +0 -0
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/src/descry/stdlib/go.json +0 -0
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/src/descry/stdlib/java.json +0 -0
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/src/descry/stdlib/node.json +0 -0
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/src/descry/stdlib/php.json +0 -0
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/src/descry/stdlib/python.json +0 -0
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/src/descry/stdlib/ruby.json +0 -0
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/src/descry/stdlib/rust.json +0 -0
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/src/descry/stdlib/typescript.json +0 -0
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/src/descry/ts_parser.py +0 -0
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/src/descry/web/__init__.py +0 -0
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/src/descry/web/server.py +0 -0
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/src/descry/web/web/index.html +0 -0
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/src/descry/web/web/style.css +0 -0
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/tests/__init__.py +0 -0
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/tests/conftest.py +0 -0
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/tests/eval/README.md +0 -0
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/tests/eval/__init__.py +0 -0
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/tests/eval/compare.py +0 -0
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/tests/eval/corpus.py +0 -0
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/tests/eval/metrics.py +0 -0
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/tests/eval/registry.py +0 -0
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/tests/eval/results/results-descry.json +0 -0
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/tests/eval/results/results-ripgrep.json +0 -0
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/tests/eval/results/search-after.json +0 -0
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/tests/eval/results/search-before.json +0 -0
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/tests/eval/search_eval.py +0 -0
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/tests/eval/search_questions.json +0 -0
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/tests/fixtures/small_graph.json +0 -0
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/tests/ground_truth/c/Makefile +0 -0
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/tests/ground_truth/c/expected.json +0 -0
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/tests/ground_truth/c/io.c +0 -0
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/tests/ground_truth/c/io.h +0 -0
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/tests/ground_truth/c/main.c +0 -0
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/tests/ground_truth/c/order.c +0 -0
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/tests/ground_truth/cpp/Makefile +0 -0
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/tests/ground_truth/cpp/expected.json +0 -0
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/tests/ground_truth/cpp/main.cpp +0 -0
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/tests/ground_truth/cpp/models.hpp +0 -0
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/tests/ground_truth/cpp/reader.hpp +0 -0
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/tests/ground_truth/csharp/Gt.csproj +0 -0
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/tests/ground_truth/csharp/Io.cs +0 -0
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/tests/ground_truth/csharp/Models.cs +0 -0
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/tests/ground_truth/csharp/Program.cs +0 -0
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/tests/ground_truth/csharp/expected.json +0 -0
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/tests/ground_truth/dart/bin/main.dart +0 -0
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/tests/ground_truth/dart/expected.json +0 -0
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/tests/ground_truth/dart/lib/io.dart +0 -0
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/tests/ground_truth/dart/lib/models.dart +0 -0
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/tests/ground_truth/dart/pubspec.yaml +0 -0
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/tests/ground_truth/go/expected.json +0 -0
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/tests/ground_truth/go/go.mod +0 -0
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/tests/ground_truth/go/io.go +0 -0
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/tests/ground_truth/go/main.go +0 -0
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/tests/ground_truth/go/models.go +0 -0
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/tests/ground_truth/java/expected.json +0 -0
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/tests/ground_truth/java/src/gt/Base.java +0 -0
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/tests/ground_truth/java/src/gt/Io.java +0 -0
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/tests/ground_truth/java/src/gt/Main.java +0 -0
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/tests/ground_truth/java/src/gt/Order.java +0 -0
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/tests/ground_truth/java/src/gt/Reader.java +0 -0
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/tests/ground_truth/java/src/gt/User.java +0 -0
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/tests/ground_truth/javascript/expected.json +0 -0
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/tests/ground_truth/javascript/package.json +0 -0
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/tests/ground_truth/javascript/src/io.js +0 -0
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/tests/ground_truth/javascript/src/main.js +0 -0
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/tests/ground_truth/javascript/src/models.js +0 -0
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/tests/ground_truth/php/composer.json +0 -0
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/tests/ground_truth/php/expected.json +0 -0
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/tests/ground_truth/php/src/Io.php +0 -0
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/tests/ground_truth/php/src/Models.php +0 -0
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/tests/ground_truth/php/src/main.php +0 -0
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/tests/ground_truth/python/app/__init__.py +0 -0
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/tests/ground_truth/python/app/io.py +0 -0
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/tests/ground_truth/python/app/main.py +0 -0
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/tests/ground_truth/python/app/models.py +0 -0
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/tests/ground_truth/python/expected.json +0 -0
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/tests/ground_truth/python/pyproject.toml +0 -0
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/tests/ground_truth/ruby/Gemfile +0 -0
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/tests/ground_truth/ruby/expected.json +0 -0
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/tests/ground_truth/ruby/lib/io.rb +0 -0
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/tests/ground_truth/ruby/lib/main.rb +0 -0
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/tests/ground_truth/ruby/lib/models.rb +0 -0
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/tests/ground_truth/rust/Cargo.toml +0 -0
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/tests/ground_truth/rust/expected.json +0 -0
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/tests/ground_truth/rust/src/io.rs +0 -0
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/tests/ground_truth/rust/src/main.rs +0 -0
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/tests/ground_truth/rust/src/models.rs +0 -0
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/tests/ground_truth/typescript/expected.json +0 -0
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/tests/ground_truth/typescript/package.json +0 -0
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/tests/ground_truth/typescript/src/io.ts +0 -0
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/tests/ground_truth/typescript/src/main.ts +0 -0
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/tests/ground_truth/typescript/src/models.ts +0 -0
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/tests/test_backtracking.py +0 -0
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/tests/test_clang_parser.py +0 -0
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/tests/test_config.py +0 -0
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/tests/test_cross_lang.py +0 -0
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/tests/test_dart_parser.py +0 -0
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/tests/test_docs.py +0 -0
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/tests/test_dotnet_parser.py +0 -0
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/tests/test_git_history.py +0 -0
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/tests/test_go_parser.py +0 -0
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/tests/test_ground_truth.py +0 -0
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/tests/test_hardening.py +0 -0
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/tests/test_interfaces.py +0 -0
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/tests/test_java_parser.py +0 -0
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/tests/test_mcp.py +0 -0
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/tests/test_non_regular_files.py +0 -0
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/tests/test_php_parser.py +0 -0
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/tests/test_query.py +0 -0
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/tests/test_resolve_cache.py +0 -0
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/tests/test_ruby_parser.py +0 -0
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/tests/test_scip.py +0 -0
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/tests/test_scip_cache.py +0 -0
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/tests/test_scip_clang.py +0 -0
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/tests/test_scip_dart.py +0 -0
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/tests/test_scip_dotnet.py +0 -0
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/tests/test_scip_go.py +0 -0
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/tests/test_scip_java.py +0 -0
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/tests/test_scip_php.py +0 -0
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/tests/test_scip_ruby.py +0 -0
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/tests/test_stdlib.py +0 -0
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/tools/docs/gen_tools.py +0 -0
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/tools/stdlib/README.md +0 -0
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/tools/stdlib/common.py +0 -0
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/tools/stdlib/gen_c.py +0 -0
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/tools/stdlib/gen_cpp.py +0 -0
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/tools/stdlib/gen_dart.py +0 -0
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/tools/stdlib/gen_dotnet.py +0 -0
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/tools/stdlib/gen_go.py +0 -0
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/tools/stdlib/gen_java.py +0 -0
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/tools/stdlib/gen_node.py +0 -0
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/tools/stdlib/gen_php.py +0 -0
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/tools/stdlib/gen_python.py +0 -0
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/tools/stdlib/gen_ruby.py +0 -0
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/tools/stdlib/gen_rust.py +0 -0
- {descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/tools/stdlib/gen_typescript.py +0 -0
|
@@ -32,16 +32,16 @@ jobs:
|
|
|
32
32
|
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
|
33
33
|
with:
|
|
34
34
|
python-version: "3.12"
|
|
35
|
-
- name: Verify tag matches
|
|
35
|
+
- name: Verify tag matches package version
|
|
36
36
|
# A manual run that targets PyPI must be dispatched from the release tag.
|
|
37
37
|
if: github.event_name == 'push' || inputs.target == 'pypi'
|
|
38
38
|
env:
|
|
39
39
|
GIT_TAG: ${{ github.ref_name }}
|
|
40
40
|
run: |
|
|
41
|
-
PKG_VERSION=$(python -c "import
|
|
41
|
+
PKG_VERSION=$(PYTHONPATH=src python -c "import descry; print(descry.__version__)")
|
|
42
42
|
TAG_VERSION="${GIT_TAG#v}"
|
|
43
43
|
if [ "$PKG_VERSION" != "$TAG_VERSION" ]; then
|
|
44
|
-
echo "::error::tag $GIT_TAG does not match
|
|
44
|
+
echo "::error::tag $GIT_TAG does not match package version $PKG_VERSION"
|
|
45
45
|
exit 1
|
|
46
46
|
fi
|
|
47
47
|
- name: Build sdist + wheel
|
|
@@ -23,7 +23,7 @@ just test tests/test_config.py::TestFromToml::test_from_toml_basic # one test
|
|
|
23
23
|
just test -k "cross_lang and not slow" # by expression
|
|
24
24
|
```
|
|
25
25
|
|
|
26
|
-
Python 3.11+ required (enforced by `pyproject.toml`). Pre-commit gate: `just lint && just fmt && just test` all pass (
|
|
26
|
+
Python 3.11+ required (enforced by `pyproject.toml`). Pre-commit gate: `just lint && just fmt && just test` all pass (3382 tests pass; a handful more are skipped where the environment lacks their fixture — a drop is a regression signal).
|
|
27
27
|
|
|
28
28
|
Invoke the tool while developing:
|
|
29
29
|
|
|
@@ -43,7 +43,7 @@ Packaging: `pyproject.toml` defines four console scripts — `descry`, `descry-m
|
|
|
43
43
|
|
|
44
44
|
**Two graph phases:**
|
|
45
45
|
|
|
46
|
-
1. **Generate** (`generate.py`, invoked via `descry index` or `descry-generate`) — walks the project, parses source files with regex + AST (`_parsing.py` holds what every parser shares; each language has its `<lang>_parser.py`), resolves calls (`CallResolver` in `resolver.py`) from imports, declared and returned types, scope and the class hierarchy, optionally runs SCIP indexers (rust-analyzer, scip-typescript) for type-aware call resolution, optionally builds embeddings. Writes `.descry_cache/codebase_graph.json
|
|
46
|
+
1. **Generate** (`generate.py`, invoked via `descry index` or `descry-generate`) — walks the project, parses source files with regex + AST (`_parsing.py` holds what every parser shares; each language has its `<lang>_parser.py`), resolves calls (`CallResolver` in `resolver.py`) from imports, declared and returned types, scope and the class hierarchy, optionally runs SCIP indexers (rust-analyzer, scip-typescript) for type-aware call resolution, optionally builds embeddings. Writes `.descry_cache/codebase_graph.json` last, once its embeddings are written (the graph is built under a temporary name and dated when the walk began), and holds `index.lock` for the whole run. A call is resolved, left unresolved with the definitions that fit recorded as `candidates`, or dropped as external. Never resolve by name alone when more than one definition fits, never by file path, and never guess the type of a receiver: an unknown receiver stays unresolved.
|
|
47
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
48
|
|
|
49
49
|
**Graph schema is versioned.** `src/descry/_graph.py` exports `CURRENT_SCHEMA` (currently `2`) 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`.
|
|
@@ -70,7 +70,7 @@ These are load-bearing — see the `### Security` sections of `CHANGELOG.md`. Do
|
|
|
70
70
|
- **The default cache directory stays inside the project.** A repository can ship `.descry_cache`, or its `scip` or `parse` subdirectory, as a symlink to another directory; `DescryConfig.cache_dir_error()` refuses indexing and embedding there, and only `DESCRY_CACHE_DIR` names a cache outside the project.
|
|
71
71
|
- **A parse is reused only from an entry descry wrote.** Each file's parse is kept in `<cache>/parse` as JSON, never pickle, with an HMAC of the entry's name and content under a key in the user's state directory (`$XDG_STATE_HOME/descry/parse.key` when that is absolute, else `~/.local/state`; `%LOCALAPPDATA%` on Windows); where the key would be inside the project, no entry is read or kept. An entry a repository ships, or one moved to another file's name, fails the check and the file is parsed again; restoring an entry cannot stop an index.
|
|
72
72
|
- **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.
|
|
73
|
-
- **Web UI is local-only by design.** `descry-web` binds `127.0.0.1`, has no auth, and deliberately omits `CORSMiddleware` so browsers enforce same-origin — a tab on `evil.com` cannot read `/api/source`. State-changing requests are refused unless same-origin (`Origin` / `Sec-Fetch-Site`), so it cannot trigger `/api/index` either. The host middleware rejects non-loopback `Host` headers to defeat DNS-rebinding that would otherwise bypass the 127.0.0.1 bind. `/api/source` enforces project-root containment, rejects non-regular files, files over `[code_files] max_file_mb` (10 by default; 0, which a repository's `.descry.toml` can set, lifts the cap) and files with a NUL byte in their first 8 KiB, and opens every path component with `O_NOFOLLOW` (on Windows, which has no `openat`, it checks after opening that the file opened is the one the path resolves to, inside the root). `/api/index` (and `/api/index/stream`) take no path parameter (always indexes the configured root). `DescryService` runs one index at a time for every interface and stops the indexer's whole process tree when a run ends early. The page's remote scripts carry SRI hashes and every response carries the Content-Security-Policy that `web/server.py` builds from the page's own tags, allowing exactly those asset URLs; a new remote script needs an SRI hash. Don't add a `--host 0.0.0.0` flag, don't add `CORSMiddleware`, and don't expose network listeners.
|
|
73
|
+
- **Web UI is local-only by design.** `descry-web` binds `127.0.0.1`, has no auth, and deliberately omits `CORSMiddleware` so browsers enforce same-origin — a tab on `evil.com` cannot read `/api/source`. State-changing requests are refused unless same-origin (`Origin` / `Sec-Fetch-Site`), so it cannot trigger `/api/index` either. The host middleware rejects non-loopback `Host` headers to defeat DNS-rebinding that would otherwise bypass the 127.0.0.1 bind. `/api/source` enforces project-root containment, rejects non-regular files, files over `[code_files] max_file_mb` (10 by default; 0, which a repository's `.descry.toml` can set, lifts the cap) and files with a NUL byte in their first 8 KiB, and opens every path component with `O_NOFOLLOW` (on Windows, which has no `openat`, it checks after opening that the file opened is the one the path resolves to, inside the root). `/api/index` (and `/api/index/stream`) take no path parameter (always indexes the configured root). `DescryService` runs one index at a time for every interface, refuses one while another process holds the project's `index.lock`, and stops the indexer's whole process tree when a run ends early. The page's remote scripts carry SRI hashes and every response carries the Content-Security-Policy that `web/server.py` builds from the page's own tags, allowing exactly those asset URLs; a new remote script needs an SRI hash. Don't add a `--host 0.0.0.0` flag, don't add `CORSMiddleware`, and don't expose network listeners.
|
|
74
74
|
- **MCP `index(path=...)`** is restricted to the configured project root; a path below it narrows the walk but still writes the root's graph.
|
|
75
75
|
- **Embedding cache** uses numpy safe-mode load + JSON sidecar + atomic writes, keyed by the graph's bytes and time and the recipe. A rebuild reuses a node's vector, by the digest of its text, only from a cache descry signed with the key in the user's state directory (`signing_key()` in `_parse_cache.py`), so a shipped cache is never reused. `MODEL_REGISTRY` in `embeddings.py` is the single source of truth for which models descry ships pinned configurations for — each entry carries its revision and its task prompts. **`trust_remote_code` is always `False`**: there is no registry field, loader parameter or config key that enables it, so descry never executes code from a model repository — do not add one. A model named in `.descry.toml` that is not registered is loaded unpinned. Adding a model means adding a registry entry, not editing the loader.
|
|
76
76
|
- **The cache key is the embedding recipe's identity.** `_recipe_fingerprint()` folds in the resolved repo id, the pinned revision, `RECIPE_VERSION`, `DOCSTRING_CHAR_LIMIT`, the prompt names, the entry's loading options, and whether source code is embedded (`CODE_CHAR_LIMIT`). Anything that changes the vectors without changing the graph must be added there and `RECIPE_VERSION` bumped, or a stale cache is served against freshly-encoded queries — comparing two different embedding spaces.
|
|
@@ -4,6 +4,59 @@ All notable changes to Descry will be documented in this file.
|
|
|
4
4
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [semver](https://semver.org/) from 1.0.0.
|
|
6
6
|
|
|
7
|
+
## [1.2.1] — 2026-11-13
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
|
|
11
|
+
- Python: an index no longer ends with `RecursionError` on a class statement
|
|
12
|
+
in a method whose base is written through `self` or `cls`
|
|
13
|
+
(`class Extra(self.form_class)`). The base is read in the scope around the
|
|
14
|
+
class statement, as Python evaluates it.
|
|
15
|
+
- Python: an index no longer ends with `TypeError: unhashable type: 'list'`
|
|
16
|
+
where a method assigns an attribute that a base declares as a property
|
|
17
|
+
with a setter and then reads it.
|
|
18
|
+
|
|
19
|
+
## [1.2.0] — 2026-11-04
|
|
20
|
+
|
|
21
|
+
### Added
|
|
22
|
+
|
|
23
|
+
- `descry --version`; the skill's CLI reference lists it.
|
|
24
|
+
|
|
25
|
+
### Changed
|
|
26
|
+
|
|
27
|
+
- An index publishes the graph only once its embeddings are written, so
|
|
28
|
+
searches during a re-index stay hybrid on the previous graph. Before, the
|
|
29
|
+
new graph appeared first: searches fell back to keyword-only until the
|
|
30
|
+
embeddings were done, one stalled for `[timeouts] embedding_seconds`
|
|
31
|
+
(60 s by default), and after ten minutes `semantic` reported the model
|
|
32
|
+
unavailable.
|
|
33
|
+
- During a first index, a tool that needs the graph and `status` answer
|
|
34
|
+
"No graph yet: an index is building it", and the text header reads
|
|
35
|
+
`[Graph: BUILDING]`, instead of saying no graph exists.
|
|
36
|
+
- An index of a project another process is indexing is refused as `busy`
|
|
37
|
+
(`index`, `ensure`, the web's index stream, and `descry-generate`).
|
|
38
|
+
- `ensure` re-indexes when files the graph tracks changed or were removed
|
|
39
|
+
since it was written, as well as when it is older than `max_stale_hours`.
|
|
40
|
+
A file dated in the future is not counted as changed, there or in
|
|
41
|
+
`status`.
|
|
42
|
+
- The graph is dated when its index began, so a file edited while an index
|
|
43
|
+
runs counts as changed since it; before, an edit made before the graph
|
|
44
|
+
was written was missed.
|
|
45
|
+
|
|
46
|
+
### Security
|
|
47
|
+
|
|
48
|
+
- Windows: a lock file in the cache directory (`embeddings.lock`,
|
|
49
|
+
`index.lock`) that the repository ships as a symlink is refused instead
|
|
50
|
+
of followed, so an index neither locks nor creates the file it names, as
|
|
51
|
+
on other platforms.
|
|
52
|
+
|
|
53
|
+
### Documentation
|
|
54
|
+
|
|
55
|
+
- README: the MCP server is `descry-mcp.exe` on Windows, and
|
|
56
|
+
`python -m descry.mcp_server` starts it on every platform. Install
|
|
57
|
+
lines quote the extra (`"descry-codegraph[all]"`) so zsh does not
|
|
58
|
+
read the brackets as a glob.
|
|
59
|
+
|
|
7
60
|
## [1.1.0] — 2026-11-03
|
|
8
61
|
|
|
9
62
|
### Added
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: descry-codegraph
|
|
3
|
-
Version: 1.1
|
|
3
|
+
Version: 1.2.1
|
|
4
4
|
Summary: Codebase knowledge graph: polyglot indexer, semantic search, call-graph analysis
|
|
5
5
|
Project-URL: Homepage, https://github.com/06chaynes/descry
|
|
6
6
|
Project-URL: Repository, https://github.com/06chaynes/descry
|
|
@@ -165,7 +165,7 @@ yourself, which is also loaded unpinned.
|
|
|
165
165
|
|
|
166
166
|
```bash
|
|
167
167
|
# Install with all optional features
|
|
168
|
-
pip install descry-codegraph[all]
|
|
168
|
+
pip install "descry-codegraph[all]"
|
|
169
169
|
|
|
170
170
|
# Index your project
|
|
171
171
|
cd your-project
|
|
@@ -225,6 +225,8 @@ Or with a specific Python path:
|
|
|
225
225
|
|
|
226
226
|
Descry uses the standard MCP stdio transport. Any MCP-compatible host can spawn `descry-mcp` as a subprocess.
|
|
227
227
|
|
|
228
|
+
On Windows the executable is `descry-mcp.exe`. `python -m descry.mcp_server`, with the Python descry is installed in, starts the same server on every platform.
|
|
229
|
+
|
|
228
230
|
## Tools
|
|
229
231
|
|
|
230
232
|
Descry provides 19 tools, available through all interfaces:
|
|
@@ -233,7 +235,7 @@ Descry provides 19 tools, available through all interfaces:
|
|
|
233
235
|
|------|-------------|
|
|
234
236
|
| `health` | Diagnostic check — version, graph status, feature availability |
|
|
235
237
|
| `status` | Graph existence and freshness |
|
|
236
|
-
| `ensure` | Ensure graph exists and is fresh (regenerates if stale) |
|
|
238
|
+
| `ensure` | Ensure graph exists and is fresh (regenerates if stale or files changed) |
|
|
237
239
|
| `index` | Regenerate graph, SCIP indices, and embeddings |
|
|
238
240
|
| `search` | Search symbol names and docstrings (keyword + semantic) |
|
|
239
241
|
| `semantic` | Pure semantic search using embeddings only |
|
|
@@ -410,19 +412,19 @@ pip install descry-codegraph
|
|
|
410
412
|
### With MCP server
|
|
411
413
|
|
|
412
414
|
```bash
|
|
413
|
-
pip install descry-codegraph[mcp]
|
|
415
|
+
pip install "descry-codegraph[mcp]"
|
|
414
416
|
```
|
|
415
417
|
|
|
416
418
|
### With Web UI
|
|
417
419
|
|
|
418
420
|
```bash
|
|
419
|
-
pip install descry-codegraph[web]
|
|
421
|
+
pip install "descry-codegraph[web]"
|
|
420
422
|
```
|
|
421
423
|
|
|
422
424
|
### With semantic search
|
|
423
425
|
|
|
424
426
|
```bash
|
|
425
|
-
pip install descry-codegraph[embeddings]
|
|
427
|
+
pip install "descry-codegraph[embeddings]"
|
|
426
428
|
```
|
|
427
429
|
|
|
428
430
|
To select `embeddinggemma-2`, install its extra instead (adds Pillow and
|
|
@@ -435,7 +437,7 @@ pip install "descry-codegraph[embeddinggemma-2]"
|
|
|
435
437
|
### Everything
|
|
436
438
|
|
|
437
439
|
```bash
|
|
438
|
-
pip install descry-codegraph[all]
|
|
440
|
+
pip install "descry-codegraph[all]"
|
|
439
441
|
```
|
|
440
442
|
|
|
441
443
|
### Choosing a torch build
|
|
@@ -489,7 +491,7 @@ Requires [uv](https://github.com/astral-sh/uv) and [just](https://github.com/cas
|
|
|
489
491
|
|
|
490
492
|
3. **Query** — All tools query the cached graph. Keyword search uses TF-IDF scoring. Semantic search uses sentence-transformer embeddings. Call-graph traversal follows edges in the graph.
|
|
491
493
|
|
|
492
|
-
4. **Freshness** — `ensure` regenerates the graph when its age exceeds `[project] max_stale_hours` (default 24), or the `--max-age-hours` flag (CLI) / `max_age_hours` argument (MCP) when one is given. The same setting drives the "STALE" badge in `descry status`. The MCP server pre-warms the graph on startup.
|
|
494
|
+
4. **Freshness** — `ensure` regenerates the graph when files it indexed changed or were removed since it was written, and when its age exceeds `[project] max_stale_hours` (default 24), or the `--max-age-hours` flag (CLI) / `max_age_hours` argument (MCP) when one is given. The same setting drives the "STALE" badge in `descry status`. The MCP server pre-warms the graph on startup.
|
|
493
495
|
|
|
494
496
|
## Design Notes
|
|
495
497
|
|
|
@@ -104,7 +104,7 @@ yourself, which is also loaded unpinned.
|
|
|
104
104
|
|
|
105
105
|
```bash
|
|
106
106
|
# Install with all optional features
|
|
107
|
-
pip install descry-codegraph[all]
|
|
107
|
+
pip install "descry-codegraph[all]"
|
|
108
108
|
|
|
109
109
|
# Index your project
|
|
110
110
|
cd your-project
|
|
@@ -164,6 +164,8 @@ Or with a specific Python path:
|
|
|
164
164
|
|
|
165
165
|
Descry uses the standard MCP stdio transport. Any MCP-compatible host can spawn `descry-mcp` as a subprocess.
|
|
166
166
|
|
|
167
|
+
On Windows the executable is `descry-mcp.exe`. `python -m descry.mcp_server`, with the Python descry is installed in, starts the same server on every platform.
|
|
168
|
+
|
|
167
169
|
## Tools
|
|
168
170
|
|
|
169
171
|
Descry provides 19 tools, available through all interfaces:
|
|
@@ -172,7 +174,7 @@ Descry provides 19 tools, available through all interfaces:
|
|
|
172
174
|
|------|-------------|
|
|
173
175
|
| `health` | Diagnostic check — version, graph status, feature availability |
|
|
174
176
|
| `status` | Graph existence and freshness |
|
|
175
|
-
| `ensure` | Ensure graph exists and is fresh (regenerates if stale) |
|
|
177
|
+
| `ensure` | Ensure graph exists and is fresh (regenerates if stale or files changed) |
|
|
176
178
|
| `index` | Regenerate graph, SCIP indices, and embeddings |
|
|
177
179
|
| `search` | Search symbol names and docstrings (keyword + semantic) |
|
|
178
180
|
| `semantic` | Pure semantic search using embeddings only |
|
|
@@ -349,19 +351,19 @@ pip install descry-codegraph
|
|
|
349
351
|
### With MCP server
|
|
350
352
|
|
|
351
353
|
```bash
|
|
352
|
-
pip install descry-codegraph[mcp]
|
|
354
|
+
pip install "descry-codegraph[mcp]"
|
|
353
355
|
```
|
|
354
356
|
|
|
355
357
|
### With Web UI
|
|
356
358
|
|
|
357
359
|
```bash
|
|
358
|
-
pip install descry-codegraph[web]
|
|
360
|
+
pip install "descry-codegraph[web]"
|
|
359
361
|
```
|
|
360
362
|
|
|
361
363
|
### With semantic search
|
|
362
364
|
|
|
363
365
|
```bash
|
|
364
|
-
pip install descry-codegraph[embeddings]
|
|
366
|
+
pip install "descry-codegraph[embeddings]"
|
|
365
367
|
```
|
|
366
368
|
|
|
367
369
|
To select `embeddinggemma-2`, install its extra instead (adds Pillow and
|
|
@@ -374,7 +376,7 @@ pip install "descry-codegraph[embeddinggemma-2]"
|
|
|
374
376
|
### Everything
|
|
375
377
|
|
|
376
378
|
```bash
|
|
377
|
-
pip install descry-codegraph[all]
|
|
379
|
+
pip install "descry-codegraph[all]"
|
|
378
380
|
```
|
|
379
381
|
|
|
380
382
|
### Choosing a torch build
|
|
@@ -428,7 +430,7 @@ Requires [uv](https://github.com/astral-sh/uv) and [just](https://github.com/cas
|
|
|
428
430
|
|
|
429
431
|
3. **Query** — All tools query the cached graph. Keyword search uses TF-IDF scoring. Semantic search uses sentence-transformer embeddings. Call-graph traversal follows edges in the graph.
|
|
430
432
|
|
|
431
|
-
4. **Freshness** — `ensure` regenerates the graph when its age exceeds `[project] max_stale_hours` (default 24), or the `--max-age-hours` flag (CLI) / `max_age_hours` argument (MCP) when one is given. The same setting drives the "STALE" badge in `descry status`. The MCP server pre-warms the graph on startup.
|
|
433
|
+
4. **Freshness** — `ensure` regenerates the graph when files it indexed changed or were removed since it was written, and when its age exceeds `[project] max_stale_hours` (default 24), or the `--max-age-hours` flag (CLI) / `max_age_hours` argument (MCP) when one is given. The same setting drives the "STALE" badge in `descry status`. The MCP server pre-warms the graph on startup.
|
|
432
434
|
|
|
433
435
|
## Design Notes
|
|
434
436
|
|
|
@@ -29,7 +29,7 @@ without forcing it on simple file-read tasks.
|
|
|
29
29
|
## Prerequisites
|
|
30
30
|
|
|
31
31
|
- Python 3.11+.
|
|
32
|
-
- `descry-codegraph` installed: `pip install descry-codegraph[all]`.
|
|
32
|
+
- `descry-codegraph` installed: `pip install "descry-codegraph[all]"`.
|
|
33
33
|
- Optional but recommended: the MCP server registered (see below).
|
|
34
34
|
- Optional: `rust-analyzer` (Rust SCIP), `scip-typescript` (TS SCIP),
|
|
35
35
|
`scip-python` (Python SCIP) for type-aware call resolution.
|
|
@@ -83,7 +83,7 @@ under the project root. Before the first query in a session:
|
|
|
83
83
|
|
|
84
84
|
```bash
|
|
85
85
|
descry status # Is there a graph? How old is it?
|
|
86
|
-
descry ensure # Regenerate if missing
|
|
86
|
+
descry ensure # Regenerate if missing, stale (max_stale_hours) or files changed
|
|
87
87
|
```
|
|
88
88
|
|
|
89
89
|
`descry ensure` is safe to call unconditionally — it no-ops if the graph is
|
|
@@ -92,12 +92,12 @@ refreshes SCIP indices and embeddings, which can take minutes on large
|
|
|
92
92
|
Rust workspaces). If the user hasn't asked for indexing and no graph exists,
|
|
93
93
|
prompt them before running `descry index`.
|
|
94
94
|
|
|
95
|
-
Graph freshness is tracked by file mtime
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
95
|
+
Graph freshness is tracked by file mtime: `descry ensure` rebuilds a graph
|
|
96
|
+
older than `[project] max_stale_hours` (24 by default), or one whose files
|
|
97
|
+
changed or were removed since it was written. A file added since is not
|
|
98
|
+
noticed: after the user adds files, run `descry index` before querying
|
|
99
|
+
again. Cached queriers, semantic searchers, and dedup caches all invalidate
|
|
100
|
+
by mtime.
|
|
101
101
|
|
|
102
102
|
## Commands — grouped by intent
|
|
103
103
|
|
{descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/claude-skill/descry/references/cli-reference.md
RENAMED
|
@@ -7,9 +7,10 @@ the detected project root. Freshness is managed via `descry status` /
|
|
|
7
7
|
## Global discovery
|
|
8
8
|
|
|
9
9
|
```bash
|
|
10
|
+
descry --version # installed version
|
|
10
11
|
descry health # version, graph status, SCIP/embeddings/git state
|
|
11
12
|
descry status # just the graph: exists? nodes? edges? stale?
|
|
12
|
-
descry ensure # regenerate only if missing or
|
|
13
|
+
descry ensure # regenerate only if missing, unusable, stale or files changed
|
|
13
14
|
descry index [path] # force full rebuild (SCIP + embeddings)
|
|
14
15
|
descry embedding-models # registered embedding models, and the one selected
|
|
15
16
|
```
|
{descry_codegraph-1.1.0 → descry_codegraph-1.2.1}/claude-skill/descry/references/mcp-tools.md
RENAMED
|
@@ -23,7 +23,7 @@ This is descry's prompt-injection defense — preserve it when quoting.
|
|
|
23
23
|
### Freshness and diagnostics
|
|
24
24
|
- `descry_health` — version, graph status, feature availability.
|
|
25
25
|
- `descry_status` — graph existence / freshness only.
|
|
26
|
-
- `descry_ensure` — regenerate iff missing or
|
|
26
|
+
- `descry_ensure` — regenerate iff missing, unusable, stale or files it indexed changed.
|
|
27
27
|
- `descry_index(path=".")` — authoritative rebuild. A `path` below the
|
|
28
28
|
project root narrows which files are walked; the result still replaces
|
|
29
29
|
the root's graph. Paths outside the root are rejected.
|
|
@@ -140,9 +140,9 @@ Requires an OpenAPI spec: set `[cross_lang] openapi_path = "..."` in
|
|
|
140
140
|
- **Don't** use `descry search` for behavior-worded queries; `descry semantic`
|
|
141
141
|
is built for that and will do better.
|
|
142
142
|
- **Don't** bypass `status` on a stale graph. Out-of-date graphs
|
|
143
|
-
produce plausible-looking wrong answers. After edits
|
|
144
|
-
|
|
145
|
-
|
|
143
|
+
produce plausible-looking wrong answers. After edits, `descry ensure`
|
|
144
|
+
rebuilds a graph whose files changed or were removed; after adding
|
|
145
|
+
files, `descry index` brings the graph current.
|
|
146
146
|
- **Don't** shell out to grep when the question is structural. Even one
|
|
147
147
|
descry call beats three Greps for "who calls X".
|
|
148
148
|
|
|
@@ -54,8 +54,9 @@ answers; a call dropped as one out of the project is not among them.
|
|
|
54
54
|
|
|
55
55
|
## Keeping up with the source
|
|
56
56
|
|
|
57
|
-
An index is a snapshot. `status` says how old it is
|
|
58
|
-
again when it is older than
|
|
59
|
-
|
|
60
|
-
|
|
57
|
+
An index is a snapshot. `status` says how old it is and how many of its
|
|
58
|
+
files changed since; `ensure` builds it again when it is older than
|
|
59
|
+
`[project] max_stale_hours` or files it holds changed or were removed. An
|
|
60
|
+
index after a change parses only the files that changed, and resolves again
|
|
61
|
+
only the calls of files whose resolution read something that changed, so its
|
|
61
62
|
answers are those a full index would give.
|
|
@@ -30,7 +30,7 @@ MCP `descry_health`; web `GET /api/health`; CLI `usage: descry health [--json]`
|
|
|
30
30
|
|
|
31
31
|
## ensure
|
|
32
32
|
|
|
33
|
-
Ensure the codebase graph exists and is fresh. Call this FIRST before other descry queries. Regenerates if missing
|
|
33
|
+
Ensure the codebase graph exists and is fresh. Call this FIRST before other descry queries. Regenerates if missing, older than max_age_hours (default: the project's configured max_stale_hours), or if files it indexed changed since. Returns graph status with node/edge counts. WARNING: May take 30-60 seconds if regeneration is needed.
|
|
34
34
|
|
|
35
35
|
| parameter | type | default |
|
|
36
36
|
|---|---|---|
|
|
@@ -72,7 +72,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
72
72
|
label: "Descry Ensure",
|
|
73
73
|
description:
|
|
74
74
|
"Ensure the codebase graph exists and is fresh. Call this FIRST before other descry queries. " +
|
|
75
|
-
"Regenerates if missing
|
|
75
|
+
"Regenerates if missing, older than max_age_hours, or if files it indexed changed since. Returns graph status with node/edge counts. " +
|
|
76
76
|
"WARNING: May take 30-60 seconds if regeneration is needed.",
|
|
77
77
|
parameters: Type.Object({
|
|
78
78
|
max_age_hours: Type.Optional(Type.Number({ description: "Max graph age in hours before regeneration (default: the project's max_stale_hours)" })),
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "descry-codegraph"
|
|
3
|
-
|
|
3
|
+
dynamic = ["version"]
|
|
4
4
|
description = "Codebase knowledge graph: polyglot indexer, semantic search, call-graph analysis"
|
|
5
5
|
requires-python = ">=3.11"
|
|
6
6
|
authors = [{name = "Christian Haynes"}]
|
|
@@ -72,5 +72,8 @@ extend-exclude = ["src/descry/scip/pb2.py"]
|
|
|
72
72
|
requires = ["hatchling>=1.27"]
|
|
73
73
|
build-backend = "hatchling.build"
|
|
74
74
|
|
|
75
|
+
[tool.hatch.version]
|
|
76
|
+
path = "src/descry/__init__.py"
|
|
77
|
+
|
|
75
78
|
[tool.hatch.build.targets.wheel]
|
|
76
79
|
packages = ["src/descry"]
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
"""Writing files into a directory the indexed repository may have shipped."""
|
|
2
|
+
|
|
3
|
+
import contextlib
|
|
4
|
+
import fnmatch
|
|
5
|
+
import glob
|
|
6
|
+
import os
|
|
7
|
+
import re
|
|
8
|
+
import secrets
|
|
9
|
+
import stat
|
|
10
|
+
import time
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
|
|
13
|
+
try:
|
|
14
|
+
import fcntl as _fcntl # Unix only
|
|
15
|
+
except ImportError:
|
|
16
|
+
_fcntl = None
|
|
17
|
+
|
|
18
|
+
# A temporary nothing has written to for this long belongs to a writer that
|
|
19
|
+
# was killed before it could replace its target or remove the temporary.
|
|
20
|
+
_ORPHANED_AFTER = 3600
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def create_sibling(path: Path) -> tuple[int, Path]:
|
|
24
|
+
"""Create a new file beside `path` to write it atomically, then replace it.
|
|
25
|
+
|
|
26
|
+
The name is random and the file is created with `O_EXCL`, so neither a
|
|
27
|
+
leftover nor a symlink a repository put at a name it could guess is
|
|
28
|
+
opened or followed. The mode is the one `open(path, "w")` gives.
|
|
29
|
+
Temporaries for `path` that are orphaned are removed first.
|
|
30
|
+
Returns the open descriptor and the file's path.
|
|
31
|
+
"""
|
|
32
|
+
remove_orphans(path.parent, glob.escape(path.name))
|
|
33
|
+
tmp = path.with_name(f".{path.name}.{secrets.token_hex(8)}.tmp")
|
|
34
|
+
flags = os.O_WRONLY | os.O_CREAT | os.O_EXCL | getattr(os, "O_BINARY", 0)
|
|
35
|
+
return os.open(tmp, flags, 0o666), tmp
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def open_regular(path: Path, mode: str = "rb", **kwargs):
|
|
39
|
+
"""`path` opened for reading if it is a regular file: never through a
|
|
40
|
+
symlink at its name, nor a FIFO or a device, which a repository can ship
|
|
41
|
+
where descry keeps files and a read of which would never end. OSError
|
|
42
|
+
for anything else."""
|
|
43
|
+
return os.fdopen(_open_regular_fd(path, os.O_RDONLY), mode, **kwargs)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _open_regular_fd(path: Path, flags: int) -> int:
|
|
47
|
+
"""`os.open(path, flags)` if the file is regular: never through a symlink
|
|
48
|
+
at its name, nor a FIFO or a device. OSError for anything else."""
|
|
49
|
+
flags |= getattr(os, "O_NOFOLLOW", 0) | getattr(os, "O_NONBLOCK", 0)
|
|
50
|
+
# Without O_NOFOLLOW (Windows), the name must be a regular file, or
|
|
51
|
+
# missing, before the open and the very file opened after it.
|
|
52
|
+
named = None
|
|
53
|
+
if not hasattr(os, "O_NOFOLLOW"):
|
|
54
|
+
with contextlib.suppress(FileNotFoundError):
|
|
55
|
+
named = os.lstat(path)
|
|
56
|
+
if named and not stat.S_ISREG(named.st_mode):
|
|
57
|
+
raise OSError(f"{path} is not a regular file")
|
|
58
|
+
fd = os.open(path, flags, 0o666)
|
|
59
|
+
opened = os.fstat(fd)
|
|
60
|
+
if not stat.S_ISREG(opened.st_mode) or (
|
|
61
|
+
named and not os.path.samestat(named, opened)
|
|
62
|
+
):
|
|
63
|
+
os.close(fd)
|
|
64
|
+
raise OSError(f"{path} is not a regular file")
|
|
65
|
+
return fd
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def remove_orphans(directory: Path, names: str) -> None:
|
|
69
|
+
"""Remove the temporaries `create_sibling` made in `directory` for targets
|
|
70
|
+
whose names match the glob `names`, that nothing has written to for
|
|
71
|
+
`_ORPHANED_AFTER` seconds."""
|
|
72
|
+
cutoff = time.time() - _ORPHANED_AFTER
|
|
73
|
+
for tmp in directory.glob(f".{names}.*.tmp"):
|
|
74
|
+
target, _, token = tmp.name[1:-4].rpartition(".")
|
|
75
|
+
try:
|
|
76
|
+
st = tmp.lstat()
|
|
77
|
+
if (
|
|
78
|
+
re.fullmatch(r"[0-9a-f]{16}", token)
|
|
79
|
+
and fnmatch.fnmatchcase(target, names)
|
|
80
|
+
and not stat.S_ISDIR(st.st_mode)
|
|
81
|
+
and st.st_mtime < cutoff
|
|
82
|
+
):
|
|
83
|
+
tmp.unlink()
|
|
84
|
+
except OSError:
|
|
85
|
+
continue
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
@contextlib.contextmanager
|
|
89
|
+
def file_lock(lock_path: Path, timeout: float = 600.0, create: bool = True):
|
|
90
|
+
"""Cross-process advisory lock; with `create` False a missing lock file
|
|
91
|
+
raises FileNotFoundError instead of being created.
|
|
92
|
+
|
|
93
|
+
Uses fcntl.flock on Unix and msvcrt.locking on Windows. Both are held on
|
|
94
|
+
an open handle, so the OS releases them when the holder exits.
|
|
95
|
+
|
|
96
|
+
The lock file is never unlinked. Removing it would break mutual
|
|
97
|
+
exclusion: a waiter blocked on the old inode and a newcomer that
|
|
98
|
+
recreates the path hold locks on two different inodes and both enter.
|
|
99
|
+
An empty lock file is cheap; correctness is not.
|
|
100
|
+
|
|
101
|
+
The cache directory may have come with the repository, so the lock file
|
|
102
|
+
is opened without following a symlink or waiting on a FIFO, never
|
|
103
|
+
truncated, and refused unless it is a regular file.
|
|
104
|
+
"""
|
|
105
|
+
if _fcntl is not None:
|
|
106
|
+
poll_seconds = 0.05
|
|
107
|
+
|
|
108
|
+
def lock(f):
|
|
109
|
+
_fcntl.flock(f, _fcntl.LOCK_EX | _fcntl.LOCK_NB)
|
|
110
|
+
|
|
111
|
+
def unlock(f):
|
|
112
|
+
_fcntl.flock(f, _fcntl.LOCK_UN)
|
|
113
|
+
|
|
114
|
+
else:
|
|
115
|
+
# Windows fallback: lock the first byte of the (always empty) lock file.
|
|
116
|
+
import msvcrt
|
|
117
|
+
|
|
118
|
+
poll_seconds = 0.1
|
|
119
|
+
|
|
120
|
+
def lock(f):
|
|
121
|
+
msvcrt.locking(f.fileno(), msvcrt.LK_NBLCK, 1)
|
|
122
|
+
|
|
123
|
+
def unlock(f):
|
|
124
|
+
msvcrt.locking(f.fileno(), msvcrt.LK_UNLCK, 1)
|
|
125
|
+
|
|
126
|
+
fd = _open_regular_fd(
|
|
127
|
+
lock_path, os.O_WRONLY | os.O_APPEND | (os.O_CREAT if create else 0)
|
|
128
|
+
)
|
|
129
|
+
with os.fdopen(fd, "a", encoding="utf-8") as f:
|
|
130
|
+
# Poll non-blocking so the timeout is honoured; a blocking lock would
|
|
131
|
+
# wait forever regardless of the argument.
|
|
132
|
+
deadline = time.monotonic() + timeout
|
|
133
|
+
while True:
|
|
134
|
+
try:
|
|
135
|
+
lock(f)
|
|
136
|
+
break
|
|
137
|
+
except OSError:
|
|
138
|
+
if time.monotonic() >= deadline:
|
|
139
|
+
raise TimeoutError(
|
|
140
|
+
f"Timed out waiting for the lock at {lock_path}"
|
|
141
|
+
) from None
|
|
142
|
+
time.sleep(poll_seconds)
|
|
143
|
+
try:
|
|
144
|
+
yield
|
|
145
|
+
finally:
|
|
146
|
+
unlock(f)
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def lock_held(lock_path: Path) -> bool:
|
|
150
|
+
"""Whether another holder has `lock_path`'s `file_lock`; False where
|
|
151
|
+
the lock file is missing or cannot be opened, which it never creates."""
|
|
152
|
+
try:
|
|
153
|
+
with file_lock(lock_path, timeout=0, create=False):
|
|
154
|
+
return False
|
|
155
|
+
except TimeoutError:
|
|
156
|
+
return True
|
|
157
|
+
except OSError:
|
|
158
|
+
return False
|
|
@@ -14,6 +14,7 @@ import json
|
|
|
14
14
|
import os
|
|
15
15
|
import sys
|
|
16
16
|
|
|
17
|
+
from descry import __version__
|
|
17
18
|
from descry._answers import fail
|
|
18
19
|
from descry._streams import write_utf8
|
|
19
20
|
from descry.handlers import (
|
|
@@ -249,6 +250,7 @@ def build_parser():
|
|
|
249
250
|
prog="descry",
|
|
250
251
|
description="Codebase knowledge graph tools",
|
|
251
252
|
)
|
|
253
|
+
parser.add_argument("--version", action="version", version=f"descry {__version__}")
|
|
252
254
|
subparsers = parser.add_subparsers(dest="command")
|
|
253
255
|
answers = argparse.ArgumentParser(add_help=False)
|
|
254
256
|
answers.add_argument(
|