descry-codegraph 1.0.0__tar.gz → 1.0.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (217) hide show
  1. descry_codegraph-1.0.0/CLAUDE.md → descry_codegraph-1.0.2/AGENTS.md +3 -3
  2. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/CHANGELOG.md +32 -0
  3. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/PKG-INFO +2 -2
  4. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/README.md +1 -1
  5. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/pyproject.toml +1 -1
  6. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/src/descry/__init__.py +1 -1
  7. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/src/descry/cli.py +9 -0
  8. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/src/descry/handlers.py +1 -1
  9. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/src/descry/mcp_server.py +2 -2
  10. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/src/descry/resolver.py +63 -11
  11. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/src/descry/rust_parser.py +5 -1
  12. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/tests/test_backtracking.py +91 -64
  13. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/tests/test_cli.py +23 -0
  14. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/tests/test_generate.py +132 -31
  15. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/tests/test_ground_truth.py +4 -1
  16. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/tests/test_mcp.py +1 -1
  17. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/tests/test_web_server.py +4 -4
  18. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/.github/assets/01-landing.png +0 -0
  19. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/.github/workflows/ci.yml +0 -0
  20. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/.github/workflows/release.yml +0 -0
  21. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/.gitignore +0 -0
  22. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/CONTRACT.md +0 -0
  23. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/LICENSE +0 -0
  24. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/SECURITY.md +0 -0
  25. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/THIRD-PARTY-NOTICES.md +0 -0
  26. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/claude-skill/README.md +0 -0
  27. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/claude-skill/descry/SKILL.md +0 -0
  28. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/claude-skill/descry/references/cli-reference.md +0 -0
  29. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/claude-skill/descry/references/mcp-tools.md +0 -0
  30. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/claude-skill/descry/references/recipes.md +0 -0
  31. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/docs/CERTAINTY.md +0 -0
  32. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/docs/TOOLS.md +0 -0
  33. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/docs/UPGRADING.md +0 -0
  34. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/justfile +0 -0
  35. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/pi-extension/README.md +0 -0
  36. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/pi-extension/descry-cli.py +0 -0
  37. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/pi-extension/descry-tools.ts +0 -0
  38. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/src/descry/_answers.py +0 -0
  39. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/src/descry/_comments.py +0 -0
  40. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/src/descry/_env.py +0 -0
  41. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/src/descry/_files.py +0 -0
  42. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/src/descry/_graph.py +0 -0
  43. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/src/descry/_parse_cache.py +0 -0
  44. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/src/descry/_parsing.py +0 -0
  45. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/src/descry/_reads.py +0 -0
  46. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/src/descry/_stdlib.py +0 -0
  47. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/src/descry/_streams.py +0 -0
  48. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/src/descry/clang_parser.py +0 -0
  49. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/src/descry/cross_lang.py +0 -0
  50. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/src/descry/dart_parser.py +0 -0
  51. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/src/descry/dotnet_parser.py +0 -0
  52. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/src/descry/embeddings.py +0 -0
  53. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/src/descry/generate.py +0 -0
  54. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/src/descry/git_history.py +0 -0
  55. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/src/descry/go_parser.py +0 -0
  56. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/src/descry/java_parser.py +0 -0
  57. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/src/descry/php_parser.py +0 -0
  58. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/src/descry/proto_parser.py +0 -0
  59. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/src/descry/python_parser.py +0 -0
  60. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/src/descry/query.py +0 -0
  61. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/src/descry/ruby_parser.py +0 -0
  62. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/src/descry/scip/__init__.py +0 -0
  63. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/src/descry/scip/adapter.py +0 -0
  64. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/src/descry/scip/adapters/__init__.py +0 -0
  65. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/src/descry/scip/adapters/_empty_environment.json +0 -0
  66. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/src/descry/scip/adapters/_java_compat.init.gradle +0 -0
  67. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/src/descry/scip/adapters/clang.py +0 -0
  68. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/src/descry/scip/adapters/dart.py +0 -0
  69. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/src/descry/scip/adapters/dotnet.py +0 -0
  70. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/src/descry/scip/adapters/go.py +0 -0
  71. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/src/descry/scip/adapters/java.py +0 -0
  72. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/src/descry/scip/adapters/php.py +0 -0
  73. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/src/descry/scip/adapters/python.py +0 -0
  74. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/src/descry/scip/adapters/ruby.py +0 -0
  75. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/src/descry/scip/adapters/rust.py +0 -0
  76. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/src/descry/scip/adapters/typescript.py +0 -0
  77. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/src/descry/scip/cache.py +0 -0
  78. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/src/descry/scip/parser.py +0 -0
  79. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/src/descry/scip/pb2.py +0 -0
  80. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/src/descry/scip/support.py +0 -0
  81. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/src/descry/stdlib/c.json +0 -0
  82. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/src/descry/stdlib/cpp.json +0 -0
  83. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/src/descry/stdlib/dart.json +0 -0
  84. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/src/descry/stdlib/dotnet.json +0 -0
  85. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/src/descry/stdlib/go.json +0 -0
  86. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/src/descry/stdlib/java.json +0 -0
  87. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/src/descry/stdlib/node.json +0 -0
  88. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/src/descry/stdlib/php.json +0 -0
  89. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/src/descry/stdlib/python.json +0 -0
  90. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/src/descry/stdlib/ruby.json +0 -0
  91. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/src/descry/stdlib/rust.json +0 -0
  92. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/src/descry/stdlib/typescript.json +0 -0
  93. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/src/descry/ts_parser.py +0 -0
  94. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/src/descry/web/__init__.py +0 -0
  95. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/src/descry/web/server.py +0 -0
  96. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/src/descry/web/web/index.html +0 -0
  97. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/src/descry/web/web/style.css +0 -0
  98. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/tests/__init__.py +0 -0
  99. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/tests/conftest.py +0 -0
  100. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/tests/eval/README.md +0 -0
  101. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/tests/eval/__init__.py +0 -0
  102. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/tests/eval/compare.py +0 -0
  103. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/tests/eval/corpus.py +0 -0
  104. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/tests/eval/metrics.py +0 -0
  105. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/tests/eval/registry.py +0 -0
  106. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/tests/eval/results/results-descry.json +0 -0
  107. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/tests/eval/results/results-ripgrep.json +0 -0
  108. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/tests/fixtures/small_graph.json +0 -0
  109. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/tests/ground_truth/c/Makefile +0 -0
  110. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/tests/ground_truth/c/expected.json +0 -0
  111. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/tests/ground_truth/c/io.c +0 -0
  112. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/tests/ground_truth/c/io.h +0 -0
  113. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/tests/ground_truth/c/main.c +0 -0
  114. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/tests/ground_truth/c/order.c +0 -0
  115. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/tests/ground_truth/cpp/Makefile +0 -0
  116. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/tests/ground_truth/cpp/expected.json +0 -0
  117. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/tests/ground_truth/cpp/main.cpp +0 -0
  118. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/tests/ground_truth/cpp/models.hpp +0 -0
  119. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/tests/ground_truth/cpp/reader.hpp +0 -0
  120. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/tests/ground_truth/csharp/Gt.csproj +0 -0
  121. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/tests/ground_truth/csharp/Io.cs +0 -0
  122. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/tests/ground_truth/csharp/Models.cs +0 -0
  123. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/tests/ground_truth/csharp/Program.cs +0 -0
  124. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/tests/ground_truth/csharp/expected.json +0 -0
  125. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/tests/ground_truth/dart/bin/main.dart +0 -0
  126. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/tests/ground_truth/dart/expected.json +0 -0
  127. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/tests/ground_truth/dart/lib/io.dart +0 -0
  128. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/tests/ground_truth/dart/lib/models.dart +0 -0
  129. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/tests/ground_truth/dart/pubspec.yaml +0 -0
  130. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/tests/ground_truth/go/expected.json +0 -0
  131. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/tests/ground_truth/go/go.mod +0 -0
  132. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/tests/ground_truth/go/io.go +0 -0
  133. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/tests/ground_truth/go/main.go +0 -0
  134. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/tests/ground_truth/go/models.go +0 -0
  135. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/tests/ground_truth/java/expected.json +0 -0
  136. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/tests/ground_truth/java/src/gt/Base.java +0 -0
  137. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/tests/ground_truth/java/src/gt/Io.java +0 -0
  138. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/tests/ground_truth/java/src/gt/Main.java +0 -0
  139. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/tests/ground_truth/java/src/gt/Order.java +0 -0
  140. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/tests/ground_truth/java/src/gt/Reader.java +0 -0
  141. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/tests/ground_truth/java/src/gt/User.java +0 -0
  142. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/tests/ground_truth/javascript/expected.json +0 -0
  143. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/tests/ground_truth/javascript/package.json +0 -0
  144. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/tests/ground_truth/javascript/src/io.js +0 -0
  145. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/tests/ground_truth/javascript/src/main.js +0 -0
  146. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/tests/ground_truth/javascript/src/models.js +0 -0
  147. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/tests/ground_truth/php/composer.json +0 -0
  148. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/tests/ground_truth/php/expected.json +0 -0
  149. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/tests/ground_truth/php/src/Io.php +0 -0
  150. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/tests/ground_truth/php/src/Models.php +0 -0
  151. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/tests/ground_truth/php/src/main.php +0 -0
  152. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/tests/ground_truth/python/app/__init__.py +0 -0
  153. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/tests/ground_truth/python/app/io.py +0 -0
  154. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/tests/ground_truth/python/app/main.py +0 -0
  155. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/tests/ground_truth/python/app/models.py +0 -0
  156. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/tests/ground_truth/python/expected.json +0 -0
  157. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/tests/ground_truth/python/pyproject.toml +0 -0
  158. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/tests/ground_truth/ruby/Gemfile +0 -0
  159. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/tests/ground_truth/ruby/expected.json +0 -0
  160. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/tests/ground_truth/ruby/lib/io.rb +0 -0
  161. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/tests/ground_truth/ruby/lib/main.rb +0 -0
  162. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/tests/ground_truth/ruby/lib/models.rb +0 -0
  163. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/tests/ground_truth/rust/Cargo.toml +0 -0
  164. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/tests/ground_truth/rust/expected.json +0 -0
  165. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/tests/ground_truth/rust/src/io.rs +0 -0
  166. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/tests/ground_truth/rust/src/main.rs +0 -0
  167. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/tests/ground_truth/rust/src/models.rs +0 -0
  168. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/tests/ground_truth/typescript/expected.json +0 -0
  169. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/tests/ground_truth/typescript/package.json +0 -0
  170. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/tests/ground_truth/typescript/src/io.ts +0 -0
  171. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/tests/ground_truth/typescript/src/main.ts +0 -0
  172. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/tests/ground_truth/typescript/src/models.ts +0 -0
  173. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/tests/test_clang_parser.py +0 -0
  174. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/tests/test_config.py +0 -0
  175. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/tests/test_cross_lang.py +0 -0
  176. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/tests/test_dart_parser.py +0 -0
  177. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/tests/test_docs.py +0 -0
  178. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/tests/test_dotnet_parser.py +0 -0
  179. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/tests/test_embeddings.py +0 -0
  180. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/tests/test_git_history.py +0 -0
  181. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/tests/test_go_parser.py +0 -0
  182. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/tests/test_handlers.py +0 -0
  183. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/tests/test_hardening.py +0 -0
  184. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/tests/test_interfaces.py +0 -0
  185. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/tests/test_java_parser.py +0 -0
  186. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/tests/test_non_regular_files.py +0 -0
  187. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/tests/test_parse_cache.py +0 -0
  188. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/tests/test_php_parser.py +0 -0
  189. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/tests/test_query.py +0 -0
  190. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/tests/test_resolve_cache.py +0 -0
  191. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/tests/test_ruby_parser.py +0 -0
  192. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/tests/test_scip.py +0 -0
  193. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/tests/test_scip_cache.py +0 -0
  194. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/tests/test_scip_clang.py +0 -0
  195. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/tests/test_scip_dart.py +0 -0
  196. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/tests/test_scip_dotnet.py +0 -0
  197. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/tests/test_scip_go.py +0 -0
  198. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/tests/test_scip_java.py +0 -0
  199. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/tests/test_scip_php.py +0 -0
  200. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/tests/test_scip_ruby.py +0 -0
  201. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/tests/test_stdlib.py +0 -0
  202. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/tests/test_written_through.py +0 -0
  203. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/tools/docs/gen_tools.py +0 -0
  204. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/tools/stdlib/README.md +0 -0
  205. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/tools/stdlib/common.py +0 -0
  206. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/tools/stdlib/gen_c.py +0 -0
  207. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/tools/stdlib/gen_cpp.py +0 -0
  208. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/tools/stdlib/gen_dart.py +0 -0
  209. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/tools/stdlib/gen_dotnet.py +0 -0
  210. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/tools/stdlib/gen_go.py +0 -0
  211. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/tools/stdlib/gen_java.py +0 -0
  212. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/tools/stdlib/gen_node.py +0 -0
  213. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/tools/stdlib/gen_php.py +0 -0
  214. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/tools/stdlib/gen_python.py +0 -0
  215. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/tools/stdlib/gen_ruby.py +0 -0
  216. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/tools/stdlib/gen_rust.py +0 -0
  217. {descry_codegraph-1.0.0 → descry_codegraph-1.0.2}/tools/stdlib/gen_typescript.py +0 -0
