graphifyy 0.9.74__tar.gz → 0.9.76__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 (571) hide show
  1. {graphifyy-0.9.74/graphifyy.egg-info → graphifyy-0.9.76}/PKG-INFO +17 -2
  2. {graphifyy-0.9.74 → graphifyy-0.9.76}/README.md +14 -0
  3. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/__main__.py +3 -0
  4. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/analyze.py +13 -1
  5. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/build.py +41 -4
  6. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/cache.py +46 -5
  7. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/cli.py +138 -34
  8. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/cross_repo_calls.py +1 -1
  9. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/cross_repo_types.py +1 -1
  10. graphifyy-0.9.76/graphify/csharp_dispatch.py +39 -0
  11. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/dedup.py +39 -32
  12. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/detect.py +43 -1
  13. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/export.py +29 -1
  14. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/extract.py +253 -41
  15. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/extractors/bash.py +48 -0
  16. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/extractors/dart.py +40 -15
  17. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/extractors/elixir.py +37 -1
  18. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/extractors/engine.py +408 -4
  19. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/extractors/json_config.py +45 -9
  20. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/extractors/pascal.py +32 -0
  21. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/extractors/rust.py +52 -0
  22. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/extractors/vbnet.py +12 -3
  23. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/extractors/zig.py +45 -6
  24. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/hooks.py +73 -2
  25. graphifyy-0.9.74/graphify/csharp_dispatch.py → graphifyy-0.9.76/graphify/interface_dispatch.py +40 -35
  26. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/llm.py +7 -1
  27. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/serve.py +103 -22
  28. graphifyy-0.9.76/graphify/swift_dispatch.py +46 -0
  29. {graphifyy-0.9.74 → graphifyy-0.9.76/graphifyy.egg-info}/PKG-INFO +17 -2
  30. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphifyy.egg-info/SOURCES.txt +13 -0
  31. {graphifyy-0.9.74 → graphifyy-0.9.76}/pyproject.toml +3 -2
  32. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_analyze.py +54 -0
  33. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_cache.py +32 -0
  34. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_cli_export.py +103 -0
  35. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_cpp_nested_and_cli.py +39 -0
  36. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_cross_repo_member_calls.py +1 -0
  37. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_cross_repo_shared_types.py +1 -0
  38. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_csharp_interface_dispatch.py +4 -0
  39. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_csharp_partial_classes.py +89 -1
  40. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_dart.py +84 -2
  41. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_dedup.py +82 -14
  42. graphifyy-0.9.76/tests/test_dedup_shrink_refuses_force_write.py +209 -0
  43. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_detect.py +161 -0
  44. graphifyy-0.9.76/tests/test_elixir_unqualified_call_scope.py +182 -0
  45. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_extract.py +639 -16
  46. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_hollow_chunks_arm_shrink_guard.py +2 -2
  47. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_hook_guard.py +46 -0
  48. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_hooks.py +64 -0
  49. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_incomplete_build_guard.py +3 -2
  50. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_inferred_confidence_rubric.py +72 -12
  51. graphifyy-0.9.76/tests/test_json_schema_config.py +227 -0
  52. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_languages.py +152 -0
  53. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_llm_backends.py +20 -0
  54. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_loose_sibling_import_resolution.py +8 -4
  55. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_merge_graphs_cli.py +81 -0
  56. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_partial_extraction_warning.py +63 -0
  57. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_pascal.py +46 -0
  58. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_path_cli.py +208 -0
  59. graphifyy-0.9.76/tests/test_php_language_construct_calls.py +258 -0
  60. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_python_import_resolution.py +96 -0
  61. graphifyy-0.9.76/tests/test_rust_macro_rules.py +93 -0
  62. graphifyy-0.9.76/tests/test_scala_context_bounds.py +139 -0
  63. graphifyy-0.9.76/tests/test_scala_top_level_binding.py +139 -0
  64. graphifyy-0.9.76/tests/test_scala_type_definition.py +135 -0
  65. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_serve.py +24 -0
  66. graphifyy-0.9.76/tests/test_stat_index_multi_root.py +110 -0
  67. graphifyy-0.9.76/tests/test_svelte_extraction.py +205 -0
  68. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_swift_cross_file_calls.py +3 -3
  69. graphifyy-0.9.76/tests/test_swift_protocol_dispatch.py +266 -0
  70. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_vbnet_extractor.py +36 -0
  71. {graphifyy-0.9.74 → graphifyy-0.9.76}/LICENSE +0 -0
  72. {graphifyy-0.9.74 → graphifyy-0.9.76}/LICENSE-MIT +0 -0
  73. {graphifyy-0.9.74 → graphifyy-0.9.76}/NOTICE +0 -0
  74. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/__init__.py +0 -0
  75. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/_minhash.py +0 -0
  76. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/affected.py +0 -0
  77. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/always_on/agents-md.md +0 -0
  78. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/always_on/antigravity-rules.md +0 -0
  79. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/always_on/claude-md.md +0 -0
  80. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/always_on/gemini-md.md +0 -0
  81. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/always_on/kiro-steering.md +0 -0
  82. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/always_on/vscode-instructions.md +0 -0
  83. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/benchmark.py +0 -0
  84. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/callflow_html.py +0 -0
  85. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/cargo_introspect.py +0 -0
  86. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/cluster.py +0 -0
  87. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/command-kilo.md +0 -0
  88. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/diagnostics.py +0 -0
  89. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/exporters/__init__.py +0 -0
  90. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/exporters/base.py +0 -0
  91. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/exporters/graphdb.py +0 -0
  92. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/exporters/html.py +0 -0
  93. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/extractors/__init__.py +0 -0
  94. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/extractors/apex.py +0 -0
  95. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/extractors/base.py +0 -0
  96. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/extractors/blade.py +0 -0
  97. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/extractors/cobol.py +0 -0
  98. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/extractors/commonlisp.py +0 -0
  99. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/extractors/csharp.py +0 -0
  100. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/extractors/dm.py +0 -0
  101. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/extractors/erlang.py +0 -0
  102. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/extractors/fortran.py +0 -0
  103. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/extractors/go.py +0 -0
  104. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/extractors/julia.py +0 -0
  105. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/extractors/markdown.py +0 -0
  106. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/extractors/models.py +0 -0
  107. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/extractors/objc.py +0 -0
  108. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/extractors/ocaml.py +0 -0
  109. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/extractors/pascal_forms.py +0 -0
  110. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/extractors/powershell.py +0 -0
  111. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/extractors/r.py +0 -0
  112. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/extractors/razor.py +0 -0
  113. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/extractors/resolution.py +0 -0
  114. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/extractors/robot.py +0 -0
  115. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/extractors/sln.py +0 -0
  116. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/extractors/solidity.py +0 -0
  117. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/extractors/sql.py +0 -0
  118. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/extractors/terraform.py +0 -0
  119. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/extractors/verilog.py +0 -0
  120. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/file_slice.py +0 -0
  121. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/global_graph.py +0 -0
  122. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/google_workspace.py +0 -0
  123. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/ids.py +0 -0
  124. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/ingest.py +0 -0
  125. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/install.py +0 -0
  126. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/manifest.py +0 -0
  127. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/manifest_ingest.py +0 -0
  128. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/markdown_resolution.py +0 -0
  129. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/mcp_ingest.py +0 -0
  130. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/multigraph_compat.py +0 -0
  131. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/pascal_resolution.py +0 -0
  132. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/paths.py +0 -0
  133. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/pg_introspect.py +0 -0
  134. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/prs.py +0 -0
  135. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/querylog.py +0 -0
  136. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/reflect.py +0 -0
  137. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/report.py +0 -0
  138. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/resolver_registry.py +0 -0
  139. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/ruby_resolution.py +0 -0
  140. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/scip_ingest.py +0 -0
  141. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/security.py +0 -0
  142. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/semantic_cleanup.py +0 -0
  143. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/skill-agents.md +0 -0
  144. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/skill-aider.md +0 -0
  145. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/skill-amp.md +0 -0
  146. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/skill-claw.md +0 -0
  147. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/skill-codex.md +0 -0
  148. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/skill-copilot.md +0 -0
  149. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/skill-devin.md +0 -0
  150. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/skill-droid.md +0 -0
  151. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/skill-kilo.md +0 -0
  152. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/skill-kiro.md +0 -0
  153. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/skill-opencode.md +0 -0
  154. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/skill-pi.md +0 -0
  155. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/skill-trae.md +0 -0
  156. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/skill-vscode.md +0 -0
  157. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/skill-windows.md +0 -0
  158. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/skill.md +0 -0
  159. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/skills/agents/references/add-watch.md +0 -0
  160. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/skills/agents/references/exports.md +0 -0
  161. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/skills/agents/references/extraction-spec.md +0 -0
  162. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/skills/agents/references/github-and-merge.md +0 -0
  163. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/skills/agents/references/hooks.md +0 -0
  164. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/skills/agents/references/query.md +0 -0
  165. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/skills/agents/references/transcribe.md +0 -0
  166. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/skills/agents/references/update.md +0 -0
  167. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/skills/amp/references/add-watch.md +0 -0
  168. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/skills/amp/references/exports.md +0 -0
  169. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/skills/amp/references/extraction-spec.md +0 -0
  170. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/skills/amp/references/github-and-merge.md +0 -0
  171. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/skills/amp/references/hooks.md +0 -0
  172. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/skills/amp/references/query.md +0 -0
  173. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/skills/amp/references/transcribe.md +0 -0
  174. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/skills/amp/references/update.md +0 -0
  175. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/skills/claude/references/add-watch.md +0 -0
  176. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/skills/claude/references/exports.md +0 -0
  177. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/skills/claude/references/extraction-spec.md +0 -0
  178. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/skills/claude/references/github-and-merge.md +0 -0
  179. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/skills/claude/references/hooks.md +0 -0
  180. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/skills/claude/references/query.md +0 -0
  181. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/skills/claude/references/transcribe.md +0 -0
  182. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/skills/claude/references/update.md +0 -0
  183. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/skills/claw/references/add-watch.md +0 -0
  184. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/skills/claw/references/exports.md +0 -0
  185. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/skills/claw/references/extraction-spec.md +0 -0
  186. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/skills/claw/references/github-and-merge.md +0 -0
  187. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/skills/claw/references/hooks.md +0 -0
  188. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/skills/claw/references/query.md +0 -0
  189. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/skills/claw/references/transcribe.md +0 -0
  190. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/skills/claw/references/update.md +0 -0
  191. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/skills/codex/references/add-watch.md +0 -0
  192. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/skills/codex/references/exports.md +0 -0
  193. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/skills/codex/references/extraction-spec.md +0 -0
  194. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/skills/codex/references/github-and-merge.md +0 -0
  195. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/skills/codex/references/hooks.md +0 -0
  196. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/skills/codex/references/query.md +0 -0
  197. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/skills/codex/references/transcribe.md +0 -0
  198. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/skills/codex/references/update.md +0 -0
  199. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/skills/copilot/references/add-watch.md +0 -0
  200. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/skills/copilot/references/exports.md +0 -0
  201. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/skills/copilot/references/extraction-spec.md +0 -0
  202. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/skills/copilot/references/github-and-merge.md +0 -0
  203. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/skills/copilot/references/hooks.md +0 -0
  204. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/skills/copilot/references/query.md +0 -0
  205. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/skills/copilot/references/transcribe.md +0 -0
  206. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/skills/copilot/references/update.md +0 -0
  207. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/skills/droid/references/add-watch.md +0 -0
  208. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/skills/droid/references/exports.md +0 -0
  209. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/skills/droid/references/extraction-spec.md +0 -0
  210. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/skills/droid/references/github-and-merge.md +0 -0
  211. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/skills/droid/references/hooks.md +0 -0
  212. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/skills/droid/references/query.md +0 -0
  213. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/skills/droid/references/transcribe.md +0 -0
  214. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/skills/droid/references/update.md +0 -0
  215. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/skills/kilo/references/add-watch.md +0 -0
  216. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/skills/kilo/references/exports.md +0 -0
  217. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/skills/kilo/references/extraction-spec.md +0 -0
  218. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/skills/kilo/references/github-and-merge.md +0 -0
  219. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/skills/kilo/references/hooks.md +0 -0
  220. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/skills/kilo/references/query.md +0 -0
  221. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/skills/kilo/references/transcribe.md +0 -0
  222. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/skills/kilo/references/update.md +0 -0
  223. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/skills/kiro/references/add-watch.md +0 -0
  224. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/skills/kiro/references/exports.md +0 -0
  225. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/skills/kiro/references/extraction-spec.md +0 -0
  226. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/skills/kiro/references/github-and-merge.md +0 -0
  227. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/skills/kiro/references/hooks.md +0 -0
  228. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/skills/kiro/references/query.md +0 -0
  229. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/skills/kiro/references/transcribe.md +0 -0
  230. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/skills/kiro/references/update.md +0 -0
  231. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/skills/opencode/references/add-watch.md +0 -0
  232. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/skills/opencode/references/exports.md +0 -0
  233. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/skills/opencode/references/extraction-spec.md +0 -0
  234. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/skills/opencode/references/github-and-merge.md +0 -0
  235. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/skills/opencode/references/hooks.md +0 -0
  236. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/skills/opencode/references/query.md +0 -0
  237. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/skills/opencode/references/transcribe.md +0 -0
  238. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/skills/opencode/references/update.md +0 -0
  239. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/skills/pi/references/add-watch.md +0 -0
  240. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/skills/pi/references/exports.md +0 -0
  241. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/skills/pi/references/extraction-spec.md +0 -0
  242. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/skills/pi/references/github-and-merge.md +0 -0
  243. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/skills/pi/references/hooks.md +0 -0
  244. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/skills/pi/references/query.md +0 -0
  245. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/skills/pi/references/transcribe.md +0 -0
  246. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/skills/pi/references/update.md +0 -0
  247. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/skills/trae/references/add-watch.md +0 -0
  248. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/skills/trae/references/exports.md +0 -0
  249. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/skills/trae/references/extraction-spec.md +0 -0
  250. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/skills/trae/references/github-and-merge.md +0 -0
  251. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/skills/trae/references/hooks.md +0 -0
  252. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/skills/trae/references/query.md +0 -0
  253. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/skills/trae/references/transcribe.md +0 -0
  254. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/skills/trae/references/update.md +0 -0
  255. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/skills/vscode/references/add-watch.md +0 -0
  256. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/skills/vscode/references/exports.md +0 -0
  257. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/skills/vscode/references/extraction-spec.md +0 -0
  258. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/skills/vscode/references/github-and-merge.md +0 -0
  259. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/skills/vscode/references/hooks.md +0 -0
  260. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/skills/vscode/references/query.md +0 -0
  261. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/skills/vscode/references/transcribe.md +0 -0
  262. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/skills/vscode/references/update.md +0 -0
  263. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/skills/windows/references/add-watch.md +0 -0
  264. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/skills/windows/references/exports.md +0 -0
  265. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/skills/windows/references/extraction-spec.md +0 -0
  266. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/skills/windows/references/github-and-merge.md +0 -0
  267. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/skills/windows/references/hooks.md +0 -0
  268. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/skills/windows/references/query.md +0 -0
  269. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/skills/windows/references/transcribe.md +0 -0
  270. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/skills/windows/references/update.md +0 -0
  271. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/symbol_resolution.py +0 -0
  272. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/transcribe.py +0 -0
  273. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/tree_html.py +0 -0
  274. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/validate.py +0 -0
  275. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/watch.py +0 -0
  276. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphify/wiki.py +0 -0
  277. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphifyy.egg-info/dependency_links.txt +0 -0
  278. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphifyy.egg-info/entry_points.txt +0 -0
  279. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphifyy.egg-info/requires.txt +0 -0
  280. {graphifyy-0.9.74 → graphifyy-0.9.76}/graphifyy.egg-info/top_level.txt +0 -0
  281. {graphifyy-0.9.74 → graphifyy-0.9.76}/setup.cfg +0 -0
  282. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_affected_cli.py +0 -0
  283. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_affected_member_seed.py +0 -0
  284. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_agents_platform.py +0 -0
  285. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_anthropic_custom_endpoint.py +0 -0
  286. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_antigravity_install.py +0 -0
  287. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_apm_fallback_version.py +0 -0
  288. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_architecture_doc.py +0 -0
  289. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_astro_extraction.py +0 -0
  290. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_astro_import_ids.py +0 -0
  291. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_atomic_canvas_export.py +0 -0
  292. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_atomic_version_stamp.py +0 -0
  293. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_atomic_writes.py +0 -0
  294. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_backend_env_isolation.py +0 -0
  295. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_backend_extras.py +0 -0
  296. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_benchmark.py +0 -0
  297. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_benchmark_raw_graph.py +0 -0
  298. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_blade_extractor.py +0 -0
  299. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_build.py +0 -0
  300. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_build_merge_dedup_scope.py +0 -0
  301. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_build_merge_hyperedges_and_prune.py +0 -0
  302. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_build_merge_shrink_guard.py +0 -0
  303. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_builtin_global_type_refs.py +0 -0
  304. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_callflow_html.py +0 -0
  305. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_cargo_introspect.py +0 -0
  306. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_cargo_missing_manifest.py +0 -0
  307. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_carried_hyperedge_remap.py +0 -0
  308. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_case_sensitive_resolution.py +0 -0
  309. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_charmap_encoding.py +0 -0
  310. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_chunking.py +0 -0
  311. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_cjs_module_extension.py +0 -0
  312. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_claude_cli_backend.py +0 -0
  313. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_claude_md.py +0 -0
  314. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_cli_broken_pipe.py +0 -0
  315. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_cli_help.py +0 -0
  316. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_cluster.py +0 -0
  317. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_cluster_exclude_hubs.py +0 -0
  318. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_cobol_extractor.py +0 -0
  319. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_codebuddy.py +0 -0
  320. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_community_hub_labels.py +0 -0
  321. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_community_labels_skill.py +0 -0
  322. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_confidence.py +0 -0
  323. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_corrupt_graph_json.py +0 -0
  324. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_cpp_method_declarations.py +0 -0
  325. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_cpp_objc_cross_file_calls.py +0 -0
  326. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_cpp_preprocess.py +0 -0
  327. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_cross_extension_reexport_self_cycle.py +0 -0
  328. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_cross_language_call_resolution.py +0 -0
  329. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_cross_repo_external_call_guards.py +0 -0
  330. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_csharp_call_site_generic_args.py +0 -0
  331. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_csharp_enum_members.py +0 -0
  332. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_csharp_field_generic_args.py +0 -0
  333. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_csharp_generic_callsites.py +0 -0
  334. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_csharp_member_calls.py +0 -0
  335. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_csharp_member_nodes.py +0 -0
  336. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_csharp_object_creation.py +0 -0
  337. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_csharp_tuple_type_refs.py +0 -0
  338. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_csharp_type_resolution.py +0 -0
  339. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_dedup_remaps_hyperedges.py +0 -0
  340. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_dedup_survivor_richness.py +0 -0
  341. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_definition_file_portability.py +0 -0
  342. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_devin.py +0 -0
  343. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_docx_to_markdown.py +0 -0
  344. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_dotnet.py +0 -0
  345. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_duplicate_annotation_edges.py +0 -0
  346. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_elixir_import_resolution.py +0 -0
  347. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_erlang_extractor.py +0 -0
  348. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_evidence_binding.py +0 -0
  349. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_explain_cli.py +0 -0
  350. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_export.py +0 -0
  351. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_export_control_characters.py +0 -0
  352. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_export_idempotent_writes.py +0 -0
  353. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_export_path_length.py +0 -0
  354. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_external_stub_endpoints.py +0 -0
  355. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_extract_cache_location.py +0 -0
  356. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_extract_cli.py +0 -0
  357. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_extract_code_only_cli.py +0 -0
  358. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_extract_php_closures.py +0 -0
  359. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_extraction_spec_ids.py +0 -0
  360. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_extractors_registry.py +0 -0
  361. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_falkordb_integration.py +0 -0
  362. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_file_label_disambiguation.py +0 -0
  363. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_file_node_id_spec.py +0 -0
  364. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_file_slice.py +0 -0
  365. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_file_truncation_warning.py +0 -0
  366. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_forwarding_review_findings.py +0 -0
  367. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_gemini_hook.py +0 -0
  368. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_global_add_tag_inference.py +0 -0
  369. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_global_graph.py +0 -0
  370. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_go_builtin_call_targets.py +0 -0
  371. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_go_import_repoint.py +0 -0
  372. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_go_interface_methods.py +0 -0
  373. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_go_qualified_resolution.py +0 -0
  374. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_god_nodes_cli.py +0 -0
  375. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_god_nodes_exclude_hubs.py +0 -0
  376. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_google_workspace.py +0 -0
  377. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_graphdb_push_indexes.py +0 -0
  378. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_home_sandbox.py +0 -0
  379. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_hook_chain_survives_skip.py +0 -0
  380. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_hook_guard_token_match.py +0 -0
  381. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_hook_out_of_project_paths.py +0 -0
  382. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_hook_strict.py +0 -0
  383. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_hyperedge_member_shapes.py +0 -0
  384. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_hyperedge_roundtrip.py +0 -0
  385. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_hypergraph.py +0 -0
  386. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_id_normalization_contract.py +0 -0
  387. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_ignore_file_encoding.py +0 -0
  388. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_image_vision.py +0 -0
  389. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_import_extension_resolution.py +0 -0
  390. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_import_self_loops.py +0 -0
  391. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_imported_export_forwarding.py +0 -0
  392. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_incremental.py +0 -0
  393. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_incremental_mtime_collision.py +0 -0
  394. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_indirect_call_arrow_single_param_shadow.py +0 -0
  395. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_indirect_call_block_scoped_shadow.py +0 -0
  396. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_indirect_call_catch_binding_shadow.py +0 -0
  397. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_indirect_call_external_import_shadow.py +0 -0
  398. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_indirect_call_for_of_binding_shadow.py +0 -0
  399. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_indirect_call_function_expression_shadow.py +0 -0
  400. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_indirect_call_nested_closure_shadow.py +0 -0
  401. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_indirect_dispatch.py +0 -0
  402. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_indirect_dispatch_assign_return.py +0 -0
  403. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_indirect_dispatch_getattr.py +0 -0
  404. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_ingest.py +0 -0
  405. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_ingest_url_type.py +0 -0
  406. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_inherited_field_receivers.py +0 -0
  407. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_injection_sentinel_coverage.py +0 -0
  408. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_install.py +0 -0
  409. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_install_references.py +0 -0
  410. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_install_roundtrip.py +0 -0
  411. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_install_strings.py +0 -0
  412. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_install_upgrade.py +0 -0
  413. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_install_version_stamp.py +0 -0
  414. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_install_version_warning.py +0 -0
  415. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_issue_3405_python_resolution.py +0 -0
  416. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_issue_3472_source_file_collision.py +0 -0
  417. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_java_enum_members.py +0 -0
  418. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_java_member_calls.py +0 -0
  419. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_java_type_resolution.py +0 -0
  420. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_js_callback_calls.py +0 -0
  421. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_js_destructured_export.py +0 -0
  422. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_js_dynamic_import_affected.py +0 -0
  423. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_js_dynamic_imports.py +0 -0
  424. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_js_exported_fn_call_resolution.py +0 -0
  425. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_js_exported_scalar_bindings.py +0 -0
  426. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_js_import_resolution.py +0 -0
  427. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_js_module_level_anonymous_calls.py +0 -0
  428. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_js_this_member_expressions.py +0 -0
  429. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_jsconfig_baseurl.py +0 -0
  430. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_jsx_component_usage_calls.py +0 -0
  431. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_kotlin_grammar.py +0 -0
  432. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_kotlin_object_literal.py +0 -0
  433. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_label_retry.py +0 -0
  434. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_labeling.py +0 -0
  435. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_language_resolvers.py +0 -0
  436. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_llm_parser.py +0 -0
  437. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_llm_parser_reasoning.py +0 -0
  438. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_long_path_hashing.py +0 -0
  439. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_lua_import.py +0 -0
  440. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_lua_method_calls.py +0 -0
  441. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_manifest_ingest.py +0 -0
  442. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_manifest_tomli_required.py +0 -0
  443. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_markdown_code_spans.py +0 -0
  444. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_mcp_ingest.py +0 -0
  445. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_merge_chunks_validation.py +0 -0
  446. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_minhash.py +0 -0
  447. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_multigraph_compat.py +0 -0
  448. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_multigraph_diagnostics.py +0 -0
  449. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_multilang.py +0 -0
  450. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_no_dedup_flag.py +0 -0
  451. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_node_id_canonical.py +0 -0
  452. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_non_regular_files.py +0 -0
  453. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_non_string_node_ids.py +0 -0
  454. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_objc_category_interfaces.py +0 -0
  455. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_objc_field_table_remap.py +0 -0
  456. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_objc_member_calls.py +0 -0
  457. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_objc_property_ivar_receivers.py +0 -0
  458. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_obsidian_dangling_member.py +0 -0
  459. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_obsidian_filename_cap.py +0 -0
  460. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_obsidian_unicode_tags.py +0 -0
  461. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_obsidian_vault_migration.py +0 -0
  462. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_ocaml.py +0 -0
  463. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_office_incremental.py +0 -0
  464. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_office_limits.py +0 -0
  465. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_ollama.py +0 -0
  466. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_ollama_retry_cap.py +0 -0
  467. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_openai_custom_endpoint.py +0 -0
  468. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_out_dir_evidence.py +0 -0
  469. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_oversized_document_slicing.py +0 -0
  470. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_owned_type_sources.py +0 -0
  471. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_package_json_subpath_imports.py +0 -0
  472. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_partial_cache.py +0 -0
  473. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_pascal_call_scoping.py +0 -0
  474. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_pascal_resolution.py +0 -0
  475. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_paths.py +0 -0
  476. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_pdf_extra_warning.py +0 -0
  477. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_pdf_slicing.py +0 -0
  478. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_pdf_token_estimate.py +0 -0
  479. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_pg_introspect.py +0 -0
  480. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_phantom_cross_package_call.py +0 -0
  481. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_phantom_external_import.py +0 -0
  482. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_php_object_creation.py +0 -0
  483. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_php_type_resolution.py +0 -0
  484. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_pin_hash_seed.py +0 -0
  485. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_pipeline.py +0 -0
  486. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_provider_registry.py +0 -0
  487. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_prs.py +0 -0
  488. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_prune_sweeps_orphans.py +0 -0
  489. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_python_decorators.py +0 -0
  490. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_python_parse_memoization.py +0 -0
  491. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_python_underscore_resolution.py +0 -0
  492. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_query_cli.py +0 -0
  493. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_query_induced_edges.py +0 -0
  494. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_query_mcp_direction.py +0 -0
  495. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_query_names_its_graph.py +0 -0
  496. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_querylog.py +0 -0
  497. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_r_extractor.py +0 -0
  498. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_rationale.py +0 -0
  499. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_read_hook.py +0 -0
  500. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_reflect.py +0 -0
  501. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_relation_collapse_precedence.py +0 -0
  502. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_replace_or_append_section.py +0 -0
  503. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_report.py +0 -0
  504. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_report_gap_thresholds.py +0 -0
  505. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_resolve_memoization.py +0 -0
  506. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_ruby_bare_self_send_calls.py +0 -0
  507. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_ruby_resolution.py +0 -0
  508. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_rust_builtin_type_hubs.py +0 -0
  509. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_rust_self_member_calls.py +0 -0
  510. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_scala_abstract_methods.py +0 -0
  511. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_scala_self_type.py +0 -0
  512. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_scip_ingest.py +0 -0
  513. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_search_hook.py +0 -0
  514. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_security.py +0 -0
  515. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_semantic_cache_basename_recovery.py +0 -0
  516. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_semantic_cache_out_root.py +0 -0
  517. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_semantic_cleanup.py +0 -0
  518. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_semantic_fragment_sanitize.py +0 -0
  519. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_semantic_id_remap_root.py +0 -0
  520. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_semantic_similarity.py +0 -0
  521. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_serve_http.py +0 -0
  522. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_settings_merge.py +0 -0
  523. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_skill_auto_refresh.py +0 -0
  524. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_skill_version_warning.py +0 -0
  525. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_skillgen.py +0 -0
  526. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_skillgen_input_path_injection.py +0 -0
  527. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_solidity_extractor.py +0 -0
  528. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_source_key_memoization.py +0 -0
  529. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_sourceless_call_targets.py +0 -0
  530. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_src_layout_import_resolution.py +0 -0
  531. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_stale_prune.py +0 -0
  532. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_stat_index_husk.py +0 -0
  533. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_stat_index_portability.py +0 -0
  534. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_swift_builtin_noise.py +0 -0
  535. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_swift_computed_properties.py +0 -0
  536. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_swift_import_resolution.py +0 -0
  537. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_swift_protocol_requirements.py +0 -0
  538. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_symbol_resolution.py +0 -0
  539. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_terraform.py +0 -0
  540. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_terraform_modules.py +0 -0
  541. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_trailing_newline_not_a_syntax_error.py +0 -0
  542. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_transcribe.py +0 -0
  543. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_tree_html.py +0 -0
  544. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_ts_abstract_methods.py +0 -0
  545. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_ts_decorators.py +0 -0
  546. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_ts_generators.py +0 -0
  547. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_ts_import_require.py +0 -0
  548. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_ts_import_type_arguments.py +0 -0
  549. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_ts_inheritance.py +0 -0
  550. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_ts_namespace.py +0 -0
  551. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_ts_new_expression_calls.py +0 -0
  552. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_ts_parse_warning.py +0 -0
  553. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_ts_receiver_member_calls.py +0 -0
  554. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_type_only_import_cycles.py +0 -0
  555. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_typescript_enum_members.py +0 -0
  556. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_typescript_module_extensions.py +0 -0
  557. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_uninstall_scope.py +0 -0
  558. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_uninstall_symlink.py +0 -0
  559. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_unmapped_at_alias_resolution.py +0 -0
  560. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_unverified_semantic_shrink.py +0 -0
  561. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_validate.py +0 -0
  562. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_vue_extraction.py +0 -0
  563. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_walk_python_tree_iterative.py +0 -0
  564. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_watch.py +0 -0
  565. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_watch_manifest_location.py +0 -0
  566. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_wheel_packaging.py +0 -0
  567. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_wiki.py +0 -0
  568. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_wiki_link_filename_parity.py +0 -0
  569. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_wiki_truncation_indicator.py +0 -0
  570. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_word_count_cache.py +0 -0
  571. {graphifyy-0.9.74 → graphifyy-0.9.76}/tests/test_zero_node_no_cache.py +0 -0
