graphifyy 0.9.4__tar.gz → 0.9.6__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 (329) hide show
  1. {graphifyy-0.9.4/graphifyy.egg-info → graphifyy-0.9.6}/PKG-INFO +2 -2
  2. {graphifyy-0.9.4 → graphifyy-0.9.6}/README.md +1 -1
  3. {graphifyy-0.9.4 → graphifyy-0.9.6}/graphify/__main__.py +77 -5
  4. {graphifyy-0.9.4 → graphifyy-0.9.6}/graphify/analyze.py +1 -1
  5. {graphifyy-0.9.4 → graphifyy-0.9.6}/graphify/build.py +8 -1
  6. {graphifyy-0.9.4 → graphifyy-0.9.6}/graphify/cluster.py +21 -0
  7. {graphifyy-0.9.4 → graphifyy-0.9.6}/graphify/detect.py +31 -14
  8. {graphifyy-0.9.4 → graphifyy-0.9.6}/graphify/export.py +7 -1
  9. {graphifyy-0.9.4 → graphifyy-0.9.6}/graphify/extract.py +1041 -71
  10. {graphifyy-0.9.4 → graphifyy-0.9.6}/graphify/extractors/base.py +8 -1
  11. {graphifyy-0.9.4 → graphifyy-0.9.6}/graphify/extractors/elixir.py +30 -2
  12. {graphifyy-0.9.4 → graphifyy-0.9.6}/graphify/hooks.py +38 -9
  13. {graphifyy-0.9.4 → graphifyy-0.9.6}/graphify/llm.py +84 -19
  14. {graphifyy-0.9.4 → graphifyy-0.9.6}/graphify/ruby_resolution.py +31 -3
  15. {graphifyy-0.9.4 → graphifyy-0.9.6}/graphify/serve.py +150 -52
  16. {graphifyy-0.9.4 → graphifyy-0.9.6}/graphify/skill-agents.md +2 -2
  17. {graphifyy-0.9.4 → graphifyy-0.9.6}/graphify/skill-aider.md +1 -1
  18. {graphifyy-0.9.4 → graphifyy-0.9.6}/graphify/skill-amp.md +2 -2
  19. {graphifyy-0.9.4 → graphifyy-0.9.6}/graphify/skill-claw.md +2 -2
  20. {graphifyy-0.9.4 → graphifyy-0.9.6}/graphify/skill-codex.md +2 -2
  21. {graphifyy-0.9.4 → graphifyy-0.9.6}/graphify/skill-copilot.md +2 -2
  22. {graphifyy-0.9.4 → graphifyy-0.9.6}/graphify/skill-devin.md +2 -2
  23. {graphifyy-0.9.4 → graphifyy-0.9.6}/graphify/skill-droid.md +2 -2
  24. {graphifyy-0.9.4 → graphifyy-0.9.6}/graphify/skill-kilo.md +2 -2
  25. {graphifyy-0.9.4 → graphifyy-0.9.6}/graphify/skill-kiro.md +2 -2
  26. {graphifyy-0.9.4 → graphifyy-0.9.6}/graphify/skill-opencode.md +2 -2
  27. {graphifyy-0.9.4 → graphifyy-0.9.6}/graphify/skill-pi.md +2 -2
  28. {graphifyy-0.9.4 → graphifyy-0.9.6}/graphify/skill-trae.md +2 -2
  29. {graphifyy-0.9.4 → graphifyy-0.9.6}/graphify/skill-vscode.md +2 -2
  30. {graphifyy-0.9.4 → graphifyy-0.9.6}/graphify/skill-windows.md +1 -1
  31. {graphifyy-0.9.4 → graphifyy-0.9.6}/graphify/skill.md +2 -2
  32. {graphifyy-0.9.4/graphify/skills/amp → graphifyy-0.9.6/graphify/skills/agents}/references/query.md +5 -5
  33. {graphifyy-0.9.4/graphify/skills/claude → graphifyy-0.9.6/graphify/skills/amp}/references/query.md +5 -5
  34. {graphifyy-0.9.4/graphify/skills/claw → graphifyy-0.9.6/graphify/skills/claude}/references/query.md +5 -5
  35. {graphifyy-0.9.4/graphify/skills/agents → graphifyy-0.9.6/graphify/skills/claw}/references/query.md +5 -5
  36. graphifyy-0.9.6/graphify/skills/codex/references/query.md +311 -0
  37. graphifyy-0.9.6/graphify/skills/copilot/references/query.md +311 -0
  38. graphifyy-0.9.6/graphify/skills/droid/references/query.md +311 -0
  39. graphifyy-0.9.6/graphify/skills/kilo/references/query.md +311 -0
  40. graphifyy-0.9.6/graphify/skills/kiro/references/query.md +311 -0
  41. graphifyy-0.9.6/graphify/skills/opencode/references/query.md +311 -0
  42. graphifyy-0.9.6/graphify/skills/pi/references/query.md +311 -0
  43. graphifyy-0.9.6/graphify/skills/trae/references/query.md +311 -0
  44. graphifyy-0.9.6/graphify/skills/vscode/references/query.md +311 -0
  45. graphifyy-0.9.6/graphify/skills/windows/references/query.md +311 -0
  46. {graphifyy-0.9.4 → graphifyy-0.9.6}/graphify/watch.py +277 -112
  47. {graphifyy-0.9.4 → graphifyy-0.9.6/graphifyy.egg-info}/PKG-INFO +2 -2
  48. {graphifyy-0.9.4 → graphifyy-0.9.6}/graphifyy.egg-info/SOURCES.txt +12 -0
  49. {graphifyy-0.9.4 → graphifyy-0.9.6}/pyproject.toml +1 -1
  50. graphifyy-0.9.6/tests/test_case_sensitive_resolution.py +87 -0
  51. {graphifyy-0.9.4 → graphifyy-0.9.6}/tests/test_chunking.py +47 -0
  52. {graphifyy-0.9.4 → graphifyy-0.9.6}/tests/test_claude_cli_backend.py +37 -0
  53. {graphifyy-0.9.4 → graphifyy-0.9.6}/tests/test_cli_export.py +30 -0
  54. {graphifyy-0.9.4 → graphifyy-0.9.6}/tests/test_community_hub_labels.py +17 -0
  55. graphifyy-0.9.6/tests/test_csharp_member_calls.py +143 -0
  56. {graphifyy-0.9.4 → graphifyy-0.9.6}/tests/test_detect.py +34 -11
  57. {graphifyy-0.9.4 → graphifyy-0.9.6}/tests/test_extract.py +26 -0
  58. {graphifyy-0.9.4 → graphifyy-0.9.6}/tests/test_hooks.py +44 -1
  59. {graphifyy-0.9.4 → graphifyy-0.9.6}/tests/test_image_vision.py +31 -0
  60. {graphifyy-0.9.4 → graphifyy-0.9.6}/tests/test_indirect_dispatch.py +27 -0
  61. {graphifyy-0.9.4 → graphifyy-0.9.6}/tests/test_languages.py +134 -0
  62. {graphifyy-0.9.4 → graphifyy-0.9.6}/tests/test_llm_parser.py +19 -10
  63. graphifyy-0.9.6/tests/test_merge_graphs_cli.py +48 -0
  64. {graphifyy-0.9.4 → graphifyy-0.9.6}/tests/test_multilang.py +26 -0
  65. {graphifyy-0.9.4 → graphifyy-0.9.6}/tests/test_obsidian_dangling_member.py +20 -0
  66. graphifyy-0.9.6/tests/test_phantom_external_import.py +116 -0
  67. {graphifyy-0.9.4 → graphifyy-0.9.6}/tests/test_ruby_resolution.py +97 -0
  68. graphifyy-0.9.6/tests/test_semantic_fragment_sanitize.py +71 -0
  69. graphifyy-0.9.6/tests/test_semantic_id_remap_root.py +50 -0
  70. {graphifyy-0.9.4 → graphifyy-0.9.6}/tests/test_serve.py +50 -1
  71. {graphifyy-0.9.4 → graphifyy-0.9.6}/tests/test_serve_http.py +69 -0
  72. {graphifyy-0.9.4 → graphifyy-0.9.6}/tests/test_swift_cross_file_calls.py +25 -0
  73. graphifyy-0.9.6/tests/test_ts_decorators.py +153 -0
  74. graphifyy-0.9.6/tests/test_ts_generators.py +74 -0
  75. graphifyy-0.9.6/tests/test_ts_import_require.py +119 -0
  76. graphifyy-0.9.6/tests/test_ts_namespace.py +73 -0
  77. graphifyy-0.9.6/tests/test_ts_receiver_member_calls.py +81 -0
  78. graphifyy-0.9.6/tests/test_typescript_module_extensions.py +84 -0
  79. {graphifyy-0.9.4 → graphifyy-0.9.6}/tests/test_watch.py +380 -0
  80. graphifyy-0.9.4/graphify/skills/codex/references/query.md +0 -311
  81. graphifyy-0.9.4/graphify/skills/copilot/references/query.md +0 -311
  82. graphifyy-0.9.4/graphify/skills/droid/references/query.md +0 -311
  83. graphifyy-0.9.4/graphify/skills/kilo/references/query.md +0 -311
  84. graphifyy-0.9.4/graphify/skills/kiro/references/query.md +0 -311
  85. graphifyy-0.9.4/graphify/skills/opencode/references/query.md +0 -311
  86. graphifyy-0.9.4/graphify/skills/pi/references/query.md +0 -311
  87. graphifyy-0.9.4/graphify/skills/trae/references/query.md +0 -311
  88. graphifyy-0.9.4/graphify/skills/vscode/references/query.md +0 -311
  89. graphifyy-0.9.4/graphify/skills/windows/references/query.md +0 -311
  90. {graphifyy-0.9.4 → graphifyy-0.9.6}/LICENSE +0 -0
  91. {graphifyy-0.9.4 → graphifyy-0.9.6}/graphify/__init__.py +0 -0
  92. {graphifyy-0.9.4 → graphifyy-0.9.6}/graphify/_minhash.py +0 -0
  93. {graphifyy-0.9.4 → graphifyy-0.9.6}/graphify/affected.py +0 -0
  94. {graphifyy-0.9.4 → graphifyy-0.9.6}/graphify/always_on/agents-md.md +0 -0
  95. {graphifyy-0.9.4 → graphifyy-0.9.6}/graphify/always_on/antigravity-rules.md +0 -0
  96. {graphifyy-0.9.4 → graphifyy-0.9.6}/graphify/always_on/claude-md.md +0 -0
  97. {graphifyy-0.9.4 → graphifyy-0.9.6}/graphify/always_on/gemini-md.md +0 -0
  98. {graphifyy-0.9.4 → graphifyy-0.9.6}/graphify/always_on/kiro-steering.md +0 -0
  99. {graphifyy-0.9.4 → graphifyy-0.9.6}/graphify/always_on/vscode-instructions.md +0 -0
  100. {graphifyy-0.9.4 → graphifyy-0.9.6}/graphify/benchmark.py +0 -0
  101. {graphifyy-0.9.4 → graphifyy-0.9.6}/graphify/cache.py +0 -0
  102. {graphifyy-0.9.4 → graphifyy-0.9.6}/graphify/callflow_html.py +0 -0
  103. {graphifyy-0.9.4 → graphifyy-0.9.6}/graphify/cargo_introspect.py +0 -0
  104. {graphifyy-0.9.4 → graphifyy-0.9.6}/graphify/command-kilo.md +0 -0
  105. {graphifyy-0.9.4 → graphifyy-0.9.6}/graphify/dedup.py +0 -0
  106. {graphifyy-0.9.4 → graphifyy-0.9.6}/graphify/diagnostics.py +0 -0
  107. {graphifyy-0.9.4 → graphifyy-0.9.6}/graphify/extractors/__init__.py +0 -0
  108. {graphifyy-0.9.4 → graphifyy-0.9.6}/graphify/extractors/blade.py +0 -0
  109. {graphifyy-0.9.4 → graphifyy-0.9.6}/graphify/extractors/csharp.py +0 -0
  110. {graphifyy-0.9.4 → graphifyy-0.9.6}/graphify/extractors/razor.py +0 -0
  111. {graphifyy-0.9.4 → graphifyy-0.9.6}/graphify/extractors/zig.py +0 -0
  112. {graphifyy-0.9.4 → graphifyy-0.9.6}/graphify/file_slice.py +0 -0
  113. {graphifyy-0.9.4 → graphifyy-0.9.6}/graphify/global_graph.py +0 -0
  114. {graphifyy-0.9.4 → graphifyy-0.9.6}/graphify/google_workspace.py +0 -0
  115. {graphifyy-0.9.4 → graphifyy-0.9.6}/graphify/ids.py +0 -0
  116. {graphifyy-0.9.4 → graphifyy-0.9.6}/graphify/ingest.py +0 -0
  117. {graphifyy-0.9.4 → graphifyy-0.9.6}/graphify/manifest.py +0 -0
  118. {graphifyy-0.9.4 → graphifyy-0.9.6}/graphify/manifest_ingest.py +0 -0
  119. {graphifyy-0.9.4 → graphifyy-0.9.6}/graphify/mcp_ingest.py +0 -0
  120. {graphifyy-0.9.4 → graphifyy-0.9.6}/graphify/multigraph_compat.py +0 -0
  121. {graphifyy-0.9.4 → graphifyy-0.9.6}/graphify/paths.py +0 -0
  122. {graphifyy-0.9.4 → graphifyy-0.9.6}/graphify/pg_introspect.py +0 -0
  123. {graphifyy-0.9.4 → graphifyy-0.9.6}/graphify/prs.py +0 -0
  124. {graphifyy-0.9.4 → graphifyy-0.9.6}/graphify/querylog.py +0 -0
  125. {graphifyy-0.9.4 → graphifyy-0.9.6}/graphify/reflect.py +0 -0
  126. {graphifyy-0.9.4 → graphifyy-0.9.6}/graphify/report.py +0 -0
  127. {graphifyy-0.9.4 → graphifyy-0.9.6}/graphify/resolver_registry.py +0 -0
  128. {graphifyy-0.9.4 → graphifyy-0.9.6}/graphify/scip_ingest.py +0 -0
  129. {graphifyy-0.9.4 → graphifyy-0.9.6}/graphify/security.py +0 -0
  130. {graphifyy-0.9.4 → graphifyy-0.9.6}/graphify/semantic_cleanup.py +0 -0
  131. {graphifyy-0.9.4 → graphifyy-0.9.6}/graphify/skills/agents/references/add-watch.md +0 -0
  132. {graphifyy-0.9.4 → graphifyy-0.9.6}/graphify/skills/agents/references/exports.md +0 -0
  133. {graphifyy-0.9.4 → graphifyy-0.9.6}/graphify/skills/agents/references/extraction-spec.md +0 -0
  134. {graphifyy-0.9.4 → graphifyy-0.9.6}/graphify/skills/agents/references/github-and-merge.md +0 -0
  135. {graphifyy-0.9.4 → graphifyy-0.9.6}/graphify/skills/agents/references/hooks.md +0 -0
  136. {graphifyy-0.9.4 → graphifyy-0.9.6}/graphify/skills/agents/references/transcribe.md +0 -0
  137. {graphifyy-0.9.4 → graphifyy-0.9.6}/graphify/skills/agents/references/update.md +0 -0
  138. {graphifyy-0.9.4 → graphifyy-0.9.6}/graphify/skills/amp/references/add-watch.md +0 -0
  139. {graphifyy-0.9.4 → graphifyy-0.9.6}/graphify/skills/amp/references/exports.md +0 -0
  140. {graphifyy-0.9.4 → graphifyy-0.9.6}/graphify/skills/amp/references/extraction-spec.md +0 -0
  141. {graphifyy-0.9.4 → graphifyy-0.9.6}/graphify/skills/amp/references/github-and-merge.md +0 -0
  142. {graphifyy-0.9.4 → graphifyy-0.9.6}/graphify/skills/amp/references/hooks.md +0 -0
  143. {graphifyy-0.9.4 → graphifyy-0.9.6}/graphify/skills/amp/references/transcribe.md +0 -0
  144. {graphifyy-0.9.4 → graphifyy-0.9.6}/graphify/skills/amp/references/update.md +0 -0
  145. {graphifyy-0.9.4 → graphifyy-0.9.6}/graphify/skills/claude/references/add-watch.md +0 -0
  146. {graphifyy-0.9.4 → graphifyy-0.9.6}/graphify/skills/claude/references/exports.md +0 -0
  147. {graphifyy-0.9.4 → graphifyy-0.9.6}/graphify/skills/claude/references/extraction-spec.md +0 -0
  148. {graphifyy-0.9.4 → graphifyy-0.9.6}/graphify/skills/claude/references/github-and-merge.md +0 -0
  149. {graphifyy-0.9.4 → graphifyy-0.9.6}/graphify/skills/claude/references/hooks.md +0 -0
  150. {graphifyy-0.9.4 → graphifyy-0.9.6}/graphify/skills/claude/references/transcribe.md +0 -0
  151. {graphifyy-0.9.4 → graphifyy-0.9.6}/graphify/skills/claude/references/update.md +0 -0
  152. {graphifyy-0.9.4 → graphifyy-0.9.6}/graphify/skills/claw/references/add-watch.md +0 -0
  153. {graphifyy-0.9.4 → graphifyy-0.9.6}/graphify/skills/claw/references/exports.md +0 -0
  154. {graphifyy-0.9.4 → graphifyy-0.9.6}/graphify/skills/claw/references/extraction-spec.md +0 -0
  155. {graphifyy-0.9.4 → graphifyy-0.9.6}/graphify/skills/claw/references/github-and-merge.md +0 -0
  156. {graphifyy-0.9.4 → graphifyy-0.9.6}/graphify/skills/claw/references/hooks.md +0 -0
  157. {graphifyy-0.9.4 → graphifyy-0.9.6}/graphify/skills/claw/references/transcribe.md +0 -0
  158. {graphifyy-0.9.4 → graphifyy-0.9.6}/graphify/skills/claw/references/update.md +0 -0
  159. {graphifyy-0.9.4 → graphifyy-0.9.6}/graphify/skills/codex/references/add-watch.md +0 -0
  160. {graphifyy-0.9.4 → graphifyy-0.9.6}/graphify/skills/codex/references/exports.md +0 -0
  161. {graphifyy-0.9.4 → graphifyy-0.9.6}/graphify/skills/codex/references/extraction-spec.md +0 -0
  162. {graphifyy-0.9.4 → graphifyy-0.9.6}/graphify/skills/codex/references/github-and-merge.md +0 -0
  163. {graphifyy-0.9.4 → graphifyy-0.9.6}/graphify/skills/codex/references/hooks.md +0 -0
  164. {graphifyy-0.9.4 → graphifyy-0.9.6}/graphify/skills/codex/references/transcribe.md +0 -0
  165. {graphifyy-0.9.4 → graphifyy-0.9.6}/graphify/skills/codex/references/update.md +0 -0
  166. {graphifyy-0.9.4 → graphifyy-0.9.6}/graphify/skills/copilot/references/add-watch.md +0 -0
  167. {graphifyy-0.9.4 → graphifyy-0.9.6}/graphify/skills/copilot/references/exports.md +0 -0
  168. {graphifyy-0.9.4 → graphifyy-0.9.6}/graphify/skills/copilot/references/extraction-spec.md +0 -0
  169. {graphifyy-0.9.4 → graphifyy-0.9.6}/graphify/skills/copilot/references/github-and-merge.md +0 -0
  170. {graphifyy-0.9.4 → graphifyy-0.9.6}/graphify/skills/copilot/references/hooks.md +0 -0
  171. {graphifyy-0.9.4 → graphifyy-0.9.6}/graphify/skills/copilot/references/transcribe.md +0 -0
  172. {graphifyy-0.9.4 → graphifyy-0.9.6}/graphify/skills/copilot/references/update.md +0 -0
  173. {graphifyy-0.9.4 → graphifyy-0.9.6}/graphify/skills/droid/references/add-watch.md +0 -0
  174. {graphifyy-0.9.4 → graphifyy-0.9.6}/graphify/skills/droid/references/exports.md +0 -0
  175. {graphifyy-0.9.4 → graphifyy-0.9.6}/graphify/skills/droid/references/extraction-spec.md +0 -0
  176. {graphifyy-0.9.4 → graphifyy-0.9.6}/graphify/skills/droid/references/github-and-merge.md +0 -0
  177. {graphifyy-0.9.4 → graphifyy-0.9.6}/graphify/skills/droid/references/hooks.md +0 -0
  178. {graphifyy-0.9.4 → graphifyy-0.9.6}/graphify/skills/droid/references/transcribe.md +0 -0
  179. {graphifyy-0.9.4 → graphifyy-0.9.6}/graphify/skills/droid/references/update.md +0 -0
  180. {graphifyy-0.9.4 → graphifyy-0.9.6}/graphify/skills/kilo/references/add-watch.md +0 -0
  181. {graphifyy-0.9.4 → graphifyy-0.9.6}/graphify/skills/kilo/references/exports.md +0 -0
  182. {graphifyy-0.9.4 → graphifyy-0.9.6}/graphify/skills/kilo/references/extraction-spec.md +0 -0
  183. {graphifyy-0.9.4 → graphifyy-0.9.6}/graphify/skills/kilo/references/github-and-merge.md +0 -0
  184. {graphifyy-0.9.4 → graphifyy-0.9.6}/graphify/skills/kilo/references/hooks.md +0 -0
  185. {graphifyy-0.9.4 → graphifyy-0.9.6}/graphify/skills/kilo/references/transcribe.md +0 -0
  186. {graphifyy-0.9.4 → graphifyy-0.9.6}/graphify/skills/kilo/references/update.md +0 -0
  187. {graphifyy-0.9.4 → graphifyy-0.9.6}/graphify/skills/kiro/references/add-watch.md +0 -0
  188. {graphifyy-0.9.4 → graphifyy-0.9.6}/graphify/skills/kiro/references/exports.md +0 -0
  189. {graphifyy-0.9.4 → graphifyy-0.9.6}/graphify/skills/kiro/references/extraction-spec.md +0 -0
  190. {graphifyy-0.9.4 → graphifyy-0.9.6}/graphify/skills/kiro/references/github-and-merge.md +0 -0
  191. {graphifyy-0.9.4 → graphifyy-0.9.6}/graphify/skills/kiro/references/hooks.md +0 -0
  192. {graphifyy-0.9.4 → graphifyy-0.9.6}/graphify/skills/kiro/references/transcribe.md +0 -0
  193. {graphifyy-0.9.4 → graphifyy-0.9.6}/graphify/skills/kiro/references/update.md +0 -0
  194. {graphifyy-0.9.4 → graphifyy-0.9.6}/graphify/skills/opencode/references/add-watch.md +0 -0
  195. {graphifyy-0.9.4 → graphifyy-0.9.6}/graphify/skills/opencode/references/exports.md +0 -0
  196. {graphifyy-0.9.4 → graphifyy-0.9.6}/graphify/skills/opencode/references/extraction-spec.md +0 -0
  197. {graphifyy-0.9.4 → graphifyy-0.9.6}/graphify/skills/opencode/references/github-and-merge.md +0 -0
  198. {graphifyy-0.9.4 → graphifyy-0.9.6}/graphify/skills/opencode/references/hooks.md +0 -0
  199. {graphifyy-0.9.4 → graphifyy-0.9.6}/graphify/skills/opencode/references/transcribe.md +0 -0
  200. {graphifyy-0.9.4 → graphifyy-0.9.6}/graphify/skills/opencode/references/update.md +0 -0
  201. {graphifyy-0.9.4 → graphifyy-0.9.6}/graphify/skills/pi/references/add-watch.md +0 -0
  202. {graphifyy-0.9.4 → graphifyy-0.9.6}/graphify/skills/pi/references/exports.md +0 -0
  203. {graphifyy-0.9.4 → graphifyy-0.9.6}/graphify/skills/pi/references/extraction-spec.md +0 -0
  204. {graphifyy-0.9.4 → graphifyy-0.9.6}/graphify/skills/pi/references/github-and-merge.md +0 -0
  205. {graphifyy-0.9.4 → graphifyy-0.9.6}/graphify/skills/pi/references/hooks.md +0 -0
  206. {graphifyy-0.9.4 → graphifyy-0.9.6}/graphify/skills/pi/references/transcribe.md +0 -0
  207. {graphifyy-0.9.4 → graphifyy-0.9.6}/graphify/skills/pi/references/update.md +0 -0
  208. {graphifyy-0.9.4 → graphifyy-0.9.6}/graphify/skills/trae/references/add-watch.md +0 -0
  209. {graphifyy-0.9.4 → graphifyy-0.9.6}/graphify/skills/trae/references/exports.md +0 -0
  210. {graphifyy-0.9.4 → graphifyy-0.9.6}/graphify/skills/trae/references/extraction-spec.md +0 -0
  211. {graphifyy-0.9.4 → graphifyy-0.9.6}/graphify/skills/trae/references/github-and-merge.md +0 -0
  212. {graphifyy-0.9.4 → graphifyy-0.9.6}/graphify/skills/trae/references/hooks.md +0 -0
  213. {graphifyy-0.9.4 → graphifyy-0.9.6}/graphify/skills/trae/references/transcribe.md +0 -0
  214. {graphifyy-0.9.4 → graphifyy-0.9.6}/graphify/skills/trae/references/update.md +0 -0
  215. {graphifyy-0.9.4 → graphifyy-0.9.6}/graphify/skills/vscode/references/add-watch.md +0 -0
  216. {graphifyy-0.9.4 → graphifyy-0.9.6}/graphify/skills/vscode/references/exports.md +0 -0
  217. {graphifyy-0.9.4 → graphifyy-0.9.6}/graphify/skills/vscode/references/extraction-spec.md +0 -0
  218. {graphifyy-0.9.4 → graphifyy-0.9.6}/graphify/skills/vscode/references/github-and-merge.md +0 -0
  219. {graphifyy-0.9.4 → graphifyy-0.9.6}/graphify/skills/vscode/references/hooks.md +0 -0
  220. {graphifyy-0.9.4 → graphifyy-0.9.6}/graphify/skills/vscode/references/transcribe.md +0 -0
  221. {graphifyy-0.9.4 → graphifyy-0.9.6}/graphify/skills/vscode/references/update.md +0 -0
  222. {graphifyy-0.9.4 → graphifyy-0.9.6}/graphify/skills/windows/references/add-watch.md +0 -0
  223. {graphifyy-0.9.4 → graphifyy-0.9.6}/graphify/skills/windows/references/exports.md +0 -0
  224. {graphifyy-0.9.4 → graphifyy-0.9.6}/graphify/skills/windows/references/extraction-spec.md +0 -0
  225. {graphifyy-0.9.4 → graphifyy-0.9.6}/graphify/skills/windows/references/github-and-merge.md +0 -0
  226. {graphifyy-0.9.4 → graphifyy-0.9.6}/graphify/skills/windows/references/hooks.md +0 -0
  227. {graphifyy-0.9.4 → graphifyy-0.9.6}/graphify/skills/windows/references/transcribe.md +0 -0
  228. {graphifyy-0.9.4 → graphifyy-0.9.6}/graphify/skills/windows/references/update.md +0 -0
  229. {graphifyy-0.9.4 → graphifyy-0.9.6}/graphify/symbol_resolution.py +0 -0
  230. {graphifyy-0.9.4 → graphifyy-0.9.6}/graphify/transcribe.py +0 -0
  231. {graphifyy-0.9.4 → graphifyy-0.9.6}/graphify/tree_html.py +0 -0
  232. {graphifyy-0.9.4 → graphifyy-0.9.6}/graphify/validate.py +0 -0
  233. {graphifyy-0.9.4 → graphifyy-0.9.6}/graphify/wiki.py +0 -0
  234. {graphifyy-0.9.4 → graphifyy-0.9.6}/graphifyy.egg-info/dependency_links.txt +0 -0
  235. {graphifyy-0.9.4 → graphifyy-0.9.6}/graphifyy.egg-info/entry_points.txt +0 -0
  236. {graphifyy-0.9.4 → graphifyy-0.9.6}/graphifyy.egg-info/requires.txt +0 -0
  237. {graphifyy-0.9.4 → graphifyy-0.9.6}/graphifyy.egg-info/top_level.txt +0 -0
  238. {graphifyy-0.9.4 → graphifyy-0.9.6}/setup.cfg +0 -0
  239. {graphifyy-0.9.4 → graphifyy-0.9.6}/tests/test_affected_cli.py +0 -0
  240. {graphifyy-0.9.4 → graphifyy-0.9.6}/tests/test_agents_platform.py +0 -0
  241. {graphifyy-0.9.4 → graphifyy-0.9.6}/tests/test_analyze.py +0 -0
  242. {graphifyy-0.9.4 → graphifyy-0.9.6}/tests/test_anthropic_custom_endpoint.py +0 -0
  243. {graphifyy-0.9.4 → graphifyy-0.9.6}/tests/test_antigravity_install.py +0 -0
  244. {graphifyy-0.9.4 → graphifyy-0.9.6}/tests/test_astro_extraction.py +0 -0
  245. {graphifyy-0.9.4 → graphifyy-0.9.6}/tests/test_backend_extras.py +0 -0
  246. {graphifyy-0.9.4 → graphifyy-0.9.6}/tests/test_benchmark.py +0 -0
  247. {graphifyy-0.9.4 → graphifyy-0.9.6}/tests/test_build.py +0 -0
  248. {graphifyy-0.9.4 → graphifyy-0.9.6}/tests/test_build_merge_hyperedges_and_prune.py +0 -0
  249. {graphifyy-0.9.4 → graphifyy-0.9.6}/tests/test_cache.py +0 -0
  250. {graphifyy-0.9.4 → graphifyy-0.9.6}/tests/test_callflow_html.py +0 -0
  251. {graphifyy-0.9.4 → graphifyy-0.9.6}/tests/test_cargo_introspect.py +0 -0
  252. {graphifyy-0.9.4 → graphifyy-0.9.6}/tests/test_charmap_encoding.py +0 -0
  253. {graphifyy-0.9.4 → graphifyy-0.9.6}/tests/test_claude_md.py +0 -0
  254. {graphifyy-0.9.4 → graphifyy-0.9.6}/tests/test_cluster.py +0 -0
  255. {graphifyy-0.9.4 → graphifyy-0.9.6}/tests/test_codebuddy.py +0 -0
  256. {graphifyy-0.9.4 → graphifyy-0.9.6}/tests/test_confidence.py +0 -0
  257. {graphifyy-0.9.4 → graphifyy-0.9.6}/tests/test_corrupt_graph_json.py +0 -0
  258. {graphifyy-0.9.4 → graphifyy-0.9.6}/tests/test_cpp_objc_cross_file_calls.py +0 -0
  259. {graphifyy-0.9.4 → graphifyy-0.9.6}/tests/test_cpp_preprocess.py +0 -0
  260. {graphifyy-0.9.4 → graphifyy-0.9.6}/tests/test_csharp_type_resolution.py +0 -0
  261. {graphifyy-0.9.4 → graphifyy-0.9.6}/tests/test_dart.py +0 -0
  262. {graphifyy-0.9.4 → graphifyy-0.9.6}/tests/test_dedup.py +0 -0
  263. {graphifyy-0.9.4 → graphifyy-0.9.6}/tests/test_devin.py +0 -0
  264. {graphifyy-0.9.4 → graphifyy-0.9.6}/tests/test_dotnet.py +0 -0
  265. {graphifyy-0.9.4 → graphifyy-0.9.6}/tests/test_explain_cli.py +0 -0
  266. {graphifyy-0.9.4 → graphifyy-0.9.6}/tests/test_export.py +0 -0
  267. {graphifyy-0.9.4 → graphifyy-0.9.6}/tests/test_extract_cli.py +0 -0
  268. {graphifyy-0.9.4 → graphifyy-0.9.6}/tests/test_extraction_spec_ids.py +0 -0
  269. {graphifyy-0.9.4 → graphifyy-0.9.6}/tests/test_falkordb_integration.py +0 -0
  270. {graphifyy-0.9.4 → graphifyy-0.9.6}/tests/test_file_node_id_spec.py +0 -0
  271. {graphifyy-0.9.4 → graphifyy-0.9.6}/tests/test_file_slice.py +0 -0
  272. {graphifyy-0.9.4 → graphifyy-0.9.6}/tests/test_global_graph.py +0 -0
  273. {graphifyy-0.9.4 → graphifyy-0.9.6}/tests/test_google_workspace.py +0 -0
  274. {graphifyy-0.9.4 → graphifyy-0.9.6}/tests/test_hypergraph.py +0 -0
  275. {graphifyy-0.9.4 → graphifyy-0.9.6}/tests/test_id_normalization_contract.py +0 -0
  276. {graphifyy-0.9.4 → graphifyy-0.9.6}/tests/test_import_extension_resolution.py +0 -0
  277. {graphifyy-0.9.4 → graphifyy-0.9.6}/tests/test_incremental.py +0 -0
  278. {graphifyy-0.9.4 → graphifyy-0.9.6}/tests/test_indirect_dispatch_assign_return.py +0 -0
  279. {graphifyy-0.9.4 → graphifyy-0.9.6}/tests/test_indirect_dispatch_getattr.py +0 -0
  280. {graphifyy-0.9.4 → graphifyy-0.9.6}/tests/test_ingest.py +0 -0
  281. {graphifyy-0.9.4 → graphifyy-0.9.6}/tests/test_install.py +0 -0
  282. {graphifyy-0.9.4 → graphifyy-0.9.6}/tests/test_install_references.py +0 -0
  283. {graphifyy-0.9.4 → graphifyy-0.9.6}/tests/test_install_roundtrip.py +0 -0
  284. {graphifyy-0.9.4 → graphifyy-0.9.6}/tests/test_install_strings.py +0 -0
  285. {graphifyy-0.9.4 → graphifyy-0.9.6}/tests/test_install_upgrade.py +0 -0
  286. {graphifyy-0.9.4 → graphifyy-0.9.6}/tests/test_java_type_resolution.py +0 -0
  287. {graphifyy-0.9.4 → graphifyy-0.9.6}/tests/test_js_import_resolution.py +0 -0
  288. {graphifyy-0.9.4 → graphifyy-0.9.6}/tests/test_label_retry.py +0 -0
  289. {graphifyy-0.9.4 → graphifyy-0.9.6}/tests/test_labeling.py +0 -0
  290. {graphifyy-0.9.4 → graphifyy-0.9.6}/tests/test_language_resolvers.py +0 -0
  291. {graphifyy-0.9.4 → graphifyy-0.9.6}/tests/test_llm_backends.py +0 -0
  292. {graphifyy-0.9.4 → graphifyy-0.9.6}/tests/test_manifest_ingest.py +0 -0
  293. {graphifyy-0.9.4 → graphifyy-0.9.6}/tests/test_mcp_ingest.py +0 -0
  294. {graphifyy-0.9.4 → graphifyy-0.9.6}/tests/test_minhash.py +0 -0
  295. {graphifyy-0.9.4 → graphifyy-0.9.6}/tests/test_multigraph_compat.py +0 -0
  296. {graphifyy-0.9.4 → graphifyy-0.9.6}/tests/test_multigraph_diagnostics.py +0 -0
  297. {graphifyy-0.9.4 → graphifyy-0.9.6}/tests/test_obsidian_filename_cap.py +0 -0
  298. {graphifyy-0.9.4 → graphifyy-0.9.6}/tests/test_office_limits.py +0 -0
  299. {graphifyy-0.9.4 → graphifyy-0.9.6}/tests/test_ollama.py +0 -0
  300. {graphifyy-0.9.4 → graphifyy-0.9.6}/tests/test_openai_custom_endpoint.py +0 -0
  301. {graphifyy-0.9.4 → graphifyy-0.9.6}/tests/test_pascal.py +0 -0
  302. {graphifyy-0.9.4 → graphifyy-0.9.6}/tests/test_path_cli.py +0 -0
  303. {graphifyy-0.9.4 → graphifyy-0.9.6}/tests/test_paths.py +0 -0
  304. {graphifyy-0.9.4 → graphifyy-0.9.6}/tests/test_pg_introspect.py +0 -0
  305. {graphifyy-0.9.4 → graphifyy-0.9.6}/tests/test_pipeline.py +0 -0
  306. {graphifyy-0.9.4 → graphifyy-0.9.6}/tests/test_provider_registry.py +0 -0
  307. {graphifyy-0.9.4 → graphifyy-0.9.6}/tests/test_prs.py +0 -0
  308. {graphifyy-0.9.4 → graphifyy-0.9.6}/tests/test_python_import_resolution.py +0 -0
  309. {graphifyy-0.9.4 → graphifyy-0.9.6}/tests/test_query_cli.py +0 -0
  310. {graphifyy-0.9.4 → graphifyy-0.9.6}/tests/test_querylog.py +0 -0
  311. {graphifyy-0.9.4 → graphifyy-0.9.6}/tests/test_rationale.py +0 -0
  312. {graphifyy-0.9.4 → graphifyy-0.9.6}/tests/test_read_hook.py +0 -0
  313. {graphifyy-0.9.4 → graphifyy-0.9.6}/tests/test_reflect.py +0 -0
  314. {graphifyy-0.9.4 → graphifyy-0.9.6}/tests/test_report.py +0 -0
  315. {graphifyy-0.9.4 → graphifyy-0.9.6}/tests/test_scip_ingest.py +0 -0
  316. {graphifyy-0.9.4 → graphifyy-0.9.6}/tests/test_security.py +0 -0
  317. {graphifyy-0.9.4 → graphifyy-0.9.6}/tests/test_semantic_cleanup.py +0 -0
  318. {graphifyy-0.9.4 → graphifyy-0.9.6}/tests/test_semantic_similarity.py +0 -0
  319. {graphifyy-0.9.4 → graphifyy-0.9.6}/tests/test_skill_version_warning.py +0 -0
  320. {graphifyy-0.9.4 → graphifyy-0.9.6}/tests/test_skillgen.py +0 -0
  321. {graphifyy-0.9.4 → graphifyy-0.9.6}/tests/test_swift_import_resolution.py +0 -0
  322. {graphifyy-0.9.4 → graphifyy-0.9.6}/tests/test_symbol_resolution.py +0 -0
  323. {graphifyy-0.9.4 → graphifyy-0.9.6}/tests/test_terraform.py +0 -0
  324. {graphifyy-0.9.4 → graphifyy-0.9.6}/tests/test_transcribe.py +0 -0
  325. {graphifyy-0.9.4 → graphifyy-0.9.6}/tests/test_ts_inheritance.py +0 -0
  326. {graphifyy-0.9.4 → graphifyy-0.9.6}/tests/test_validate.py +0 -0
  327. {graphifyy-0.9.4 → graphifyy-0.9.6}/tests/test_vue_extraction.py +0 -0
  328. {graphifyy-0.9.4 → graphifyy-0.9.6}/tests/test_wheel_packaging.py +0 -0
  329. {graphifyy-0.9.4 → graphifyy-0.9.6}/tests/test_wiki.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: graphifyy
