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,311 @@
|
|
|
1
|
+
"""Fortran extractor. Moved verbatim from graphify/extract.py."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from graphify.extractors.base import _file_stem, _make_id, _read_text
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
_FORTRAN_CPP_EXTS = {".F", ".F90", ".F95", ".F03", ".F08"}
|
|
10
|
+
|
|
11
|
+
def _cpp_preprocess(path: Path) -> bytes:
|
|
12
|
+
"""Run cpp -w -P on a capital-F Fortran file and return preprocessed bytes.
|
|
13
|
+
|
|
14
|
+
Falls back to raw file bytes if cpp is not available. Capital-F extensions
|
|
15
|
+
conventionally require C preprocessor expansion (#ifdef MPI, #define REAL8, etc.)
|
|
16
|
+
before parsing.
|
|
17
|
+
|
|
18
|
+
Security (F-007): we pass `-nostdinc` and `-I /dev/null` so a malicious
|
|
19
|
+
source file containing `#include "/home/victim/.ssh/id_rsa"` (or any other
|
|
20
|
+
include directive) cannot inline arbitrary host files into the output that
|
|
21
|
+
we then ship to an LLM. Without these flags `cpp` happily resolves any
|
|
22
|
+
relative or absolute include path it can read, which is a corpus-side
|
|
23
|
+
file-exfiltration vector.
|
|
24
|
+
"""
|
|
25
|
+
import shutil
|
|
26
|
+
import subprocess
|
|
27
|
+
if not shutil.which("cpp"):
|
|
28
|
+
return path.read_bytes()
|
|
29
|
+
try:
|
|
30
|
+
# Pass an absolute path so a corpus file named like "-I/etc/x.F90" cannot
|
|
31
|
+
# be parsed by cpp as an option (cpp does not accept a "--" end-of-options
|
|
32
|
+
# terminator). What matters is that an absolute path cannot begin with
|
|
33
|
+
# "-" — not that it begins with "/", which only holds on POSIX (a Windows
|
|
34
|
+
# absolute path starts with a drive letter, and is equally safe).
|
|
35
|
+
result = subprocess.run(
|
|
36
|
+
["cpp", "-w", "-P", "-nostdinc", "-I", "/dev/null", str(path.resolve())],
|
|
37
|
+
capture_output=True,
|
|
38
|
+
timeout=30,
|
|
39
|
+
)
|
|
40
|
+
if result.returncode == 0 and result.stdout:
|
|
41
|
+
return result.stdout
|
|
42
|
+
except Exception:
|
|
43
|
+
pass
|
|
44
|
+
return path.read_bytes()
|
|
45
|
+
|
|
46
|
+
def extract_fortran(path: Path) -> dict:
|
|
47
|
+
"""Extract programs, modules, subroutines, functions, use statements, and calls from Fortran files.
|
|
48
|
+
|
|
49
|
+
Capital-F extensions (.F, .F90, etc.) are run through the C preprocessor before
|
|
50
|
+
parsing so #ifdef/#define macros are resolved.
|
|
51
|
+
"""
|
|
52
|
+
try:
|
|
53
|
+
import tree_sitter_fortran as tsfortran
|
|
54
|
+
from tree_sitter import Language, Parser
|
|
55
|
+
except ImportError:
|
|
56
|
+
return {"nodes": [], "edges": [], "error": "tree-sitter-fortran not installed"}
|
|
57
|
+
|
|
58
|
+
try:
|
|
59
|
+
language = Language(tsfortran.language())
|
|
60
|
+
parser = Parser(language)
|
|
61
|
+
source = _cpp_preprocess(path) if path.suffix in _FORTRAN_CPP_EXTS else path.read_bytes()
|
|
62
|
+
tree = parser.parse(source)
|
|
63
|
+
root = tree.root_node
|
|
64
|
+
except Exception as e:
|
|
65
|
+
return {"nodes": [], "edges": [], "error": str(e)}
|
|
66
|
+
|
|
67
|
+
stem = _file_stem(path)
|
|
68
|
+
str_path = str(path)
|
|
69
|
+
nodes: list[dict] = []
|
|
70
|
+
edges: list[dict] = []
|
|
71
|
+
seen_ids: set[str] = set()
|
|
72
|
+
scope_bodies: list[tuple[str, object]] = []
|
|
73
|
+
|
|
74
|
+
def add_node(nid: str, label: str, line: int) -> None:
|
|
75
|
+
if nid not in seen_ids:
|
|
76
|
+
seen_ids.add(nid)
|
|
77
|
+
nodes.append({
|
|
78
|
+
"id": nid,
|
|
79
|
+
"label": label,
|
|
80
|
+
"file_type": "code",
|
|
81
|
+
"source_file": str_path,
|
|
82
|
+
"source_location": f"L{line}",
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
def add_edge(src: str, tgt: str, relation: str, line: int,
|
|
86
|
+
confidence: str = "EXTRACTED", weight: float = 1.0,
|
|
87
|
+
context: str | None = None) -> None:
|
|
88
|
+
edge = {
|
|
89
|
+
"source": src,
|
|
90
|
+
"target": tgt,
|
|
91
|
+
"relation": relation,
|
|
92
|
+
"confidence": confidence,
|
|
93
|
+
"source_file": str_path,
|
|
94
|
+
"source_location": f"L{line}",
|
|
95
|
+
"weight": weight,
|
|
96
|
+
}
|
|
97
|
+
if context:
|
|
98
|
+
edge["context"] = context
|
|
99
|
+
edges.append(edge)
|
|
100
|
+
|
|
101
|
+
file_nid = _make_id(str(path))
|
|
102
|
+
add_node(file_nid, path.name, 1)
|
|
103
|
+
|
|
104
|
+
def _fortran_name(stmt_node) -> str | None:
|
|
105
|
+
"""Extract name from a *_statement node. Fortran is case-insensitive; lowercase."""
|
|
106
|
+
for child in stmt_node.children:
|
|
107
|
+
if child.type in ("name", "identifier"):
|
|
108
|
+
return _read_text(child, source).lower()
|
|
109
|
+
return None
|
|
110
|
+
|
|
111
|
+
def ensure_named_node(name: str, line: int) -> str:
|
|
112
|
+
nid = _make_id(stem, name)
|
|
113
|
+
if nid in seen_ids:
|
|
114
|
+
return nid
|
|
115
|
+
nid = _make_id(name)
|
|
116
|
+
if nid not in seen_ids:
|
|
117
|
+
# The name isn't defined in this file, so this is a cross-file reference
|
|
118
|
+
# (e.g. a `Thing` type annotation imported from another module). Emit a
|
|
119
|
+
# SOURCELESS stub — like the inheritance-base path below — so the
|
|
120
|
+
# corpus-level rewire can collapse it onto the real definition. A sourced
|
|
121
|
+
# stub here makes _disambiguate_colliding_node_ids bake the referencing
|
|
122
|
+
# file's path (with extension) into the id and blocks the rewire, which is
|
|
123
|
+
# the phantom-duplicate-node bug (#1402).
|
|
124
|
+
seen_ids.add(nid)
|
|
125
|
+
nodes.append({
|
|
126
|
+
"id": nid,
|
|
127
|
+
"label": name,
|
|
128
|
+
"file_type": "code",
|
|
129
|
+
"source_file": "",
|
|
130
|
+
"source_location": "",
|
|
131
|
+
"origin_file": str_path,
|
|
132
|
+
})
|
|
133
|
+
return nid
|
|
134
|
+
|
|
135
|
+
def emit_signature_refs(scope_node, fn_nid: str, is_function: bool) -> None:
|
|
136
|
+
"""Emit references[parameter_type] / references[return_type] edges for
|
|
137
|
+
a subroutine/function based on its variable_declaration siblings."""
|
|
138
|
+
stmt_type = "function_statement" if is_function else "subroutine_statement"
|
|
139
|
+
stmt = next((c for c in scope_node.children if c.type == stmt_type), None)
|
|
140
|
+
if stmt is None:
|
|
141
|
+
return
|
|
142
|
+
param_names: set[str] = set()
|
|
143
|
+
params_node = next((c for c in stmt.children if c.type == "parameters"), None)
|
|
144
|
+
if params_node is not None:
|
|
145
|
+
for c in params_node.children:
|
|
146
|
+
if c.type == "identifier":
|
|
147
|
+
param_names.add(_read_text(c, source).lower())
|
|
148
|
+
result_name: str | None = None
|
|
149
|
+
if is_function:
|
|
150
|
+
result_node = next((c for c in stmt.children if c.type == "function_result"), None)
|
|
151
|
+
if result_node is not None:
|
|
152
|
+
res_id = next((c for c in result_node.children if c.type == "identifier"), None)
|
|
153
|
+
if res_id is not None:
|
|
154
|
+
result_name = _read_text(res_id, source).lower()
|
|
155
|
+
else:
|
|
156
|
+
# implicit result variable: same name as the function
|
|
157
|
+
result_name = _fortran_name(stmt)
|
|
158
|
+
for child in scope_node.children:
|
|
159
|
+
if child.type != "variable_declaration":
|
|
160
|
+
continue
|
|
161
|
+
derived = next((c for c in child.children if c.type == "derived_type"), None)
|
|
162
|
+
if derived is None:
|
|
163
|
+
continue
|
|
164
|
+
type_name_node = next((c for c in derived.children if c.type == "type_name"), None)
|
|
165
|
+
if type_name_node is None:
|
|
166
|
+
continue
|
|
167
|
+
type_name = _read_text(type_name_node, source).lower()
|
|
168
|
+
for var in child.children:
|
|
169
|
+
if var.type != "identifier":
|
|
170
|
+
continue
|
|
171
|
+
var_name = _read_text(var, source).lower()
|
|
172
|
+
var_line = var.start_point[0] + 1
|
|
173
|
+
if var_name in param_names:
|
|
174
|
+
tgt = ensure_named_node(type_name, var_line)
|
|
175
|
+
if tgt != fn_nid:
|
|
176
|
+
add_edge(fn_nid, tgt, "references", var_line, context="parameter_type")
|
|
177
|
+
elif is_function and var_name == result_name:
|
|
178
|
+
tgt = ensure_named_node(type_name, var_line)
|
|
179
|
+
if tgt != fn_nid:
|
|
180
|
+
add_edge(fn_nid, tgt, "references", var_line, context="return_type")
|
|
181
|
+
|
|
182
|
+
def walk_calls(node, scope_nid: str) -> None:
|
|
183
|
+
if node is None:
|
|
184
|
+
return
|
|
185
|
+
t = node.type
|
|
186
|
+
if t in ("subroutine", "function", "module", "program", "internal_procedures"):
|
|
187
|
+
return
|
|
188
|
+
# call FOO(args) — tree-sitter-fortran uses subroutine_call
|
|
189
|
+
if t == "subroutine_call":
|
|
190
|
+
name_node = next((c for c in node.children if c.type == "identifier"), None)
|
|
191
|
+
if name_node:
|
|
192
|
+
callee = _read_text(name_node, source).lower()
|
|
193
|
+
target_nid = _make_id(stem, callee)
|
|
194
|
+
add_edge(scope_nid, target_nid, "calls", node.start_point[0] + 1,
|
|
195
|
+
confidence="EXTRACTED", context="call")
|
|
196
|
+
# x = compute(args) — function invocations are `call_expression`, which
|
|
197
|
+
# shares Fortran's `name(...)` syntax with array indexing. Only emit a
|
|
198
|
+
# call edge when the callee resolves to a procedure defined in this file
|
|
199
|
+
# (an array variable produces no matching node), so array accesses can't
|
|
200
|
+
# fabricate spurious `calls` edges.
|
|
201
|
+
elif t == "call_expression":
|
|
202
|
+
name_node = next((c for c in node.children if c.type == "identifier"), None)
|
|
203
|
+
if name_node:
|
|
204
|
+
callee = _read_text(name_node, source).lower()
|
|
205
|
+
target_nid = _make_id(stem, callee)
|
|
206
|
+
if target_nid in seen_ids and target_nid != scope_nid:
|
|
207
|
+
add_edge(scope_nid, target_nid, "calls", node.start_point[0] + 1,
|
|
208
|
+
confidence="EXTRACTED", context="call")
|
|
209
|
+
for child in node.children:
|
|
210
|
+
walk_calls(child, scope_nid)
|
|
211
|
+
|
|
212
|
+
def walk(node, scope_nid: str) -> None:
|
|
213
|
+
t = node.type
|
|
214
|
+
|
|
215
|
+
if t == "program":
|
|
216
|
+
stmt = next((c for c in node.children if c.type == "program_statement"), None)
|
|
217
|
+
name = _fortran_name(stmt) if stmt else None
|
|
218
|
+
if name:
|
|
219
|
+
nid = _make_id(stem, name)
|
|
220
|
+
line = node.start_point[0] + 1
|
|
221
|
+
add_node(nid, name, line)
|
|
222
|
+
add_edge(file_nid, nid, "defines", line)
|
|
223
|
+
scope_bodies.append((nid, node))
|
|
224
|
+
for child in node.children:
|
|
225
|
+
walk(child, nid)
|
|
226
|
+
return
|
|
227
|
+
|
|
228
|
+
if t == "module":
|
|
229
|
+
stmt = next((c for c in node.children if c.type == "module_statement"), None)
|
|
230
|
+
name = _fortran_name(stmt) if stmt else None
|
|
231
|
+
if name:
|
|
232
|
+
nid = _make_id(stem, name)
|
|
233
|
+
line = node.start_point[0] + 1
|
|
234
|
+
add_node(nid, name, line)
|
|
235
|
+
add_edge(file_nid, nid, "defines", line)
|
|
236
|
+
for child in node.children:
|
|
237
|
+
walk(child, nid)
|
|
238
|
+
return
|
|
239
|
+
|
|
240
|
+
# subroutines/functions inside a module live under internal_procedures
|
|
241
|
+
if t == "internal_procedures":
|
|
242
|
+
for child in node.children:
|
|
243
|
+
walk(child, scope_nid)
|
|
244
|
+
return
|
|
245
|
+
|
|
246
|
+
if t == "derived_type_definition":
|
|
247
|
+
stmt = next((c for c in node.children if c.type == "derived_type_statement"), None)
|
|
248
|
+
if stmt is not None:
|
|
249
|
+
name_node = next((c for c in stmt.children if c.type == "type_name"), None)
|
|
250
|
+
if name_node is not None:
|
|
251
|
+
type_name = _read_text(name_node, source).lower()
|
|
252
|
+
type_nid = _make_id(stem, type_name)
|
|
253
|
+
line = node.start_point[0] + 1
|
|
254
|
+
add_node(type_nid, type_name, line)
|
|
255
|
+
add_edge(scope_nid, type_nid, "defines", line)
|
|
256
|
+
return
|
|
257
|
+
|
|
258
|
+
if t == "subroutine":
|
|
259
|
+
stmt = next((c for c in node.children if c.type == "subroutine_statement"), None)
|
|
260
|
+
name = _fortran_name(stmt) if stmt else None
|
|
261
|
+
if name:
|
|
262
|
+
nid = _make_id(stem, name)
|
|
263
|
+
line = node.start_point[0] + 1
|
|
264
|
+
add_node(nid, f"{name}()", line)
|
|
265
|
+
add_edge(scope_nid, nid, "defines", line)
|
|
266
|
+
scope_bodies.append((nid, node))
|
|
267
|
+
emit_signature_refs(node, nid, is_function=False)
|
|
268
|
+
for child in node.children:
|
|
269
|
+
walk(child, nid)
|
|
270
|
+
return
|
|
271
|
+
|
|
272
|
+
if t == "function":
|
|
273
|
+
stmt = next((c for c in node.children if c.type == "function_statement"), None)
|
|
274
|
+
name = _fortran_name(stmt) if stmt else None
|
|
275
|
+
if name:
|
|
276
|
+
nid = _make_id(stem, name)
|
|
277
|
+
line = node.start_point[0] + 1
|
|
278
|
+
add_node(nid, f"{name}()", line)
|
|
279
|
+
add_edge(scope_nid, nid, "defines", line)
|
|
280
|
+
scope_bodies.append((nid, node))
|
|
281
|
+
emit_signature_refs(node, nid, is_function=True)
|
|
282
|
+
for child in node.children:
|
|
283
|
+
walk(child, nid)
|
|
284
|
+
return
|
|
285
|
+
|
|
286
|
+
if t == "use_statement":
|
|
287
|
+
line = node.start_point[0] + 1
|
|
288
|
+
# tree-sitter-fortran uses module_name node for the used module
|
|
289
|
+
name_node = next((c for c in node.children if c.type in ("module_name", "name", "identifier")), None)
|
|
290
|
+
if name_node:
|
|
291
|
+
mod_name = _read_text(name_node, source).lower()
|
|
292
|
+
imp_nid = _make_id(mod_name)
|
|
293
|
+
add_node(imp_nid, mod_name, line)
|
|
294
|
+
add_edge(scope_nid, imp_nid, "imports", line, context="use")
|
|
295
|
+
return
|
|
296
|
+
|
|
297
|
+
for child in node.children:
|
|
298
|
+
walk(child, scope_nid)
|
|
299
|
+
|
|
300
|
+
walk(root, file_nid)
|
|
301
|
+
|
|
302
|
+
_stmt_headers = {
|
|
303
|
+
"subroutine_statement", "function_statement",
|
|
304
|
+
"program_statement", "module_statement",
|
|
305
|
+
}
|
|
306
|
+
for scope_nid, body_node in scope_bodies:
|
|
307
|
+
for child in body_node.children:
|
|
308
|
+
if child.type not in _stmt_headers:
|
|
309
|
+
walk_calls(child, scope_nid)
|
|
310
|
+
|
|
311
|
+
return {"nodes": nodes, "edges": edges}
|