@@ -1,6 +1,6 @@
1
- # CLAUDE.md
1
+ # AGENTS.md
2
2
 
3
- This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
3
+ This file provides guidance to coding agents when working with code in this repository.
4
4
 
5
5
  ## Commands
6
6
 
@@ -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 (3306 tests pass; a handful more are skipped where the environment lacks their fixture — a drop is a regression signal).
26
+ Python 3.11+ required (enforced by `pyproject.toml`). Pre-commit gate: `just lint && just fmt && just test` all pass (3343 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
 
@@ -4,6 +4,38 @@ 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.0.2] — 2026-10-31
8
+
9
+ ### Fixed
10
+
11
+ - Rust glob re-exports (`pub use m::*`) that reach a module by several
12
+ routes, or lead back round a cycle, no longer make indexing exponential:
13
+ each name a glob brings, and each type alias, is looked up once per
14
+ file.
15
+ - Rust: `<Self>::f(x)` is read as `Self::f(x)`; it, and a crate-root path
16
+ `::f(x)` with an argument of a project type, stopped the index.
17
+ - Rust: indexing a nested `vec![vec![..]; n]` twice stopped the index.
18
+ - Rust: a `static` or `const` in a `quote!` template, its type written
19
+ with `#` interpolations, stopped the index.
20
+ - MCP: arguments a tool cannot read are refused naming what is wrong with
21
+ each (`terms: Field required`), not the field alone.
22
+ - CLI: without a terminal on stderr, loading the embedding model prints no
23
+ progress bars or Hugging Face Hub notices; `HF_HUB_DISABLE_PROGRESS_BARS`,
24
+ `HF_HUB_VERBOSITY` and `TRANSFORMERS_VERBOSITY` set by the user are kept.
25
+ - README: `[timeouts] index_minutes = 0` is no time limit.
26
+
27
+ ## [1.0.1] — 2026-10-30
28
+
29
+ ### Fixed
30
+
31
+ - On macOS, stopping `descry index` (Ctrl-C, `SIGINT`) stops the run as
32
+ `SIGTERM` and `SIGHUP` do; it reported "Index error: Operation not
33
+ permitted" and exited 2.
34
+
35
+ ### Changed
36
+
37
+ - `CLAUDE.md` is `AGENTS.md`.
38
+
7
39
  ## [1.0.0] — 2026-10-29