3
- Version: 0.9.4
3
+ Version: 0.9.6
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: MIT License
6
6
 
@@ -151,7 +151,7 @@ Dynamic: license-file
151
151
  <a href="https://pypi.org/project/graphifyy/"><img src="https://img.shields.io/pypi/v/graphifyy" alt="PyPI"/></a>
152
152
  <a href="https://pepy.tech/project/graphifyy"><img src="https://img.shields.io/pepy/dt/graphifyy?color=blue&label=downloads" alt="Downloads"/></a>
153
153
  <a href="https://github.com/sponsors/safishamsi"><img src="https://img.shields.io/badge/sponsor-safishamsi-ea4aaa?logo=github-sponsors" alt="Sponsor"/></a>
154
- <a href="https://www.linkedin.com/in/safi-shamsi"><img src="https://img.shields.io/badge/LinkedIn-Safi%20Shamsi-0077B5?logo=linkedin" alt="LinkedIn"/></a>
154
+ <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>
155
155
  <a href="https://x.com/graphifyy"><img src="https://img.shields.io/badge/X-graphifyy-000000?logo=x&logoColor=white" alt="X"/></a>
156
156
  </p>
157
157
 
@@ -14,7 +14,7 @@
14
14
  <a href="https://pypi.org/project/graphifyy/"><img src="https://img.shields.io/pypi/v/graphifyy" alt="PyPI"/></a>
15
15
  <a href="https://pepy.tech/project/graphifyy"><img src="https://img.shields.io/pepy/dt/graphifyy?color=blue&label=downloads" alt="Downloads"/></a>
16
16
  <a href="https://github.com/sponsors/safishamsi"><img src="https://img.shields.io/badge/sponsor-safishamsi-ea4aaa?logo=github-sponsors" alt="Sponsor"/></a>
17
- <a href="https://www.linkedin.com/in/safi-shamsi"><img src="https://img.shields.io/badge/LinkedIn-Safi%20Shamsi-0077B5?logo=linkedin" alt="LinkedIn"/></a>
17
+ <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>
18
18
  <a href="https://x.com/graphifyy"><img src="https://img.shields.io/badge/X-graphifyy-000000?logo=x&logoColor=white" alt="X"/></a>
19
19
  </p>
20
20
 
@@ -3550,10 +3550,59 @@ def main() -> None:
3550
3550
  except Exception:
3551
3551
  existing_labels = {}
3552
3552
  if labels_path.exists() and not force_relabel:
3553
- try:
3554
- labels = existing_labels
3555
- except Exception:
3556
- labels = {cid: f"Community {cid}" for cid in communities}
3553
+ # Reuse saved labels, but don't blindly trust them: the graph may have
3554
+ # been re-scoped/re-clustered since labeling, in which case a cid now
3555
+ # covers a DIFFERENT community and its old (LLM) name is wrong (#label-stale).
3556
+ # Validate each community against the membership signature saved beside the
3557
+ # labels; any community that changed (or has no saved label) is renamed by
3558
+ # its current hub — deterministic and correct-by-construction — and the user
3559
+ # is told to `graphify label` for fresh LLM names. Unchanged communities keep
3560
+ # their saved label. When no signature sidecar exists (labels predate this),
3561
+ # fall back to hub-filling only the communities missing a label.
3562
+ from graphify.cluster import community_member_sigs, label_communities_by_hub
3563
+ sig_path = labels_path.parent / (labels_path.name + ".sig")
3564
+ saved_sigs: dict[int, str] = {}
3565
+ if sig_path.exists():
3566
+ try:
3567
+ saved_sigs = {
3568
+ int(k): v for k, v in
3569
+ json.loads(sig_path.read_text(encoding="utf-8")).items()
3570
+ if isinstance(v, str)
3571
+ }
3572
+ except Exception:
3573
+ saved_sigs = {}
3574
+ cur_sigs = community_member_sigs(communities)
3575
+ count_mismatch = len(existing_labels) != len(communities)
3576
+ labels = {}
3577
+ hub_labels: dict[int, str] | None = None
3578
+ changed = 0
3579
+ for cid in communities:
3580
+ have_label = cid in existing_labels
3581
+ if saved_sigs:
3582
+ # Precise: the membership signature tells us if this exact
3583
+ # community changed since it was labeled.
3584
+ fresh = have_label and saved_sigs.get(cid) == cur_sigs.get(cid)
3585
+ else:
3586
+ # No signature sidecar (labels predate it). A differing community
3587
+ # COUNT means the labels describe a different clustering, so a cid's
3588
+ # old label can't be trusted; equal count is the best "same" signal.
3589
+ fresh = have_label and not count_mismatch
3590
+ if fresh:
3591
+ labels[cid] = existing_labels[cid]
3592
+ else:
3593
+ if hub_labels is None:
3594
+ hub_labels = label_communities_by_hub(G, communities)
3595
+ labels[cid] = hub_labels[cid]
3596
+ if have_label:
3597
+ changed += 1
3598
+ if changed:
3599
+ print(
3600
+ f"[graphify] community set changed since labeling "
3601
+ f"({len(existing_labels)} saved labels, {len(communities)} communities now; "
3602
+ f"renamed {changed} community(ies) by their hub). "
3603
+ f"Run `graphify label` to refresh names with the LLM.",
3604
+ file=sys.stderr,
3605
+ )
3557
3606
  elif no_label and not force_relabel:
