Skip to content

Generate and publish AI reference docs for rs.wordpress.api:kotlin#1416

Open
oguzkocer wants to merge 26 commits into
trunkfrom
add/ai-docs-generation
Open

Generate and publish AI reference docs for rs.wordpress.api:kotlin#1416
oguzkocer wants to merge 26 commits into
trunkfrom
add/ai-docs-generation

Conversation

@oguzkocer

@oguzkocer oguzkocer commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

Description

This adds a compact, per-endpoint Markdown API reference (the "AI docs") that is generated from the UniFFI-generated Kotlin bindings and published alongside rs.wordpress.api:kotlin as an ai-docs Maven classifier zip.

The goal is to let agents and tooling fetch and read one small Markdown file per endpoint instead of crawling the ~191K-line generated wp_api.kt (or the Rust source). A consumer resolves the ai-docs artifact for the rs.wordpress.api version it already depends on and unpacks one file per endpoint domain.

All of the logic lives in native/kotlin/buildSrc and is self-contained: the whole feature is wired into a module with a single id("ai-docs") line.

Changes

  • Doc generator (native/kotlin/buildSrc/src/main/kotlin/aidocs/): a GenerateAiDocsTask that parses the generated wp_api.kt into a structured model and renders Markdown — executor interfaces become method-signature lists, data classes become field tables, and sealed/enum classes become variant lists.
  • Structured for testability: BindingsParser (parse()ParsedBindings) and DocsGenerator (generate()List<GeneratedDoc>) are pure, with all filesystem I/O isolated in GenerateAiDocsTask.
  • Publishing: a precompiled script plugin ai-docs.gradle.kts (applied via id("ai-docs") in api/kotlin/build.gradle.kts) generates the docs, zips them reproducibly with Gradle's built-in Zip (stable order, fixed timestamps), and attaches the zip to the rs.wordpress.api:kotlin Maven publication under the ai-docs classifier.
  • Unit tests: BindingsParserTest covers the parser's non-trivial cases (method-signature parsing, data class fields and defaults, sealed/enum variants, and generic-aware parameter splitting), using kotlin("test") on the JUnit Platform.
  • CI: a dedicated :kotlin: buildSrc Unit Tests Buildkite step runs ./gradlew -p buildSrc test, since buildSrc's test task is not part of the main build's task graph.

Changelog

  • I've added an entry to CHANGELOG.md under ## [Unreleased], using the Keep a Changelog categories (Added, Changed, Deprecated, Removed, Fixed, Security). Prefix breaking changes with **BREAKING:**.

oguzkocer added 11 commits June 30, 2026 16:03
Generate compact, structured markdown API reference from the UniFFI
bindings so tooling can consume endpoint signatures and types without
crawling the generated `wp_api.kt`.

- Add `buildSrc` `GenerateAiDocsTask` that parses `*RequestExecutorInterface`
  blocks, `data class`/`sealed class`/`enum class` declarations from the
  generated bindings into per-endpoint markdown
- Output is a build artifact under `build/ai-docs/ai-reference`; `stats*`
  endpoints are grouped into a single `stats.md`
- Wire `aiDocs { from(...) }` to the generator output so the publish-to-s3
  `zipAiDocs` task and `ai-docs` classifier artifact pick it up
Attach the generated AI reference docs to the `rs.wordpress.api:kotlin`
publication as an `ai-docs` classifier zip, and refactor the generator into
pure, testable pieces.

- Add the `buildSrc` precompiled script plugin `ai-docs.gradle.kts` (id `ai-docs`):
  it generates the docs, zips them reproducibly, and attaches the zip to the
  module's Maven publication under the `ai-docs` classifier.
- Apply `id("ai-docs")` in `api/kotlin/build.gradle.kts`, replacing the previous
  `aiDocs { from(...) }` plugin DSL.
- Split `GenerateAiDocsTask.kt` into package `aidocs`: pure `BindingsParser`
  (`parse(): ParsedBindings`) and `DocsGenerator` (`generate(): List<GeneratedDoc>`),
  with all filesystem I/O isolated in `GenerateAiDocsTask`.
- Add `buildSrc` unit tests for `BindingsParser` (`kotlin("test")` on JUnit Platform)
  and set `rootProject.name` for `buildSrc`.
- Replace positional `Pair`/`Triple` with `Param(name, type)` and
  `Field(name, type, default)` in `MethodSignature.params` and
  `DataClassInfo.fields`, so call sites read by name instead of `.first`/`.third`.
- Remove dead code in `BindingsParser`: the no-op `.substringBefore(" \n")`
  calls in `parseField` (lines are already split) and the unreachable
  `== "): Disposable{"` terminator clause in `parseDataClasses`.
Replace the four hand-threaded `var i` index loops in `BindingsParser` with a
shared `blocks(isHeader)` helper plus `takeWhile`/`map`/`filter` per declaration
kind. Removes the duplicated scan-until-terminator structure and the mutable
cursor threading. Behavior is unchanged: the parser tests pass and the generated
docs are byte-identical.
Covers the `}`-exclusive branch of `enumVariants`, which the existing
`;`-terminated case did not exercise.
Replace the hardcoded eight-entry `replace` chain with a single `\bkotlin\.`
strip, so any built-in (e.g. a future `kotlin.Float`) is handled instead of
silently leaking into the docs. Generated output is unchanged.
Replaces the positional `Pair<String, List<String>>` with a named data class,
consistent with the `Param`/`Field` types; call sites still destructure.
`split(", ")` would break a generic param like `filter: Map<String, Int>` into
two. Replace it with a depth-aware `splitParams` that ignores commas inside
`<...>`, and add a test. Current bindings are unaffected (output unchanged).
Move the structural string literals (`public interface `, `data class `,
`enum class `, `val `, etc.) into a private companion object, centralizing the
binding-format markers. No behavior change.
The AI-docs generator parser tests live in `buildSrc` and are not part of the
main build task graph, so run them in a dedicated `:kotlin: buildSrc Unit Tests`
step (`./gradlew -p buildSrc test`) with JUnit result collection.
@wpmobilebot

