Search/Make Tested, Review-Ready Changes in a Large Codebase

Make Tested, Review-Ready Changes in a Large Codebase

Use when an AI agent edits a large, multi-language codebase. It builds through the project's wrapper command, puts tests next to the code and checks they fail without the change, follows the review rules, and reports only what it actually ran.

To use this AI rule
## Build and run tests only through the project's wrapper command Large projects usually wrap their compiler and test runner in a single project command that recompiles and injects your local changes before running anything. Always build and run through that wrapper. Never invoke the raw test runner or a previously compiled binary directly, because it executes stale artifacts that do not include your change, so a passing result proves nothing. Learn which edits can skip the native rebuild. Changes that touch only non-compiled inputs, such as type declarations or bundled scripts that reload on their own, do not need a full build, and forcing one only wastes time; run the lighter targeted check for those directly instead. Do not put a timeout on a full build. A legitimate cold build can run long, and killing it produces confusing, misleading failures. When the wrapper forwards trailing arguments to the built program, place build flags before those execution arguments so the routing stays unambiguous. ## Add tests to the existing test file for the code you change Default to adding your test to the existing test file for the code you are changing. A bug in one area's behavior belongs in that area's existing test file, not a brand-new file. Keeping a test next to related coverage is what makes it discoverable and prevents duplicated setup. Mirror the repository's established test layout: unit tests grouped by subsystem, compatibility tests, platform and web API tests, command-line tests, and end-to-end integration tests, each in its own area. Put your test where equivalent coverage already lives. Reserve special-purpose folders for exactly the case they are meant for. A regression folder keyed by an issue number is only for a true regression, meaning behavior that worked in a previous release and then broke, and that identifier must be real, never a placeholder. If the behavior was never correct, it is not a regression, and the test belongs in the ordinary file for that module. ## Write tests that give the same result on every run Prefer snapshot assertions over brittle equality checks on large output, and normalize the snapshot so incidental values like temporary paths do not cause needless churn. Never write a test that only checks for the absence of a crash or unhandled-exception string; such a test can never fail in the pipeline and gives false confidence. Remove every source of nondeterminism. Do not hardcode a network port; bind to an ephemeral port and read back the one that was assigned. Never sleep or use a timer to wait for something, because you are testing the condition, not the passage of time; await the actual condition instead. Use the test harness's temporary-directory helper rather than creating temporary directories by hand, and avoid shell utilities such as file search or text search inside a test, using the project's own file APIs. When a test spawns a process, assert its output before asserting its exit code, so a failure gives a useful message instead of a bare nonzero status. ## Check that new tests fail without the change, and report only what you ran A change is not finished until it is tested. If you did not run the tests, assume the code does not work; running them is part of finishing, not an optional follow-up. Prove that each new test actually exercises your change. It must fail against the unmodified baseline and pass against your build. A test that already passes without your change proves nothing, so confirm both directions before you trust it. Be humble and honest in every commit message, review reply, and status update. Never overstate what you accomplished or what actually works. Report only outcomes you have observed from your own run, and state plainly what is still unverified or unfinished rather than implying the work is complete. ## Learn how the codebase is organized before editing it Before editing, learn how the codebase is organized: which layer owns which responsibility, where shared utilities live, and which files are generated versus hand-written. Generated files are produced from source definitions, so edit the source and let the build regenerate them; never hand-edit generated output, because the next build will overwrite it. Identify the subsystem that owns the behavior you are touching, whether that is parsing, module resolution, the runtime APIs, networking, storage, the bundler, or the package manager, so that both your change and its test land in the right place. Follow the existing code style. Check neighboring files for the established patterns and match them instead of importing conventions from another project. Prefer the language's own resource-management idiom, such as scope-based automatic cleanup, over manual teardown, and respect documented edge cases, for example values allocated in a region that skips their destructors on reset and must be released explicitly first. ## Read the review rules and follow the project's required conventions A mature project distills what its reviewers block merges over into a written rule set. Read it before writing code that makes a non-obvious choice, and read any situational guide for the area you are touching, such as compatibility layers, user-facing API design, performance-sensitive hot paths, platform-gated code, dependency and vendoring changes, or documentation and type declarations, before doing that specific work. Match the project's required conventions exactly, because the automated pipeline enforces them. Use the mandated branch-name prefix, keep code comments short with deeper explanation moved into docs and written only when it is explicitly requested, and use absolute paths in file operations. When you make a platform-specific change, compile across every supported target, since conditionally compiled code is not type-checked unless its matching target is actually built. ## Use the project's diagnostic commands on failed builds, and fix them if they mislead When an automated build fails, use the project's dedicated diagnostic commands, the ones that render the failing tests, summarize job status, and save full logs, rather than reconstructing that picture by hand. They already separate new failures from ones that also fail elsewhere and filter out routine noise. When you read feedback on a change, use the command that returns the complete picture. A general listing often silently omits review summaries and line-level comments, so prefer the tool that returns top-level comments, reviews, and inline comments together in one chronological, labelled view. If a helper's output looks wrong, such as a mis-parsed annotation or a field whose upstream shape changed, fix the presenter itself rather than working around it downstream. The helper is a thin layer over the underlying tool, and repairing it keeps every later run correct.