d990fc1b41
CI / benchmark-publish (pull_request) Has been skipped
CI / lint (pull_request) Successful in 15s
CI / build (pull_request) Successful in 16s
CI / quality (pull_request) Successful in 19s
CI / typecheck (pull_request) Successful in 37s
CI / security (pull_request) Successful in 37s
CI / unit_tests (pull_request) Successful in 2m30s
CI / integration_tests (pull_request) Successful in 2m57s
CI / docker (pull_request) Successful in 38s
CI / coverage (pull_request) Successful in 4m22s
CI / lint (push) Successful in 12s
CI / build (push) Successful in 15s
CI / quality (push) Successful in 16s
CI / security (push) Successful in 33s
CI / typecheck (push) Successful in 33s
CI / benchmark-regression (push) Has been skipped
CI / unit_tests (push) Successful in 2m8s
CI / docker (push) Successful in 39s
CI / integration_tests (push) Successful in 3m9s
CI / coverage (push) Successful in 4m35s
CI / benchmark-publish (push) Successful in 15m41s
CI / benchmark-regression (pull_request) Successful in 28m58s
Implemented the analyzer plugin framework with AnalyzerProtocol, AnalyzerRegistry for registration/discovery by file extension, PythonAnalyzer (AST-based extraction of modules, classes, functions, imports, docstrings), and MarkdownAnalyzer (section, code block, and link extraction). Both analyzers produce well-formed UKO triples with proper URI schemes. ISSUES CLOSED: #551
239 lines
11 KiB
Gherkin
239 lines
11 KiB
Gherkin
Feature: UKO Analyzer Plugin Framework
|
|
As a developer
|
|
I want pluggable domain analyzers that produce UKO triples from resources
|
|
So that the ACMS can index code and documents into the UKO knowledge graph
|
|
|
|
# ---- UKOTriple model ----
|
|
|
|
Scenario: Create a valid UKOTriple with object_uri
|
|
Given a UKOTriple with subject "uko://code/module/foo" predicate "rdf:type" and object_uri "uko-code:Module"
|
|
Then the triple subject_uri should be "uko://code/module/foo"
|
|
And the triple predicate should be "rdf:type"
|
|
And the triple object_uri should be "uko-code:Module"
|
|
And the triple confidence should be 1.0
|
|
|
|
Scenario: Create a UKOTriple with object_value
|
|
Given a UKOTriple with subject "uko://code/class/foo/Bar" predicate "rdfs:label" and object_value "Bar"
|
|
Then the triple object_value should be "Bar"
|
|
|
|
Scenario: UKOTriple rejects empty subject_uri
|
|
Then creating a UKOTriple with empty subject_uri should raise ValueError
|
|
|
|
Scenario: UKOTriple rejects empty predicate
|
|
Then creating a UKOTriple with empty predicate should raise ValueError
|
|
|
|
Scenario: UKOTriple rejects confidence above 1.0
|
|
Then creating a UKOTriple with confidence 1.5 should raise ValueError
|
|
|
|
Scenario: UKOTriple rejects confidence below 0.0
|
|
Then creating a UKOTriple with confidence -0.1 should raise ValueError
|
|
|
|
Scenario: UKOTriple is immutable
|
|
Given a UKOTriple with subject "uko://a" predicate "rdf:type" and object_uri "uko:B"
|
|
Then modifying the triple confidence should raise an error
|
|
|
|
# ---- AnalyzerRegistry ----
|
|
|
|
Scenario: Register and look up an analyzer by extension
|
|
Given an AnalyzerRegistry with a PythonAnalyzer registered
|
|
When I look up the analyzer for extension ".py"
|
|
Then the analyzer domain should be "python"
|
|
|
|
Scenario: Registry returns None for unregistered extension
|
|
Given an empty AnalyzerRegistry
|
|
When I look up the analyzer for extension ".rs"
|
|
Then the lookup result should be None
|
|
|
|
Scenario: Registry rejects non-protocol objects
|
|
Given an empty AnalyzerRegistry
|
|
Then registering a plain object should raise TypeError
|
|
|
|
Scenario: Registry rejects analyzer with no extensions
|
|
Given an empty AnalyzerRegistry
|
|
Then registering an analyzer with no extensions should raise ValueError
|
|
|
|
Scenario: Registry lists all extensions
|
|
Given an AnalyzerRegistry with both Python and Markdown analyzers
|
|
Then the registry should list extensions including ".py" and ".md"
|
|
|
|
Scenario: Registry counts analyzers
|
|
Given an AnalyzerRegistry with both Python and Markdown analyzers
|
|
Then the registry length should be 2
|
|
|
|
Scenario: Registry first-registered-wins for duplicate extensions
|
|
Given an AnalyzerRegistry with a PythonAnalyzer registered
|
|
When I register another analyzer for ".py"
|
|
And I look up the analyzer for extension ".py"
|
|
Then the analyzer domain should be "python"
|
|
|
|
# ---- PythonAnalyzer ----
|
|
|
|
Scenario: PythonAnalyzer satisfies AnalyzerProtocol
|
|
Given a PythonAnalyzer instance
|
|
Then it should satisfy the AnalyzerProtocol
|
|
|
|
Scenario: PythonAnalyzer supports .py and .pyi extensions
|
|
Given a PythonAnalyzer instance
|
|
Then its supported extensions should include ".py" and ".pyi"
|
|
|
|
Scenario: PythonAnalyzer extracts module declaration
|
|
Given a PythonAnalyzer instance
|
|
When I analyze Python content "x = 1\n" with resource URI "src/foo.py"
|
|
Then the triples should contain a triple with predicate "rdf:type" and object_uri "uko-code:Module"
|
|
|
|
Scenario: PythonAnalyzer extracts class definition
|
|
Given a PythonAnalyzer instance
|
|
When I analyze Python content with a class "MyClass"
|
|
Then the triples should contain a triple with predicate "rdf:type" and object_uri "uko-py:Class"
|
|
And the triples should contain a triple with predicate "rdfs:label" and object_value "MyClass"
|
|
|
|
Scenario: PythonAnalyzer extracts class containment
|
|
Given a PythonAnalyzer instance
|
|
When I analyze Python content with a class "MyClass"
|
|
Then the triples should contain a triple with predicate "uko:contains" linking module to class
|
|
|
|
Scenario: PythonAnalyzer extracts function definition
|
|
Given a PythonAnalyzer instance
|
|
When I analyze Python content with a function "do_work"
|
|
Then the triples should contain a triple with predicate "rdf:type" and object_uri "uko-py:Function"
|
|
And the triples should contain a triple with predicate "rdfs:label" and object_value "do_work"
|
|
|
|
Scenario: PythonAnalyzer extracts method inside class
|
|
Given a PythonAnalyzer instance
|
|
When I analyze Python content with class "Cls" and method "run"
|
|
Then the triples should contain a method triple with predicate "rdfs:label" and object_value "run"
|
|
|
|
Scenario: PythonAnalyzer extracts import statement
|
|
Given a PythonAnalyzer instance
|
|
When I analyze Python content with import "os"
|
|
Then the triples should contain a triple with predicate "uko:references" referencing "os"
|
|
|
|
Scenario: PythonAnalyzer extracts from-import statement
|
|
Given a PythonAnalyzer instance
|
|
When I analyze Python content with from-import "pathlib"
|
|
Then the triples should contain a triple with predicate "uko:references" referencing "pathlib"
|
|
|
|
Scenario: PythonAnalyzer extracts module docstring
|
|
Given a PythonAnalyzer instance
|
|
When I analyze Python content with docstring "Module docs."
|
|
Then the triples should contain a triple with predicate "uko-doc:hasDocstring" and object_value "Module docs."
|
|
|
|
Scenario: PythonAnalyzer extracts class docstring
|
|
Given a PythonAnalyzer instance
|
|
When I analyze Python content with class docstring
|
|
Then the triples should contain a docstring triple for the class
|
|
|
|
Scenario: PythonAnalyzer handles syntax errors gracefully
|
|
Given a PythonAnalyzer instance
|
|
When I analyze malformed Python content
|
|
Then the analyzer result should be an empty triple list
|
|
|
|
Scenario: PythonAnalyzer rejects empty content
|
|
Given a PythonAnalyzer instance
|
|
Then analyzing empty content should raise ValueError
|
|
|
|
Scenario: PythonAnalyzer rejects empty resource_uri
|
|
Given a PythonAnalyzer instance
|
|
Then analyzing with empty resource_uri should raise ValueError
|
|
|
|
Scenario: PythonAnalyzer extracts base class inheritance
|
|
Given a PythonAnalyzer instance
|
|
When I analyze Python content with a class inheriting from "BaseClass"
|
|
Then the triples should contain a triple with predicate "uko-py:inheritsFrom" and object_value "BaseClass"
|
|
|
|
# ---- MarkdownAnalyzer ----
|
|
|
|
Scenario: MarkdownAnalyzer satisfies AnalyzerProtocol
|
|
Given a MarkdownAnalyzer instance
|
|
Then it should satisfy the AnalyzerProtocol
|
|
|
|
Scenario: MarkdownAnalyzer supports .md and .markdown extensions
|
|
Given a MarkdownAnalyzer instance
|
|
Then its supported extensions should include ".md" and ".markdown"
|
|
|
|
Scenario: MarkdownAnalyzer extracts document declaration
|
|
Given a MarkdownAnalyzer instance
|
|
When I analyze Markdown content "# Hello\nWorld\n" with resource URI "docs/readme.md"
|
|
Then the triples should contain a triple with predicate "rdf:type" and object_uri "uko-doc:Document"
|
|
|
|
Scenario: MarkdownAnalyzer extracts headings as sections
|
|
Given a MarkdownAnalyzer instance
|
|
When I analyze Markdown content with heading "Introduction"
|
|
Then the triples should contain a triple with predicate "rdf:type" and object_uri "uko-doc:Section"
|
|
And the triples should contain a triple with predicate "uko-doc:headingText" and object_value "Introduction"
|
|
And the triples should contain a triple with predicate "uko-doc:headingLevel" and object_value "1"
|
|
|
|
Scenario: MarkdownAnalyzer extracts nested heading hierarchy
|
|
Given a MarkdownAnalyzer instance
|
|
When I analyze Markdown with nested headings
|
|
Then the triples should contain section containment for nested sections
|
|
|
|
Scenario: MarkdownAnalyzer extracts fenced code blocks
|
|
Given a MarkdownAnalyzer instance
|
|
When I analyze Markdown content with a Python code block
|
|
Then the triples should contain a triple with predicate "rdf:type" and object_uri "uko-code:CodeBlock"
|
|
And the triples should contain a triple with predicate "uko-code:language" and object_value "python"
|
|
|
|
Scenario: MarkdownAnalyzer extracts links
|
|
Given a MarkdownAnalyzer instance
|
|
When I analyze Markdown content with a link to "https://example.com"
|
|
Then the triples should contain a triple with predicate "uko:references" and object_value "https://example.com"
|
|
|
|
Scenario: MarkdownAnalyzer rejects empty content
|
|
Given a MarkdownAnalyzer instance
|
|
Then analyzing empty markdown content should raise ValueError
|
|
|
|
Scenario: MarkdownAnalyzer rejects empty resource_uri
|
|
Given a MarkdownAnalyzer instance
|
|
Then analyzing markdown with empty resource_uri should raise ValueError
|
|
|
|
Scenario: MarkdownAnalyzer handles bare code fences
|
|
Given a MarkdownAnalyzer instance
|
|
When I analyze Markdown content with a bare code fence
|
|
Then the triples should contain a code block triple
|
|
|
|
# ---- Coverage gap: AnalyzerRegistry.get_for_extension("") ----
|
|
|
|
Scenario: Registry returns None for empty extension
|
|
Given an AnalyzerRegistry with a PythonAnalyzer registered
|
|
When I look up the analyzer for an empty extension
|
|
Then the lookup result should be None
|
|
|
|
Scenario: Registry list_analyzers returns all registered analyzers
|
|
Given an AnalyzerRegistry with both Python and Markdown analyzers
|
|
Then the registry list_analyzers should return 2 analyzers
|
|
|
|
# ---- Coverage gap: PythonAnalyzer function docstring ----
|
|
|
|
Scenario: PythonAnalyzer extracts function docstring
|
|
Given a PythonAnalyzer instance
|
|
When I analyze Python content with a function with docstring
|
|
Then the triples should contain a triple with predicate "uko-doc:hasDocstring" for a function
|
|
|
|
# ---- Coverage gap: PythonAnalyzer method docstring ----
|
|
|
|
Scenario: PythonAnalyzer extracts method docstring
|
|
Given a PythonAnalyzer instance
|
|
When I analyze Python content with class and method with docstring
|
|
Then the triples should contain a triple with predicate "uko-doc:hasDocstring" for a method
|
|
|
|
# ---- Coverage gap: PythonAnalyzer dotted base class (ast.Attribute) ----
|
|
|
|
Scenario: PythonAnalyzer extracts dotted base class inheritance
|
|
Given a PythonAnalyzer instance
|
|
When I analyze Python content with a class inheriting from "collections.abc.Mapping"
|
|
Then the triples should contain a triple with predicate "uko-py:inheritsFrom" and object_value "collections.abc.Mapping"
|
|
|
|
# ---- Coverage gap: MarkdownAnalyzer domain property ----
|
|
|
|
Scenario: MarkdownAnalyzer domain is "markdown"
|
|
Given a MarkdownAnalyzer instance
|
|
Then the analyzer domain property should be "markdown"
|
|
|
|
# ---- Coverage gap: MarkdownAnalyzer sibling heading pops stack ----
|
|
|
|
Scenario: MarkdownAnalyzer handles sibling headings at same level
|
|
Given a MarkdownAnalyzer instance
|
|
When I analyze Markdown with sibling headings at the same level
|
|
Then each sibling section should be contained by the document
|