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
|
@@ -0,0 +1,494 @@
|
|
|
1
|
+
"""Dm extractor. Moved verbatim from graphify/extract.py."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import re
|
|
5
|
+
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Any
|
|
8
|
+
from graphify.extractors.base import _file_stem, _make_id, _read_text
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def extract_dm(path: Path) -> dict:
|
|
12
|
+
"""Extract types, procs, includes, and calls from a .dm/.dme file."""
|
|
13
|
+
try:
|
|
14
|
+
import tree_sitter_dm as tsdm
|
|
15
|
+
from tree_sitter import Language, Parser
|
|
16
|
+
except ImportError:
|
|
17
|
+
return {"nodes": [], "edges": [], "error": "tree-sitter-dm not installed"}
|
|
18
|
+
try:
|
|
19
|
+
language = Language(tsdm.language())
|
|
20
|
+
parser = Parser(language)
|
|
21
|
+
source = path.read_bytes()
|
|
22
|
+
tree = parser.parse(source)
|
|
23
|
+
root = tree.root_node
|
|
24
|
+
except Exception as e:
|
|
25
|
+
return {"nodes": [], "edges": [], "error": str(e)}
|
|
26
|
+
|
|
27
|
+
stem = _file_stem(path)
|
|
28
|
+
str_path = str(path)
|
|
29
|
+
nodes: list[dict] = []
|
|
30
|
+
edges: list[dict] = []
|
|
31
|
+
seen_ids: set[str] = set()
|
|
32
|
+
function_bodies: list[tuple[str, Any, "str | None"]] = []
|
|
33
|
+
|
|
34
|
+
def add_node(nid: str, label: str, line: int) -> None:
|
|
35
|
+
if nid and nid not in seen_ids:
|
|
36
|
+
seen_ids.add(nid)
|
|
37
|
+
nodes.append({"id": nid, "label": label, "file_type": "code",
|
|
38
|
+
"source_file": str_path, "source_location": f"L{line}"})
|
|
39
|
+
|
|
40
|
+
def add_edge(src: str, tgt: str, relation: str, line: int,
|
|
41
|
+
confidence: str = "EXTRACTED", weight: float = 1.0,
|
|
42
|
+
context: str | None = None) -> None:
|
|
43
|
+
if not src or not tgt or src == tgt:
|
|
44
|
+
return
|
|
45
|
+
edge: dict = {"source": src, "target": tgt, "relation": relation,
|
|
46
|
+
"confidence": confidence, "source_file": str_path,
|
|
47
|
+
"source_location": f"L{line}", "weight": weight}
|
|
48
|
+
if context:
|
|
49
|
+
edge["context"] = context
|
|
50
|
+
edges.append(edge)
|
|
51
|
+
|
|
52
|
+
file_nid = _make_id(str(path))
|
|
53
|
+
add_node(file_nid, path.name, 1)
|
|
54
|
+
|
|
55
|
+
def _type_path_text(node) -> str:
|
|
56
|
+
return _read_text(node, source).strip()
|
|
57
|
+
|
|
58
|
+
def _ensure_type(path_text: str, line: int) -> str:
|
|
59
|
+
nid = _make_id(stem, path_text)
|
|
60
|
+
add_node(nid, path_text, line)
|
|
61
|
+
return nid
|
|
62
|
+
|
|
63
|
+
def _find_child(node, type_name: str):
|
|
64
|
+
for c in node.children:
|
|
65
|
+
if c.type == type_name:
|
|
66
|
+
return c
|
|
67
|
+
return None
|
|
68
|
+
|
|
69
|
+
def _read_include_path(file_node) -> str:
|
|
70
|
+
if file_node is None:
|
|
71
|
+
return ""
|
|
72
|
+
if file_node.type == "string_literal":
|
|
73
|
+
parts = []
|
|
74
|
+
for c in file_node.children:
|
|
75
|
+
if c.type == "string_content":
|
|
76
|
+
parts.append(_read_text(c, source))
|
|
77
|
+
return "".join(parts)
|
|
78
|
+
return _read_text(file_node, source).strip("'\"")
|
|
79
|
+
|
|
80
|
+
def walk(node, parent_type_path: "str | None" = None,
|
|
81
|
+
parent_type_nid: "str | None" = None) -> None:
|
|
82
|
+
t = node.type
|
|
83
|
+
line = node.start_point[0] + 1
|
|
84
|
+
|
|
85
|
+
if t == "preproc_include":
|
|
86
|
+
file_node = node.child_by_field_name("file")
|
|
87
|
+
raw = _read_include_path(file_node)
|
|
88
|
+
if raw:
|
|
89
|
+
norm = re.sub(r"^\./", "", raw.replace("\\", "/"))
|
|
90
|
+
resolved = (path.parent / norm).resolve()
|
|
91
|
+
edge: dict = {
|
|
92
|
+
"source": file_nid,
|
|
93
|
+
"target": _make_id(str(resolved)) if resolved.exists() else _make_id(norm),
|
|
94
|
+
"relation": "imports_from" if resolved.exists() else "imports",
|
|
95
|
+
"context": "import",
|
|
96
|
+
"confidence": "EXTRACTED",
|
|
97
|
+
"source_file": str_path,
|
|
98
|
+
"source_location": f"L{line}",
|
|
99
|
+
"weight": 1.0,
|
|
100
|
+
}
|
|
101
|
+
if not resolved.exists():
|
|
102
|
+
edge["external"] = True
|
|
103
|
+
edges.append(edge)
|
|
104
|
+
return
|
|
105
|
+
|
|
106
|
+
if t == "type_definition":
|
|
107
|
+
tp_node = _find_child(node, "type_path")
|
|
108
|
+
if tp_node is None:
|
|
109
|
+
return
|
|
110
|
+
type_path_str = _type_path_text(tp_node)
|
|
111
|
+
type_nid = _ensure_type(type_path_str, line)
|
|
112
|
+
add_edge(file_nid, type_nid, "contains", line)
|
|
113
|
+
body = _find_child(node, "type_body")
|
|
114
|
+
if body is not None:
|
|
115
|
+
for c in body.children:
|
|
116
|
+
walk(c, parent_type_path=type_path_str, parent_type_nid=type_nid)
|
|
117
|
+
return
|
|
118
|
+
|
|
119
|
+
if t in ("type_body_intended", "type_body_braced"):
|
|
120
|
+
for c in node.children:
|
|
121
|
+
walk(c, parent_type_path, parent_type_nid)
|
|
122
|
+
return
|
|
123
|
+
|
|
124
|
+
if t in ("type_proc_definition", "type_proc_override"):
|
|
125
|
+
if parent_type_nid is None or parent_type_path is None:
|
|
126
|
+
return
|
|
127
|
+
name_node = node.child_by_field_name("name")
|
|
128
|
+
if name_node is None:
|
|
129
|
+
return
|
|
130
|
+
proc_name = _read_text(name_node, source)
|
|
131
|
+
proc_nid = _make_id(stem, parent_type_path, proc_name)
|
|
132
|
+
add_node(proc_nid, f"{parent_type_path}/{proc_name}()", line)
|
|
133
|
+
add_edge(parent_type_nid, proc_nid, "method", line)
|
|
134
|
+
block = _find_child(node, "block")
|
|
135
|
+
if block is not None:
|
|
136
|
+
function_bodies.append((proc_nid, block, parent_type_path))
|
|
137
|
+
return
|
|
138
|
+
|
|
139
|
+
if t in ("proc_definition", "proc_override"):
|
|
140
|
+
tp_node = _find_child(node, "type_path")
|
|
141
|
+
owner_path: "str | None" = None
|
|
142
|
+
owner_nid: "str | None" = None
|
|
143
|
+
if tp_node is not None:
|
|
144
|
+
owner_path = _type_path_text(tp_node)
|
|
145
|
+
owner_nid = _ensure_type(owner_path, line)
|
|
146
|
+
add_edge(file_nid, owner_nid, "contains", line)
|
|
147
|
+
name_node = node.child_by_field_name("name")
|
|
148
|
+
if name_node is None:
|
|
149
|
+
return
|
|
150
|
+
proc_name = _read_text(name_node, source)
|
|
151
|
+
if owner_path and owner_nid:
|
|
152
|
+
proc_nid = _make_id(stem, owner_path, proc_name)
|
|
153
|
+
add_node(proc_nid, f"{owner_path}/{proc_name}()", line)
|
|
154
|
+
add_edge(owner_nid, proc_nid, "method", line)
|
|
155
|
+
else:
|
|
156
|
+
proc_nid = _make_id(stem, proc_name)
|
|
157
|
+
add_node(proc_nid, f"{proc_name}()", line)
|
|
158
|
+
add_edge(file_nid, proc_nid, "contains", line)
|
|
159
|
+
block = _find_child(node, "block")
|
|
160
|
+
if block is not None:
|
|
161
|
+
function_bodies.append((proc_nid, block, owner_path))
|
|
162
|
+
return
|
|
163
|
+
|
|
164
|
+
if t in ("operator_override", "type_operator_override"):
|
|
165
|
+
return
|
|
166
|
+
|
|
167
|
+
for child in node.children:
|
|
168
|
+
walk(child, parent_type_path, parent_type_nid)
|
|
169
|
+
|
|
170
|
+
walk(root)
|
|
171
|
+
|
|
172
|
+
label_to_nids: dict[str, list[str]] = {}
|
|
173
|
+
path_to_nids: dict[str, list[str]] = {}
|
|
174
|
+
for n in nodes:
|
|
175
|
+
label = n["label"].strip("()")
|
|
176
|
+
last = label.rsplit("/", 1)[-1] if "/" in label else label
|
|
177
|
+
if last:
|
|
178
|
+
label_to_nids.setdefault(last.lower(), []).append(n["id"])
|
|
179
|
+
if label.startswith("/"):
|
|
180
|
+
path_to_nids.setdefault(label.lower(), []).append(n["id"])
|
|
181
|
+
|
|
182
|
+
seen_call_pairs: set[tuple[str, str]] = set()
|
|
183
|
+
raw_calls: list[dict] = []
|
|
184
|
+
|
|
185
|
+
def _emit_call(caller_nid: str, callee: str, line: int, is_member: bool) -> None:
|
|
186
|
+
candidates = label_to_nids.get(callee.lower(), [])
|
|
187
|
+
tgt_nid = candidates[0] if len(candidates) == 1 else None
|
|
188
|
+
if tgt_nid and tgt_nid != caller_nid:
|
|
189
|
+
pair = (caller_nid, tgt_nid)
|
|
190
|
+
if pair in seen_call_pairs:
|
|
191
|
+
return
|
|
192
|
+
seen_call_pairs.add(pair)
|
|
193
|
+
edges.append({
|
|
194
|
+
"source": caller_nid, "target": tgt_nid, "relation": "calls",
|
|
195
|
+
"context": "call", "confidence": "EXTRACTED",
|
|
196
|
+
"source_file": str_path, "source_location": f"L{line}", "weight": 1.0,
|
|
197
|
+
})
|
|
198
|
+
else:
|
|
199
|
+
raw_calls.append({
|
|
200
|
+
"caller_nid": caller_nid, "callee": callee,
|
|
201
|
+
"is_member_call": is_member, "source_file": str_path,
|
|
202
|
+
"source_location": f"L{line}",
|
|
203
|
+
})
|
|
204
|
+
|
|
205
|
+
def walk_calls(body_node, caller_nid: str) -> None:
|
|
206
|
+
if body_node is None:
|
|
207
|
+
return
|
|
208
|
+
t = body_node.type
|
|
209
|
+
if t in ("proc_definition", "proc_override", "type_proc_definition",
|
|
210
|
+
"type_proc_override", "type_definition"):
|
|
211
|
+
return
|
|
212
|
+
if t == "call_expression":
|
|
213
|
+
name_node = body_node.child_by_field_name("name")
|
|
214
|
+
if name_node is not None:
|
|
215
|
+
callee = _read_text(name_node, source)
|
|
216
|
+
if callee and callee != "..":
|
|
217
|
+
_emit_call(caller_nid, callee, body_node.start_point[0] + 1,
|
|
218
|
+
is_member=False)
|
|
219
|
+
elif t == "field_proc_expression":
|
|
220
|
+
proc_field = body_node.child_by_field_name("proc")
|
|
221
|
+
if proc_field is not None:
|
|
222
|
+
callee = _read_text(proc_field, source)
|
|
223
|
+
if callee:
|
|
224
|
+
_emit_call(caller_nid, callee, body_node.start_point[0] + 1,
|
|
225
|
+
is_member=True)
|
|
226
|
+
elif t == "new_expression":
|
|
227
|
+
tp_node = _find_child(body_node, "type_path")
|
|
228
|
+
if tp_node is not None:
|
|
229
|
+
target_text = _type_path_text(tp_node)
|
|
230
|
+
candidates = path_to_nids.get(target_text.lower(), [])
|
|
231
|
+
tgt_nid = candidates[0] if len(candidates) == 1 else None
|
|
232
|
+
if tgt_nid and tgt_nid != caller_nid:
|
|
233
|
+
pair = (caller_nid, tgt_nid)
|
|
234
|
+
if pair not in seen_call_pairs:
|
|
235
|
+
seen_call_pairs.add(pair)
|
|
236
|
+
edges.append({
|
|
237
|
+
"source": caller_nid, "target": tgt_nid,
|
|
238
|
+
"relation": "instantiates", "context": "call",
|
|
239
|
+
"confidence": "EXTRACTED", "source_file": str_path,
|
|
240
|
+
"source_location": f"L{body_node.start_point[0] + 1}",
|
|
241
|
+
"weight": 1.0,
|
|
242
|
+
})
|
|
243
|
+
for child in body_node.children:
|
|
244
|
+
walk_calls(child, caller_nid)
|
|
245
|
+
|
|
246
|
+
for proc_nid, block, _owner_path in function_bodies:
|
|
247
|
+
walk_calls(block, proc_nid)
|
|
248
|
+
|
|
249
|
+
return {"nodes": nodes, "edges": edges, "raw_calls": raw_calls}
|
|
250
|
+
|
|
251
|
+
def _read_dmi_description(data: bytes) -> str:
|
|
252
|
+
"""Pull the BYOND metadata text out of a .dmi PNG, or empty string on failure."""
|
|
253
|
+
import struct
|
|
254
|
+
import zlib as _zlib
|
|
255
|
+
if not data.startswith(b"\x89PNG\r\n\x1a\n"):
|
|
256
|
+
return ""
|
|
257
|
+
i = 8
|
|
258
|
+
while i + 8 <= len(data):
|
|
259
|
+
length = struct.unpack(">I", data[i:i + 4])[0]
|
|
260
|
+
chunk_type = data[i + 4:i + 8]
|
|
261
|
+
payload = data[i + 8:i + 8 + length]
|
|
262
|
+
if chunk_type in (b"tEXt", b"zTXt"):
|
|
263
|
+
try:
|
|
264
|
+
null = payload.index(b"\x00")
|
|
265
|
+
except ValueError:
|
|
266
|
+
return ""
|
|
267
|
+
keyword = payload[:null]
|
|
268
|
+
if keyword == b"Description":
|
|
269
|
+
if chunk_type == b"zTXt":
|
|
270
|
+
return _zlib.decompressobj().decompress(payload[null + 2:], max_length=1024 * 1024).decode("utf-8", errors="replace")
|
|
271
|
+
return payload[null + 1:].decode("utf-8", errors="replace")
|
|
272
|
+
i += 8 + length + 4
|
|
273
|
+
return ""
|
|
274
|
+
|
|
275
|
+
def extract_dmi(path: Path) -> dict:
|
|
276
|
+
"""Extract icon state names from a .dmi (BYOND PNG icon sheet)."""
|
|
277
|
+
try:
|
|
278
|
+
data = path.read_bytes()
|
|
279
|
+
except Exception as e:
|
|
280
|
+
return {"nodes": [], "edges": [], "error": str(e)}
|
|
281
|
+
|
|
282
|
+
str_path = str(path)
|
|
283
|
+
stem = _file_stem(path)
|
|
284
|
+
file_nid = _make_id(str(path))
|
|
285
|
+
nodes: list[dict] = [{"id": file_nid, "label": path.name, "file_type": "code",
|
|
286
|
+
"source_file": str_path, "source_location": "L1"}]
|
|
287
|
+
edges: list[dict] = []
|
|
288
|
+
seen: set[str] = {file_nid}
|
|
289
|
+
|
|
290
|
+
description = _read_dmi_description(data)
|
|
291
|
+
if not description:
|
|
292
|
+
return {"nodes": nodes, "edges": edges}
|
|
293
|
+
|
|
294
|
+
line_no = 0
|
|
295
|
+
for raw_line in description.splitlines():
|
|
296
|
+
line_no += 1
|
|
297
|
+
stripped = raw_line.strip()
|
|
298
|
+
if not stripped.startswith("state ="):
|
|
299
|
+
continue
|
|
300
|
+
value = stripped.split("=", 1)[1].strip()
|
|
301
|
+
if value.startswith('"') and value.endswith('"') and len(value) >= 2:
|
|
302
|
+
state_name = value[1:-1]
|
|
303
|
+
else:
|
|
304
|
+
state_name = value
|
|
305
|
+
if not state_name:
|
|
306
|
+
continue
|
|
307
|
+
nid = _make_id(stem, "state", state_name)
|
|
308
|
+
if nid in seen:
|
|
309
|
+
continue
|
|
310
|
+
seen.add(nid)
|
|
311
|
+
nodes.append({"id": nid, "label": f'"{state_name}"', "file_type": "code",
|
|
312
|
+
"source_file": str_path, "source_location": f"L{line_no}"})
|
|
313
|
+
edges.append({"source": file_nid, "target": nid, "relation": "contains",
|
|
314
|
+
"confidence": "EXTRACTED", "source_file": str_path,
|
|
315
|
+
"source_location": f"L{line_no}", "weight": 1.0})
|
|
316
|
+
|
|
317
|
+
return {"nodes": nodes, "edges": edges}
|
|
318
|
+
|
|
319
|
+
_DMM_GRID_RE = re.compile(r"^\(\s*\d+\s*,\s*\d+\s*,\s*\d+\s*\)\s*=", re.MULTILINE)
|
|
320
|
+
|
|
321
|
+
def _split_dmm_tile(body: str) -> list[str]:
|
|
322
|
+
out: list[str] = []
|
|
323
|
+
buf: list[str] = []
|
|
324
|
+
depth = 0
|
|
325
|
+
in_string = False
|
|
326
|
+
escape = False
|
|
327
|
+
for ch in body:
|
|
328
|
+
if escape:
|
|
329
|
+
buf.append(ch)
|
|
330
|
+
escape = False
|
|
331
|
+
continue
|
|
332
|
+
if in_string:
|
|
333
|
+
buf.append(ch)
|
|
334
|
+
if ch == "\\":
|
|
335
|
+
escape = True
|
|
336
|
+
elif ch == '"':
|
|
337
|
+
in_string = False
|
|
338
|
+
continue
|
|
339
|
+
if ch == '"':
|
|
340
|
+
in_string = True
|
|
341
|
+
buf.append(ch)
|
|
342
|
+
elif ch in "({[":
|
|
343
|
+
depth += 1
|
|
344
|
+
buf.append(ch)
|
|
345
|
+
elif ch in ")}]":
|
|
346
|
+
depth -= 1
|
|
347
|
+
buf.append(ch)
|
|
348
|
+
elif ch == "," and depth == 0:
|
|
349
|
+
out.append("".join(buf).strip())
|
|
350
|
+
buf = []
|
|
351
|
+
else:
|
|
352
|
+
buf.append(ch)
|
|
353
|
+
tail = "".join(buf).strip()
|
|
354
|
+
if tail:
|
|
355
|
+
out.append(tail)
|
|
356
|
+
return out
|
|
357
|
+
|
|
358
|
+
def _dmm_type_path(entry: str) -> str:
|
|
359
|
+
brace = entry.find("{")
|
|
360
|
+
if brace != -1:
|
|
361
|
+
entry = entry[:brace]
|
|
362
|
+
return entry.strip()
|
|
363
|
+
|
|
364
|
+
def extract_dmm(path: Path) -> dict:
|
|
365
|
+
"""Extract type-path references from a .dmm map file's tile dictionary."""
|
|
366
|
+
try:
|
|
367
|
+
if path.stat().st_size > 50 * 1024 * 1024:
|
|
368
|
+
return {"nodes": [], "edges": [], "error": "file too large (>50 MB)"}
|
|
369
|
+
text = path.read_text(encoding="utf-8", errors="replace")
|
|
370
|
+
except Exception as e:
|
|
371
|
+
return {"nodes": [], "edges": [], "error": str(e)}
|
|
372
|
+
|
|
373
|
+
str_path = str(path)
|
|
374
|
+
file_nid = _make_id(str(path))
|
|
375
|
+
nodes: list[dict] = [{"id": file_nid, "label": path.name, "file_type": "code",
|
|
376
|
+
"source_file": str_path, "source_location": "L1"}]
|
|
377
|
+
edges: list[dict] = []
|
|
378
|
+
|
|
379
|
+
grid_match = _DMM_GRID_RE.search(text)
|
|
380
|
+
dict_text = text[:grid_match.start()] if grid_match else text
|
|
381
|
+
|
|
382
|
+
seen_targets: set[str] = set()
|
|
383
|
+
buf: list[str] = []
|
|
384
|
+
open_line = 0
|
|
385
|
+
depth = 0
|
|
386
|
+
in_string = False
|
|
387
|
+
escape = False
|
|
388
|
+
for line_idx, line in enumerate(dict_text.splitlines(), start=1):
|
|
389
|
+
for ch in line:
|
|
390
|
+
if escape:
|
|
391
|
+
escape = False
|
|
392
|
+
elif in_string:
|
|
393
|
+
if ch == "\\":
|
|
394
|
+
escape = True
|
|
395
|
+
elif ch == '"':
|
|
396
|
+
in_string = False
|
|
397
|
+
elif ch == '"':
|
|
398
|
+
in_string = True
|
|
399
|
+
elif ch == "(":
|
|
400
|
+
if depth == 0:
|
|
401
|
+
open_line = line_idx
|
|
402
|
+
depth += 1
|
|
403
|
+
elif ch == ")":
|
|
404
|
+
depth -= 1
|
|
405
|
+
buf.append(ch)
|
|
406
|
+
buf.append("\n")
|
|
407
|
+
if depth == 0 and buf:
|
|
408
|
+
chunk = "".join(buf)
|
|
409
|
+
buf = []
|
|
410
|
+
lp = chunk.find("(")
|
|
411
|
+
rp = chunk.rfind(")")
|
|
412
|
+
if lp == -1 or rp == -1 or rp <= lp:
|
|
413
|
+
continue
|
|
414
|
+
inner = chunk[lp + 1:rp]
|
|
415
|
+
for entry in _split_dmm_tile(inner):
|
|
416
|
+
tpath = _dmm_type_path(entry)
|
|
417
|
+
if not tpath.startswith("/"):
|
|
418
|
+
continue
|
|
419
|
+
tgt = _make_id(tpath)
|
|
420
|
+
if tgt in seen_targets:
|
|
421
|
+
continue
|
|
422
|
+
seen_targets.add(tgt)
|
|
423
|
+
edges.append({"source": file_nid, "target": tgt, "relation": "uses",
|
|
424
|
+
"context": "map", "confidence": "EXTRACTED",
|
|
425
|
+
"source_file": str_path,
|
|
426
|
+
"source_location": f"L{open_line}", "weight": 1.0})
|
|
427
|
+
|
|
428
|
+
return {"nodes": nodes, "edges": edges}
|
|
429
|
+
|
|
430
|
+
_DMF_WINDOW_RE = re.compile(r'^\s*window\s+"([^"]+)"\s*$')
|
|
431
|
+
|
|
432
|
+
_DMF_ELEM_RE = re.compile(r'^\s*elem\s+"([^"]+)"\s*$')
|
|
433
|
+
|
|
434
|
+
_DMF_TYPE_RE = re.compile(r'^\s*type\s*=\s*(\S+)\s*$')
|
|
435
|
+
|
|
436
|
+
def extract_dmf(path: Path) -> dict:
|
|
437
|
+
"""Extract windows and controls from a .dmf interface file."""
|
|
438
|
+
try:
|
|
439
|
+
text = path.read_text(encoding="utf-8", errors="replace")
|
|
440
|
+
except Exception as e:
|
|
441
|
+
return {"nodes": [], "edges": [], "error": str(e)}
|
|
442
|
+
|
|
443
|
+
str_path = str(path)
|
|
444
|
+
stem = _file_stem(path)
|
|
445
|
+
file_nid = _make_id(str(path))
|
|
446
|
+
nodes: list[dict] = [{"id": file_nid, "label": path.name, "file_type": "code",
|
|
447
|
+
"source_file": str_path, "source_location": "L1"}]
|
|
448
|
+
edges: list[dict] = []
|
|
449
|
+
seen: set[str] = {file_nid}
|
|
450
|
+
|
|
451
|
+
current_window_nid: str | None = None
|
|
452
|
+
current_elem_nid: str | None = None
|
|
453
|
+
current_elem_name: str | None = None
|
|
454
|
+
|
|
455
|
+
for line_idx, line in enumerate(text.splitlines(), start=1):
|
|
456
|
+
m = _DMF_WINDOW_RE.match(line)
|
|
457
|
+
if m:
|
|
458
|
+
name = m.group(1)
|
|
459
|
+
nid = _make_id(stem, "window", name)
|
|
460
|
+
if nid not in seen:
|
|
461
|
+
seen.add(nid)
|
|
462
|
+
nodes.append({"id": nid, "label": f'window "{name}"', "file_type": "code",
|
|
463
|
+
"source_file": str_path, "source_location": f"L{line_idx}"})
|
|
464
|
+
edges.append({"source": file_nid, "target": nid, "relation": "contains",
|
|
465
|
+
"confidence": "EXTRACTED", "source_file": str_path,
|
|
466
|
+
"source_location": f"L{line_idx}", "weight": 1.0})
|
|
467
|
+
current_window_nid = nid
|
|
468
|
+
current_elem_nid = None
|
|
469
|
+
current_elem_name = None
|
|
470
|
+
continue
|
|
471
|
+
m = _DMF_ELEM_RE.match(line)
|
|
472
|
+
if m and current_window_nid is not None:
|
|
473
|
+
name = m.group(1)
|
|
474
|
+
nid = _make_id(stem, "elem", current_window_nid, name)
|
|
475
|
+
if nid not in seen:
|
|
476
|
+
seen.add(nid)
|
|
477
|
+
nodes.append({"id": nid, "label": f'elem "{name}"', "file_type": "code",
|
|
478
|
+
"source_file": str_path, "source_location": f"L{line_idx}"})
|
|
479
|
+
edges.append({"source": current_window_nid, "target": nid,
|
|
480
|
+
"relation": "contains", "confidence": "EXTRACTED",
|
|
481
|
+
"source_file": str_path, "source_location": f"L{line_idx}",
|
|
482
|
+
"weight": 1.0})
|
|
483
|
+
current_elem_nid = nid
|
|
484
|
+
current_elem_name = name
|
|
485
|
+
continue
|
|
486
|
+
m = _DMF_TYPE_RE.match(line)
|
|
487
|
+
if m and current_elem_nid is not None and current_elem_name is not None:
|
|
488
|
+
ctype = m.group(1)
|
|
489
|
+
for n in nodes:
|
|
490
|
+
if n["id"] == current_elem_nid and " [" not in n["label"]:
|
|
491
|
+
n["label"] = f'elem "{current_elem_name}" [{ctype}]'
|
|
492
|
+
break
|
|
493
|
+
|
|
494
|
+
return {"nodes": nodes, "edges": edges}
|