Files
2026-06-16 15:02:11 +00:00

163 lines
6.6 KiB
Markdown

# lib-core.api-discovery
Pure-Lua-Library für Surface-Discovery (extrahiert `M.*` aus Lua-Quelltext)
und README-Parsing (extrahiert die dokumentierte API aus Markdown). Außerdem
ein README-Struktur-Linter, der die API-Doc-Convention (ADR-0038) gegen die
MUST-Sections eines Tiers prüft. Sandbox-safe — kein FS-Zugriff, keine
Seiteneffekte. Verwendet vom Sporel-`--lint`-CLI-Modus.
**Version:** 0.1.0
**Lib-ID:** lib-core.api-discovery
**Requires:** (none)
**Tags:** api-discovery, lint, readme, surface, topology, tooling
## Topology
<!-- topology:start (auto-generated; do not edit) -->
```mermaid
graph LR
this["lib-core.api-discovery"]
```
<!-- topology:end -->
Reine Lua-Lib ohne Dependencies und ohne `engine.*`-Aufrufe — daher kein
`engine`-Knoten im Topology-Graphen (sandbox-safe per Design, damit der
Linter im `--lint`-CLI-Modus ohne Engine-Kontext laufen kann).
## API
### `api-discovery.parse_lua_surface(source_string)`
**Syntax:** `parse_lua_surface(source_string: string) -> {public: string[], private: string[]}`
**Example:**
```lua
local ad = require("lib-core.api-discovery")
local src = io.read_file("init.lua")
local s = ad.parse_lua_surface(src)
-- s.public == { "load", "create", ... }
-- s.private == { "_normalize", ... }
```
**Description:** Extrahiert die öffentliche und private Surface aus
Lua-Quelltext. Konvention: `M.<name>` ist öffentlich, `M._<name>` privat.
Erkennt beide Schreibweisen — explizite Zuweisung (`M.foo = function`) und
Syntax-Sugar (`function M.foo`). Lua-Zeilenkommentare (`--` bis Zeilenende)
werden vor dem Pattern-Matching entfernt, damit auskommentierte
Forward-Compat-Stubs (z. B. `DEPRECATED-MVP`-Platzhalter) keine
False-Positives erzeugen. Block-Kommentare (`--[[ ... ]]`) werden nicht
behandelt (in Sporel-Lib-Code nicht verwendet). Dedupliziert über alle
Namen. Liefert `{ public = [...], private = [...] }`.
### `api-discovery.parse_readme_api(markdown_string)`
**Syntax:** `parse_readme_api(markdown_string: string) -> {documented: string[]}`
**Example:**
```lua
local r = ad.parse_readme_api(readme_text)
-- r.documented == { "bind", "unbind", ... }
```
**Description:** Extrahiert die dokumentierten Funktionsnamen aus dem
`## API`-Abschnitt eines READMEs. Sucht den Abschnitt zwischen `## API` und
der nächsten H2-Überschrift und parst die H3-Header darin. Konvention: ein
H3-Header öffnet mit einem Backtick, der Funktionsname folgt nach einem
optionalen Namespace-Punkt — sowohl die namespaced-Form `ns.func(...)`
(Capture `func`) als auch die namespace-lose Form `func(...)` werden
erkannt. Dedupliziert; Pattern 2 matcht
keine Namen, die Pattern 1 bereits erfasst hat. Fehlt der `## API`-Abschnitt,
ist `documented` leer.
### `api-discovery.diff_surface(surface, readme)`
**Syntax:** `diff_surface(surface: {public: string[]}, readme: {documented: string[]}) -> {missing_docs: string[], stale_docs: string[]}`
**Example:**
```lua
local s = ad.parse_lua_surface(src)
local r = ad.parse_readme_api(readme_text)
local diff = ad.diff_surface(s, r)
-- diff.missing_docs == öffentliche Funktionen ohne README-Eintrag
-- diff.stale_docs == README-Einträge ohne öffentliche Funktion
```
**Description:** Vergleicht die Code-Surface (`surface.public`) gegen die
README-Dokumentation (`readme.documented`). `missing_docs` sind öffentliche
Funktionen ohne dokumentierten Eintrag; `stale_docs` sind dokumentierte
Einträge ohne korrespondierende öffentliche Funktion. Beide Listen sind
alphabetisch sortiert.
### `api-discovery.grep_engine_calls(source_string)`
**Syntax:** `grep_engine_calls(source_string: string) -> string[]`
**Example:**
```lua
local calls = ad.grep_engine_calls(src)
-- calls == { "engine.render.draw_rect", "engine.window.size", ... }
```
**Description:** Extrahiert alle eindeutigen `engine.<namespace>.<func>`-Aufrufe
aus Lua-Quelltext. Liefert ein dedupliziertes, alphabetisch sortiertes Array.
Wird von `generate_topology_block` genutzt, um zu entscheiden, ob ein
`engine.*`-Knoten in den Topology-Graphen aufgenommen wird.
### `api-discovery.generate_topology_block(manifest, engine_calls)`
**Syntax:** `generate_topology_block(manifest: {id: string, deps?: {id: string}[]}, engine_calls: string[]) -> string`
**Example:**
```lua
local manifest = { id = "lib-core.maps", deps = {} }
local calls = ad.grep_engine_calls(src)
local mermaid = ad.generate_topology_block(manifest, calls)
-- mermaid == "graph LR\n this[\"lib-core.maps\"]\n engine[\"engine.*\"]\n this --> engine"
```
**Description:** Generiert den Mermaid-Topology-Block aus Manifest +
`engine_calls`. Der `this`-Knoten trägt die `manifest.id`. Für jede
Dependency in `manifest.deps` wird ein Knoten plus `this --> dep`-Kante
ausgegeben (die Dep-ID wird für den Mermaid-Variablennamen entschärft: `-`
und `.``_`). Enthält `engine_calls` mindestens einen Eintrag, kommt ein
`engine["engine.*"]`-Knoten mit `this --> engine`-Kante hinzu. Liefert den
reinen Mermaid-Quelltext ohne Marker — der Aufrufer wrappt ihn in die
`<!-- topology:start -->` / `<!-- topology:end -->`-Marker.
### `api-discovery.validate_readme_structure(markdown_string, tier)`
**Syntax:** `validate_readme_structure(markdown_string: string, tier: "core"|"engine"|"module"|"community") -> {missing_sections: string[], section_order_ok: boolean}`
**Example:**
```lua
local res = ad.validate_readme_structure(readme_text, "core")
if #res.missing_sections > 0 or not res.section_order_ok then
-- README verletzt die API-Doc-Convention
end
```
**Description:** Prüft die README-Struktur gegen die MUST-Sections des
angegebenen Tiers.
- **`core`** verlangt: `H1`, `Badges` (ein `**Lib-ID:** lib-…`-Block),
`Topology` (`## Topology`), `Topology-Block` (`<!-- topology:start`-Marker),
`API` (`## API`) und `References` (`## References`).
- **`engine`** verlangt dieselben Sections, lockert aber den Badges-Check auf
eine beliebige fettgedruckte `**Key:**`-Zeile (die Engine ist keine Lib und
trägt kein `Lib-ID:`, sondern `Version`/`License`).
- **`module`** ersetzt `## API` durch `## Controls` + `## Demonstrates` und
identifiziert über `**Module-ID:**` statt `**Lib-ID:**` (Module haben keine
konsumierbare Public-API).
- **`community`** erzwingt nichts (liefert ein leeres Ergebnis).
`section_order_ok` ist `true`, wenn `## API` vor `## References` steht
(bzw. `## Demonstrates` vor `## References` im Module-Tier) — oder wenn
keine der beiden Sections vorhanden ist. Liefert
`{ missing_sections = [...], section_order_ok = bool }`.
## References
- API-Doc-Convention Spec: `meta/docs/superpowers/specs/2026-05-16-api-doc-convention-design.md`
- ADR-0038 (API-Doc-Convention)
- ADR-0001 (engine knows verbs, libs bring nouns)