feat(coding): generate changelog binding via fluessig#327
Conversation
Swap the hand-written `#[napi]` export from coding-agent utils/changelog.ts to
the fluessig-generated path: author the op in schema/api.json, add the
`core_impl` delegation, regenerate src/generated.rs, and delete the hand-written
export from src/lib.rs.
`normalizeChangelogLinks(markdown: string, versionJson: string) -> string` is a
bucket-C JSON-string op: pi's `version` argument (`string | ChangelogEntry`)
crosses the boundary as a JSON string and the op is fallible (throws on bad
input). The JSON parsing moves into `core_impl`, which branches on a bare string
vs an object exactly as the hand-written export did. Error messages are
reproduced verbatim through anyhow: a malformed payload returns
`serde_json::from_str(..)?` whose `anyhow::Error` Display is the serde message,
and a non-string/non-object value returns `anyhow::anyhow!("version must be a
string or ChangelogEntry object")` — the generated wrapper throws
`napi::Error::from_reason(e.to_string())`, so both thrown messages are
byte-identical to the pre-swap `napi::Error::from_reason` calls.
Verified: the swapped symbol's generated `.d.ts` JSDoc+signature block, its `.js`
`module.exports` line, and its nativeBinding destructure entry are byte-identical
to a pre-swap build of the base (module 1). cargo build/fmt/clippy clean;
codespell 0; straitjacket 0 errors; the changelog oracle slice
(coding-agent/test/changelog.test.ts, 2 tests) passes 2/2 against the
locally-built generated addon.
Conformance smoke: agent + ai + tui
Headline is rust-backed: passing cases in files whose module-under-test is a native (Rust addon) module. Raw all-pass is shown secondary — it is inflated by unflipped TypeScript that passes without touching any Rust.
agent: 55/180 (30.6%) · ai: 74/1295 (5.7%) · tui: 375/678 (55.3%) rust-backed = passing / total tests run, per the manifest's per-native-row AttributionA pi test file is rust-backed iff the module it primarily exercises — its module-under-test — is Per-file decisions (from each native manifest row's
CLI conformance (black-box, against the pidgin binary)
The four repointed coding-agent CLI test files spawn the compiled pidgin binary via |
Before
normalizeChangelogLinks(markdown, versionJson) → stringwas a hand-written#[napi]wrapper insrc/lib.rs. It crosses Pidgin'sstring | ChangelogEntryunion as a JSON string and is fallible (throws).After
The export is now generated by fluessig instead of being hand-written, byte-identical to the previous build (1/1 identical:
.d.tsblock,.jsmodule.exportsline,nativeBindingmembership). Error messages are reproduced verbatim:serde_json::from_str(..)?(anyhow Display == serde Display) andanyhow::anyhow!("version must be a string or ChangelogEntry object")— the generated wrapper throwsfrom_reason(e.to_string()), byte-identical to the pre-swap throws.How
schema/api.json(bucket C JSON-string arg; catalog.json unchanged).core_impl, branching bare-string vs object exactly as before.src/generated.rs, then deleted the hand-written#[napi]export fromsrc/lib.rs.Stacked on
napi-fluessig/resolve-config-value.Generated by Claude Code