@@ -1,9 +1,10 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: graphifyy
3
- Version: 0.9.74
3
+ Version: 0.9.76
4
4
  Summary: AI coding assistant skill (Claude Code, CodeBuddy, Codex, OpenCode, Kilo Code, Cursor, Gemini CLI, Aider, OpenClaw, Factory Droid, Trae, Hermes, Kiro, Pi, Devin CLI, Google Antigravity) - turn any folder of code, docs, papers, images, or videos into a queryable knowledge graph
5
5
  License-Expression: Apache-2.0
6
- Project-URL: Homepage, https://github.com/Graphify-Labs/graphify
6
+ Project-URL: Homepage, https://graphify.com
7
+ Project-URL: Documentation, https://docs.graphify.com
7
8
  Project-URL: Repository, https://github.com/Graphify-Labs/graphify
8
9
  Project-URL: Issues, https://github.com/Graphify-Labs/graphify/issues
9
10
  Keywords: claude,claude-code,codex,opencode,kilo,cursor,gemini,aider,kiro,pi,devin,knowledge-graph,rag,graphrag,obsidian,community-detection,tree-sitter,leiden,llm
@@ -170,6 +171,7 @@ Dynamic: license-file
170
171
  <a href="LICENSE"><img src="https://img.shields.io/badge/License-Apache%202.0-blue" alt="License: Apache-2.0"/></a>