8
40
 
9
41
  ### Breaking
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: descry-codegraph
3
- Version: 1.0.0
3
+ Version: 1.0.2
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
@@ -308,7 +308,7 @@ timeout = 30
308
308
  scip_minutes = 30 # per indexer; 0 = unlimited
309
309
  embedding_seconds = 60 # MCP and web answer `loading` past it; the CLI waits
310
310
  query_ms = 4000 # 0 = no time limit
311
- index_minutes = 30 # Timeout for `descry index` subprocess
311
+ index_minutes = 30 # Timeout for `descry index` subprocess; 0 = unlimited
312
312
 
313
313
  [query]
314
314
  max_depth = 3
@@ -247,7 +247,7 @@ timeout = 30
247
247
  scip_minutes = 30 # per indexer; 0 = unlimited
248
248
  embedding_seconds = 60 # MCP and web answer `loading` past it; the CLI waits
249
249
  query_ms = 4000 # 0 = no time limit
250
- index_minutes = 30 # Timeout for `descry index` subprocess
250
+ index_minutes = 30 # Timeout for `descry index` subprocess; 0 = unlimited
251
251
 
252
252
  [query]
253
253
  max_depth = 3
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "descry-codegraph"
3
- version = "1.0.0"
3
+ version = "1.0.2"
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"}]
@@ -1,4 +1,4 @@
1
1
  """Descry — Codebase knowledge graph: polyglot indexer, semantic search, call-graph analysis."""
2
2
 
3
- __version__ = "1.0.0"
3
+ __version__ = "1.0.2"
4
4
  __all__ = ["__version__"]
@@ -11,6 +11,7 @@ Usage:
11
11
  import argparse
12
12
  import asyncio
13
13
  import json
14
+ import os
14
15
  import sys
15
16
 
16
17
  from descry._answers import fail
@@ -516,6 +517,14 @@ def build_parser():
516
517
 
517
518
  def main():
518
519
  write_utf8()
520
+ if not sys.stderr.isatty():
521
+ # Scripted use: no model-loading progress bars or Hub notices.
522
+ for name, value in (
523
+ ("HF_HUB_DISABLE_PROGRESS_BARS", "1"),
524
+ ("HF_HUB_VERBOSITY", "error"),
525
+ ("TRANSFORMERS_VERBOSITY", "error"),
526
+ ):
527
+ os.environ.setdefault(name, value)
519
528
  parser = build_parser()
520
529
  args = parser.parse_args()
521
530
 
@@ -1058,7 +1058,7 @@ def _kill_process_tree(proc: asyncio.subprocess.Process) -> None:
1058
1058
  )
1059
1059
  else:
1060
1060
  # The child may have exited without the event loop having seen it yet.
1061
- with contextlib.suppress(ProcessLookupError):
1061
+ with contextlib.suppress(ProcessLookupError, PermissionError):
1062
1062
  os.killpg(proc.pid, signal.SIGKILL)
1063
1063
 
1064
1064
 
@@ -115,13 +115,13 @@ class _Server(MCPServer):
115
115
  if isinstance(exc.__cause__, ValidationError):
116
116
  fields = sorted(
117
117
  {
118
- ".".join(str(part) for part in err["loc"])
118
+ f"{'.'.join(str(part) for part in err['loc'])}: {err['msg']}"
119
119
  for err in exc.__cause__.errors()
120
120
  }
121
121
  )
122
122
  answer = fail(
123
123
  "invalid",
124
- f"Invalid arguments for {name}: {', '.join(fields)}.",
124
+ f"Invalid arguments for {name}: {'; '.join(fields)}.",
125
125
  )
126
126
  return _answered(json.dumps(public(answer), indent=2), answer)
127
127
  raise
@@ -1297,7 +1297,9 @@ class CallResolver:
1297
1297
  # signature a call of it is of
1298
1298
  "overloads",
1299
1299
  "reaches", # file ID -> the files its includes reach (C/C++)
1300
+ "rust_aliases", # (Rust module, name) -> see _alias_in
1300
1301
  "rust_in_scope", # Rust scope ID -> the modules named there
1302
+ "rust_used", # (Rust module, name) -> see _rust_used
1301
1303
  "rust_uses", # Rust scope ID -> what the `use`s in force bind
1302
1304
  "shared_declaration", # candidate node IDs -> node ID or None
1303
1305
  # Language -> the last name segments of the types and functions its
@@ -1327,6 +1329,8 @@ class CallResolver:
1327
1329
  "_forwarding",
1328
1330
  "open_aliases",
1329
1331
  "standard",
1332
+ "_rust_walked",
1333
+ "_rust_begun",
1330
1334
  )
1331
1335
 
1332
1336
  def begin_file(self):
@@ -1593,6 +1597,9 @@ class CallResolver:
1593
1597
  builder.rust_mods, files
1594
1598
  )
1595
1599
  self._importing = set() # (scope ID, name) of the Rust `use`s being read
1600
+ self._rust_cuts = 0 # times a Rust cycle guard stopped a walk
1601
+ self._rust_walked = {} # see _rust_keep
1602
+ self._rust_begun = [] # see _rust_recall
1596
1603
  # A module is importable by its path below its topmost package and,
1597
1604
  # through namespace packages, by any longer path no other module owns.
1598
1605
  for top, parts, file_id in py_files:
@@ -2104,10 +2111,11 @@ class CallResolver:
2104
2111
  elif (
2105
2112
  verdict == (None, _EXTERNAL)
2106
2113
  and self._path_call
2114
+ and (split := _split_callee(callee))[0]
2107
2115
  and (owner := self._receiver_type())
2108
2116
  ):
2109
2117
  # An outside trait's path: the receiver's project impl of it.
2110
- path, name = _split_callee(callee)
2118
+ path, name = split
2111
2119
  own = [
2112
2120
  m
2113
2121
  for m in self._rust_trait_methods(owner, name, path[-1])
@@ -3124,7 +3132,9 @@ class CallResolver:
3124
3132
  (target,),
3125
3133
  )
3126
3134
  found = None
3127
- if (scope_id, name) not in self._importing:
3135
+ if (scope_id, name) in self._importing:
3136
+ self._rust_cuts += 1
3137
+ else:
3128
3138
  # A `use` may lead back to itself through the module tree.
3129
3139
  self._importing.add((scope_id, name))
3130
3140
  try:
@@ -3357,11 +3367,42 @@ class CallResolver:
3357
3367
  ]
3358
3368
  return defined or found
3359
3369
 