wpmobilebot commented Jun 30, 2026

Copy link
Copy Markdown
Collaborator

XCFramework Build

This PR's XCFramework is available for testing. Add to your Package.swift:

.package(url: "https://github.com/automattic/wordpress-rs", branch: "pr-build/1416")

Built from 60f9b5d

oguzkocer added 14 commits June 30, 2026 16:32
Drop the `stats` special-case in `DocsGenerator`: every endpoint now gets its
own `<domain>.md`, consistent with the rest and with the one-file-per-endpoint
goal, and `index.md` lists them all alphabetically. Also removes the now-unused
header `level` nesting that only existed to support the merged file.
Replace the nested for-loops over mutable sets with declarative `flatMap`,
preserving iteration order. Output unchanged.
Replace `writeDataClass`, which mutated a passed-in `StringBuilder`, with
`dataClassTable` that returns the markdown; callers append it. Output unchanged.
Extract the excluded method names, the data/headerMap response envelope fields,
and the `Response`/`Params` suffixes into constants, and name the response-wrapper
filter `isDocumentedType`. Output unchanged.
Use `buildString { }` (dropping the explicit `StringBuilder`/`toString()`) and
`forEach` over the element lists in `generateEndpointDoc` and `buildIndex`.
Output unchanged.
Extract `methodsSection`, `classesSection`, and `enumsSection` as pure functions
returning a String (or empty), composed in `generateEndpointDoc`. Output unchanged.
The library exports 47 namespace-level functions (URL builders, enum converters,
error localizers, etc.) that the generator previously ignored.

- Parse them in `BindingsParser.parseFreeFunctions()`: every `fun` at brace-depth 0
  (not inside an interface/class/object), located with a regex so leading `@Throws`
  / KDoc prefixes are dropped and the `public fun uniffi*` internals are skipped.
- Clean the `kotlin.` prefix off function/method parameter and return types
  (previously only data-class fields were cleaned); endpoint docs are unaffected
  because their methods use custom types.
- Render a single sorted `functions.md`, linked from `index.md` under `## Functions`.
- Add a parser test covering depth filtering, `@Throws`/KDoc prefixes, and `suspend`.
`List<Foo>?` reduced to `Foo>` (the `>` survived because `?` was stripped last),
so a data class reachable only via a `List<T>?` reference would miss the
`dataClasses` lookup. Strip the trailing `?` first. No current output change —
such types are not reached within the two-level type collection today — but the
extraction is now correct.
A method return type (e.g. `PostListResponse`) is a consuming session's entry
point; if it is not documented, the agent knows only the type name and must read
the bindings for its shape. Drop the `isDocumentedType` envelope filter and stop
excluding `*Response` from the "Types" section, so every referenced type is
documented and the method -> response -> payload chain is navigable.
…ined

`collectReferencedTypes` stopped at two levels, so a type reached through a
documented type's field had no table of its own and the agent would still fall
back to the bindings for it. Walk the full reachable closure over data-class
fields (cycle-safe via the seen set) so every data-class type named in an
endpoint doc has its own table.
A sealed type rendered only as a list of variant names, so a consuming session
saw the cases but not each variant's fields — and had to read the source.

- Model each variant as `SealedVariant(name, fields)`; parse `object`, `class`,
  and `data class` variant shapes (constructor params become the fields) and
  strip the `: Super()` from exception-style sealed names.
- Render each variant as a signature (`Name(field: Type, ...)`), and extend the
  transitive type closure to follow variant field types so they are documented too.
- Generalize `cleanType` to strip any package prefix (`kotlin.`, `uniffi.wp_api.`),
  since sealed variant params are fully qualified.
- Rename the `## Enums` doc section to `## Enums & Sealed Types`.
Endpoint docs are now a flat structure: the method signatures followed by one
block per referenced type, each headed by its declaration kind (`## data class`,
`## sealed class`, `## enum class`) and separated by `---` so a consumer can split
the file on that delimiter. Drops the old `## Methods`/`## Parameters`/`## Types`/
`## Enums & Sealed Types` sectioning and the `Params`-suffix split.

Replace `extractTypeName` (which only unwrapped `List<>`/`?`) with
`extractTypeNames`, extracting every identifier in a type expression via regex.
This documents `Map<K, V>` value types (e.g. `JsonValue`) that were previously
silently orphaned, and subsumes the earlier `List<Foo>?` handling.

Also drop the redundant description line in `functions.md`.
The flat restructure left an endpoint's method signatures floating directly
under the `# <domain>` title, unowned by any type — inconsistent with every
other declaration, which carries its `## <kind> <Name>` header (`## data class`,
`## sealed class`, `## enum class`). Head the method list with
`## interface <ExecutorInterface.name>` so the methods are owned by their
declaring interface. The name and kind are both present in the bindings, so this
is extraction, not invented labeling.
Sealed-variant detection moved to `sealedVariantRegex`, leaving
`OBJECT_PREFIX` unreferenced.
@oguzkocer oguzkocer marked this pull request as ready for review July 1, 2026 17:17
@oguzkocer oguzkocer requested a review from a team as a code owner July 1, 2026 17:17
@oguzkocer oguzkocer requested a review from jkmassel July 1, 2026 17:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants