graphifyy 0.9.3__tar.gz → 0.9.4__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.
- {graphifyy-0.9.3/graphifyy.egg-info → graphifyy-0.9.4}/PKG-INFO +1 -1
- {graphifyy-0.9.3 → graphifyy-0.9.4}/graphify/__main__.py +51 -8
- {graphifyy-0.9.3 → graphifyy-0.9.4}/graphify/affected.py +8 -1
- {graphifyy-0.9.3 → graphifyy-0.9.4}/graphify/build.py +103 -19
- {graphifyy-0.9.3 → graphifyy-0.9.4}/graphify/cluster.py +27 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/graphify/dedup.py +20 -2
- {graphifyy-0.9.3 → graphifyy-0.9.4}/graphify/diagnostics.py +7 -1
- {graphifyy-0.9.3 → graphifyy-0.9.4}/graphify/extract.py +602 -3
- {graphifyy-0.9.3 → graphifyy-0.9.4}/graphify/hooks.py +17 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/graphify/watch.py +6 -3
- {graphifyy-0.9.3 → graphifyy-0.9.4/graphifyy.egg-info}/PKG-INFO +1 -1
- {graphifyy-0.9.3 → graphifyy-0.9.4}/graphifyy.egg-info/SOURCES.txt +7 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/pyproject.toml +1 -1
- graphifyy-0.9.4/tests/test_build_merge_hyperedges_and_prune.py +151 -0
- graphifyy-0.9.4/tests/test_community_hub_labels.py +66 -0
- graphifyy-0.9.4/tests/test_corrupt_graph_json.py +53 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/tests/test_dedup.py +40 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/tests/test_hooks.py +12 -2
- graphifyy-0.9.4/tests/test_indirect_dispatch.py +479 -0
- graphifyy-0.9.4/tests/test_indirect_dispatch_assign_return.py +133 -0
- graphifyy-0.9.4/tests/test_indirect_dispatch_getattr.py +153 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/tests/test_languages.py +45 -0
- graphifyy-0.9.4/tests/test_skill_version_warning.py +60 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/LICENSE +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/README.md +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/graphify/__init__.py +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/graphify/_minhash.py +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/graphify/always_on/agents-md.md +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/graphify/always_on/antigravity-rules.md +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/graphify/always_on/claude-md.md +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/graphify/always_on/gemini-md.md +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/graphify/always_on/kiro-steering.md +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/graphify/always_on/vscode-instructions.md +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/graphify/analyze.py +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/graphify/benchmark.py +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/graphify/cache.py +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/graphify/callflow_html.py +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/graphify/cargo_introspect.py +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/graphify/command-kilo.md +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/graphify/detect.py +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/graphify/export.py +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/graphify/extractors/__init__.py +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/graphify/extractors/base.py +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/graphify/extractors/blade.py +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/graphify/extractors/csharp.py +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/graphify/extractors/elixir.py +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/graphify/extractors/razor.py +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/graphify/extractors/zig.py +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/graphify/file_slice.py +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/graphify/global_graph.py +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/graphify/google_workspace.py +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/graphify/ids.py +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/graphify/ingest.py +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/graphify/llm.py +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/graphify/manifest.py +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/graphify/manifest_ingest.py +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/graphify/mcp_ingest.py +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/graphify/multigraph_compat.py +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/graphify/paths.py +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/graphify/pg_introspect.py +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/graphify/prs.py +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/graphify/querylog.py +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/graphify/reflect.py +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/graphify/report.py +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/graphify/resolver_registry.py +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/graphify/ruby_resolution.py +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/graphify/scip_ingest.py +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/graphify/security.py +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/graphify/semantic_cleanup.py +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/graphify/serve.py +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/graphify/skill-agents.md +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/graphify/skill-aider.md +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/graphify/skill-amp.md +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/graphify/skill-claw.md +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/graphify/skill-codex.md +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/graphify/skill-copilot.md +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/graphify/skill-devin.md +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/graphify/skill-droid.md +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/graphify/skill-kilo.md +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/graphify/skill-kiro.md +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/graphify/skill-opencode.md +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/graphify/skill-pi.md +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/graphify/skill-trae.md +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/graphify/skill-vscode.md +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/graphify/skill-windows.md +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/graphify/skill.md +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/graphify/skills/agents/references/add-watch.md +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/graphify/skills/agents/references/exports.md +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/graphify/skills/agents/references/extraction-spec.md +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/graphify/skills/agents/references/github-and-merge.md +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/graphify/skills/agents/references/hooks.md +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/graphify/skills/agents/references/query.md +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/graphify/skills/agents/references/transcribe.md +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/graphify/skills/agents/references/update.md +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/graphify/skills/amp/references/add-watch.md +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/graphify/skills/amp/references/exports.md +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/graphify/skills/amp/references/extraction-spec.md +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/graphify/skills/amp/references/github-and-merge.md +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/graphify/skills/amp/references/hooks.md +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/graphify/skills/amp/references/query.md +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/graphify/skills/amp/references/transcribe.md +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/graphify/skills/amp/references/update.md +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/graphify/skills/claude/references/add-watch.md +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/graphify/skills/claude/references/exports.md +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/graphify/skills/claude/references/extraction-spec.md +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/graphify/skills/claude/references/github-and-merge.md +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/graphify/skills/claude/references/hooks.md +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/graphify/skills/claude/references/query.md +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/graphify/skills/claude/references/transcribe.md +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/graphify/skills/claude/references/update.md +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/graphify/skills/claw/references/add-watch.md +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/graphify/skills/claw/references/exports.md +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/graphify/skills/claw/references/extraction-spec.md +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/graphify/skills/claw/references/github-and-merge.md +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/graphify/skills/claw/references/hooks.md +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/graphify/skills/claw/references/query.md +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/graphify/skills/claw/references/transcribe.md +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/graphify/skills/claw/references/update.md +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/graphify/skills/codex/references/add-watch.md +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/graphify/skills/codex/references/exports.md +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/graphify/skills/codex/references/extraction-spec.md +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/graphify/skills/codex/references/github-and-merge.md +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/graphify/skills/codex/references/hooks.md +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/graphify/skills/codex/references/query.md +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/graphify/skills/codex/references/transcribe.md +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/graphify/skills/codex/references/update.md +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/graphify/skills/copilot/references/add-watch.md +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/graphify/skills/copilot/references/exports.md +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/graphify/skills/copilot/references/extraction-spec.md +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/graphify/skills/copilot/references/github-and-merge.md +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/graphify/skills/copilot/references/hooks.md +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/graphify/skills/copilot/references/query.md +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/graphify/skills/copilot/references/transcribe.md +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/graphify/skills/copilot/references/update.md +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/graphify/skills/droid/references/add-watch.md +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/graphify/skills/droid/references/exports.md +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/graphify/skills/droid/references/extraction-spec.md +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/graphify/skills/droid/references/github-and-merge.md +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/graphify/skills/droid/references/hooks.md +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/graphify/skills/droid/references/query.md +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/graphify/skills/droid/references/transcribe.md +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/graphify/skills/droid/references/update.md +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/graphify/skills/kilo/references/add-watch.md +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/graphify/skills/kilo/references/exports.md +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/graphify/skills/kilo/references/extraction-spec.md +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/graphify/skills/kilo/references/github-and-merge.md +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/graphify/skills/kilo/references/hooks.md +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/graphify/skills/kilo/references/query.md +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/graphify/skills/kilo/references/transcribe.md +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/graphify/skills/kilo/references/update.md +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/graphify/skills/kiro/references/add-watch.md +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/graphify/skills/kiro/references/exports.md +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/graphify/skills/kiro/references/extraction-spec.md +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/graphify/skills/kiro/references/github-and-merge.md +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/graphify/skills/kiro/references/hooks.md +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/graphify/skills/kiro/references/query.md +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/graphify/skills/kiro/references/transcribe.md +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/graphify/skills/kiro/references/update.md +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/graphify/skills/opencode/references/add-watch.md +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/graphify/skills/opencode/references/exports.md +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/graphify/skills/opencode/references/extraction-spec.md +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/graphify/skills/opencode/references/github-and-merge.md +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/graphify/skills/opencode/references/hooks.md +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/graphify/skills/opencode/references/query.md +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/graphify/skills/opencode/references/transcribe.md +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/graphify/skills/opencode/references/update.md +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/graphify/skills/pi/references/add-watch.md +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/graphify/skills/pi/references/exports.md +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/graphify/skills/pi/references/extraction-spec.md +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/graphify/skills/pi/references/github-and-merge.md +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/graphify/skills/pi/references/hooks.md +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/graphify/skills/pi/references/query.md +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/graphify/skills/pi/references/transcribe.md +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/graphify/skills/pi/references/update.md +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/graphify/skills/trae/references/add-watch.md +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/graphify/skills/trae/references/exports.md +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/graphify/skills/trae/references/extraction-spec.md +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/graphify/skills/trae/references/github-and-merge.md +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/graphify/skills/trae/references/hooks.md +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/graphify/skills/trae/references/query.md +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/graphify/skills/trae/references/transcribe.md +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/graphify/skills/trae/references/update.md +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/graphify/skills/vscode/references/add-watch.md +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/graphify/skills/vscode/references/exports.md +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/graphify/skills/vscode/references/extraction-spec.md +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/graphify/skills/vscode/references/github-and-merge.md +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/graphify/skills/vscode/references/hooks.md +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/graphify/skills/vscode/references/query.md +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/graphify/skills/vscode/references/transcribe.md +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/graphify/skills/vscode/references/update.md +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/graphify/skills/windows/references/add-watch.md +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/graphify/skills/windows/references/exports.md +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/graphify/skills/windows/references/extraction-spec.md +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/graphify/skills/windows/references/github-and-merge.md +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/graphify/skills/windows/references/hooks.md +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/graphify/skills/windows/references/query.md +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/graphify/skills/windows/references/transcribe.md +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/graphify/skills/windows/references/update.md +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/graphify/symbol_resolution.py +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/graphify/transcribe.py +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/graphify/tree_html.py +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/graphify/validate.py +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/graphify/wiki.py +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/graphifyy.egg-info/dependency_links.txt +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/graphifyy.egg-info/entry_points.txt +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/graphifyy.egg-info/requires.txt +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/graphifyy.egg-info/top_level.txt +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/setup.cfg +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/tests/test_affected_cli.py +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/tests/test_agents_platform.py +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/tests/test_analyze.py +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/tests/test_anthropic_custom_endpoint.py +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/tests/test_antigravity_install.py +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/tests/test_astro_extraction.py +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/tests/test_backend_extras.py +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/tests/test_benchmark.py +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/tests/test_build.py +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/tests/test_cache.py +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/tests/test_callflow_html.py +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/tests/test_cargo_introspect.py +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/tests/test_charmap_encoding.py +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/tests/test_chunking.py +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/tests/test_claude_cli_backend.py +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/tests/test_claude_md.py +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/tests/test_cli_export.py +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/tests/test_cluster.py +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/tests/test_codebuddy.py +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/tests/test_confidence.py +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/tests/test_cpp_objc_cross_file_calls.py +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/tests/test_cpp_preprocess.py +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/tests/test_csharp_type_resolution.py +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/tests/test_dart.py +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/tests/test_detect.py +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/tests/test_devin.py +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/tests/test_dotnet.py +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/tests/test_explain_cli.py +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/tests/test_export.py +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/tests/test_extract.py +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/tests/test_extract_cli.py +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/tests/test_extraction_spec_ids.py +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/tests/test_falkordb_integration.py +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/tests/test_file_node_id_spec.py +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/tests/test_file_slice.py +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/tests/test_global_graph.py +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/tests/test_google_workspace.py +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/tests/test_hypergraph.py +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/tests/test_id_normalization_contract.py +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/tests/test_image_vision.py +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/tests/test_import_extension_resolution.py +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/tests/test_incremental.py +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/tests/test_ingest.py +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/tests/test_install.py +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/tests/test_install_references.py +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/tests/test_install_roundtrip.py +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/tests/test_install_strings.py +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/tests/test_install_upgrade.py +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/tests/test_java_type_resolution.py +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/tests/test_js_import_resolution.py +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/tests/test_label_retry.py +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/tests/test_labeling.py +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/tests/test_language_resolvers.py +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/tests/test_llm_backends.py +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/tests/test_llm_parser.py +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/tests/test_manifest_ingest.py +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/tests/test_mcp_ingest.py +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/tests/test_minhash.py +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/tests/test_multigraph_compat.py +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/tests/test_multigraph_diagnostics.py +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/tests/test_multilang.py +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/tests/test_obsidian_dangling_member.py +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/tests/test_obsidian_filename_cap.py +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/tests/test_office_limits.py +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/tests/test_ollama.py +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/tests/test_openai_custom_endpoint.py +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/tests/test_pascal.py +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/tests/test_path_cli.py +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/tests/test_paths.py +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/tests/test_pg_introspect.py +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/tests/test_pipeline.py +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/tests/test_provider_registry.py +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/tests/test_prs.py +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/tests/test_python_import_resolution.py +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/tests/test_query_cli.py +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/tests/test_querylog.py +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/tests/test_rationale.py +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/tests/test_read_hook.py +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/tests/test_reflect.py +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/tests/test_report.py +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/tests/test_ruby_resolution.py +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/tests/test_scip_ingest.py +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/tests/test_security.py +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/tests/test_semantic_cleanup.py +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/tests/test_semantic_similarity.py +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/tests/test_serve.py +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/tests/test_serve_http.py +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/tests/test_skillgen.py +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/tests/test_swift_cross_file_calls.py +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/tests/test_swift_import_resolution.py +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/tests/test_symbol_resolution.py +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/tests/test_terraform.py +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/tests/test_transcribe.py +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/tests/test_ts_inheritance.py +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/tests/test_validate.py +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/tests/test_vue_extraction.py +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/tests/test_watch.py +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/tests/test_wheel_packaging.py +0 -0
- {graphifyy-0.9.3 → graphifyy-0.9.4}/tests/test_wiki.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: graphifyy
|
|
3
|
-
Version: 0.9.
|
|
3
|
+
Version: 0.9.4
|
|
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
|
|
|
@@ -148,7 +148,41 @@ def _check_skill_version(skill_dst: Path) -> None:
|
|
|
148
148
|
except OSError:
|
|
149
149
|
return
|
|
150
150
|
if installed != __version__:
|
|
151
|
-
|
|
151
|
+
if _version_tuple(installed) > _version_tuple(__version__):
|
|
152
|
+
# The skill on disk is NEWER than the running package. `graphify install`
|
|
153
|
+
# writes the package's OWN (older) bundled skill and re-stamps the version,
|
|
154
|
+
# so following the old "run install" advice would silently DOWNGRADE the
|
|
155
|
+
# skill. The real fix is to upgrade the package (#1568). Common for a stale
|
|
156
|
+
# `uv tool` CLI, or a contributor whose dev checkout stamped a newer skill.
|
|
157
|
+
print(
|
|
158
|
+
f" warning: skill is from graphify {installed}, but the package is "
|
|
159
|
+
f"{__version__} (older). Upgrade the package "
|
|
160
|
+
f"(e.g. 'uv tool upgrade graphifyy' or 'pip install -U graphifyy'); "
|
|
161
|
+
f"running 'graphify install' would downgrade the skill.",
|
|
162
|
+
file=sys.stderr,
|
|
163
|
+
)
|
|
164
|
+
else:
|
|
165
|
+
print(f" warning: skill is from graphify {installed}, package is {__version__}. Run 'graphify install' to update.", file=sys.stderr)
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def _version_tuple(version: str) -> tuple[int, ...]:
|
|
169
|
+
"""Parse a version string into a comparable integer tuple (``0.9.2`` -> ``(0, 9, 2)``).
|
|
170
|
+
|
|
171
|
+
Reads the leading digits of each dot-segment, so pre/post-release suffixes
|
|
172
|
+
(``1.0.0rc1``) compare by their numeric core. A non-numeric or empty segment
|
|
173
|
+
becomes 0, so a malformed stamp degrades to a conservative comparison rather
|
|
174
|
+
than raising.
|
|
175
|
+
"""
|
|
176
|
+
parts: list[int] = []
|
|
177
|
+
for segment in str(version).split("."):
|
|
178
|
+
digits = ""
|
|
179
|
+
for ch in segment:
|
|
180
|
+
if ch.isdigit():
|
|
181
|
+
digits += ch
|
|
182
|
+
else:
|
|
183
|
+
break
|
|
184
|
+
parts.append(int(digits) if digits else 0)
|
|
185
|
+
return tuple(parts)
|
|
152
186
|
|
|
153
187
|
|
|
154
188
|
def _refresh_all_version_stamps() -> None:
|
|
@@ -3525,17 +3559,20 @@ def main() -> None:
|
|
|
3525
3559
|
else:
|
|
3526
3560
|
# No labels file yet (or `graphify label` forced a refresh). When run
|
|
3527
3561
|
# standalone there is no orchestrating agent to do skill.md Step 5, so
|
|
3528
|
-
# auto-name communities
|
|
3529
|
-
|
|
3562
|
+
# auto-name communities rather than leave "Community N" (#1097).
|
|
3563
|
+
from graphify.cluster import label_communities_by_hub
|
|
3530
3564
|
from graphify.llm import generate_community_labels
|
|
3531
3565
|
print("Labeling communities...")
|
|
3532
|
-
#
|
|
3533
|
-
#
|
|
3566
|
+
# Deterministic, LLM-free base labels: name each community after its
|
|
3567
|
+
# highest-degree hub, so the report is readable even with no backend
|
|
3568
|
+
# (previously bare "Community N"). A configured LLM backend overrides these
|
|
3569
|
+
# with richer names below; its no-backend placeholder fallback does NOT.
|
|
3570
|
+
hub_labels = label_communities_by_hub(G, communities)
|
|
3534
3571
|
label_communities_input = communities
|
|
3535
|
-
labels =
|
|
3572
|
+
labels = dict(hub_labels)
|
|
3536
3573
|
if missing_only:
|
|
3537
3574
|
labels = {
|
|
3538
|
-
cid: existing_labels.get(cid,
|
|
3575
|
+
cid: existing_labels.get(cid, hub_labels[cid])
|
|
3539
3576
|
for cid in communities
|
|
3540
3577
|
}
|
|
3541
3578
|
label_communities_input = {
|
|
@@ -3547,7 +3584,13 @@ def main() -> None:
|
|
|
3547
3584
|
G, label_communities_input, backend=label_backend, model=label_model, gods=gods,
|
|
3548
3585
|
max_concurrency=label_max_concurrency, batch_size=label_batch_size,
|
|
3549
3586
|
)
|
|
3550
|
-
|
|
3587
|
+
# Only let the LLM OVERRIDE where it produced a real name — its no-backend
|
|
3588
|
+
# fallback returns "Community {cid}" placeholders, which must not clobber
|
|
3589
|
+
# the deterministic hub labels.
|
|
3590
|
+
labels.update({
|
|
3591
|
+
cid: v for cid, v in generated_labels.items()
|
|
3592
|
+
if v and v != f"Community {cid}"
|
|
3593
|
+
})
|
|
3551
3594
|
stages.mark("label")
|
|
3552
3595
|
questions = suggest_questions(G, communities, labels)
|
|
3553
3596
|
tokens = {"input": 0, "output": 0}
|
|
@@ -11,6 +11,7 @@ import networkx as nx
|
|
|
11
11
|
|
|
12
12
|
DEFAULT_AFFECTED_RELATIONS = (
|
|
13
13
|
"calls",
|
|
14
|
+
"indirect_call",
|
|
14
15
|
"references",
|
|
15
16
|
"imports",
|
|
16
17
|
"imports_from",
|
|
@@ -209,7 +210,13 @@ def load_graph(path: Path) -> nx.Graph:
|
|
|
209
210
|
import json
|
|
210
211
|
from networkx.readwrite import json_graph
|
|
211
212
|
|
|
212
|
-
|
|
213
|
+
try:
|
|
214
|
+
raw = json.loads(path.read_text(encoding="utf-8"))
|
|
215
|
+
except (json.JSONDecodeError, OSError) as exc:
|
|
216
|
+
raise RuntimeError(
|
|
217
|
+
f"Cannot read graph file {path}: {exc}. "
|
|
218
|
+
"Re-run 'graphify extract' to regenerate it."
|
|
219
|
+
) from exc
|
|
213
220
|
# Force directed so stored caller→callee direction survives the round-trip;
|
|
214
221
|
# mirrors serve.py and __main__.py (#1174).
|
|
215
222
|
raw = {**raw, "directed": True}
|
|
@@ -117,10 +117,43 @@ def _norm_source_file(p: str | None, root: str | None = None) -> str | None:
|
|
|
117
117
|
try:
|
|
118
118
|
p = Path(p).relative_to(root).as_posix()
|
|
119
119
|
except ValueError:
|
|
120
|
-
|
|
120
|
+
# Lexical relative_to failed. Retry with both sides fully resolved:
|
|
121
|
+
# a symlinked scan root (macOS /var -> /private/var, or a symlinked
|
|
122
|
+
# home/worktree) makes the raw prefixes differ even though they point
|
|
123
|
+
# at the same dir, which otherwise silently defeats prune/replace
|
|
124
|
+
# matching. Only the slow path resolves, so the common lexical match
|
|
125
|
+
# stays filesystem-free.
|
|
126
|
+
try:
|
|
127
|
+
p = Path(p).resolve().relative_to(Path(root).resolve()).as_posix()
|
|
128
|
+
except (ValueError, OSError):
|
|
129
|
+
pass
|
|
121
130
|
return p
|
|
122
131
|
|
|
123
132
|
|
|
133
|
+
def _infer_merge_root(graph_path: Path) -> str | None:
|
|
134
|
+
"""Best-effort scan root for relativizing paths in build_merge when the caller
|
|
135
|
+
passes no ``root`` (#1571).
|
|
136
|
+
|
|
137
|
+
Prefers the committed ``graphify-out/.graphify_root`` marker — the authoritative
|
|
138
|
+
scan root graphify records at build/watch time (#686/#1423) — then falls back to
|
|
139
|
+
the directory that contains the output dir (``graph.json``'s grandparent, i.e.
|
|
140
|
+
``<root>/graphify-out/graph.json`` -> ``<root>``). Returns None if neither
|
|
141
|
+
resolves, in which case normalization is a no-op (prior behavior).
|
|
142
|
+
"""
|
|
143
|
+
try:
|
|
144
|
+
marker = graph_path.parent / ".graphify_root"
|
|
145
|
+
if marker.exists():
|
|
146
|
+
recorded = marker.read_text(encoding="utf-8").strip()
|
|
147
|
+
if recorded:
|
|
148
|
+
return str(Path(recorded).resolve())
|
|
149
|
+
except OSError:
|
|
150
|
+
pass
|
|
151
|
+
try:
|
|
152
|
+
return str(graph_path.parent.parent.resolve())
|
|
153
|
+
except Exception:
|
|
154
|
+
return None
|
|
155
|
+
|
|
156
|
+
|
|
124
157
|
def edge_data(G: nx.Graph, u: str, v: str) -> dict:
|
|
125
158
|
"""Return one edge attribute dict for (u, v), tolerating MultiGraph.
|
|
126
159
|
|
|
@@ -642,7 +675,15 @@ def deduplicate_by_label(nodes: list[dict], edges: list[dict]) -> tuple[list[dic
|
|
|
642
675
|
"""Merge nodes that share a normalised label, rewriting edge references.
|
|
643
676
|
|
|
644
677
|
Prefers IDs without chunk suffixes (_c\\d+) and shorter IDs when tied.
|
|
645
|
-
Drops self-loops created by the merge.
|
|
678
|
+
Drops self-loops created by the merge.
|
|
679
|
+
|
|
680
|
+
Dormant: this is NOT wired into ``build()`` — the active dedup path is
|
|
681
|
+
``deduplicate_entities`` (imported and called in ``build``), which supersedes
|
|
682
|
+
it. The previous "Called in build() automatically" note was never true. It
|
|
683
|
+
also merges by label alone with no ``file_type`` guard, so it must not be
|
|
684
|
+
enabled for code nodes: same-label symbols from different files/packages
|
|
685
|
+
(e.g. two ``Account`` types) would collapse into one — the cross-file
|
|
686
|
+
conflation ``deduplicate_entities`` deliberately avoids for code (#1205).
|
|
646
687
|
"""
|
|
647
688
|
_CHUNK_SUFFIX = re.compile(r"_c\d+$")
|
|
648
689
|
canonical: dict[str, dict] = {} # norm_label -> surviving node
|
|
@@ -714,16 +755,36 @@ def build_merge(
|
|
|
714
755
|
# NetworkX round-trip loses direction permanently (#760).
|
|
715
756
|
from graphify.security import check_graph_file_size_cap
|
|
716
757
|
check_graph_file_size_cap(graph_path)
|
|
717
|
-
|
|
758
|
+
try:
|
|
759
|
+
data = json.loads(graph_path.read_text(encoding="utf-8"))
|
|
760
|
+
except (json.JSONDecodeError, OSError) as exc:
|
|
761
|
+
raise RuntimeError(
|
|
762
|
+
f"Cannot read {graph_path} for incremental merge: {exc}. "
|
|
763
|
+
"Delete the file and run a full rebuild."
|
|
764
|
+
) from exc
|
|
718
765
|
links_key = "links" if "links" in data else "edges"
|
|
719
766
|
existing_nodes = list(data.get("nodes", []))
|
|
720
767
|
existing_edges = list(data.get(links_key, []))
|
|
768
|
+
existing_hyperedges = list(data.get("hyperedges", []))
|
|
721
769
|
had_graph = True
|
|
722
770
|
else:
|
|
723
771
|
existing_nodes = []
|
|
724
772
|
existing_edges = []
|
|
773
|
+
existing_hyperedges = []
|
|
725
774
|
had_graph = False
|
|
726
775
|
|
|
776
|
+
# Effective root for relativizing absolute source_file / prune paths back to the
|
|
777
|
+
# stored relative source_file keys. When the caller passes root we use it;
|
|
778
|
+
# otherwise fall back to the graph's recorded scan root, so absolute
|
|
779
|
+
# prune_sources and new-chunk paths still match even when a caller omits root
|
|
780
|
+
# (#1571 — the skill's --update runbook calls build_merge without root, so
|
|
781
|
+
# absolute deleted-file paths never matched the relative node keys and their
|
|
782
|
+
# nodes survived as ghosts).
|
|
783
|
+
_eff_root = (
|
|
784
|
+
str(Path(root).resolve()) if root is not None
|
|
785
|
+
else _infer_merge_root(graph_path)
|
|
786
|
+
)
|
|
787
|
+
|
|
727
788
|
# Re-extracted files REPLACE their prior contribution. Every source_file
|
|
728
789
|
# present in new_chunks is dropped from the loaded base before merging, so a
|
|
729
790
|
# CHANGED file's stale nodes/edges don't accumulate across incremental
|
|
@@ -733,7 +794,7 @@ def build_merge(
|
|
|
733
794
|
# for them; genuinely deleted files are still handled via prune_sources.
|
|
734
795
|
# Matched in both raw and _norm_source_file form because new_chunks may carry
|
|
735
796
|
# absolute win32 paths while the stored graph keeps relative posix (#1007).
|
|
736
|
-
_replace_root =
|
|
797
|
+
_replace_root = _eff_root
|
|
737
798
|
new_sources: set[str] = set()
|
|
738
799
|
for ch in new_chunks:
|
|
739
800
|
for n in ch.get("nodes", []):
|
|
@@ -756,23 +817,46 @@ def build_merge(
|
|
|
756
817
|
all_chunks = base + list(new_chunks)
|
|
757
818
|
G = build(all_chunks, directed=directed, dedup=dedup, dedup_llm_backend=dedup_llm_backend, root=root)
|
|
758
819
|
|
|
820
|
+
# Prune set for deleted source files — both the raw form (matches nodes that
|
|
821
|
+
# kept absolute source_file) and the normalised relative form (matches nodes
|
|
822
|
+
# relativised by _norm_source_file at build time). .resolve() (via _eff_root)
|
|
823
|
+
# handles symlinked roots and ".." / "./" segments so Path.relative_to()
|
|
824
|
+
# succeeds even when the scan root is a symlink. (#1007, #1571)
|
|
825
|
+
prune_set: set[str] = set()
|
|
826
|
+
for p in (prune_sources or []):
|
|
827
|
+
if not p:
|
|
828
|
+
continue
|
|
829
|
+
prune_set.add(p)
|
|
830
|
+
norm = _norm_source_file(p, _eff_root)
|
|
831
|
+
if norm:
|
|
832
|
+
prune_set.add(norm)
|
|
833
|
+
|
|
834
|
+
# Carry forward hyperedges from files that were neither re-extracted nor
|
|
835
|
+
# deleted (#1574). build() only sees the new chunks' hyperedges, so without
|
|
836
|
+
# this every --update collapses the graph's hyperedge set down to just the
|
|
837
|
+
# changed files'. Re-extracted files' prior hyperedges are dropped (their new
|
|
838
|
+
# version is already in G — replace-per-source, like nodes/edges); deleted
|
|
839
|
+
# files' are dropped via prune_set. id-dedup (attach_hyperedges) so a carried
|
|
840
|
+
# hyperedge never duplicates one the new chunks re-emitted. Mirrors watch.py,
|
|
841
|
+
# which already preserves existing hyperedges across a rebuild.
|
|
842
|
+
if existing_hyperedges:
|
|
843
|
+
carried = []
|
|
844
|
+
for he in existing_hyperedges:
|
|
845
|
+
if not isinstance(he, dict):
|
|
846
|
+
continue
|
|
847
|
+
sf = he.get("source_file")
|
|
848
|
+
norm = _norm_source_file(sf, _eff_root)
|
|
849
|
+
if sf in new_sources or norm in new_sources:
|
|
850
|
+
continue # re-extracted — replaced by the new chunk's version
|
|
851
|
+
if sf in prune_set or norm in prune_set:
|
|
852
|
+
continue # deleted — pruned
|
|
853
|
+
carried.append(he)
|
|
854
|
+
if carried:
|
|
855
|
+
from graphify.export import attach_hyperedges
|
|
856
|
+
attach_hyperedges(G, carried)
|
|
857
|
+
|
|
759
858
|
# Prune nodes and edges from deleted source files
|
|
760
859
|
if prune_sources:
|
|
761
|
-
# Build a set containing both the raw form (matches nodes that kept
|
|
762
|
-
# absolute source_file) and the normalised relative form (matches nodes
|
|
763
|
-
# that were relativised by _norm_source_file at build time).
|
|
764
|
-
# .resolve() handles symlinked roots and redundant ".." / "./" segments
|
|
765
|
-
# so Path.relative_to() succeeds even when the scan root is a symlink.
|
|
766
|
-
# (#1007: manifest absolute paths vs graph relative source_file mismatch)
|
|
767
|
-
_root_str = str(Path(root).resolve()) if root is not None else None
|
|
768
|
-
prune_set: set[str] = set()
|
|
769
|
-
for p in prune_sources:
|
|
770
|
-
if not p:
|
|
771
|
-
continue
|
|
772
|
-
prune_set.add(p)
|
|
773
|
-
norm = _norm_source_file(p, _root_str)
|
|
774
|
-
if norm:
|
|
775
|
-
prune_set.add(norm)
|
|
776
860
|
to_remove = [
|
|
777
861
|
n for n, d in G.nodes(data=True)
|
|
778
862
|
if d.get("source_file") in prune_set
|
|
@@ -83,6 +83,33 @@ _COHESION_SPLIT_THRESHOLD = 0.05 # re-split communities with cohesion below this
|
|
|
83
83
|
_COHESION_SPLIT_MIN_SIZE = 50 # only cohesion-split if community has at least this many nodes
|
|
84
84
|
|
|
85
85
|
|
|
86
|
+
def label_communities_by_hub(
|
|
87
|
+
G: nx.Graph, communities: dict[int, list[str]]
|
|
88
|
+
) -> dict[int, str]:
|
|
89
|
+
"""Deterministic, LLM-free community labels: name each community after its
|
|
90
|
+
highest-degree member — the structural hub — so a report reads ``auth`` /
|
|
91
|
+
``log_action`` instead of ``Community 70``. Degree is measured on the full graph
|
|
92
|
+
``G``; ties break by node id for run-to-run stability. A community whose members
|
|
93
|
+
are all absent from ``G`` falls back to ``Community {cid}``.
|
|
94
|
+
|
|
95
|
+
Used as the default (no-backend) labeler; an LLM naming pass, when configured,
|
|
96
|
+
overrides these with richer names.
|
|
97
|
+
"""
|
|
98
|
+
labels: dict[int, str] = {}
|
|
99
|
+
for cid, members in communities.items():
|
|
100
|
+
present = [n for n in members if n in G]
|
|
101
|
+
if not present:
|
|
102
|
+
labels[cid] = f"Community {cid}"
|
|
103
|
+
continue
|
|
104
|
+
# highest degree wins; ties broken by node id (ascending) for determinism
|
|
105
|
+
hub = min(present, key=lambda n: (-G.degree(n), str(n)))
|
|
106
|
+
name = str(G.nodes[hub].get("label") or hub).strip()
|
|
107
|
+
if name.endswith("()"):
|
|
108
|
+
name = name[:-2]
|
|
109
|
+
labels[cid] = name or f"Community {cid}"
|
|
110
|
+
return labels
|
|
111
|
+
|
|
112
|
+
|
|
86
113
|
def cluster(
|
|
87
114
|
G: nx.Graph,
|
|
88
115
|
resolution: float = 1.0,
|
|
@@ -6,6 +6,7 @@ Jaro-Winkler verification → same-community boost → union-find merge.
|
|
|
6
6
|
from __future__ import annotations
|
|
7
7
|
import math
|
|
8
8
|
import re
|
|
9
|
+
import sys
|
|
9
10
|
import unicodedata
|
|
10
11
|
from collections import defaultdict
|
|
11
12
|
|
|
@@ -219,12 +220,29 @@ def deduplicate_entities(
|
|
|
219
220
|
if len(nodes) <= 1:
|
|
220
221
|
return nodes, edges
|
|
221
222
|
|
|
222
|
-
# Pre-deduplicate: keep first occurrence of each id
|
|
223
|
+
# Pre-deduplicate: keep first occurrence of each id.
|
|
224
|
+
# Warn when two nodes share an ID but originate from different source files —
|
|
225
|
+
# this indicates a cross-chunk ID collision (#1504) where silent data loss occurs.
|
|
223
226
|
seen_ids: dict[str, dict] = {}
|
|
224
227
|
for node in nodes:
|
|
225
228
|
nid = node.get("id", "")
|
|
226
|
-
if
|
|
229
|
+
if not nid:
|
|
230
|
+
continue
|
|
231
|
+
if nid not in seen_ids:
|
|
227
232
|
seen_ids[nid] = node
|
|
233
|
+
else:
|
|
234
|
+
existing_sf = seen_ids[nid].get("source_file") or ""
|
|
235
|
+
new_sf = node.get("source_file") or ""
|
|
236
|
+
if existing_sf != new_sf:
|
|
237
|
+
print(
|
|
238
|
+
f"[graphify] WARNING: node '{nid}' from '{new_sf}' collides with "
|
|
239
|
+
f"node from '{existing_sf}' — the second node will be dropped. "
|
|
240
|
+
f"This is a cross-chunk ID collision caused by two files with the "
|
|
241
|
+
f"same name in different directories. To avoid data loss, run "
|
|
242
|
+
f"'graphify extract' per subfolder and merge with "
|
|
243
|
+
f"'graphify merge-graphs'.",
|
|
244
|
+
file=sys.stderr,
|
|
245
|
+
)
|
|
228
246
|
unique_nodes = list(seen_ids.values())
|
|
229
247
|
|
|
230
248
|
if len(unique_nodes) <= 1:
|
|
@@ -274,7 +274,13 @@ def _read_json_file(path: str | Path) -> dict[str, Any]:
|
|
|
274
274
|
|
|
275
275
|
json_path = Path(path)
|
|
276
276
|
check_graph_file_size_cap(json_path)
|
|
277
|
-
|
|
277
|
+
try:
|
|
278
|
+
data = json.loads(json_path.read_text(encoding="utf-8"))
|
|
279
|
+
except (json.JSONDecodeError, OSError) as exc:
|
|
280
|
+
raise RuntimeError(
|
|
281
|
+
f"Cannot parse {json_path}: {exc}. "
|
|
282
|
+
"The file may be corrupted — re-run 'graphify extract'."
|
|
283
|
+
) from exc
|
|
278
284
|
if not isinstance(data, dict):
|
|
279
285
|
raise ValueError("diagnostic input must be a JSON object")
|
|
280
286
|
return data
|