3370
+ def _rust_recall(self, memo, key, seen):
3371
+ """``[value]`` kept for the Rust walk through `use`s and globs for
3372
+ ``key`` whose cycle guard holds ``seen`` (see `_rust_keep`); else
3373
+ None, and the walk is begun."""
3374
+ if key in memo:
3375
+ return [memo[key]]
3376
+ if seen and key in self._rust_walked:
3377
+ self._rust_cuts += 1
3378
+ return [self._rust_walked[key]]
3379
+ self._rust_begun.append((self._rust_cuts, self._rust_walked))
3380
+ if not seen:
3381
+ self._rust_walked = {}
3382
+ return None
3383
+
3384
+ def _rust_keep(self, memo, key, seen, found):
3385
+ """``found``, what the walk `_rust_recall` began gives: kept in
3386
+ ``memo`` for the file; where a cycle guard cut it short, only until
3387
+ the walk that began with no ``seen`` ends. A module that globs reach
3388
+ by several routes is then walked once."""
3389
+ cuts, outer = self._rust_begun.pop()
3390
+ if not seen:
3391
+ self._rust_walked = outer
3392
+ if self._rust_cuts == cuts:
3393
+ memo[key] = found
3394
+ elif seen:
3395
+ self._rust_walked[key] = found
3396
+ return found
3397
+
3360
3398
  def _rust_used(self, module, segment, seen):
3361
3399
  """What a `use` of the Rust module ``module`` binds ``segment`` to,
3362
3400
  a path from the module or from a crate, else what each glob `use`
3363
3401
  of the module's own does: IDs, ``"external"`` where one leads
3364
3402
  outside the project. ``seen`` as `_rust_path`'s."""
3403
+ key = (module, segment)
3404
+ if kept := self._rust_recall(self.rust_used, key, seen):
3405
+ return kept[0]
3365
3406
  uses = self._uses(module)
3366
3407
  alternatives = self.builder.rust_use_alternatives.get((module, segment))
3367
3408
  written = [uses[segment]] if segment in uses else []
@@ -3376,16 +3417,18 @@ class CallResolver:
3376
3417
  if segment in uses and not self._rust_rooted(target, module):
3377
3418
  held = self._imported(segment, module)
3378
3419
  elif self._rust_rooted(target, module):
3379
- if (module, segment) not in seen:
3380
- followed = seen | {(module, segment)}
3381
- held = self._rust_path(target.split("::"), module, followed)
3420
+ if key in seen:
3421
+ self._rust_cuts += 1
3422
+ else:
3423
+ held = self._rust_path(target.split("::"), module, seen | {key})
3382
3424
  else:
3383
3425
  # A glob of another crate's module may bring the name.
3384
3426
  held = _EXTERNAL
3385
3427
  if held == _EXTERNAL:
3386
- return _EXTERNAL
3428
+ hits = _EXTERNAL
3429
+ break
3387
3430
  hits += held or []
3388
- return hits
3431
+ return self._rust_keep(self.rust_used, key, seen, hits)
3389
3432
 
3390
3433
  def _rust_library(self, name, scope_id):
3391
3434
  """The root of the library of the project crate ``name`` written in
@@ -4820,7 +4863,7 @@ class CallResolver:
4820
4863
  value = self._called(member, value if len(value) > 2 else value[1])
4821
4864
  value = value[1] if value and value[0] == "pointer" else value
4822
4865
  continue
4823
- if step == "[]" and isinstance(value, tuple):
4866
+ if step == "[]" and isinstance(value, tuple) and value[0] != "literal":
4824
4867
  # A subscript gives what the type's `operator[]` returns, in
4825
4868
  # Rust its `Index` impl's `Output`, the element an iterator
4826
4869
  # gives where the index decides it: by the data for a
@@ -8152,17 +8195,24 @@ class CallResolver:
8152
8195
  found = self.builder.rust_alias_types.get((module, name))
8153
8196
  if found:
8154
8197
  return module, name, _rust_declared(found)
8198
+ key = (module, name)
8199
+ if kept := self._rust_recall(self.rust_aliases, key, seen):
8200
+ return kept[0]
8155
8201
  uses = self._uses(module)
8156
8202
  written = [uses[name]] if name in uses else []
8157
8203
  written = written or [
8158
8204
  f"{path[:-1]}{name}" for path in uses.values() if path[-3:] == "::*"
8159
8205
  ]
8206
+ hit = None
8160
8207
  for target in written:
8161
8208
  *qualifier, leaf = target.split("::")
8162
8209
  if qualifier and self._rust_child(module, qualifier[0]):
8163
8210
  qualifier = ["self", *qualifier]
8164
8211
  path = "::".join([*qualifier, leaf])
8165
- if not qualifier or (module, path) in seen:
8212
+ if not qualifier:
8213
+ continue
8214
+ if (module, path) in seen:
8215
+ self._rust_cuts += 1
8166
8216
  continue
8167
8217
  if not self._rust_rooted(path, module):
8168
8218
  continue
@@ -8170,8 +8220,10 @@ class CallResolver:
8170
8220
  for scope in held if isinstance(held, list) else ():
8171
8221
  hit = self._alias_in(scope, leaf, seen | {(module, path)})
8172
8222
  if hit:
8173
- return hit
8174
- return None
8223
+ break
8224
+ if hit:
8225
+ break
8226
+ return self._rust_keep(self.rust_aliases, key, seen, hit)
8175
8227
 
8176
8228
  def _alias_value(self, alias, written, scope_id, receiver):
8177
8229
  """Value of the type ``written`` in ``scope_id`` that names the Rust
