Search/Develop a Framework That Offers One API Across Several Programming Languages

Develop a Framework That Offers One API Across Several Programming Languages

Use when building a framework that generates packages for several languages from one codebase. It sets rules for the JSON protocol between processes, allowed exported types, code generation, and tests that check every language behaves the same.

To use this AI rule
## Keep the same behavior and backward compatibility in every language A cross-language framework must optimize for behavior that remains understandable and equivalent in every supported language, not for every idiom available in its implementation language. ### Core Invariants - Preserve backward compatibility for the assembly format, runtime protocol, and public APIs. - Require equivalent observable behavior across all supported language targets. - Generate code that follows each target language's normal naming, packaging, and type conventions. - Accept only source-language features that can be represented faithfully in every target. - Prefer correctness and interoperability over target-specific performance shortcuts. ### Package Boundaries - Separate the protocol kernel, host runtimes, schema definitions, code generators, reflection tools, compatibility checks, and test fixtures. - Keep build utilities and compliance reporting outside runtime packages. - Treat each language runtime and optional integration as an independently owned package. - Place shared fixtures where every compiler, generator, and runtime test can consume the same behavioral cases. - Keep components maintained in separate repositories behind explicit versioned dependencies. ## Run the code in a kernel process that talks to each language over JSON Use a two-sided runtime model when host languages cannot execute the implementation language directly. The host process communicates with a kernel process through JSON messages over standard input and standard output, while all implementation-language execution remains in the kernel. ### Process Boundary - Give every request an explicit operation discriminator. - Cover assembly loading, object lifecycle, method calls, property access, asynchronous work, statistics, and callbacks with distinct protocol operations. - Use an additional wrapper process only when a host runtime cannot preserve the file descriptors required by the core process. - Keep diagnostic output on the error stream so it cannot corrupt the protocol stream. ### Callback Sequence When implementation-language code reaches a method or property overridden by the host language: 1. The kernel sends a callback request instead of the original response. 2. The host executes the override and returns a completion message with either the result or failure. 3. The kernel resumes the original operation and returns its actual response. ### Protocol Evolution - Treat request names, field meanings, response order, and callback sequencing as compatibility contracts. - Update every host runtime together when the protocol changes. - Add compatibility tests before changing a message shape or lifecycle rule. ## Decide whether values pass between languages by copy or by reference Define a wire representation for every portable value category and make ownership semantics visible. A value that crosses by copy must not be confused with an object that remains owned by the kernel. ### Wire Representation - Send booleans, numbers, and strings by value using their JSON representation. - Send lists and maps by value, with an explicit tagged envelope when needed to distinguish them from other objects. - Encode enumerations with a fully qualified member identifier. - Encode dates with a tagged, unambiguous timestamp representation. - Encode data-only structures with their fully qualified type and field data. - Encode class instances by reference with a stable object identifier. ### Boundary Rules - Preserve UTF-8 strings, floating-point behavior, nullability, and collection copying semantics across languages. - Keep tagged wire keys stable once consumers depend on them. - Release object references explicitly when the protocol owns remote lifecycle management. - Test round trips through every host runtime, not only through the implementation-language side. ## Limit exported types to ones every language can represent Apply portability restrictions to declarations exported from the module's main entry point while allowing internal implementation code to use broader language features. ### Portable API Surface - Support primitives, lists, maps, enumerations, dates, classes, interfaces, data structures, promises, and opaque values only where every target has a sound representation. - Distinguish behavioral interfaces from data-only structures through an explicit naming or schema rule. - Require data-only structures to contain readonly properties and no methods. - Permit promises only in positions that every target can expose consistently, such as method return types. - Treat opaque values as implicitly nullable when static targets cannot recover a more precise type. - Pass classes by reference and value-like structures by copy. ### Restrictions - Reject unsupported exported constructs such as tuples, uninhabited types, arbitrary-precision integers, symbols, and method overloads. - Require overriding methods to keep exactly compatible signatures; do not rely on covariant returns or contravariant parameters. - Avoid unions when they collapse to an untyped object in statically typed targets. - Do not allow behavioral interfaces and data-only structures to inherit from one another. ## Generate each language's package from the same compiled assembly Generate each target from the same compiled assembly so that target packages share one source of API truth. ### Generation Pipeline 1. Read the assembly from the compiled implementation-language package. 2. Generate idiomatic source for the selected target language. 3. Compile the generated source with that language's toolchain. 4. Package the result with the target ecosystem's standard package manager. ### Target Design - Give each language target its own adapter built on a shared traversal abstraction. - Use a mature target as a reference when adding a new target, but preserve the new language's idioms. - Bundle the original implementation artifact only when the runtime architecture requires it. - Include the assembly metadata needed by downstream reflection or generation tools. - Keep formatting, indentation, file creation, and code-block utilities shared across targets. - Update generated-code snapshots whenever a target changes. ## Build and test inside the monorepo with its pinned tool versions Run installation, builds, tests, linting, snapshot updates, and compliance generation through the repository's pinned workspace toolchain. ### Environment and Scope - Start at the repository root for full bootstrap and repository-wide validation. - Run package-scoped scripts from the package directory for focused iteration. - Respect declared package-manager and orchestration versions. - Limit build concurrency when inter-package dependencies make parallel execution unsafe. ### Change Workflow - Identify the package that owns the change before editing or running commands. - Build and test the focused package first, then run broader validation for shared contracts. - Edit source rather than generated bundles, and rebuild through the designated pipeline. - Keep temporary runtime overrides and debugger settings local. - Update snapshots only after confirming that the generated change is intentional. ## Test with snapshots and a shared suite every language must pass Combine fast package-level tests with generated-output regression checks and a cross-language compliance suite. ### Validation Layers - Use unit tests for package logic and protocol edge cases. - Use snapshots to detect unintended changes in generated source. - Use shared fixtures that exercise classes, interfaces, data structures, enumerations, collections, asynchronous calls, and overrides. - Use compliance tests to verify equivalent behavior in every language binding. ### Fixtures and Reports - Compile shared fixtures through the same compiler used by real consumers. - Refresh every affected generator, reflection, and runtime snapshot when fixtures change. - Define each compliance case once, then require every language binding to implement or explicitly classify it. - Regenerate and commit the compliance report whenever cases or outcomes change. - Fail validation when a binding adds a case absent from the suite definition or when the report is stale. ## Release changes with updated snapshots and reports, never force-pushing Treat generated code, protocol formats, runtime libraries, and reports as coordinated release surfaces. ### Non-Negotiable Constraints - Do not weaken exported type restrictions without validation across all targets. - Do not change protocol operations or tagged serialization formats without coordinated host-runtime updates. - Do not change generation patterns without updating and reviewing snapshots. - Do not assume thread safety when runtime libraries are intentionally single-threaded. - Do not introduce circular package dependencies when the build graph rejects cycles. - Do not modify bundled output directly. ### Pull Request and Handoff - Use the repository's required title format, scope conventions, and contribution-license confirmation. - Include all generated snapshots and compliance reports required by the source change. - Keep unrelated generated files, lockfile changes, and local settings out of the change. - If the remote branch advanced, fetch and rebase or merge, resolve conflicts, validate, and push normally; never force-push. - Record downstream repository or documentation synchronization in the release checklist rather than relying on memory.