3558
3607
  labels = {cid: f"Community {cid}" for cid in communities}
3559
3608
  else:
@@ -3606,8 +3655,24 @@ def main() -> None:
3606
3655
  stages.mark("report")
3607
3656
  from graphify.export import backup_if_protected as _backup
3608
3657
  _backup(out)
3658
+ analysis = {
3659
+ "communities": {str(k): v for k, v in communities.items()},
3660
+ "cohesion": {str(k): v for k, v in cohesion.items()},
3661
+ "gods": gods,
3662
+ "surprises": surprises,
3663
+ "questions": questions,
3664
+ }
3665
+ (out / ".graphify_analysis.json").write_text(
3666
+ json.dumps(analysis, indent=2, ensure_ascii=False),
3667
+ encoding="utf-8",
3668
+ )
3609
3669
  to_json(G, communities, str(out / "graph.json"), community_labels=labels)
3610
3670
  labels_path.write_text(json.dumps({str(k): v for k, v in labels.items()}, ensure_ascii=False), encoding="utf-8")
3671
+ # Membership signatures beside the labels so a later cluster-only can detect
3672
+ # which communities changed and avoid reusing a stale label (see reuse above).
3673
+ from graphify.cluster import community_member_sigs as _cms
3674
+ (labels_path.parent / (labels_path.name + ".sig")).write_text(
3675
+ json.dumps({str(k): v for k, v in _cms(communities).items()}), encoding="utf-8")
3611
3676
 