@@ -50,7 +50,7 @@ _RUST_STRING_LET_RE = re.compile(
50
50
 
51
51
  _RUST_CONST_RE = re.compile(
52
52
  r"\b(?:const|static)\s+(?:mut\s+)?(?P<name>[A-Z][A-Z0-9_]*)\s*:\s*"
53
- r"(?P<type>[^=;().]+?)\s*="
53
+ r"(?P<type>[^=;().#]+?)\s*="
54
54
  )
55
55
 
56
56
 
@@ -1960,6 +1960,10 @@ class RustParser(BaseParser):
1960
1960
  # with its arguments.
1961
1961
  typed = re.fullmatch(r"(\w+)::<(.+)>::(\w+)", callee)
1962
1962
  typed = typed and f"{typed[1]}<{typed[2]}>.{typed[3]}"
1963
+ # `<Self>::f` is `Self::f`.
1964
+ closed = callee[:1] == "<" and _angle_end(callee, 0)
1965
+ if closed and callee[closed + 1 : closed + 3] == "::":
1966
+ callee = callee[1:closed] + callee[closed + 1 :]
1963
1967
  # `parse::<T>` calls `parse`; the type arguments are the
1964
1968
  # call site's, not part of the callee's name.
1965
1969
  callee = _strip_generics(callee)
@@ -21,45 +21,80 @@ from descry.generate import CodeGraphBuilder
21
21
 
22
22
  _GO_FIELD = next(r for r in go_parser._DECLARATIONS if "(?!(?:return" in r.pattern)
23
23
 
24
- # 3,000 characters took seconds to minutes where the time was cubic, and
25
- # 20,000 seconds where it was quadratic; each now takes milliseconds.
26
- _RUN = " " * 3000
27
- _LONG_RUN = " " * 20000
28
24
 
25
+ def _seconds(run):
26
+ """The least CPU time of three calls of `run`."""
27
+
28
+ def once():
29
+ start = time.process_time()
30
+ run()
31
+ return time.process_time() - start
32
+
33
+ return min(once() for _ in range(3))
34
+
35
+
36
+ def assert_linear(run, n):
37
+ """`run(n)` takes under ten times the CPU time of `run(n // 4)`: time
38
+ linear in `n` gives four times, quadratic sixteen."""
39
+ quarter = _seconds(lambda: run(n // 4))
40
+ assert _seconds(lambda: run(n)) < 10 * max(quarter, 0.05)
29
41
 
42
+
43
+ # 3,000 characters took seconds to minutes where the time was cubic, and
44
+ # 20,000 seconds where it was quadratic.
30
45
  @pytest.mark.parametrize(
31
- ("find", "text"),
46
+ ("find", "text", "length"),
32
47
  [
33
- (_RE_LOCAL_FUNCTION.match, _RUN + "!"),
34
- (DartParser.binding_re.search, "const" + _RUN),
35
- (java_parser._RE_METHOD.match, "final" + _RUN),
36
- (java_parser._RE_FIELD.match, "final" + _RUN),
37
- (ts_parser._TS_IMPORT_RE.findall, "import" + _RUN + "x;\n"),
38
- (rust_parser._RUST_ARM_RE.search, _RUN + "!"),
39
- (java_parser._RE_METHOD.match, "public <T>" + _RUN),
40
- (java_parser._RE_METHOD.match, "public @A" + _RUN),
41
- (java_parser._RE_ENUM_CONSTANT.match, "A" + _RUN + ")"),
42
- (rust_parser._RUST_BINDER_RES[1].findall, "for" + _RUN),
43
- (rust_parser._RUST_FOR_RE.search, " for" + _RUN + "x"),
44
- (rust_parser._RUST_IF_LET_RE.search, "while let" + _RUN),
45
- (rust_parser._RUST_MATCH_RE.search, "match" + _RUN + "a b"),
46
- (ruby_parser._RE_INLINE_BODY.match, "x" + "\r\n" * 1500 + ")"),
47
- (dotnet_parser._RE_MEMBER.match, "partial unsafe" + _LONG_RUN + ")"),
48
- (dotnet_parser._RE_PARAMETER.search, "scoped readonly" + _LONG_RUN + "x;"),
49
- (_GO_FIELD.search, " chan a" + _LONG_RUN + "x("),
50
- (ruby_parser._RE_ALIAS.search, "alias" + _LONG_RUN + ")"),
51
- (ruby_parser._RE_MIXIN.match, " prepend" + _LONG_RUN + "="),
52
- (_RE_TYPE.fullmatch, _LONG_RUN + ")"),
53
- (java_parser._RE_METHOD.match, "private final a" + _LONG_RUN + "b"),
54
- (java_parser._RE_FIELD.match, "public static a" + _LONG_RUN + "b("),
55
- (ruby_parser._RE_ENDLESS.match, _LONG_RUN * 3 + "x"),
56
- (ruby_parser._RE_INLINE_BODY.match, "end" + "\n" * 60000 + "x"),
57
- (dotnet_parser._RE_PRIMARY_CONSTRUCTOR.match, _LONG_RUN * 3 + "x"),
58
- (dotnet_parser._RE_PARAMETER.findall, "readonly" + "\t\n " * 20000 + ")"),
59
- (_RE_CLOSURE.findall, "\n" * 60000),
60
- (ts_parser._TS_IMPORT_RE.findall, "\n" * 60000),
61
- (ts_parser._TS_DEFAULT_EXPORT_RE.findall, "\u2028" * 60000),
62
- (rust_parser._RUST_USE_RE.findall, "\n" * 60000),
48
+ (_RE_LOCAL_FUNCTION.match, lambda n: " " * n + "!", 3000),
49
+ (DartParser.binding_re.search, lambda n: "const" + " " * n, 3000),
50
+ (java_parser._RE_METHOD.match, lambda n: "final" + " " * n, 3000),
51
+ (java_parser._RE_FIELD.match, lambda n: "final" + " " * n, 3000),
52
+ (ts_parser._TS_IMPORT_RE.findall, lambda n: "import" + " " * n + "x;\n", 3000),
53
+ (rust_parser._RUST_ARM_RE.search, lambda n: " " * n + "!", 3000),
54
+ (java_parser._RE_METHOD.match, lambda n: "public <T>" + " " * n, 3000),
55
+ (java_parser._RE_METHOD.match, lambda n: "public @A" + " " * n, 3000),
56
+ (java_parser._RE_ENUM_CONSTANT.match, lambda n: "A" + " " * n + ")", 3000),
57
+ (rust_parser._RUST_BINDER_RES[1].findall, lambda n: "for" + " " * n, 3000),
58
+ (rust_parser._RUST_FOR_RE.search, lambda n: " for" + " " * n + "x", 3000),
59
+ (rust_parser._RUST_IF_LET_RE.search, lambda n: "while let" + " " * n, 3000),
60
+ (rust_parser._RUST_MATCH_RE.search, lambda n: "match" + " " * n + "a b", 3000),
61
+ (ruby_parser._RE_INLINE_BODY.match, lambda n: "x" + "\r\n" * n + ")", 1500),
62
+ (
63
+ dotnet_parser._RE_MEMBER.match,
64
+ lambda n: "partial unsafe" + " " * n + ")",
65
+ 20000,
66
+ ),
67
+ (
68
+ dotnet_parser._RE_PARAMETER.search,
69
+ lambda n: "scoped readonly" + " " * n + "x;",
70
+ 20000,
71
+ ),
72
+ (_GO_FIELD.search, lambda n: " chan a" + " " * n + "x(", 20000),
73
+ (ruby_parser._RE_ALIAS.search, lambda n: "alias" + " " * n + ")", 20000),
74
+ (ruby_parser._RE_MIXIN.match, lambda n: " prepend" + " " * n + "=", 20000),
75
+ (_RE_TYPE.fullmatch, lambda n: " " * n + ")", 20000),
76
+ (
77
+ java_parser._RE_METHOD.match,
78
+ lambda n: "private final a" + " " * n + "b",
79
+ 20000,
80
+ ),
81
+ (
82
+ java_parser._RE_FIELD.match,
83
+ lambda n: "public static a" + " " * n + "b(",
84
+ 20000,
85
+ ),
86
+ (ruby_parser._RE_ENDLESS.match, lambda n: " " * n + "x", 60000),
87
+ (ruby_parser._RE_INLINE_BODY.match, lambda n: "end" + "\n" * n + "x", 60000),
88
+ (dotnet_parser._RE_PRIMARY_CONSTRUCTOR.match, lambda n: " " * n + "x", 60000),
89
+ (
90
+ dotnet_parser._RE_PARAMETER.findall,
91
+ lambda n: "readonly" + "\t\n " * n + ")",
92
+ 20000,
93
+ ),
94
+ (_RE_CLOSURE.findall, lambda n: "\n" * n, 60000),
95
+ (ts_parser._TS_IMPORT_RE.findall, lambda n: "\n" * n, 60000),
96
+ (ts_parser._TS_DEFAULT_EXPORT_RE.findall, lambda n: "\u2028" * n, 60000),
97
+ (rust_parser._RUST_USE_RE.findall, lambda n: "\n" * n, 60000),
63
98
  ],
64
99
  ids=[
65
100
  "dart-local-function",
@@ -94,34 +129,30 @@ _LONG_RUN = " " * 20000
94
129
  "rust-use-blank-lines",
95
130
  ],
96
131
  )
97
- def test_a_whitespace_run_is_taken_once(find, text):
98
- start = time.process_time()
99
-
100
- find(text)
101
-
102
- assert time.process_time() - start < 1
132
+ def test_a_whitespace_run_is_taken_once(find, text, length):
133
+ assert_linear(lambda n: find(text(n)), length)
103
134
 
104
135
 
105
136
  @pytest.mark.parametrize(
106
137
  ("name", "source", "count"),
107
138
  [
108
- ("m.js", lambda n: "const b = { a: g() };\n" * n, 20000),
109
- ("m.js", lambda n: "let b = 1;\n" + "b = g();\nb.x();\n" * n, 10000),
139
+ ("m.js", lambda n: "const b = { a: g() };\n" * n, 10000),
140
+ ("m.js", lambda n: "let b = 1;\n" + "b = g();\nb.x();\n" * n, 5000),
110
141
  (
111
142
  "m.js",
112
143
  lambda n: (
113
144
  "function f(xs) {\n" + " for (const x of xs) { x.a(); }\n" * n + "}\n"
114
145
  ),
115
- 15000,
146
+ 7500,
116
147
  ),
117
- ("m.rb", lambda n: "b = 2\n" * n, 20000),
118
- ("m.rb", lambda n: "a = []\n" + "a <<-b\n" * n, 60000),
119
- ("m.dart", lambda n: "void main() {\n" + " var b = 2;\n" * n + "}\n", 20000),
120
- ("m.py", lambda n: "def f():\n" + " b = B()\n b.x()\n" * n, 20000),
148
+ ("m.rb", lambda n: "b = 2\n" * n, 10000),
149
+ ("m.rb", lambda n: "a = []\n" + "a <<-b\n" * n, 30000),
150
+ ("m.dart", lambda n: "void main() {\n" + " var b = 2;\n" * n + "}\n", 10000),
151
+ ("m.py", lambda n: "def f():\n" + " b = B()\n b.x()\n" * n, 10000),
121
152
  (
122
153
  "m.py",
123
154
  lambda n: "def f(xs):\n" + " for x in xs:\n x.a()\n" * n,
124
- 10000,
155
+ 5000,
125
156
  ),
126
157
  (
127
158
  "M.java",
@@ -130,7 +161,7 @@ def test_a_whitespace_run_is_taken_once(find, text):
130
161
  + " B b = new B();\n b.x();\n" * n
131
162
  + " }\n}\n"
132
163
  ),
133
- 10000,
164
+ 5000,
134
165
  ),
135
166
  (
136
167
  "M.cs",
@@ -139,14 +170,14 @@ def test_a_whitespace_run_is_taken_once(find, text):
139
170
  + " var b = new B();\n b.X();\n" * n
140
171
  + " }\n}\n"
141
172
  ),
142
- 8000,
173
+ 4000,
143
174
  ),
144
175
  (
145
176
  "M.cs",
146
177
  lambda n: "".join(
147
178
  f"class C{i} {{ int M() {{ return {i}; }} }}\n" for i in range(n)
148
179
  ),
149
- 3000,
180
+ 1500,
150
181
  ),
151
182
  (
152
183
  "m.go",
@@ -156,7 +187,7 @@ def test_a_whitespace_run_is_taken_once(find, text):
156
187
  * n
157
188
  + "\treturn nil\n}\n"
158
189
  ),
159
- 10000,
190
+ 5000,
160
191
  ),
161
192
  (
162
193
  "m.php",
@@ -165,14 +196,14 @@ def test_a_whitespace_run_is_taken_once(find, text):
165
196
  + " foreach ($xs as $x) { $x->a(); }\n" * n
166
197
  + "}\n"
167
198
  ),
168
- 8000,
199
+ 4000,
169
200
  ),
170
201
  (
171
202
  "src/lib.rs",
172
203
  lambda n: "".join(
173
204
  f"fn f{i}<T: Clone>(a: T) -> T {{ a.clone() }}\n" for i in range(n)
174
205
  ),
175
- 12000,
206
+ 6000,
176
207
  ),
177
208
  (
178
209
  "m.js",
@@ -182,7 +213,7 @@ def test_a_whitespace_run_is_taken_once(find, text):
182
213
  * n
183
214
  + "}\n"
184
215
  ),
185
- 5000,
216
+ 2500,
186
217
  ),