171
172
  <a href="https://pepy.tech/project/graphifyy"><img src="https://img.shields.io/pepy/dt/graphifyy?color=blue&label=downloads" alt="Downloads"/></a>
172
173
  <a href="https://docs.graphify.com"><img src="https://img.shields.io/badge/Docs-docs.graphify.com-0b7285?style=flat&logo=readthedocs&logoColor=white" alt="Docs"/></a>
174
+ <a href="https://smithery.ai/servers/graphify/graphify"><img src="https://img.shields.io/badge/Smithery-MCP%20server-5c4ee5?style=flat&logo=data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0iI2ZmZiI+PHBhdGggZD0iTTEyIDJMMiA3djEwbDEwIDUgMTAtNVY3eiIvPjwvc3ZnPg==" alt="Smithery"/></a>
173
175
  <a href="https://discord.gg/XDnKVpzdXB"><img src="https://img.shields.io/badge/Discord-Join-5865F2?style=flat&logo=discord&logoColor=white" alt="Discord"/></a>
174
176
  <a href="https://www.youtube.com/@graphifylabs"><img src="https://img.shields.io/badge/YouTube-Graphify%20Labs-FF0000?style=flat&logo=youtube&logoColor=white" alt="YouTube"/></a>
175
177
  <a href="https://www.linkedin.com/company/graphify-labs"><img src="https://img.shields.io/badge/LinkedIn-Graphify%20Labs-0077B5?logo=linkedin" alt="LinkedIn"/></a>
