feat: Robot Framework extractor for .robot/.resource files (#3192) - #3211
feat: Robot Framework extractor for .robot/.resource files (#3192)#3211nshiveg wants to merge 4 commits into
Conversation
…Labs#3192) - graphify/extractors/robot.py: official robot.api parser (no maintained tree-sitter grammar exists for Robot Framework); extracts suites, test cases, user keywords, Resource/Library/Variables imports, and keyword-call edges (incl. suite/test fixtures, [Template]/Test Template, and calls nested in FOR/IF blocks). Keyword nodes use bare-name ids so cross-file calls land on the defining resource without a resolution pass; RF stdlib imports are filtered like _LANGUAGE_BUILTIN_GLOBALS (Graphify-Labs#726); third-party libraries get stub nodes like the svelte bare-import stubs. - detect.py/_DISPATCH wiring, optional [robot] extra (terraform/ocaml pattern), _EXTRA_FOR_EXTENSION so the Graphify-Labs#1745 warning names the extra. - 5 tests + 2 fixtures following the test_languages.py conventions. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
There was a problem hiding this comment.
Graphify reviewed this change.
Worth a look — the grounded gate found no coupling regressions or blocking issues, but 4 advisory finding(s) below merit a look before merge.
Graphify review — findings
Adds a Robot Framework extractor for .robot/.resource files, wired through the robot optional extra. extract_robot parses via the official robot.api model and emits nodes for the suite/resource file, its test cases, and its user keywords, plus contains, imports, and calls edges — resolving Resource/Variables/path-form Library imports onto the imported file's own node while non-stdlib named libraries (e.g. SeleniumLibrary) get stub nodes so suites sharing a library cluster; standard libraries are treated as noise and skipped. Keyword nodes are keyed by bare name so cross-file call edges land on the defining node without a resolution pass, test cases stay stem-qualified, and calls to keywords absent from the corpus drop out during graph build; when robotframework isn't installed the extractor returns an install-hint error instead of failing.
Worth a look
- Optional robot extra is imported unconditionally —
graphify/extract.py:55· Escalate · high- agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
- Keyword IDs collide across files by bare name, merging distinct keywords —
graphify/extractors/robot.py· Escalate · medium- agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
- ${CURDIR} imports from relative source paths are prefixed twice —
graphify/extractors/robot.py:28· Escalate · medium- agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
- Robot keyword calls are not normalized like Robot Framework matching —
graphify/extractors/robot.py:118· Escalate · medium- agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
Analysis details — impact, health, verification
Impact & health
Graphify review
Impact — 2618 functions depend on the 911 functions this change touches.
Health — this change adds coupling hotspots:
- new:
extract()— 517 callers, 43 callees - new:
_rebuild_code()— 108 callers, 50 callees - new:
detect()— 108 callers, 15 callees - new:
save_manifest()— 40 callers, 11 callees - new:
extract_files_direct()— 17 callers, 20 callees - new:
extract_js()— 85 callers, 4 callees - new:
extract_xaml()— 19 callers, 17 callees - new:
extract_corpus_parallel()— 26 callers, 11 callees - …and 41 more — each is listed as a finding
Verification — 2618 functions in the blast radius were not formally verified this run (proofs are advisory here).
Gate & verification
graphify gate
PASS — objectively clean (no health regressions, tests not run — proofs not run this pass (advisory)). Grounded, not self-assessed.
Advisory (not blocking):
- verification_scope: 2380 function(s) in the blast radius were not formally verified this run
· 1 grounded finding(s) anchored inline below; 48 more finding(s) on lines outside this diff (see the check run).
| return p | ||
|
|
||
|
|
||
| def extract_robot(path: Path) -> dict: |
There was a problem hiding this comment.
extract_robot()
6 callers depend on it (afferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
- ${CURDIR}/${EXECDIR} imports no longer double the source-dir prefix for
relative scan paths, and resolved paths are normpathed unconditionally so
../ segments collapse (ids now always match the imported file's node).
- Keyword node ids now normalize the way Robot Framework matches keywords
(case-, space-, and underscore-insensitive) via _kw_id, so OpenSession /
open_session / Open Session share one node.
- Regression tests for both, plus a test proving graphify.extract imports
and degrades gracefully when robotframework is absent (the robot.api
import is lazy - answers the unconditional-import finding).
- Same-name keyword merging across files documented as deliberate (mirrors
Robot's single global keyword namespace).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Thanks for the automated review — findings addressed in 58e19cc:
All 8 robot tests pass; ruff and |
There was a problem hiding this comment.
Graphify reviewed this change.
Worth a look — the grounded gate found no coupling regressions or blocking issues, but 2 advisory finding(s) below merit a look before merge.
Graphify review — findings
Adds a Robot Framework extractor (extract_robot) for .robot/.resource files, wiring .robot/.resource into CODE_EXTENSIONS, the extractor dispatch, and the robot optional extra so files without robotframework installed return a clear install-hint error instead of parsing. It emits suite/test-case/keyword nodes plus contains, imports, and calls edges via the official robot.api parser: keywords key on a bare name normalized case/space/underscore-insensitively (_kw_id) so cross-file calls land on the defining node and same-named keywords merge like Robot's global namespace, while resource/library imports resolve through _resolve_robot_import (handling ${CURDIR}/${EXECDIR} anchoring and bailing to None on unresolved variables) and standard libraries are skipped as noise.
Worth a look
- ${EXECDIR} imports become relative even when source paths are absolute —
graphify/extractors/robot.py:34· Escalate · medium- agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
- BDD-style Robot calls do not resolve to their keyword definitions —
graphify/extractors/robot.py:130· Escalate · medium- agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
Analysis details — impact, health, verification
Impact & health
Graphify review
Impact — 2623 functions depend on the 916 functions this change touches.
Health — this change adds coupling hotspots:
- new:
extract()— 517 callers, 43 callees - new:
_rebuild_code()— 108 callers, 50 callees - new:
detect()— 108 callers, 15 callees - new:
save_manifest()— 40 callers, 11 callees - new:
extract_files_direct()— 17 callers, 20 callees - new:
extract_js()— 85 callers, 4 callees - new:
extract_xaml()— 19 callers, 17 callees - new:
extract_corpus_parallel()— 26 callers, 11 callees - …and 41 more — each is listed as a finding
Verification — 2623 functions in the blast radius were not formally verified this run (proofs are advisory here).
Gate & verification
graphify gate
PASS — objectively clean (no health regressions, tests not run — proofs not run this pass (advisory)). Grounded, not self-assessed.
Advisory (not blocking):
- verification_scope: 2385 function(s) in the blast radius were not formally verified this run
· 1 grounded finding(s) anchored inline below; 48 more finding(s) on lines outside this diff (see the check run).
| return _make_id(name.replace(" ", "").replace("_", "")) | ||
|
|
||
|
|
||
| def extract_robot(path: Path) -> dict: |
There was a problem hiding this comment.
extract_robot()
7 callers depend on it (afferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
- ${EXECDIR} is the directory Robot was launched from - statically knowable
only for a relative scan (where '.' aligns with the scan root). For an
absolute source path the resolved id could never match a node, so the
import now emits nothing instead of a guaranteed-dangling relative edge.
- BDD-style calls (Given/When/Then/And/But <keyword>) now emit call edges
for both the full and the prefix-stripped candidate, mirroring Robot's
try-full-then-stripped resolution; the nonexistent candidate dangles and
is dropped by the graph builder.
- Regression tests for both.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Both round-2 findings addressed in 8181676:
9/9 robot tests pass; ruff clean. |
There was a problem hiding this comment.
Graphify reviewed this change.
Worth a look — the grounded gate found no coupling regressions or blocking issues, but 3 advisory finding(s) below merit a look before merge.
Graphify review — findings
Adds a Robot Framework extractor for .robot/.resource files behind a new robot optional dependency, wiring .robot/.resource into CODE_EXTENSIONS, the _DISPATCH map, and _EXTRA_FOR_EXTENSION so missing robotframework yields an install-hint error rather than a crash. extract_robot parses via the official robot.api model to emit suite/test-case/user-keyword nodes plus contains, imports, and calls edges, resolving Resource/Library/Variables imports onto the imported file's own node ID (skipping standard libraries and unresolvable ${VAR} paths) and keying keywords by name normalized case/space/underscore-insensitively so cross-file calls land on the defining node. BDD-prefixed calls (Given/When/Then/And/But) resolve by trying the full name then the stripped name, and ${EXECDIR}-anchored imports emit nothing under an absolute scan since the ID could never match.
Worth a look
- Built-in Robot path variables are matched case-sensitively —
graphify/extractors/robot.py:28· Escalate · medium- agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
- _resolve_robot_import double-prefixes when EXECDIR path also needs parent join is prevented but CURDIR replacement may produce non-anchored absolute —
graphify/extractors/robot.py:47· Escalate · medium- agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
- Robot keyword IDs are not case-normalized —
graphify/extractors/robot.py:63· Escalate · medium- agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
Analysis details — impact, health, verification
Impact & health
Graphify review
Impact — 2625 functions depend on the 918 functions this change touches.
Health — this change adds coupling hotspots:
- new:
extract()— 517 callers, 43 callees - new:
_rebuild_code()— 108 callers, 50 callees - new:
detect()— 108 callers, 15 callees - new:
save_manifest()— 40 callers, 11 callees - new:
extract_files_direct()— 17 callers, 20 callees - new:
extract_js()— 85 callers, 4 callees - new:
extract_xaml()— 19 callers, 17 callees - new:
extract_corpus_parallel()— 26 callers, 11 callees - …and 41 more — each is listed as a finding
Verification — 2625 functions in the blast radius were not formally verified this run (proofs are advisory here).
Gate & verification
graphify gate
PASS — objectively clean (no health regressions, tests not run — proofs not run this pass (advisory)). Grounded, not self-assessed.
Advisory (not blocking):
- verification_scope: 2387 function(s) in the blast radius were not formally verified this run
· 1 grounded finding(s) anchored inline below; 48 more finding(s) on lines outside this diff (see the check run).
| return _make_id(name.replace(" ", "").replace("_", "")) | ||
|
|
||
|
|
||
| def extract_robot(path: Path) -> dict: |
There was a problem hiding this comment.
extract_robot()
8 callers depend on it (afferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
Robot matches variable names case-, space-, and underscore-insensitively,
so ${curdir} / ${Cur_Dir} / ${EXEC DIR} now resolve like their canonical
forms in Settings-section imports; any other variable, in any casing,
still yields no edge. Regression test:
test_robot_path_variables_match_case_space_underscore_insensitively.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Round-3 findings, with evidence:
10/10 robot tests pass; ruff clean. |
There was a problem hiding this comment.
Graphify reviewed this change.
Worth a look — the grounded gate found no coupling regressions or blocking issues, but 1 advisory finding(s) below merit a look before merge.
Graphify review — findings
Adds a Robot Framework extractor for .robot/.resource files, driven by the official robot.api parser and gated behind the new robot install extra (returning an error node when robotframework isn't installed). extract_robot emits nodes for the suite/resource file, its test cases, and user keywords, plus contains, imports, and calls edges; keyword nodes are keyed by a name normalized case-, space-, and underscore-insensitively via _kw_id so cross-file calls land on the defining node under Robot's single global namespace, while test cases stay stem-qualified. _resolve_robot_import resolves Settings-section imports onto the imported file's own node ID, handling ${CURDIR}/${EXECDIR} anchoring and skipping unresolvable variables (including ${EXECDIR} under absolute scans, where no matching node could exist), and standard libraries in _ROBOT_STDLIBS are treated as noise so only third-party libraries get stub nodes.
Worth a look
- Optional Robot extractor is imported at module load —
graphify/extract.py:55· Escalate · medium- agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
Analysis details — impact, health, verification
Impact & health
Graphify review
Impact — 2626 functions depend on the 919 functions this change touches.
Health — this change adds coupling hotspots:
- new:
extract()— 517 callers, 43 callees - new:
_rebuild_code()— 108 callers, 50 callees - new:
detect()— 108 callers, 15 callees - new:
save_manifest()— 40 callers, 11 callees - new:
extract_files_direct()— 17 callers, 20 callees - new:
extract_js()— 85 callers, 4 callees - new:
extract_xaml()— 19 callers, 17 callees - new:
extract_corpus_parallel()— 26 callers, 11 callees - …and 41 more — each is listed as a finding
Verification — 2626 functions in the blast radius were not formally verified this run (proofs are advisory here).
Gate & verification
graphify gate
PASS — objectively clean (no health regressions, tests not run — proofs not run this pass (advisory)). Grounded, not self-assessed.
Advisory (not blocking):
- verification_scope: 2388 function(s) in the blast radius were not formally verified this run
· 1 grounded finding(s) anchored inline below; 48 more finding(s) on lines outside this diff (see the check run).
| return _make_id(name.replace(" ", "").replace("_", "")) | ||
|
|
||
|
|
||
| def extract_robot(path: Path) -> dict: |
There was a problem hiding this comment.
extract_robot()
8 callers depend on it (afferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
|
Shipped in v0.9.53 via authorship-preserving cherry-pick so you keep contributor-graph credit. Thanks @nshiveg! Release: https://github.com/Graphify-Labs/graphify/releases/tag/v0.9.53 |
feat: Robot Framework extractor for
.robot/.resourcefilesCloses #3192
Problem
Robot Framework files are invisible to graphify:
.robot/.resourceare in neitherdetect.CODE_EXTENSIONSnor_DISPATCH, so on Robot-based automation repos the graph maps the Python keyword libraries but none of the suites, test cases, user keywords, or the resource-import topology that actually structures such a repo (details in #3192).Approach
No maintained tree-sitter grammar exists for Robot Framework, so
graphify/extractors/robot.pyuses the official parser (robot.api.get_model/get_resource_model+ModelVisitor), as proposed in the issue. Pure Python, wheels everywhere, added as an optionalrobotextra following theterraform/ocamlpattern, and wired into_EXTRA_FOR_EXTENSIONso the #1745 warning names the extra when the parser is missing.Nodes: the suite/resource file, its test cases, its user keywords, plus stub nodes for third-party named libraries (SeleniumLibrary, RequestsLibrary, ...) so suites sharing a library cluster together (same pattern as the Svelte bare-import stubs). RF standard libraries (BuiltIn, Collections, ...) are filtered like the built-ins in
_LANGUAGE_BUILTIN_GLOBALS(#726) — they would otherwise become instant god nodes.Edges:
contains(file → test/keyword),imports(Resource / path-form Library / Variables imports resolved onto the imported file's own node id, preserving the relative/absolute path form like the JS relative-import resolver;${CURDIR}/${/}/${EXECDIR}are substituted, and an import whose path still holds an unresolvable${VARIABLE}emits nothing), andcalls(test/keyword/suite-fixture → keyword, including[Setup]/[Teardown]/[Template]/Test Templateand calls nested in FOR/IF blocks).Design note — keyword ids are bare names, not stem-qualified: Robot resolves keywords globally by name across imported resources, so bare ids make a suite's call edge land on the defining resource's keyword node with no separate cross-file resolution pass. Test-case nodes stay stem-qualified because test names repeat across suites. Calls to keywords never defined in the corpus (e.g. BuiltIn's
Log) dangle and are dropped bybuild_from_jsonlike any external reference.One parser subtlety encoded in the tests:
Template/Test Templatestatements carry their keyword in.value(not.namelike Setup/Teardown) — missing this silently drops all template edges.Tests
5 tests in
tests/test_languages.py(skipif-guarded like thedm/commonlispextras) + 2 fixtures (sample.robot,robot_keywords.resource) covering: node extraction, call edges incl. fixtures/loops/templates, import resolution + stdlib filtering + unresolvable-variable paths,.resourceparsing, and the cross-file id guarantee (suite call-edge target == resource definition id).ruffandpython -m tools.skillgen --checkpass.Real-world validation
Run against an industrial Robot Framework automation repo (~850 code files, ~5,000 tests/keywords): the graph grew from 5,104 to 15,768 nodes — 10,577 robot-sourced — with 44,423 keyword-call edges and 440 robot→python
Libraryedges resolving onto the existing Python nodes. God nodes shifted from generic Python helpers to the actual shared simulator-control keywords, which matches how that codebase is really wired.🤖 Generated with Claude Code