187
218
  (
188
219
  "src/lib.rs",
@@ -191,7 +222,7 @@ def test_a_whitespace_run_is_taken_once(find, text):
191
222
  + " match v { Some(b) => b.a(), None => () }\n" * n
192
223
  + "}\n"
193
224
  ),
194
- 10000,
225
+ 5000,
195
226
  ),
196
227
  ],
197
228
  ids=[
@@ -216,22 +247,18 @@ def test_a_whitespace_run_is_taken_once(find, text):
216
247
  def test_a_repeated_line_is_read_in_linear_time(name, source, count, tmp_path):
217
248
  """A line that binds a name again, loops over it, matches it, or
218
249
  declares a type or a generic function costs the same time however many
219
- came before it: four times the lines take about four times as long,
220
- where they took sixteen times, minutes in all. The ratio, not a time,
221
- is checked, so a slow machine does not fail it."""
250
+ came before it, where four times the lines took sixteen times as long,
251
+ minutes in all."""
222
252
 
223
253
  def reading(lines):
224
254
  root = tmp_path / str(lines)
225
255
  (root / name).parent.mkdir(parents=True, exist_ok=True)
226
256
  (root / name).write_text(source(lines))
227
- start = time.process_time()
228
257
  builder = CodeGraphBuilder(root)
229
258
  builder.process_directory()
230
259
  builder.resolve_references()
231
- return time.process_time() - start
232
260
 
233
- quarter = reading(count // 4)
234
- assert reading(count) < 8 * max(quarter, 0.05)
261
+ assert_linear(reading, count)
235
262
 
236
263
 
237
264
  def test_the_patterns_still_match_source():
@@ -65,6 +65,29 @@ class TestCliNoCommand:
65
65
  assert result.returncode == 1
66
66
 
67
67
 
68
+ class TestCliHubOutput:
69
+ _NAMES = (
70
+ "HF_HUB_DISABLE_PROGRESS_BARS",
71
+ "HF_HUB_VERBOSITY",
72
+ "TRANSFORMERS_VERBOSITY",
73
+ )
74
+
75
+ @pytest.mark.parametrize("terminal", [False, True])
76
+ def test_scripted_use_quiets_model_loading(self, monkeypatch, terminal):
77
+ """Without a terminal on stderr, model loading shows no progress bars
78
+ or Hub notices; a value the user set is kept."""
79
+ for name in self._NAMES:
80
+ monkeypatch.delenv(name, raising=False)
81
+ monkeypatch.setenv("HF_HUB_VERBOSITY", "info")
82
+ monkeypatch.setattr(sys.stderr, "isatty", lambda: terminal)
83
+ monkeypatch.setattr(sys, "argv", ["descry"])
84
+ with pytest.raises(SystemExit):
85
+ cli.main()
86
+ assert [os.environ.get(name) for name in self._NAMES] == (
87
+ [None, "info", None] if terminal else ["1", "info", "error"]
88
+ )
89
+
90
+
68
91
  class TestCliArguments:
69
92
  @pytest.mark.parametrize(
70
93
  "args", [["callers", "x", "--limit", "ten"], ["search", "x", "--frob", "1"]]