Search/Change a Large Multi-Language Runtime Codebase Without Breaking Old Behavior

Change a Large Multi-Language Runtime Codebase Without Breaking Old Behavior

Use in a big native-and-script runtime project with patched vendored dependencies. The AI reads the nearest guide first, uses the project's build shortcuts and test targets, follows its custom types and strict bans, and puts behavior changes behind compatibility flags.

To use this AI rule
## Read the Guide Nearest the Code and Check for a Parent Project Guidance in this kind of repository is layered. A root guide gives high-level orientation; guides inside subdirectories carry component-specific context such as key classes, where-to-look tables, local conventions, and anti-patterns. Always read the guide nearest to the code you are about to touch, in addition to the root one. - Always determine whether the repository was checked out standalone or nested as a component of a larger parent project. If it is nested, important documentation lives in the parent root and is not visible from here; read the parent readme, parent guide, and other top-level documents before assuming anything. - Investigate and read freely, but gather this context before proposing or making edits. - When you discover durable high-level information that is missing from the guides, suggest adding it rather than keeping it in the conversation only. ## Build and Test with the Project's Task Shortcuts, Not Ad Hoc Commands The project has one authoritative build system plus a thin task-runner layer of shortcuts. Use the shortcuts for day-to-day work and drop to the underlying build tool only when a shortcut does not expose the option you need. Do not invent ad hoc compile commands. Typical shortcuts to look for and reuse: build, run all tests, format, lint the scripting sources, lint the native sources, lint the systems-language packages, stream test output for debugging, run a single compatibility or conformance suite, scaffold a new test, generate type definitions, generate the editor index database, build and test with the address sanitizer, produce a coverage report, and watch source directories to re-run a command on change. ## Find the Right Test Target and Its Auto-Generated Variants Several test families coexist and each has its own entry point: - Config-driven integration tests: a serialized configuration file that declares a service with embedded script modules, a compatibility date, feature flags, and bindings such as service bindings, structured data, key-value stores, and stateful object namespaces. - Native unit tests built by a dedicated macro, with a hyphenated test suffix in the filename. - Runtime compatibility suites and web standards conformance suites, each run by name. - Benchmarks, run by path. Both the build and test commands accept explicit targets and default to the whole tree. Target names carry a mandatory suffix marker, and the build macros automatically generate several variants per test: a default variant pinned to the oldest compatibility baseline, a variant at the newest date with all flags enabled, and a variant with all temporary gates enabled. To find the correct target for a file, read the build file in the same directory or query the build system for all targets in that package. ## Map the Main Code Areas and Follow a Similar Existing Implementation Build a mental map before editing, and for any new work find a similar existing implementation and follow its pattern. - Runtime APIs: native implementations of the interfaces exposed to scripts, plus the standard-library compatibility layer and their tests. - I/O subsystem: stateful object storage and caching, request tracking, resource limits, threading, and worker lifecycle. A per-request context object ties the request together. - Glue layer: engine bindings, type wrappers, promise handling, memory management, and the module system. - Server layer: the binary entry point and configuration handling, using a capability-based security model described by a schema file. - Language-specific trees for the compatibility layer, an alternate scripting runtime, and systems-language integration components. Compatibility layers are dual: the native half lives in one tree and the scripting half in another, and new modules must be registered in the designated module table. Type definitions are derived from native type information plus hand-written declarations and are regenerated, never hand-edited. ## Follow the House Style and Keep Each Commit Small and Passing Follow the referenced house style guide, including its documented exceptions; where the project deviates deliberately from that guide, the deviation is the rule. - Match the fixed language standard, the project's unusual source file extensions, and the hyphenated test-file suffix exactly. - One formatting command runs every formatter across all languages. Run it, and the full test command, before submitting a change. - A pre-commit check blocks debug-print macros in staged code and re-runs the format check. Do not work around it. - Split changes into small commits; each commit must compile and pass tests on its own. Do not leave fixup commits. - Scripting-side sources are strictly typed: exact optional property semantics, private field syntax, and explicit return types are enforced. ## Use the Project's Own Base Library Types Instead of Standard Ones The codebase deliberately uses its own base library instead of the language standard library for most types. Match the surrounding file rather than reaching for standard containers. Substitutions to apply: owned string and non-owning string view instead of the standard string; fixed-size array and growable vector instead of the standard vector; unique, reference-counted, thread-safe reference-counted, and weak ownership handles instead of the standard smart pointers; a maybe type instead of optional; a callable wrapper instead of the standard function object; a tagged union instead of variant; an array slice instead of raw spans; the library exception type; and the library promise and forked promise instead of standard futures. For safety, prefer an array slice over a raw pointer, and a lifetime-bound handle over a bare reference when the reference is bound by the owner's lifetime. For errors, use the dedicated macro to unwrap a maybe, the script-facing requirement macros when the failure must surface as a script exception with a proper exception type, and the native assertion and precondition macros for internal invariants. ## Avoid the Banned Patterns That Cause Leaks and Hidden Corruption These are absolute; violating them produces subtle corruption, leaks, or compatibility breaks. - Never place engine-local or engine-global handles in value-type struct declarations; use the provided reference wrappers. - Never place engine handles inside promise types, and never pass the engine lock into asynchronous coroutines. - Never hold script-heap references to I/O objects without the owning wrapper; a scope guard enforces this. - Prefer prototype properties over instance properties, which defeat garbage-collection optimization. - Never deep-freeze user-provided content, which is unsafe for cyclic values. - Never add methods to an interface that dispatches unknown names remotely without gating them behind a compatibility flag, and never change serialized enum values. - Prefer the additive task-registration call over the raw task-list accessor, and avoid boolean parameters in favor of strong boolean types. - Any reference stored in a native object reachable from the script heap must implement garbage-collection visitation; native reference cycles are never collected. A dedicated lint check validates this. - Embedded database misuse errors always throw and are never suppressed; transactions are disallowed in the stateful object database. - Module evaluation must never run inside a request context, and asynchronous I/O in global scope is forbidden. - In shared type declaration files, never use generic ambient type names; always prefix them with the product or feature name. Never use a bare untyped object as a type; declare a proper interface with known fields, optionally extending an open record for forward compatibility. - Never hand-edit generated snapshots. Fix the source layer and regenerate. ## Put Behavior Changes Behind Compatibility Flags or Temporary Gates The project makes a strong backward compatibility commitment: once a behavior is deployed it cannot be removed or silently changed. Behavior changes must therefore be introduced behind a gate, and you choose the gate by permanence. - Date-driven compatibility flags are per-instance, permanent, and defined in a schema whose annotations carry the flag name and enable date. Every flag must be documented before its enable date arrives. - Temporary process-level gates are configuration-driven and intended for risky rollouts with conditional activation. They are defined as an enumeration plus a string map, and the two must stay in sync. Keep the bar high for non-standard interfaces; prefer implementing published standards over inventing project-specific behavior. ## Upgrade Vendored Dependencies While Keeping Local Patches Third-party dependencies are vendored into an external directory by the build system's module mechanism. Integration lives in the module manifest and a dependency build directory; a legacy workspace file may still exist but is no longer the primary mechanism. Consult the vendored base library sources directly for questions about its headers and namespaces rather than guessing. Several dependencies carry locally maintained patch sets applied during the build. These patches introduce project-specific customizations and new interfaces, so review and update them whenever the dependency is upgraded. Note also that the project uses an alternative memory allocator, whose behavior differs from the default; any memory analysis must account for that. For a major engine or dependency upgrade: ask for the exact target version and which patches or customizations must be preserved before starting, then follow the documented sync, patch-apply, rebase, patch-regenerate, and version-bump steps. Merge conflicts are common and almost always require human judgment to keep project-specific changes intact; never resolve them automatically without human review. Confirm the full test suite passes and surface any new deprecations you encounter. ## Find Scattered Docs and Propose Fixes When They Are Wrong Documentation is distributed, not centralized. A documents directory covers cross-cutting topics such as environment setup, guidelines for adding new interfaces, and dependency upgrade procedures, while individual source directories often carry their own readme or guide with deep subsystem detail. Proactively look for these when working in an unfamiliar area, before guessing or asking. When documentation is missing, inaccurate, or out of date, propose an update rather than staying silent. Verify the facts first, and do not commit documentation edits without confirming their accuracy with a maintainer.