3612
3677
  # Mirror watch.py pattern: gate to_html so core outputs (graph.json +
3613
3678
  # GRAPH_REPORT.md) always land. Honor --no-viz explicitly; otherwise
@@ -3858,7 +3923,14 @@ def main() -> None:
3858
3923
  # may be a MultiGraph and another a Graph. Normalise everything to Graph
3859
3924
  # (the graphify default) by converting MultiGraphs with nx.Graph().
3860
3925
  def _to_simple(g: "_nx.Graph") -> "_nx.Graph":
3861
- if isinstance(g, _nx.MultiGraph):
3926
+ # nx.compose requires every graph to be the same type. Inputs may
3927
+ # disagree on BOTH axes — directed vs undirected, and multi vs simple
3928
+ # — because per-repo graph.json files are written by different extract
3929
+ # paths at different times. Normalise everything to a plain undirected
3930
+ # Graph (the merged cross-repo view is undirected anyway), which covers
3931
+ # DiGraph / MultiGraph / MultiDiGraph. Without this a directed input
3932
+ # crashed compose with "All graphs must be directed or undirected" (#1606).
3933
+ if type(g) is not _nx.Graph:
3862
3934
  return _nx.Graph(g)
3863
3935
  return g
3864
3936
  merged = _nx.Graph()
