graphitect 0.2.0__py3-none-any.whl
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.
- graphify/__init__.py +30 -0
- graphify/__main__.py +757 -0
- graphify/_minhash.py +107 -0
- graphify/affected.py +318 -0
- graphify/always_on/agents-md.md +12 -0
- graphify/always_on/antigravity-rules.md +14 -0
- graphify/always_on/claude-md.md +9 -0
- graphify/always_on/gemini-md.md +9 -0
- graphify/always_on/kiro-steering.md +5 -0
- graphify/always_on/vscode-instructions.md +17 -0
- graphify/analyze.py +769 -0
- graphify/benchmark.py +152 -0
- graphify/build.py +2300 -0
- graphify/cache.py +1746 -0
- graphify/callflow_html.py +2051 -0
- graphify/cargo_introspect.py +109 -0
- graphify/cli.py +4745 -0
- graphify/cluster.py +409 -0
- graphify/command-kilo.md +15 -0
- graphify/cross_repo_calls.py +216 -0
- graphify/cross_repo_types.py +75 -0
- graphify/csharp_dispatch.py +154 -0
- graphify/dedup.py +1213 -0
- graphify/detect.py +2566 -0
- graphify/diagnostics.py +406 -0
- graphify/export.py +1349 -0
- graphify/exporters/__init__.py +1 -0
- graphify/exporters/base.py +14 -0
- graphify/exporters/graphdb.py +173 -0
- graphify/exporters/html.py +637 -0
- graphify/extract.py +7856 -0
- graphify/extractors/MIGRATION.md +107 -0
- graphify/extractors/__init__.py +66 -0
- graphify/extractors/apex.py +215 -0
- graphify/extractors/base.py +85 -0
- graphify/extractors/bash.py +579 -0
- graphify/extractors/blade.py +53 -0
- graphify/extractors/commonlisp.py +540 -0
- graphify/extractors/csharp.py +448 -0
- graphify/extractors/dart.py +564 -0
- graphify/extractors/dm.py +494 -0
- graphify/extractors/elixir.py +241 -0
- graphify/extractors/engine.py +6509 -0
- graphify/extractors/fortran.py +311 -0
- graphify/extractors/go.py +527 -0
- graphify/extractors/json_config.py +240 -0
- graphify/extractors/julia.py +289 -0
- graphify/extractors/markdown.py +408 -0
- graphify/extractors/models.py +131 -0
- graphify/extractors/objc.py +566 -0
- graphify/extractors/ocaml.py +289 -0
- graphify/extractors/pascal.py +688 -0
- graphify/extractors/pascal_forms.py +196 -0
- graphify/extractors/powershell.py +522 -0
- graphify/extractors/razor.py +192 -0
- graphify/extractors/resolution.py +3584 -0
- graphify/extractors/robot.py +296 -0
- graphify/extractors/rust.py +470 -0
- graphify/extractors/sln.py +92 -0
- graphify/extractors/sql.py +720 -0
- graphify/extractors/terraform.py +181 -0
- graphify/extractors/verilog.py +329 -0
- graphify/extractors/zig.py +181 -0
- graphify/file_slice.py +246 -0
- graphify/global_graph.py +194 -0
- graphify/google_workspace.py +237 -0
- graphify/hooks.py +933 -0
- graphify/ids.py +93 -0
- graphify/ingest.py +358 -0
- graphify/install.py +2366 -0
- graphify/llm.py +3544 -0
- graphify/manifest.py +4 -0
- graphify/manifest_ingest.py +311 -0
- graphify/mcp_ingest.py +386 -0
- graphify/multigraph_compat.py +212 -0
- graphify/pascal_resolution.py +129 -0
- graphify/paths.py +436 -0
- graphify/pg_introspect.py +165 -0
- graphify/prs.py +770 -0
- graphify/querylog.py +80 -0
- graphify/reflect.py +882 -0
- graphify/report.py +346 -0
- graphify/resolver_registry.py +85 -0
- graphify/ruby_resolution.py +242 -0
- graphify/scip_ingest.py +363 -0
- graphify/security.py +460 -0
- graphify/semantic_cleanup.py +336 -0
- graphify/serve.py +2608 -0
- graphify/skill-agents.md +710 -0
- graphify/skill-aider.md +1283 -0
- graphify/skill-amp.md +710 -0
- graphify/skill-claw.md +713 -0
- graphify/skill-codex.md +710 -0
- graphify/skill-copilot.md +713 -0
- graphify/skill-devin.md +1410 -0
- graphify/skill-droid.md +710 -0
- graphify/skill-kilo.md +722 -0
- graphify/skill-kiro.md +713 -0
- graphify/skill-opencode.md +705 -0
- graphify/skill-pi.md +713 -0
- graphify/skill-trae.md +711 -0
- graphify/skill-vscode.md +709 -0
- graphify/skill-windows.md +755 -0
- graphify/skill.md +713 -0
- graphify/skills/agents/references/add-watch.md +56 -0
- graphify/skills/agents/references/exports.md +87 -0
- graphify/skills/agents/references/extraction-spec.md +70 -0
- graphify/skills/agents/references/github-and-merge.md +46 -0
- graphify/skills/agents/references/hooks.md +33 -0
- graphify/skills/agents/references/query.md +311 -0
- graphify/skills/agents/references/transcribe.md +52 -0
- graphify/skills/agents/references/update.md +210 -0
- graphify/skills/amp/references/add-watch.md +56 -0
- graphify/skills/amp/references/exports.md +87 -0
- graphify/skills/amp/references/extraction-spec.md +70 -0
- graphify/skills/amp/references/github-and-merge.md +46 -0
- graphify/skills/amp/references/hooks.md +33 -0
- graphify/skills/amp/references/query.md +311 -0
- graphify/skills/amp/references/transcribe.md +52 -0
- graphify/skills/amp/references/update.md +210 -0
- graphify/skills/claude/references/add-watch.md +56 -0
- graphify/skills/claude/references/exports.md +87 -0
- graphify/skills/claude/references/extraction-spec.md +70 -0
- graphify/skills/claude/references/github-and-merge.md +46 -0
- graphify/skills/claude/references/hooks.md +33 -0
- graphify/skills/claude/references/query.md +311 -0
- graphify/skills/claude/references/transcribe.md +52 -0
- graphify/skills/claude/references/update.md +210 -0
- graphify/skills/claw/references/add-watch.md +56 -0
- graphify/skills/claw/references/exports.md +87 -0
- graphify/skills/claw/references/extraction-spec.md +31 -0
- graphify/skills/claw/references/github-and-merge.md +46 -0
- graphify/skills/claw/references/hooks.md +33 -0
- graphify/skills/claw/references/query.md +311 -0
- graphify/skills/claw/references/transcribe.md +52 -0
- graphify/skills/claw/references/update.md +210 -0
- graphify/skills/codex/references/add-watch.md +56 -0
- graphify/skills/codex/references/exports.md +87 -0
- graphify/skills/codex/references/extraction-spec.md +31 -0
- graphify/skills/codex/references/github-and-merge.md +46 -0
- graphify/skills/codex/references/hooks.md +33 -0
- graphify/skills/codex/references/query.md +311 -0
- graphify/skills/codex/references/transcribe.md +52 -0
- graphify/skills/codex/references/update.md +210 -0
- graphify/skills/copilot/references/add-watch.md +56 -0
- graphify/skills/copilot/references/exports.md +87 -0
- graphify/skills/copilot/references/extraction-spec.md +70 -0
- graphify/skills/copilot/references/github-and-merge.md +46 -0
- graphify/skills/copilot/references/hooks.md +33 -0
- graphify/skills/copilot/references/query.md +311 -0
- graphify/skills/copilot/references/transcribe.md +52 -0
- graphify/skills/copilot/references/update.md +210 -0
- graphify/skills/droid/references/add-watch.md +56 -0
- graphify/skills/droid/references/exports.md +87 -0
- graphify/skills/droid/references/extraction-spec.md +70 -0
- graphify/skills/droid/references/github-and-merge.md +46 -0
- graphify/skills/droid/references/hooks.md +33 -0
- graphify/skills/droid/references/query.md +311 -0
- graphify/skills/droid/references/transcribe.md +52 -0
- graphify/skills/droid/references/update.md +210 -0
- graphify/skills/kilo/references/add-watch.md +56 -0
- graphify/skills/kilo/references/exports.md +87 -0
- graphify/skills/kilo/references/extraction-spec.md +70 -0
- graphify/skills/kilo/references/github-and-merge.md +46 -0
- graphify/skills/kilo/references/hooks.md +33 -0
- graphify/skills/kilo/references/query.md +311 -0
- graphify/skills/kilo/references/transcribe.md +52 -0
- graphify/skills/kilo/references/update.md +210 -0
- graphify/skills/kiro/references/add-watch.md +56 -0
- graphify/skills/kiro/references/exports.md +87 -0
- graphify/skills/kiro/references/extraction-spec.md +31 -0
- graphify/skills/kiro/references/github-and-merge.md +46 -0
- graphify/skills/kiro/references/hooks.md +33 -0
- graphify/skills/kiro/references/query.md +311 -0
- graphify/skills/kiro/references/transcribe.md +52 -0
- graphify/skills/kiro/references/update.md +210 -0
- graphify/skills/opencode/references/add-watch.md +56 -0
- graphify/skills/opencode/references/exports.md +87 -0
- graphify/skills/opencode/references/extraction-spec.md +70 -0
- graphify/skills/opencode/references/github-and-merge.md +46 -0
- graphify/skills/opencode/references/hooks.md +33 -0
- graphify/skills/opencode/references/query.md +311 -0
- graphify/skills/opencode/references/transcribe.md +52 -0
- graphify/skills/opencode/references/update.md +210 -0
- graphify/skills/pi/references/add-watch.md +56 -0
- graphify/skills/pi/references/exports.md +87 -0
- graphify/skills/pi/references/extraction-spec.md +31 -0
- graphify/skills/pi/references/github-and-merge.md +46 -0
- graphify/skills/pi/references/hooks.md +33 -0
- graphify/skills/pi/references/query.md +311 -0
- graphify/skills/pi/references/transcribe.md +52 -0
- graphify/skills/pi/references/update.md +210 -0
- graphify/skills/trae/references/add-watch.md +56 -0
- graphify/skills/trae/references/exports.md +87 -0
- graphify/skills/trae/references/extraction-spec.md +70 -0
- graphify/skills/trae/references/github-and-merge.md +46 -0
- graphify/skills/trae/references/hooks.md +35 -0
- graphify/skills/trae/references/query.md +311 -0
- graphify/skills/trae/references/transcribe.md +52 -0
- graphify/skills/trae/references/update.md +210 -0
- graphify/skills/vscode/references/add-watch.md +56 -0
- graphify/skills/vscode/references/exports.md +87 -0
- graphify/skills/vscode/references/extraction-spec.md +70 -0
- graphify/skills/vscode/references/github-and-merge.md +46 -0
- graphify/skills/vscode/references/hooks.md +33 -0
- graphify/skills/vscode/references/query.md +311 -0
- graphify/skills/vscode/references/transcribe.md +52 -0
- graphify/skills/vscode/references/update.md +210 -0
- graphify/skills/windows/references/add-watch.md +56 -0
- graphify/skills/windows/references/exports.md +87 -0
- graphify/skills/windows/references/extraction-spec.md +70 -0
- graphify/skills/windows/references/github-and-merge.md +46 -0
- graphify/skills/windows/references/hooks.md +33 -0
- graphify/skills/windows/references/query.md +311 -0
- graphify/skills/windows/references/transcribe.md +52 -0
- graphify/skills/windows/references/update.md +210 -0
- graphify/symbol_resolution.py +556 -0
- graphify/transcribe.py +186 -0
- graphify/tree_html.py +603 -0
- graphify/validate.py +95 -0
- graphify/watch.py +2280 -0
- graphify/wiki.py +405 -0
- graphitect/__init__.py +28 -0
- graphitect/__main__.py +4 -0
- graphitect/_vendor/__init__.py +2 -0
- graphitect/_vendor/archify/LICENSE +22 -0
- graphitect/_vendor/archify/SKILL.md +137 -0
- graphitect/_vendor/archify/THIRD_PARTY_NOTICES.md +69 -0
- graphitect/_vendor/archify/assets/JetBrainsMono-OFL.txt +93 -0
- graphitect/_vendor/archify/assets/template.html +14935 -0
- graphitect/_vendor/archify/bin/archify.mjs +2091 -0
- graphitect/_vendor/archify/bin/open-artifact.mjs +86 -0
- graphitect/_vendor/archify/bin/preview.mjs +653 -0
- graphitect/_vendor/archify/bin/visual-check.mjs +829 -0
- graphitect/_vendor/archify/brand-marks/README.md +31 -0
- graphitect/_vendor/archify/brand-marks/catalog.json +131 -0
- graphitect/_vendor/archify/delta/architecture-delta.mjs +1221 -0
- graphitect/_vendor/archify/examples/agent-run.lifecycle.json +60 -0
- graphitect/_vendor/archify/examples/agent-tool-call.workflow.json +94 -0
- graphitect/_vendor/archify/examples/async-job-roundtrip.sequence.json +61 -0
- graphitect/_vendor/archify/examples/brand-aware-delivery.architecture.json +47 -0
- graphitect/_vendor/archify/examples/cache-miss-request.sequence.json +82 -0
- graphitect/_vendor/archify/examples/checkout-platform.base.architecture.json +31 -0
- graphitect/_vendor/archify/examples/checkout-platform.head.architecture.json +31 -0
- graphitect/_vendor/archify/examples/dataflow-product-analytics.html +15045 -0
- graphitect/_vendor/archify/examples/deployment-release.lifecycle.json +49 -0
- graphitect/_vendor/archify/examples/event-stream.dataflow.json +57 -0
- graphitect/_vendor/archify/examples/incident-response.workflow.json +64 -0
- graphitect/_vendor/archify/examples/lifecycle-agent-run.html +14980 -0
- graphitect/_vendor/archify/examples/product-analytics.dataflow.json +76 -0
- graphitect/_vendor/archify/examples/production-deployment.architecture.json +71 -0
- graphitect/_vendor/archify/examples/release-delivery.workflow.json +62 -0
- graphitect/_vendor/archify/examples/sequence-cache-miss-request.html +15060 -0
- graphitect/_vendor/archify/examples/web-app-rendered.html +15009 -0
- graphitect/_vendor/archify/examples/web-app.architecture.json +46 -0
- graphitect/_vendor/archify/examples/workflow-agent-tool-call-rendered.html +15051 -0
- graphitect/_vendor/archify/migrations/workflow-v2.mjs +279 -0
- graphitect/_vendor/archify/package-lock.json +149 -0
- graphitect/_vendor/archify/package.json +39 -0
- graphitect/_vendor/archify/recipes/scenarios.mjs +391 -0
- graphitect/_vendor/archify/references/authoring-contract.md +243 -0
- graphitect/_vendor/archify/references/brand-marks.md +65 -0
- graphitect/_vendor/archify/references/delivery-contract.md +120 -0
- graphitect/_vendor/archify/references/viewer-runtime.md +45 -0
- graphitect/_vendor/archify/renderers/architecture/grid.mjs +62 -0
- graphitect/_vendor/archify/renderers/architecture/render-architecture.mjs +1078 -0
- graphitect/_vendor/archify/renderers/dataflow/README.md +104 -0
- graphitect/_vendor/archify/renderers/dataflow/render-dataflow.mjs +483 -0
- graphitect/_vendor/archify/renderers/lifecycle/README.md +115 -0
- graphitect/_vendor/archify/renderers/lifecycle/render-lifecycle.mjs +561 -0
- graphitect/_vendor/archify/renderers/sequence/README.md +114 -0
- graphitect/_vendor/archify/renderers/sequence/render-sequence.mjs +464 -0
- graphitect/_vendor/archify/renderers/shared/brand-marks.mjs +563 -0
- graphitect/_vendor/archify/renderers/shared/cli.mjs +218 -0
- graphitect/_vendor/archify/renderers/shared/desktop-readability.mjs +26 -0
- graphitect/_vendor/archify/renderers/shared/diagnostics.mjs +127 -0
- graphitect/_vendor/archify/renderers/shared/engineering-profiles.mjs +157 -0
- graphitect/_vendor/archify/renderers/shared/generated-brand-marks.mjs +2003 -0
- graphitect/_vendor/archify/renderers/shared/generated-validators.mjs +13 -0
- graphitect/_vendor/archify/renderers/shared/geometry.mjs +1423 -0
- graphitect/_vendor/archify/renderers/shared/i18n.mjs +595 -0
- graphitect/_vendor/archify/renderers/shared/layout-report.mjs +40 -0
- graphitect/_vendor/archify/renderers/shared/legend.mjs +217 -0
- graphitect/_vendor/archify/renderers/shared/output-path.mjs +340 -0
- graphitect/_vendor/archify/renderers/shared/repository-evidence.mjs +238 -0
- graphitect/_vendor/archify/renderers/shared/repository-location.mjs +58 -0
- graphitect/_vendor/archify/renderers/shared/text-fit.mjs +49 -0
- graphitect/_vendor/archify/renderers/shared/utils.mjs +232 -0
- graphitect/_vendor/archify/renderers/shared/validator.mjs +86 -0
- graphitect/_vendor/archify/renderers/workflow/README.md +223 -0
- graphitect/_vendor/archify/renderers/workflow/render-workflow.mjs +35 -0
- graphitect/_vendor/archify/renderers/workflow/workflow-compiler.mjs +4400 -0
- graphitect/_vendor/archify/renderers/workflow/workflow-migration-geometry.mjs +144 -0
- graphitect/_vendor/archify/schemas/README.md +211 -0
- graphitect/_vendor/archify/schemas/architecture.schema.json +178 -0
- graphitect/_vendor/archify/schemas/common.schema.json +115 -0
- graphitect/_vendor/archify/schemas/dataflow.schema.json +243 -0
- graphitect/_vendor/archify/schemas/lifecycle.schema.json +266 -0
- graphitect/_vendor/archify/schemas/sequence.schema.json +223 -0
- graphitect/_vendor/archify/schemas/workflow.schema.json +428 -0
- graphitect/_vendor/archify/scripts/check-render-output.mjs +836 -0
- graphitect/_vendor/archify/scripts/check-update.mjs +1667 -0
- graphitect/_vendor/archify/scripts/generate-brand-marks.mjs +141 -0
- graphitect/_vendor/archify/scripts/generate-validators.mjs +66 -0
- graphitect/_vendor/archify/scripts/render-examples.mjs +26 -0
- graphitect/_vendor/archify/scripts/update-contract.mjs +182 -0
- graphitect/_vendor/archify/skill-release.json +10 -0
- graphitect/cli.py +981 -0
- graphitect/deliver/__init__.py +5 -0
- graphitect/deliver/archify_adapter.py +1877 -0
- graphitect/deliver/archify_ir.py +160 -0
- graphitect/deliver/archify_repair.py +135 -0
- graphitect/deliver/doc_compiler.py +916 -0
- graphitect/ground/__init__.py +5 -0
- graphitect/ground/describe_source.py +27 -0
- graphitect/ground/fullread_source.py +56 -0
- graphitect/ground/graphify_source.py +107 -0
- graphitect/models.py +118 -0
- graphitect/skill/SKILL.md +80 -0
- graphitect/skill/agents/openai.yaml +4 -0
- graphitect/synthesize/__init__.py +5 -0
- graphitect/synthesize/engine.py +281 -0
- graphitect/synthesize/llm_backend.py +331 -0
- graphitect/synthesize/questions.py +139 -0
- graphitect/synthesize/rubric.py +104 -0
- graphitect-0.2.0.dist-info/METADATA +284 -0
- graphitect-0.2.0.dist-info/RECORD +336 -0
- graphitect-0.2.0.dist-info/WHEEL +5 -0
- graphitect-0.2.0.dist-info/entry_points.txt +2 -0
- graphitect-0.2.0.dist-info/licenses/LICENSE +21 -0
- graphitect-0.2.0.dist-info/licenses/LICENSE-ARCHIFY-MIT +22 -0
- graphitect-0.2.0.dist-info/licenses/LICENSE-GRAPHIFY-APACHE-2.0 +202 -0
- graphitect-0.2.0.dist-info/licenses/LICENSE-GRAPHIFY-MIT +21 -0
- graphitect-0.2.0.dist-info/licenses/NOTICE-ARCHIFY-THIRD-PARTY.md +69 -0
- graphitect-0.2.0.dist-info/licenses/NOTICE-GRAPHIFY +8 -0
- graphitect-0.2.0.dist-info/top_level.txt +2 -0
graphify/scip_ingest.py
ADDED
|
@@ -0,0 +1,363 @@
|
|
|
1
|
+
"""scip_ingest.py — SCIP JSON ingestion (simplified subset).
|
|
2
|
+
|
|
3
|
+
Reads a simplified SCIP-style JSON structure and converts it into
|
|
4
|
+
Graphify nodes and edges. NOT a full SCIP protobuf implementation —
|
|
5
|
+
this is a skeleton that consumes the simplified shape described below.
|
|
6
|
+
|
|
7
|
+
Not wired to the CLI in this phase.
|
|
8
|
+
|
|
9
|
+
Entry point:
|
|
10
|
+
ingest_scip_json(doc: object, source_file: str = "",
|
|
11
|
+
language: str = "python") -> dict[str, Any]
|
|
12
|
+
|
|
13
|
+
Returns {"nodes": [...], "edges": [...]} compatible with Graphify's
|
|
14
|
+
extraction result format. All edges emitted are endpoint-safe — the
|
|
15
|
+
function builds a symbol → node_id index in a first pass and either
|
|
16
|
+
resolves relationship targets via that index or creates a stub
|
|
17
|
+
external node so `build_from_json()` will keep the edge.
|
|
18
|
+
|
|
19
|
+
Supported (simplified) JSON shape:
|
|
20
|
+
documents[]: { relative_path, language, symbols[] }
|
|
21
|
+
symbols[]: { symbol, kind, display_name, documentation[],
|
|
22
|
+
relationships[], occurrences[] }
|
|
23
|
+
relationships[]: { symbol, is_reference, is_implementation,
|
|
24
|
+
is_type_definition, is_definition }
|
|
25
|
+
occurrences[]: { range[], symbol, symbol_roles }
|
|
26
|
+
|
|
27
|
+
This shape diverges from the official SCIP protobuf (where occurrences
|
|
28
|
+
live on the document, not on each symbol). We consume the simplified
|
|
29
|
+
shape that LLM-generated SCIP-style JSON commonly produces. Future
|
|
30
|
+
cycles may add document-level occurrence support.
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
from __future__ import annotations
|
|
34
|
+
|
|
35
|
+
import hashlib
|
|
36
|
+
import re
|
|
37
|
+
from typing import Any
|
|
38
|
+
|
|
39
|
+
from graphify.security import sanitize_metadata
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def ingest_scip_json(
|
|
43
|
+
doc: object,
|
|
44
|
+
source_file: str = "",
|
|
45
|
+
language: str = "python",
|
|
46
|
+
) -> dict[str, Any]:
|
|
47
|
+
"""Convert a SCIP-style JSON document into Graphify nodes and edges.
|
|
48
|
+
|
|
49
|
+
Parameter ``doc`` is ``object`` (not ``dict[str, Any]``) because SCIP
|
|
50
|
+
documents come from external tools — we may be handed arbitrary
|
|
51
|
+
deserialized JSON. The first check rejects anything that isn't a dict
|
|
52
|
+
and returns the empty result.
|
|
53
|
+
|
|
54
|
+
Two-pass design:
|
|
55
|
+
1. Build a ``symbol_str → node_id`` index across every valid symbol
|
|
56
|
+
in every valid document, plus collect per-symbol metadata.
|
|
57
|
+
2. Emit nodes for every indexed symbol and then emit relationship
|
|
58
|
+
edges. Relationship targets are resolved via the index when
|
|
59
|
+
present; otherwise a stub ``scip_external`` node is added so
|
|
60
|
+
edges never dangle.
|
|
61
|
+
"""
|
|
62
|
+
nodes: list[dict[str, Any]] = []
|
|
63
|
+
edges: list[dict[str, Any]] = []
|
|
64
|
+
seen_node_ids: set[str] = set()
|
|
65
|
+
seen_edges: set[tuple[str, str, str, str | None]] = set()
|
|
66
|
+
|
|
67
|
+
if not isinstance(doc, dict):
|
|
68
|
+
return {"nodes": nodes, "edges": edges}
|
|
69
|
+
|
|
70
|
+
documents = doc.get("documents", [])
|
|
71
|
+
if not isinstance(documents, list):
|
|
72
|
+
return {"nodes": nodes, "edges": edges}
|
|
73
|
+
|
|
74
|
+
# ---- pass 1: build symbol → node_id indices -----------------------------
|
|
75
|
+
# Two indices so relationship resolution can be document-aware:
|
|
76
|
+
# per_doc: (symbol_id, doc_path) → node_id (same-document precedence)
|
|
77
|
+
# global: symbol_id → list[node_id] (cross-document fallback,
|
|
78
|
+
# used only when unambiguous)
|
|
79
|
+
per_doc_index: dict[tuple[str, str], str] = {}
|
|
80
|
+
global_index: dict[str, list[str]] = {}
|
|
81
|
+
# Per-symbol metadata kept for pass-2 node emission (avoids re-walking
|
|
82
|
+
# the document tree).
|
|
83
|
+
symbol_records: list[dict[str, Any]] = []
|
|
84
|
+
for document in documents:
|
|
85
|
+
if not isinstance(document, dict):
|
|
86
|
+
continue
|
|
87
|
+
doc_path = _coerce_str(document.get("relative_path"), source_file)
|
|
88
|
+
doc_language = _coerce_str(document.get("language"), language)
|
|
89
|
+
symbols = document.get("symbols", [])
|
|
90
|
+
if not isinstance(symbols, list):
|
|
91
|
+
continue
|
|
92
|
+
for symbol in symbols:
|
|
93
|
+
if not isinstance(symbol, dict):
|
|
94
|
+
continue
|
|
95
|
+
symbol_id = _coerce_str(symbol.get("symbol"), "")
|
|
96
|
+
if not symbol_id:
|
|
97
|
+
continue
|
|
98
|
+
node_id = _make_scip_node_id(symbol_id, doc_path)
|
|
99
|
+
per_doc_index.setdefault((symbol_id, doc_path), node_id)
|
|
100
|
+
# Dedupe node_ids in the global index — duplicate symbol records
|
|
101
|
+
# within the SAME document produce identical node_ids, and we
|
|
102
|
+
# don't want them to look like cross-document ambiguity.
|
|
103
|
+
candidates = global_index.setdefault(symbol_id, [])
|
|
104
|
+
if node_id not in candidates:
|
|
105
|
+
candidates.append(node_id)
|
|
106
|
+
symbol_records.append(
|
|
107
|
+
{
|
|
108
|
+
"node_id": node_id,
|
|
109
|
+
"symbol_id": symbol_id,
|
|
110
|
+
"doc_path": doc_path,
|
|
111
|
+
"language": doc_language,
|
|
112
|
+
"raw": symbol,
|
|
113
|
+
}
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
# ---- pass 2: emit nodes + relationship edges -----------------------------
|
|
117
|
+
for record in symbol_records:
|
|
118
|
+
_emit_symbol_node(record, nodes, seen_node_ids)
|
|
119
|
+
_emit_relationships(
|
|
120
|
+
record,
|
|
121
|
+
per_doc_index,
|
|
122
|
+
global_index,
|
|
123
|
+
nodes,
|
|
124
|
+
edges,
|
|
125
|
+
seen_node_ids,
|
|
126
|
+
seen_edges,
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
return {"nodes": nodes, "edges": edges}
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def _emit_symbol_node(
|
|
133
|
+
record: dict[str, Any],
|
|
134
|
+
nodes: list[dict[str, Any]],
|
|
135
|
+
seen_node_ids: set[str],
|
|
136
|
+
) -> None:
|
|
137
|
+
"""Append the canonical node for a SCIP symbol record."""
|
|
138
|
+
node_id = record["node_id"]
|
|
139
|
+
if node_id in seen_node_ids:
|
|
140
|
+
return
|
|
141
|
+
raw = record["raw"]
|
|
142
|
+
symbol_id = record["symbol_id"]
|
|
143
|
+
doc_path = record["doc_path"]
|
|
144
|
+
kind = _coerce_str(raw.get("kind"), "unknown")
|
|
145
|
+
display_name = _coerce_str(raw.get("display_name"), "")
|
|
146
|
+
documentation = raw.get("documentation", [])
|
|
147
|
+
description = ""
|
|
148
|
+
if isinstance(documentation, list) and documentation:
|
|
149
|
+
first = documentation[0]
|
|
150
|
+
if isinstance(first, str):
|
|
151
|
+
description = first
|
|
152
|
+
occurrences = raw.get("occurrences", [])
|
|
153
|
+
sourceline = _first_occurrence_line(occurrences)
|
|
154
|
+
suffix = symbol_id.split("#")[-1] if "#" in symbol_id else symbol_id
|
|
155
|
+
label = display_name or suffix or symbol_id
|
|
156
|
+
seen_node_ids.add(node_id) # label uses display_name or suffix (never empty for valid symbols)
|
|
157
|
+
nodes.append(
|
|
158
|
+
{
|
|
159
|
+
"id": node_id,
|
|
160
|
+
"label": label,
|
|
161
|
+
"file_type": _scip_kind_to_file_type(kind),
|
|
162
|
+
"source_file": doc_path,
|
|
163
|
+
"source_location": f"L{sourceline}" if sourceline else "",
|
|
164
|
+
"metadata": sanitize_metadata(_build_scip_metadata(symbol_id, kind, description)),
|
|
165
|
+
}
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def _emit_relationships(
|
|
170
|
+
record: dict[str, Any],
|
|
171
|
+
per_doc_index: dict[tuple[str, str], str],
|
|
172
|
+
global_index: dict[str, list[str]],
|
|
173
|
+
nodes: list[dict[str, Any]],
|
|
174
|
+
edges: list[dict[str, Any]],
|
|
175
|
+
seen_node_ids: set[str],
|
|
176
|
+
seen_edges: set[tuple[str, str, str, str | None]],
|
|
177
|
+
) -> None:
|
|
178
|
+
"""Append edges (and stub nodes when needed) for a symbol's relationships.
|
|
179
|
+
|
|
180
|
+
Relationship target resolution order:
|
|
181
|
+
1. Same-document `(target_symbol, doc_path)` — duplicate local symbol
|
|
182
|
+
names across files route to THIS file's symbol, not another's.
|
|
183
|
+
2. Unique cross-document match — when the symbol exists in exactly
|
|
184
|
+
one document and that document is different from the source.
|
|
185
|
+
3. Stub external node — for symbols not declared in any document
|
|
186
|
+
OR ambiguous duplicates across multiple documents (refusing to
|
|
187
|
+
guess silently).
|
|
188
|
+
"""
|
|
189
|
+
raw = record["raw"]
|
|
190
|
+
source_node_id = record["node_id"]
|
|
191
|
+
doc_path = record["doc_path"]
|
|
192
|
+
occurrences = raw.get("occurrences", [])
|
|
193
|
+
sourceline = _first_occurrence_line(occurrences)
|
|
194
|
+
relationships = raw.get("relationships")
|
|
195
|
+
if not isinstance(relationships, list):
|
|
196
|
+
return
|
|
197
|
+
for rel in relationships:
|
|
198
|
+
if not isinstance(rel, dict):
|
|
199
|
+
continue
|
|
200
|
+
target_symbol = _coerce_str(rel.get("symbol"), "")
|
|
201
|
+
if not target_symbol:
|
|
202
|
+
continue
|
|
203
|
+
target_node_id = _resolve_relationship_target(
|
|
204
|
+
target_symbol,
|
|
205
|
+
doc_path,
|
|
206
|
+
per_doc_index,
|
|
207
|
+
global_index,
|
|
208
|
+
)
|
|
209
|
+
if target_node_id is None:
|
|
210
|
+
# External relationship target: emit a stub node so the edge
|
|
211
|
+
# is never dangling. The stub uses the source document's path
|
|
212
|
+
# as its host context.
|
|
213
|
+
target_node_id = _make_scip_node_id(target_symbol, doc_path)
|
|
214
|
+
if target_node_id not in seen_node_ids:
|
|
215
|
+
seen_node_ids.add(target_node_id)
|
|
216
|
+
suffix = target_symbol.split("#")[-1] if "#" in target_symbol else target_symbol
|
|
217
|
+
nodes.append(
|
|
218
|
+
{
|
|
219
|
+
"id": target_node_id,
|
|
220
|
+
"label": suffix or target_symbol,
|
|
221
|
+
"file_type": "code",
|
|
222
|
+
"source_file": doc_path,
|
|
223
|
+
"source_location": "",
|
|
224
|
+
"metadata": sanitize_metadata(
|
|
225
|
+
_build_scip_metadata(target_symbol, "external", "")
|
|
226
|
+
),
|
|
227
|
+
}
|
|
228
|
+
)
|
|
229
|
+
relation = _scip_relation_for(rel)
|
|
230
|
+
source_location = f"L{sourceline}" if sourceline else ""
|
|
231
|
+
key = (source_node_id, target_node_id, relation, source_location)
|
|
232
|
+
if key in seen_edges:
|
|
233
|
+
continue
|
|
234
|
+
seen_edges.add(key)
|
|
235
|
+
edges.append(
|
|
236
|
+
{
|
|
237
|
+
"source": source_node_id,
|
|
238
|
+
"target": target_node_id,
|
|
239
|
+
"relation": relation,
|
|
240
|
+
"confidence": "EXTRACTED",
|
|
241
|
+
"confidence_score": 1.0,
|
|
242
|
+
"source_file": doc_path,
|
|
243
|
+
"source_location": source_location,
|
|
244
|
+
"weight": 1.0,
|
|
245
|
+
"context": "scip",
|
|
246
|
+
"metadata": sanitize_metadata({"scip_relationship": rel}),
|
|
247
|
+
}
|
|
248
|
+
)
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
def _resolve_relationship_target(
|
|
252
|
+
target_symbol: str,
|
|
253
|
+
source_doc_path: str,
|
|
254
|
+
per_doc_index: dict[tuple[str, str], str],
|
|
255
|
+
global_index: dict[str, list[str]],
|
|
256
|
+
) -> str | None:
|
|
257
|
+
"""Resolve a SCIP relationship target to an emitted node id, or None.
|
|
258
|
+
|
|
259
|
+
Resolution order:
|
|
260
|
+
1. Same-document match — `(target_symbol, source_doc_path)`.
|
|
261
|
+
2. Unique cross-document match — exactly one node id in the global
|
|
262
|
+
index for this symbol AND it isn't the same document we already
|
|
263
|
+
tried.
|
|
264
|
+
3. None — symbol is either absent globally OR ambiguous (defined in
|
|
265
|
+
multiple documents). The caller emits a stub external node.
|
|
266
|
+
"""
|
|
267
|
+
same_doc = per_doc_index.get((target_symbol, source_doc_path))
|
|
268
|
+
if same_doc is not None:
|
|
269
|
+
return same_doc
|
|
270
|
+
candidates = global_index.get(target_symbol, [])
|
|
271
|
+
if len(candidates) == 1:
|
|
272
|
+
return candidates[0]
|
|
273
|
+
return None
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
def _is_true(value: object) -> bool:
|
|
277
|
+
"""Return True only when value is exactly the boolean True.
|
|
278
|
+
|
|
279
|
+
Used for SCIP relationship flags. Truthy strings like ``"false"`` are
|
|
280
|
+
common in untrusted external JSON and must NOT count as a set flag.
|
|
281
|
+
"""
|
|
282
|
+
return value is True
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
def _scip_relation_for(rel: dict[str, Any]) -> str:
|
|
286
|
+
"""Pick the Graphify relation tag for a SCIP relationship dict.
|
|
287
|
+
|
|
288
|
+
Flags are accepted only when the value is exactly ``True`` — protects
|
|
289
|
+
against truthy-but-misleading values like ``"false"`` in external JSON.
|
|
290
|
+
"""
|
|
291
|
+
if _is_true(rel.get("is_implementation")):
|
|
292
|
+
return "scip_impl"
|
|
293
|
+
if _is_true(rel.get("is_type_definition")):
|
|
294
|
+
return "scip_typed"
|
|
295
|
+
if _is_true(rel.get("is_definition")):
|
|
296
|
+
return "scip_def"
|
|
297
|
+
return "scip_ref"
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
def _first_occurrence_line(occurrences: object) -> int:
|
|
301
|
+
"""Read the 1-based line number from the first occurrence range, defensively.
|
|
302
|
+
|
|
303
|
+
Note: ``bool`` is a subclass of ``int`` in Python — ``isinstance(True, int)``
|
|
304
|
+
is True. We explicitly exclude booleans so a malformed ``range: [True, …]``
|
|
305
|
+
cannot produce ``source_location = "LTrue"``.
|
|
306
|
+
"""
|
|
307
|
+
if not isinstance(occurrences, list) or not occurrences:
|
|
308
|
+
return 0
|
|
309
|
+
first = occurrences[0]
|
|
310
|
+
if not isinstance(first, dict):
|
|
311
|
+
return 0
|
|
312
|
+
rng = first.get("range", [])
|
|
313
|
+
if not isinstance(rng, list) or len(rng) < 1:
|
|
314
|
+
return 0
|
|
315
|
+
line = rng[0]
|
|
316
|
+
if isinstance(line, bool) or not isinstance(line, int) or line < 0:
|
|
317
|
+
return 0
|
|
318
|
+
return line
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
def _coerce_str(value: object, default: str) -> str:
|
|
322
|
+
"""Return ``value`` if it is a string, else the ``default`` (also a string)."""
|
|
323
|
+
if isinstance(value, str):
|
|
324
|
+
return value
|
|
325
|
+
if isinstance(default, str):
|
|
326
|
+
return default
|
|
327
|
+
return ""
|
|
328
|
+
|
|
329
|
+
|
|
330
|
+
def _make_scip_node_id(symbol: str, source_file: str) -> str:
|
|
331
|
+
"""Derive a stable Graphify node ID from a SCIP symbol identifier.
|
|
332
|
+
|
|
333
|
+
Uses SHA-1 truncated to 12 hex chars (48 bits). This is an identifier,
|
|
334
|
+
not a security boundary — collision risk is acceptable at this scale
|
|
335
|
+
given the per-document scoping prefix.
|
|
336
|
+
"""
|
|
337
|
+
raw = f"{source_file}:{symbol}"
|
|
338
|
+
h = hashlib.sha1(raw.encode(), usedforsecurity=False).hexdigest()[:12]
|
|
339
|
+
parts = symbol.split("#")
|
|
340
|
+
suffix = parts[-1] if parts else symbol
|
|
341
|
+
suffix = re.sub(r"[^a-zA-Z0-9_]", "_", suffix).strip("_").lower()
|
|
342
|
+
if suffix:
|
|
343
|
+
return f"scip_{suffix}_{h}"
|
|
344
|
+
return f"scip_{h}"
|
|
345
|
+
|
|
346
|
+
|
|
347
|
+
def _scip_kind_to_file_type(kind: str) -> str:
|
|
348
|
+
"""Map SCIP symbol kind to a Graphify file_type."""
|
|
349
|
+
# All SCIP symbols are code entities (functions, methods, classes, …);
|
|
350
|
+
# the `kind` is preserved in metadata for downstream consumers.
|
|
351
|
+
_ = kind # acknowledged but not currently used for file_type routing
|
|
352
|
+
return "code"
|
|
353
|
+
|
|
354
|
+
|
|
355
|
+
def _build_scip_metadata(symbol_id: str, kind: str, description: str) -> dict[str, str]:
|
|
356
|
+
"""Build metadata for a SCIP node."""
|
|
357
|
+
meta: dict[str, str] = {
|
|
358
|
+
"scip_symbol": symbol_id,
|
|
359
|
+
"scip_kind": kind,
|
|
360
|
+
}
|
|
361
|
+
if description:
|
|
362
|
+
meta["scip_description"] = description
|
|
363
|
+
return meta
|