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