@@ -23,7 +23,7 @@ _BUILTIN_NOISE_LABELS = frozenset({
23
23
  # Language families — extensions sharing a runtime can legitimately call each other
24
24
  _LANG_FAMILY: dict[str, str] = {
25
25
  **{e: "python" for e in (".py", ".pyw")},
26
- **{e: "js" for e in (".js", ".jsx", ".mjs", ".ejs", ".ts", ".tsx", ".vue", ".svelte")},
26
+ **{e: "js" for e in (".js", ".jsx", ".mjs", ".ejs", ".ts", ".tsx", ".mts", ".cts", ".vue", ".svelte")},
27
27
  **{e: "go" for e in (".go",)},
28
28
  **{e: "rust" for e in (".rs",)},
29
29
  **{e: "jvm" for e in (".java", ".kt", ".kts", ".scala")},
@@ -258,6 +258,11 @@ def _semantic_id_remap(nodes: list, root: str | None) -> dict:
258
258
  rel = Path(sf_norm)
259
259
  if rel.is_absolute():
260
260
  continue # can't relativize (no/failed root) — leave id untouched
261
+ if not rel.name:
262
+ # source_file equals the scan root, so _norm_source_file relativized it
263
+ # to Path('.') — a project-level node with no per-file identity to remap.
264
+ # Leave its id untouched (and avoid _file_stem's empty-name crash, #1618).
265
+ continue
261
266
  new_stem = make_id(_file_stem(rel))
262
267
  if not new_stem:
263
268
  continue
@@ -305,6 +310,8 @@ def graph_has_legacy_ids(nodes: list, root: str | Path | None = None, sample: in
305
310
  rel = Path(_norm_source_file(str(sf), _r) or str(sf))
306
311
  if rel.is_absolute():
307
312
  continue
313
+ if not rel.name:
314
+ continue # source_file == scan root -> Path('.'), no file stem (#1618)
308
315
  new_stem = make_id(_file_stem(rel))
309
316
  if not new_stem:
310
317
  continue
@@ -577,7 +584,7 @@ def build_from_json(extraction: dict, *, directed: bool = False, root: str | Pat
577
584
  _LANG_FAMILY: dict[str, str] = {
578
585
  ".py": "py", ".pyi": "py",
579
586
  ".js": "js", ".mjs": "js", ".cjs": "js", ".jsx": "js",
580
- ".ts": "js", ".tsx": "js",
587
+ ".ts": "js", ".tsx": "js", ".mts": "js", ".cts": "js",
581
588
  ".go": "go", ".rs": "rs",
582
589
  ".java": "jvm", ".kt": "jvm", ".scala": "jvm", ".groovy": "jvm",
583
590
  # C, C++, and ObjC interoperate within one compilation unit: a method
@@ -110,6 +110,27 @@ def label_communities_by_hub(
110
110
  return labels
111
111
 
112
112
 
113
+ def community_member_sigs(communities: dict[int, list[str]]) -> dict[int, str]:
114
+ """Per-community membership fingerprints: ``{cid: sha256(sorted member ids)}``.
115
+
116
+ Persisted next to ``.graphify_labels.json`` so a later ``cluster-only`` can tell
117
+ which communities actually changed since labeling. A cid whose members no longer
118
+ hash the same is a different community — reusing its old (LLM) label there is the
119
+ "stale label after re-scoping" bug this guards against. Deterministic; independent
120
+ of cid index, node order, and machine.
121
+ """
122
+ import hashlib
123
+
124
+ sigs: dict[int, str] = {}
125
+ for cid, members in communities.items():
126
+ h = hashlib.sha256()
127
+ for nid in sorted(str(n) for n in members):
128
+ h.update(nid.encode("utf-8", "replace"))
129
+ h.update(b"\x00")
130
+ sigs[cid] = h.hexdigest()[:16]
131
+ return sigs
132
+
133
+
113
134
  def cluster(
114
135
  G: nx.Graph,
115
136
  resolution: float = 1.0,
@@ -27,7 +27,7 @@ class FileType(str, Enum):
27
27
 
28
28
  _MANIFEST_PATH = str(out_path("manifest.json"))
29
29
 
30
- CODE_EXTENSIONS = {'.py', '.ts', '.tsx', '.js', '.jsx', '.mjs', '.ejs', '.ets', '.go', '.rs', '.java', '.groovy', '.gradle', '.cpp', '.cc', '.cxx', '.c', '.h', '.hpp', '.cu', '.cuh', '.metal', '.rb', '.swift', '.kt', '.kts', '.cs', '.scala', '.php', '.lua', '.luau', '.toc', '.zig', '.ps1', '.psm1', '.psd1', '.ex', '.exs', '.m', '.mm', '.jl', '.vue', '.svelte', '.astro', '.dart', '.v', '.sv', '.svh', '.sql', '.r', '.f', '.F', '.f90', '.F90', '.f95', '.F95', '.f03', '.F03', '.f08', '.F08', '.pas', '.pp', '.dpr', '.dpk', '.lpr', '.inc', '.dfm', '.lfm', '.lpk', '.sh', '.bash', '.json', '.tf', '.tfvars', '.hcl', '.dm', '.dme', '.dmi', '.dmm', '.dmf', '.sln', '.slnx', '.csproj', '.fsproj', '.vbproj', '.xaml', '.razor', '.cshtml', '.cls', '.trigger'}
30
+ CODE_EXTENSIONS = {'.py', '.ts', '.tsx', '.mts', '.cts', '.js', '.jsx', '.mjs', '.ejs', '.ets', '.go', '.rs', '.java', '.groovy', '.gradle', '.cpp', '.cc', '.cxx', '.c', '.h', '.hpp', '.cu', '.cuh', '.metal', '.rb', '.swift', '.kt', '.kts', '.cs', '.scala', '.php', '.lua', '.luau', '.toc', '.zig', '.ps1', '.psm1', '.psd1', '.ex', '.exs', '.m', '.mm', '.jl', '.vue', '.svelte', '.astro', '.dart', '.v', '.sv', '.svh', '.sql', '.r', '.f', '.F', '.f90', '.F90', '.f95', '.F95', '.f03', '.F03', '.f08', '.F08', '.pas', '.pp', '.dpr', '.dpk', '.lpr', '.inc', '.dfm', '.lfm', '.lpk', '.sh', '.bash', '.json', '.tf', '.tfvars', '.hcl', '.dm', '.dme', '.dmi', '.dmm', '.dmf', '.sln', '.slnx', '.csproj', '.fsproj', '.vbproj', '.xaml', '.razor', '.cshtml', '.cls', '.trigger'}
31
31
  DOC_EXTENSIONS = {'.md', '.mdx', '.qmd', '.txt', '.rst', '.html', '.yaml', '.yml'}
32
32
  PAPER_EXTENSIONS = {'.pdf'}
33
33
  IMAGE_EXTENSIONS = {'.png', '.jpg', '.jpeg', '.gif', '.webp', '.svg'}
@@ -991,14 +991,11 @@ def _could_contain_included_path(path: Path, root: Path, patterns: list[tuple[Pa
991
991
 
992
992
 
993
993
  def _auto_follow_symlinks(root: Path) -> bool:
994
- """Auto-detect: ``True`` if ``root`` has any direct symlinked child.
994
+ """Return whether ``root`` has any direct symlinked child.
995
995
 
996
- Allows "fake working dir" patterns (e.g. a folder full of symlinks pointing
997
- at scattered source dirs across the user's machine) to work transparently
998
- without the caller having to know to pass ``follow_symlinks=True``.
999
-
1000
- Override is always possible by passing an explicit ``follow_symlinks=True``
1001
- or ``follow_symlinks=False`` to :func:`detect` / :func:`detect_incremental`.
996
+ Kept for callers that import the private helper, but detection no longer
997
+ enables symlink following automatically. Following symlinks is now an
998
+ explicit opt-in, and out-of-root symlink targets are never indexed.
1002
999
  """
1003
1000
  try:
1004
1001
  for p in root.iterdir():
@@ -1009,10 +1006,19 @@ def _auto_follow_symlinks(root: Path) -> bool:
1009
1006
  return False
1010
1007
 
1011
1008
 
1009
+ def _resolves_under_root(path: Path, root: Path) -> bool:
1010
+ """True when ``path`` resolves to a target inside ``root``."""
1011
+ try:
1012
+ path.resolve().relative_to(root.resolve())
1013
+ except (OSError, RuntimeError, ValueError):
1014
+ return False
1015
+ return True
1016
+
1017
+
1012
1018
  def detect(root: Path, *, follow_symlinks: bool | None = None, google_workspace: bool | None = None, extra_excludes: list[str] | None = None) -> dict:
1013
1019
  root = root.resolve()
1014
1020
  if follow_symlinks is None:
1015
- follow_symlinks = _auto_follow_symlinks(root)
1021
+ follow_symlinks = False
1016
1022
  google_workspace = google_workspace_enabled() if google_workspace is None else google_workspace
1017
1023
  files: dict[FileType, list[str]] = {
1018
1024
  FileType.CODE: [],
@@ -1072,6 +1078,15 @@ def detect(root: Path, *, follow_symlinks: bool | None = None, google_workspace:
1072
1078
  if not _is_noise_dir(d, dp)
1073
1079
  and not _is_ignored(dp / d, root, ignore_patterns, _cache=ignore_cache)
1074
1080
  ]
1081
+ if follow_symlinks:
1082
+ safe_dirs: list[str] = []
1083
+ for d in dirnames:
1084
+ child = dp / d
1085
+ if child.is_symlink() and not _resolves_under_root(child, root):
1086
+ skipped_sensitive.append(str(child) + " [symlink target outside scan root]")
1087
+ continue
1088
+ safe_dirs.append(d)
1089
+ dirnames[:] = safe_dirs
1075
1090
  for fname in filenames:
1076
1091
  if fname in _SKIP_FILES:
1077
1092
  continue
@@ -1093,6 +1108,9 @@ def detect(root: Path, *, follow_symlinks: bool | None = None, google_workspace:
1093
1108
  continue
1094
1109
  if not in_memory and _is_ignored(p, root, ignore_patterns, _cache=ignore_cache):
1095
1110
  continue
1111
+ if not _resolves_under_root(p, root):
1112
+ skipped_sensitive.append(str(p) + " [symlink target outside scan root]")
1113
+ continue
1096
1114
  if _is_sensitive(p):
1097
1115
  skipped_sensitive.append(str(p))
1098
1116
  continue
@@ -1363,11 +1381,10 @@ def detect_incremental(
1363
1381
  Backwards compatible with legacy manifests storing plain float mtime values
1364
1382
  or {mtime, hash} dicts (treated as ast_hash only; semantic_hash = miss).
1365
1383
 
1366
- The ``follow_symlinks`` flag is forwarded to :func:`detect` so corpora that
1367
- rely on symlinked sub-trees (e.g. a ``state_of_truth/`` symlink pointing to a
1368
- directory outside the scan root) are scanned consistently between full and
1369
- incremental runs. ``None`` (default) means auto-detect: ``True`` when ``root``
1370
- contains at least one direct symlinked child, ``False`` otherwise.
1384
+ The ``follow_symlinks`` flag is forwarded to :func:`detect` so in-root
1385
+ symlinked sub-trees are scanned consistently between full and incremental
1386
+ runs. ``None`` (default) does not follow symlinked directories; callers must
1387
+ opt in explicitly, and resolved targets outside the scan root are skipped.
1371
1388
  """
1372
1389
  full = detect(root, follow_symlinks=follow_symlinks, google_workspace=google_workspace, extra_excludes=extra_excludes)
1373
1390
  # Pass ``root`` so a manifest written with relative keys (post-#777) is
@@ -1278,7 +1278,10 @@ def to_canvas(
1278
1278
  group_sizes: dict[int, tuple[int, int]] = {}
1279
1279
  group_cols: dict[int, int] = {}
1280
1280
  for cid in sorted_cids:
1281
- members = communities[cid]
1281
+ # Skip dangling community members with no backing node / filename, so box
1282
+ # sizing matches the cards actually laid out and `G.nodes[m]` never
1283
+ # KeyErrors below — mirrors the to_obsidian guard (#1236).
1284
+ members = [m for m in communities[cid] if m in G and m in node_filenames]
1282
1285
  n = len(members)
1283
1286
  inner_cols = max(1, math.ceil(math.sqrt(n)))
1284
1287
  w = max(600, 220 * inner_cols)
@@ -1351,6 +1354,9 @@ def to_canvas(
1351
1354
  # Node cards inside the group - laid out in the same ceil(sqrt(n))-column
1352
1355
  # grid the box was sized for (group_cols[cid]), so cards fill the box.
1353
1356
  inner_cols = group_cols[cid]
1357
+ # Same dangling-member guard as the sizing loop and to_obsidian (#1236):
1358
+ # a community id absent from G / node_filenames would KeyError the sort.
1359
+ members = [m for m in members if m in G and m in node_filenames]
1354
1360
  sorted_members = sorted(members, key=lambda n: G.nodes[n].get("label", n))
1355
1361
  for m_idx, node_id in enumerate(sorted_members):
1356
1362
  col = m_idx % inner_cols