@@ -662,6 +664,19 @@ python -m graphify.serve graphify-out/graph.json --transport http --host 0.0.0.0
662
664
 
663
665
  The MCP server gives your assistant structured access: `query_graph`, `get_node`, `get_neighbors`, `shortest_path`, `list_prs`, `get_pr_impact`, `triage_prs`.
664
666
 
667
+ ### One-click install (VS Code · Smithery)
668
+
669
+ Prefer not to edit MCP config by hand? Register the graphify MCP server in one step. Run `/graphify` first so a `graphify-out/graph.json` exists in the workspace; the server reads it on launch.
670
+
671
+ [![Install in VS Code](https://img.shields.io/badge/VS_Code-Install_graphify_MCP-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://insiders.vscode.dev/redirect/mcp/install?name=graphify&config=%7B%22command%22%3A%22uvx%22%2C%22args%22%3A%5B%22--from%22%2C%22graphifyy%5Bmcp%5D%22%2C%22graphify-mcp%22%5D%7D)
672
+
673
+ The button adds a `graphify` server that launches with `uvx --from "graphifyy[mcp]" graphify-mcp` (no API key, no prior install). Or install via [Smithery](https://smithery.ai/servers/graphify/graphify) for Claude, Cursor, VS Code, and other clients:
674
+
675
+ ```bash
676
+ # pick your client (claude, cursor, vscode, windsurf, ...)
677
+ npx -y @smithery/cli install graphify/graphify --client claude
678
+ ```
679
+
665
680
  ### Shared HTTP server
666
681
 
667
682
  `--transport stdio` (the default) spawns one local server per developer. `--transport http` serves the same tools over the MCP Streamable HTTP transport, so a single shared process can serve the graph for the whole team — clients point their IDE MCP config at `http://<host>:8080/mcp` instead of running graphify locally.
@@ -20,6 +20,7 @@
20
20
  <a href="LICENSE"><img src="https://img.shields.io/badge/License-Apache%202.0-blue" alt="License: Apache-2.0"/></a>
21
21
  <a href="https://pepy.tech/project/graphifyy"><img src="https://img.shields.io/pepy/dt/graphifyy?color=blue&label=downloads" alt="Downloads"/></a>
22
22
  <a href="https://docs.graphify.com"><img src="https://img.shields.io/badge/Docs-docs.graphify.com-0b7285?style=flat&logo=readthedocs&logoColor=white" alt="Docs"/></a>
23
+ <a href="https://smithery.ai/servers/graphify/graphify"><img src="https://img.shields.io/badge/Smithery-MCP%20server-5c4ee5?style=flat&logo=data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0iI2ZmZiI+PHBhdGggZD0iTTEyIDJMMiA3djEwbDEwIDUgMTAtNVY3eiIvPjwvc3ZnPg==" alt="Smithery"/></a>
23
24
  <a href="https://discord.gg/XDnKVpzdXB"><img src="https://img.shields.io/badge/Discord-Join-5865F2?style=flat&logo=discord&logoColor=white" alt="Discord"/></a>
24
25
  <a href="https://www.youtube.com/@graphifylabs"><img src="https://img.shields.io/badge/YouTube-Graphify%20Labs-FF0000?style=flat&logo=youtube&logoColor=white" alt="YouTube"/></a>
25
26
  <a href="https://www.linkedin.com/company/graphify-labs"><img src="https://img.shields.io/badge/LinkedIn-Graphify%20Labs-0077B5?logo=linkedin" alt="LinkedIn"/></a>
@@ -512,6 +513,19 @@ python -m graphify.serve graphify-out/graph.json --transport http --host 0.0.0.0
512
513
 
513
514
  The MCP server gives your assistant structured access: `query_graph`, `get_node`, `get_neighbors`, `shortest_path`, `list_prs`, `get_pr_impact`, `triage_prs`.
514
515
 
516
+ ### One-click install (VS Code · Smithery)
517
+
518
+ Prefer not to edit MCP config by hand? Register the graphify MCP server in one step. Run `/graphify` first so a `graphify-out/graph.json` exists in the workspace; the server reads it on launch.
519
+
520
+ [![Install in VS Code](https://img.shields.io/badge/VS_Code-Install_graphify_MCP-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://insiders.vscode.dev/redirect/mcp/install?name=graphify&config=%7B%22command%22%3A%22uvx%22%2C%22args%22%3A%5B%22--from%22%2C%22graphifyy%5Bmcp%5D%22%2C%22graphify-mcp%22%5D%7D)
521
+
522
+ The button adds a `graphify` server that launches with `uvx --from "graphifyy[mcp]" graphify-mcp` (no API key, no prior install). Or install via [Smithery](https://smithery.ai/servers/graphify/graphify) for Claude, Cursor, VS Code, and other clients:
523
+
524
+ ```bash
525
+ # pick your client (claude, cursor, vscode, windsurf, ...)
526
+ npx -y @smithery/cli install graphify/graphify --client claude
527
+ ```
528
+
515
529
  ### Shared HTTP server
516
530
 
517
531
  `--transport stdio` (the default) spawns one local server per developer. `--transport http` serves the same tools over the MCP Streamable HTTP transport, so a single shared process can serve the graph for the whole team — clients point their IDE MCP config at `http://<host>:8080/mcp` instead of running graphify locally.
@@ -760,6 +760,9 @@ def _run_cli() -> None:
760
760
  print(" merge-driver <base> <current> <other> git merge driver: union-merge two graph.json files (set up via hook install)")
761
761
  print(" merge-graphs <g1> <g2> merge two or more graph.json files into one cross-repo graph")
762
762
  print(" --out <path> output path (default: graphify-out/merged-graph.json)")
763
+ print(" --previous <path> restore the previous merged clustering's community ids")
764
+ print(" (by node id) instead of each input's own per-source ids,")
765
+ print(" so a later cluster-only run reuses labels correctly")
763
766
  print(" --branch <branch> checkout a specific branch (default: repo default)")
764
767
  print(" --out <dir> clone to a custom directory (default: ~/.graphify/repos/<owner>/<repo>)")
765
768
  print(" add <url> fetch a URL and save it to ./raw, then update the graph")
@@ -468,6 +468,9 @@ def suggest_questions(
468
468
  Generate questions the graph is uniquely positioned to answer.
469
469
  Based on: AMBIGUOUS edges, bridge nodes, underexplored god nodes, isolated nodes.
470
470
  Each question has a 'type', 'question', and 'why' field.
471
+ Interleave question types before applying top_n so an abundant early type
472
+ cannot crowd out later signals. Preserve the existing order within each type
473
+ and category generation order when the limit is smaller than the type count.
471
474
  """
472
475
  if community_labels:
473
476
  community_labels = {int(k) if isinstance(k, str) else k: v for k, v in community_labels.items()}
@@ -583,7 +586,16 @@ def suggest_questions(
583
586
  ),
584
587
  }]
585
588
 
586
- return questions[:top_n]
589
+ from itertools import zip_longest
590
+
591
+ by_type: dict[str, list[dict]] = {}
592
+ for question in questions:
593
+ by_type.setdefault(question["type"], []).append(question)
594
+ diversified = [
595
+ question for row in zip_longest(*by_type.values())
596
+ for question in row if question is not None
597
+ ]
598
+ return diversified[:top_n]
587
599
 
588
600
 
589
601
  def graph_diff(G_old: nx.Graph, G_new: nx.Graph) -> dict:
@@ -70,9 +70,12 @@ _CONFIDENCE_RANK: dict[str, int] = {"EXTRACTED": 3, "INFERRED": 2, "AMBIGUOUS":
70
70
  # on-disk graph.json (written by the incremental update path) keep the edge with
71
71
  # no matching node — an undeclared endpoint every loader materialises as an
72
72
  # attribute-less phantom (#2873). For these relations we instead mint a typed
73
- # external stub node so every edge endpoint resolves. Deliberately NOT `calls`:
74
- # a sourceless external call target is suppressed on purpose (#3156) to avoid a
75
- # phantom god-node, and that policy is unchanged here.
73
+ # external stub node so every edge endpoint resolves. `calls` is intentionally
74
+ # NOT in this set: a `calls` edge to an external target only survives when the
75
+ # module was also imported (its import edge pre-mints the stub, see the Python
76
+ # external-call resolution in #3793); a `calls` edge to a never-imported target
77
+ # is still dropped as a phantom (#3156). External stubs stay out of god-node
78
+ # ranking regardless (analyze._is_concept_node filters source_file="").
76
79
  _EXTERNAL_STUB_RELATIONS: frozenset[str] = frozenset(
77
80
  {"imports", "imports_from", "re_exports"}
78
81
  )
@@ -1564,6 +1567,7 @@ def build(
1564
1567
  combined["input_tokens"] += ext.get("input_tokens", 0)
1565
1568
  combined["output_tokens"] += ext.get("output_tokens", 0)
1566
1569
  _root = str(Path(root).resolve()) if root else None
1570
+ _dedup_collapsed = 0
1567
1571
  if dedup and combined["nodes"]:
1568
1572
  # Numeric ids must be str before dedup, which keys on them and would
1569
1573
  # raise TypeError in _pick_winner's regex search (#2326). build_from_json
@@ -1585,6 +1589,7 @@ def build(
1585
1589
  n["source_file"] = _norm_source_file(n["source_file"], _root)
1586
1590
  if "definition_file" in n:
1587
1591
  n["definition_file"] = _norm_source_file(n["definition_file"], _root)
1592
+ _before_dedup = len(combined["nodes"])
1588
1593
  combined["nodes"], combined["edges"] = deduplicate_entities(
1589
1594
  combined["nodes"], combined["edges"], communities={},
1590
1595
  dedup_llm_backend=dedup_llm_backend, root=_root,
@@ -1593,7 +1598,28 @@ def build(
1593
1598
  hyperedges=combined.get("hyperedges"),
1594
1599
  protected_ids=protected_ids,
1595
1600
  )
1596
- return build_from_json(combined, directed=directed, root=_root)
1601
+ _dedup_collapsed = _before_dedup - len(combined["nodes"])
1602
+ G = build_from_json(combined, directed=directed, root=_root)
1603
+ # CLI reads this to tell a dedup shrink from a file deletion (#3774).
1604
+ # Popped before to_json so it is not stored in graph.json.
1605
+ if _dedup_collapsed:
1606
+ G.graph["_dedup_collapsed"] = _dedup_collapsed
1607
+ return G
1608
+
1609
+
1610
+ def take_shrink_accounting(G) -> tuple[int, int]:
1611
+ """Read and remove the #3774 counters.
1612
+
1613
+ ``build`` / ``build_merge`` stash these on ``G.graph`` for the extract
1614
+ CLI. ``to_json`` also removes them so any other writer cannot persist
1615
+ them into graph.json.
1616
+ """
1617
+ attrs = getattr(G, "graph", None)
1618
+ if not isinstance(attrs, dict):
1619
+ return 0, 0
1620
+ collapsed = int(attrs.pop("_dedup_collapsed", 0) or 0)
1621
+ pruned = int(attrs.pop("_pruned_node_count", 0) or 0)
1622
+ return collapsed, pruned
1597
1623
 
1598
1624
 
1599
1625
  def _norm_label(label: str | None) -> str:
@@ -2249,6 +2275,17 @@ def build_merge(
2249
2275
  if orphaned:
2250
2276
  G.remove_nodes_from(orphaned)
2251
2277
  n_nodes += len(orphaned)
2278
+ # Excuse only saved nodes this prune removed (#3774). A stub minted
2279
+ # in this run and then orphaned was never in graph.json, so counting
2280
+ # it would hide a dedup shrink. The print below still uses n_nodes,
2281
+ # which includes that stub.
2282
+ _disk_ids = {
2283
+ n.get("id") for n in _disk_nodes
2284
+ if isinstance(n, dict) and n.get("id") is not None
2285
+ }
2286
+ G.graph["_pruned_node_count"] = sum(
2287
+ 1 for nid in (*to_remove, *orphaned) if nid in _disk_ids
2288
+ )
2252
2289
 
2253
2290
  # Report only the prune entries that ACTUALLY matched something — not
2254
2291
  # len(prune_sources), which counted every entry as pruned-from even
@@ -36,7 +36,7 @@ except Exception:
36
36
  _EXTRACTOR_VERSION = "unknown"
37
37
 
38
38
  # Bump when AST cache-key semantics change independently of the package version.
39
- _AST_CACHE_SCHEMA = 4 # Rust generic-impl identity markers + Terraform block attributes.
39
+ _AST_CACHE_SCHEMA = 5 # Python receiver-shadow facts in persisted raw calls.
40
40
 
41
41
  # Version dirs already swept this process — cleanup runs once per (base, version).
42
42
  _cleaned_ast_dirs: set[str] = set()
@@ -205,6 +205,14 @@ _stat_index_root: Path | None = None
205
205
  # (cache_root, #1774) — the two differ under --out and must not be conflated.
206
206
  _stat_index_anchor: Path | None = None
207
207
  _stat_index_dirty: bool = False
208
+ _stat_index_atexit_registered: bool = False
209
+ # The resolved on-disk path for the CURRENTLY bound root, captured at bind
210
+ # time (#3989). A mid-process root switch must flush the OUTGOING root to
211
+ # the file it was actually loaded from, not wherever the live _GRAPHIFY_OUT
212
+ # happens to point by the time the switch is detected — a caller following
213
+ # the documented one-root-per-call pattern (set _GRAPHIFY_OUT, then call)
214
+ # has already moved it on to the NEW root before the switch is noticed.
215
+ _stat_index_path: Path | None = None
208
216
 
209
217
 
210
218
  # Filesystem mtime granularity, in nanoseconds. A stat signature only proves a
@@ -328,8 +336,31 @@ def _stat_index_file(root: Path) -> Path:
328
336
 
329
337
  def _ensure_stat_index(root: Path, cache_root: "Path | None" = None) -> None:
330
338
  global _stat_index, _stat_index_root, _stat_index_anchor, _stat_index_dirty
339
+ global _stat_index_atexit_registered, _stat_index_path
340
+ new_root = Path(cache_root if cache_root is not None else root).resolve()
331
341
  if _stat_index_root is not None:
332
- return
342
+ if _stat_index_root == new_root:
343
+ return
344
+ # A later call in the same process named a DIFFERENT cache root
345
+ # (#3989): the index was bound to the first root ever seen and never
346
+ # re-bound, so every root after the first served (on read) whatever
347
+ # the first root's file happened to contain, and (on write/exit)
348
+ # deposited its own freshly-computed entries into the FIRST root's
349
+ # file instead of its own — silently poisoning one project's
350
+ # stat-index.json with paths from a completely different project.
351
+ # Flush the outgoing root's own pending entries to its own file
352
+ # before switching, then load the new root fresh, so each root's
353
+ # file only ever holds that root's own entries.
354
+ warnings.warn(
355
+ f"stat index switched from cache root {str(_stat_index_root)!r} to "
356
+ f"{str(new_root)!r} within one process; each root's "
357
+ "stat-index.json now only reflects its own files, but library "
358
+ "callers crossing project roots in one process should still use "
359
+ "a fresh process per root to avoid losing the fastpath (#3989).",
360
+ RuntimeWarning,
361
+ stacklevel=3,
362
+ )
363
+ _flush_stat_index()
333
364
  # _stat_index_root determines the cache FILE location, so honoring an
334
365
  # explicit cache_root keeps detect()'s word-count cache under the requested
335
366
  # --out dir instead of polluting the scanned corpus with a stray
@@ -337,9 +368,10 @@ def _ensure_stat_index(root: Path, cache_root: "Path | None" = None) -> None:
337
368
  # in-memory keys stay absolute, but the on-disk index stores in-anchor keys
338
369
  # relative so a moved/cloned corpus still hits (#2199) — same load/save
339
370
  # re-anchoring the detect manifest uses.
340
- _stat_index_root = Path(cache_root if cache_root is not None else root).resolve()
371
+ _stat_index_root = new_root
341
372
  _stat_index_anchor = Path(root).resolve()
342
373
  p = _stat_index_file(_stat_index_root)
374
+ _stat_index_path = p
343
375
  _stat_index = {}
344
376
  if p.exists():
345
377
  try:
@@ -357,14 +389,23 @@ def _ensure_stat_index(root: Path, cache_root: "Path | None" = None) -> None:
357
389
  _stat_index[_stat_key_to_absolute(k, _stat_index_anchor)] = v
358
390
  except (json.JSONDecodeError, OSError):
359
391
  _stat_index = {}
360
- atexit.register(_flush_stat_index)
392
+ _stat_index_dirty = False
393
+ if not _stat_index_atexit_registered:
394
+ atexit.register(_flush_stat_index)
395
+ _stat_index_atexit_registered = True
361
396
 
362
397
 
363
398
  def _flush_stat_index() -> None:
364
399
  global _stat_index_dirty, _stat_index_root
365
400
  if not _stat_index_dirty or _stat_index_root is None:
366
401
  return
367
- p = _stat_index_file(_stat_index_root)
402
+ # Use the path captured when this root was bound (#3989), not a fresh
403
+ # _stat_index_file(_stat_index_root) call: a mid-process root switch runs
404
+ # this to flush the OUTGOING root, by which point a caller following the
405
+ # documented set-_GRAPHIFY_OUT-then-call pattern has already pointed
406
+ # _GRAPHIFY_OUT at the NEW root, and re-deriving here would flush the old
407
+ # root's entries into the new root's file instead of its own.
408
+ p = _stat_index_path if _stat_index_path is not None else _stat_index_file(_stat_index_root)
368
409
  # Build the on-disk form (#2199): prune entries whose file is gone (the
369
410
  # index otherwise grows without bound), then store in-anchor keys as
370
411
  # forward-slash relative paths so the index survives a corpus move/clone.
@@ -81,6 +81,29 @@ _GEMINI_NUDGE_TEXT = (
81
81
  )
82
82
 
83
83
 
84
+ _DEFAULT_NUDGE_GRAPH = "graphify-out/graph.json"
85
+
86
+
87
+ def _nudge_for_out(nudge: str) -> str:
88
+ """Name the effective graph.json in a hook-guard reminder (#4040).
89
+
90
+ The reminder constants spell the default ``graphify-out/graph.json``. When
91
+ ``GRAPHIFY_OUT`` points elsewhere, the reminder must name the graph that was
92
+ actually found. Default output dir: returned byte-identical.
93
+ """
94
+ from graphify.paths import out_path
95
+ try:
96
+ ref = out_path("graph.json").as_posix()
97
+ except Exception:
98
+ return nudge
99
+ if ref == _DEFAULT_NUDGE_GRAPH:
100
+ return nudge
101
+ d = json.loads(nudge)
102
+ out = d["hookSpecificOutput"]
103
+ out["additionalContext"] = out["additionalContext"].replace(_DEFAULT_NUDGE_GRAPH, ref)
104
+ return json.dumps(d, ensure_ascii=False, separators=(",", ":")) + "\n"
105
+
106
+
84
107
  def _default_graph_path() -> str:
85
108
  return str(Path(_GRAPHIFY_OUT) / "graph.json")
86
109
 
@@ -867,7 +890,7 @@ def _run_hook_guard(kind: str, strict: bool = False) -> None:
867
890
  is_grep_tool = not cmd_str and bool(t.get("pattern"))
868
891
  is_bash_search = bool(cmd_str) and _bash_invokes_search(cmd_str)
869
892
  if (is_grep_tool or is_bash_search) and out_path("graph.json").is_file():
870
- sys.stdout.write(_SEARCH_NUDGE)
893
+ sys.stdout.write(_nudge_for_out(_SEARCH_NUDGE))
871
894
  elif kind == "read":
872
895
  vals = [str(t.get("file_path") or ""), str(t.get("pattern") or ""), str(t.get("path") or "")]
873
896
  tails = [
@@ -977,7 +1000,7 @@ def _run_hook_guard(kind: str, strict: bool = False) -> None:
977
1000
  except Exception:
978
1001
  pass
979
1002
  if stale:
980
- sys.stdout.write(_READ_NUDGE_STALE)
1003
+ sys.stdout.write(_nudge_for_out(_READ_NUDGE_STALE))
981
1004
  return
982
1005
  # Strict block: Read tool only, first time per session, not recently
983
1006
  # oriented, and the file is demonstrably indexed.
@@ -988,7 +1011,7 @@ def _run_hook_guard(kind: str, strict: bool = False) -> None:
988
1011
  and _mark_session_denied(str(d.get("session_id") or "")):
989
1012
  sys.stdout.write(_READ_DENY)
990
1013
  return
991
- sys.stdout.write(_READ_NUDGE)
1014
+ sys.stdout.write(_nudge_for_out(_READ_NUDGE))
992
1015
  except Exception:
993
1016
  pass
994
1017
 
@@ -1605,7 +1628,7 @@ def dispatch_command(cmd: str) -> None:
1605
1628
  file=sys.stderr,
1606
1629
  )
1607
1630
  sys.exit(1)
1608
- from graphify.serve import _pick_scored_endpoint, _score_nodes
1631
+ from graphify.serve import _resolve_path_endpoint
1609
1632
  from networkx.readwrite import json_graph
1610
1633
  import networkx as _nx
1611
1634
 
@@ -1656,16 +1679,15 @@ def dispatch_command(cmd: str) -> None:
1656
1679
  G = json_graph.node_link_graph(_raw, edges="links")
1657
1680
  except TypeError:
1658
1681
  G = json_graph.node_link_graph(_raw)
1659
- src_scored = _score_nodes(G, [t.lower() for t in source_label.split()])
1660
- tgt_scored = _score_nodes(G, [t.lower() for t in target_label.split()])
1661
- if not src_scored:
1662
- print(f"No node matching '{source_label}' found.", file=sys.stderr)
1663
- sys.exit(1)
1664
- if not tgt_scored:
1665
- print(f"No node matching '{target_label}' found.", file=sys.stderr)
1666
- sys.exit(1)
1667
- src_nid = _pick_scored_endpoint(G, src_scored, source_label)
1668
- tgt_nid = _pick_scored_endpoint(G, tgt_scored, target_label)
1682
+ src_nid, src_scored, src_err = _resolve_path_endpoint(G, source_label)
1683
+ tgt_nid, tgt_scored, tgt_err = _resolve_path_endpoint(G, target_label)
1684
+ for _label, _nid, _err in (
1685
+ (source_label, src_nid, src_err),
1686
+ (target_label, tgt_nid, tgt_err),
1687
+ ):
1688
+ if _err or _nid is None:
1689
+ print(_err or f"No node matching '{_label}' found.", file=sys.stderr)
1690
+ sys.exit(1)
1669
1691
  # Ambiguity guard: when both queries resolve to the same node, the
1670
1692
  # shortest path is trivially zero hops, which is almost never what the
1671
1693
  # caller wanted (see bug #828).
@@ -1711,6 +1733,20 @@ def dispatch_command(cmd: str) -> None:
1711
1733
  _dg.add_edges_from(sorted(
1712
1734
  (d.get("_src", u), d.get("_tgt", v)) for u, v, d in G.edges(data=True)
1713
1735
  ))
1736
+ # A `contains` edge only runs file -> symbol; there is no
1737
+ # stored edge back out to the containing file, so a route that
1738
+ # reaches a symbol (via an `imports`/`calls`/`references` hop)
1739
+ # can never continue on to the file that defines it, and a
1740
+ # file-to-file dependency routed through a shared symbol finds
1741
+ # no path at all even though both halves of the route exist
1742
+ # (#3878). Add the implied reverse hop for traversal only — the
1743
+ # printed segment still recovers the real stored `contains`
1744
+ # edge and its true direction from G, same as any other hop.
1745
+ _dg.add_edges_from(sorted(
1746
+ (d.get("_tgt", v), d.get("_src", u))
1747
+ for u, v, d in G.edges(data=True)
1748
+ if d.get("relation") == "contains"
1749
+ ))
1714
1750
  path_nodes = _nx.shortest_path(_dg, src_nid, tgt_nid)
1715
1751
  except (_nx.NetworkXNoPath, _nx.NodeNotFound):
1716
1752
  if undirected:
@@ -1768,7 +1804,7 @@ def dispatch_command(cmd: str) -> None:
1768
1804
  if len(sys.argv) < 3:
1769
1805
  print('Usage: graphify explain "<node>" [--graph path]', file=sys.stderr)
1770
1806
  sys.exit(1)
1771
- from graphify.serve import _find_node, find_node_ambiguity
1807
+ from graphify.serve import _ambiguity_message, _find_node, find_node_ambiguity
1772
1808
  from networkx.readwrite import json_graph
1773
1809
 
1774
1810
  label = sys.argv[2]
@@ -1797,14 +1833,7 @@ def dispatch_command(cmd: str) -> None:
1797
1833
  sys.exit(0)
1798
1834
  rivals = find_node_ambiguity(G, label)
1799
1835
  if rivals:
1800
- print(f"Ambiguous: '{label}' matches {len(rivals)} nodes in different files.")
1801
- for rival in rivals:
1802
- print(f" {G.nodes[rival].get('source_file') or rival}")
1803
- print(f" id: {rival}")
1804
- print(
1805
- f"Retry with path::symbol using one of the paths above (e.g. "
1806
- f"<path>::{label}) or the full node id."
1807
- )
1836
+ print(_ambiguity_message(G, label, rivals))
1808
1837
  sys.exit(1)
1809
1838
  nid = matches[0]
1810
1839
  d = G.nodes[nid]
@@ -2358,7 +2387,8 @@ def dispatch_command(cmd: str) -> None:
2358
2387
  # a report and labels describing a clustering graph.json does not contain
2359
2388
  # are worse than no run at all (#2436).
2360
2389
  if not to_json(G, communities, str(out / "graph.json"),
2361
- community_labels=labels, built_at_commit=_commit):
2390
+ community_labels=labels, built_at_commit=_commit,
2391
+ original_links=_raw.get("links") or _raw.get("edges") or []):
2362
2392
  if not stale_marker_preexisted:
2363
2393
  _clear_html_stale_marker()
2364
2394
  print(
@@ -2663,20 +2693,48 @@ def dispatch_command(cmd: str) -> None:
2663
2693
  args = sys.argv[2:]
2664
2694
  graph_paths: list[Path] = []
2665
2695
  out_path = Path(_GRAPHIFY_OUT) / "merged-graph.json"
2696
+ previous_path: "Path | None" = None
2666
2697
  i = 0
2667
2698
  while i < len(args):
2668
2699
  if args[i] == "--out" and i + 1 < len(args):
2669
2700
  out_path = Path(args[i + 1])
2670
2701
  i += 2
2702
+ elif args[i] == "--previous" and i + 1 < len(args):
2703
+ previous_path = Path(args[i + 1])
2704
+ i += 2
2671
2705
  else:
2672
2706
  graph_paths.append(Path(args[i]))
2673
2707
  i += 1
2674
2708
  if len(graph_paths) < 2:
2675
2709
  print(
2676
- "Usage: graphify merge-graphs <graph1.json> <graph2.json> [...] [--out merged.json]",
2710
+ "Usage: graphify merge-graphs <graph1.json> <graph2.json> [...] "
2711
+ "[--out merged.json] [--previous previous-merged-graph.json]",
2677
2712
  file=sys.stderr,
2678
2713
  )
2679
2714
  sys.exit(1)
2715
+ # Each input numbers its own communities from 0, so the merged output
2716
+ # carries per-source ids (offset since #3014) that have no relation to
2717
+ # the previous MERGED clustering .graphify_labels.json was written
2718
+ # against. cluster-only's label-reuse remap reads this file's
2719
+ # `community` field as "the previous clustering" and silently mismatches
2720
+ # almost every community when it is actually per-source ids (#3858).
2721
+ # --previous restores the real previous merged `community` onto each
2722
+ # node that survives the merge (by id), so a subsequent cluster-only
2723
+ # run remaps against the clustering its labels were actually built
2724
+ # from. Per-source ids stay available in `local_community` regardless.
2725
+ previous_community: dict[str, int] = {}
2726
+ if previous_path is not None:
2727
+ if not previous_path.exists():
2728
+ print(f"error: --previous file not found: {previous_path}", file=sys.stderr)
2729
+ sys.exit(1)
2730
+ try:
2731
+ _prev_raw = json.loads(previous_path.read_text(encoding="utf-8"))
2732
+ except (json.JSONDecodeError, OSError, UnicodeDecodeError) as e:
2733
+ print(f"error: --previous file is not readable JSON: {previous_path} ({e})", file=sys.stderr)
2734
+ sys.exit(1)
2735
+ for _n in _prev_raw.get("nodes", []):
2736
+ if isinstance(_n, dict) and isinstance(_n.get("community"), int) and _n.get("id"):
2737
+ previous_community[_n["id"]] = _n["community"]
2680
2738
  import networkx as _nx
2681
2739
  from networkx.readwrite import json_graph as _jg
2682
2740
  from graphify.build import prefix_graph_for_global as _prefix, distinct_repo_tags as _repo_tags
@@ -2781,6 +2839,18 @@ def dispatch_command(cmd: str) -> None:
2781
2839
  call_links = _link_calls(merged)
2782
2840
  if call_links:
2783
2841
  print(f" resolved {call_links} member call(s) across repos")
2842
+ if previous_path is not None:
2843
+ _restored = 0
2844
+ for _nid, _data in merged.nodes(data=True):
2845
+ if _nid in previous_community:
2846
+ _data["community"] = previous_community[_nid]
2847
+ _restored += 1
2848
+ else:
2849
+ _data.pop("community", None)
2850
+ print(
2851
+ f" restored the previous merged community on {_restored} node(s) "
2852
+ f"from {previous_path}"
2853
+ )
2784
2854
  # Drop whatever compose left behind (the last input's list, possibly
2785
2855
  # with internal duplicates) so attach_hyperedges dedups the full
2786
2856
  # collection by id from a clean slate.
@@ -3283,7 +3353,8 @@ def dispatch_command(cmd: str) -> None:
3283
3353
  "[--model M] [--mode deep] [--out DIR|--output DIR] [--google-workspace] [--no-cluster] "
3284
3354
  "[--no-gitignore] [--code-only] [--no-dedup] "
3285
3355
  "[--max-workers N] [--token-budget N] [--max-concurrency N] "
3286
- "[--api-timeout S] [--postgres DSN] [--cargo] [--allow-partial] [--timing]",
3356
+ "[--api-timeout S] [--postgres DSN] [--cargo] [--allow-partial] "
3357
+ "[--allow-dedup-shrink] [--timing]",
3287
3358
  file=sys.stderr,
3288
3359
  )
3289
3360
  sys.exit(1)
@@ -3305,6 +3376,9 @@ def dispatch_command(cmd: str) -> None:
3305
3376
  cli_postgres_dsn: str | None = None
3306
3377
  cli_cargo: bool = False
3307
3378
  cli_allow_partial: bool = False
3379
+ # --allow-dedup-shrink: write a graph that is smaller because dedup
3380
+ # merged nodes. A deleted file does not need this flag (#3774).
3381
+ cli_allow_dedup_shrink: bool = False
3308
3382
  no_cluster = False
3309
3383
  dedup_llm = False
3310
3384
  # --no-dedup: skip entity deduplication entirely. On an incremental
@@ -3433,6 +3507,8 @@ def dispatch_command(cmd: str) -> None:
3433
3507
  force = True; i += 1
3434
3508
  elif a == "--allow-partial":
3435
3509
  cli_allow_partial = True; i += 1
3510
+ elif a == "--allow-dedup-shrink":
3511
+ cli_allow_dedup_shrink = True; i += 1
3436
3512
  elif a == "--timing":
3437
3513
  cli_timing = True; i += 1
3438
3514
  else:
@@ -4598,15 +4674,17 @@ def dispatch_command(cmd: str) -> None:
4598
4674
  _backup(graphify_out)
4599
4675
  _invalidate_file_manifest_for_db_graph()
4600
4676
  # force=True bypasses the #479 shrink guard entirely. A full build
4601
- # legitimately shrinks (fuzzy dedup collapse, deleted code) so it keeps
4602
- # force=True — EXCEPT when this run's extraction was incomplete (an
4603
- # extractor pass crashed or some semantic chunks failed). Then a partial
4604
- # graph could silently overwrite a good complete one, so fall back to the
4605
- # shrink guard (force=False) unless the user opts in with --allow-partial.
4677
+ # legitimately shrinks when files were deleted, so a complete run keeps
4678
+ # force=True — EXCEPT:
4679
+ # * extraction was incomplete (a pass crashed or a chunk came back
4680
+ # empty). Fall back to the shrink guard unless --allow-partial.
4681
+ # * dedup merged nodes and the saved graph would shrink by more than
4682
+ # this run's deletions (#3774). Refuse the write before to_json unless
4683
+ # --allow-dedup-shrink. --allow-partial does not accept that shrink.
4606
4684
  #
4607
- # Both write paths are guarded: the clustered path here via to_json's
4608
- # #479 check, and the `--no-cluster` raw-dump path above via the same
4609
- # shrink check against the existing file (existing_graph_node_count).
4685
+ # Both write paths are guarded for an incomplete extraction: the clustered
4686
+ # path here via to_json's #479 check, and the `--no-cluster` raw-dump path
4687
+ # above via the same shrink check against the existing file.
4610
4688
  #
4611
4689
  # Trade-off: this reuses to_json's coarse node-count guard, not the
4612
4690
  # source-aware _check_shrink that watch/update use. On an incremental run
@@ -4614,7 +4692,33 @@ def dispatch_command(cmd: str) -> None:
4614
4692
  # failure can therefore be refused here — recoverable by re-running or
4615
4693
  # passing --allow-partial (the good graph is preserved and the manifest
4616
4694
  # is not stamped, so the retry re-extracts).
4695
+ from graphify.build import take_shrink_accounting as _take_shrink_accounting
4696
+ _dedup_collapsed, _pruned_nodes = _take_shrink_accounting(G)
4617
4697
  _force_write = cli_allow_partial or not _extraction_incomplete
4698
+ _dedup_shrink_counts: tuple[int, int] | None = None
4699
+ if (
4700
+ not _extraction_incomplete
4701
+ and not no_dedup
4702
+ and not cli_allow_dedup_shrink
4703
+ and _dedup_collapsed > 0
4704
+ ):
4705
+ from graphify.export import existing_graph_node_count as _existing_graph_node_count
4706
+ _existing_n = _existing_graph_node_count(graph_json_path)
4707
+ _new_n = G.number_of_nodes()
4708
+ if isinstance(_existing_n, int) and _new_n < _existing_n - _pruned_nodes:
4709
+ _dedup_shrink_counts = (_existing_n, _new_n)
4710
+ if _dedup_shrink_counts is not None:
4711
+ # Refuse before to_json. to_json's own refusal tells the caller to
4712
+ # pass force=True, which is not this flag (#3774).
4713
+ _old_n, _new_n = _dedup_shrink_counts
4714
+ print(
4715
+ f"[graphify extract] error: dedup merged {_dedup_collapsed} "
4716
+ f"node(s); writing would shrink the graph from {_old_n} nodes "
4717
+ f"to {_new_n} nodes. Refusing to overwrite {graph_json_path}. "
4718
+ "Pass --allow-dedup-shrink to write the smaller graph.",
4719
+ file=sys.stderr,
4720
+ )
4721
+ sys.exit(1)
4618
4722
  # Stamp provenance from the ANALYSED repo, not the shell's cwd: without
4619
4723
  # this, to_json's fallback asks `git rev-parse HEAD` in whatever repo the
4620
4724
  # command was invoked from, so `graphify extract <target>` run from
@@ -235,7 +235,7 @@ def link_cross_repo_member_calls(merged: "nx.Graph") -> int:
235
235
  relation="calls",
236
236
  context="cross_repo",
237
237
  confidence="INFERRED",
238
- confidence_score=0.8,
238
+ confidence_score=0.85,
239
239
  source_file=str(caller_data.get("source_file") or ""),
240
240
  source_location=entry.get("line"),
241
241
  weight=1.0,
@@ -65,7 +65,7 @@ def link_shared_type_declarations(merged: "nx.Graph") -> int:
65
65
  relation=SHARED_TYPE_RELATION,
66
66
  context="cross_repo",
67
67
  confidence="INFERRED",
68
- confidence_score=0.9,
68
+ confidence_score=0.85,
69
69
  source_file=str(merged.nodes[left].get("source_file") or ""),
70
70
  weight=1.0,
71
71
  _src=left,