From e166ff7e1db7eba573fa82c500ab03379b91adbf Mon Sep 17 00:00:00 2001 From: NiccoloN Date: Fri, 5 Jun 2026 11:36:01 +0200 Subject: [PATCH] better AGENTS.md --- AGENTS.md | 248 ++++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 183 insertions(+), 65 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index f19d8ed..9cadae5 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,92 +1,210 @@ -- Always read the full README.md before doing anything. -- Build commands: - - `cmake --build ./build_release` - - `cmake --build ./build_debug` -- Never use `ninja` directly: it bypasses cmake's configuration and invalidates the build cache. -- Always tries the release version build first and ask before building with the debug version +* Always read the full README.md before doing anything +* Build commands: + * `cmake --build ./build_release` + * `cmake --build ./build_debug` +* Never use `ninja` directly: it bypasses cmake's configuration and invalidates the build cache +* Always try the release build first before building with the debug version +* Use the debug build only when it is useful to obtain a clear stack trace with symbols, inspect names, place breakpoints, or test a small case interactively +* The debug build is very slow, so use it only on small fast tests such as operation validations, not on network validations + +# Core engineering philosophy + +* Clean architecture matters as much as making the immediate test pass +* Prefer fixes that preserve clear ownership boundaries, explicit invariants, and simple dataflow +* Do not stack compensating fixes on top of earlier mistakes. If the current approach is becoming messy, stop and explain why +* A correct fix should usually make the responsible producer, resolver, verifier, or lowering own the behavior directly +* Avoid late repair passes, defensive cleanup, or broad rewrites when a cleaner owner-side fix is possible +* Do not hide an upstream modeling bug by normalizing it later in the pipeline. Fix the producer when the producer owns the invariant +* Prefer patterns/rewrites for local IR canonicalization. Use module walks only when pass-level structural analysis genuinely requires them +* Prefer compact, structured designs over long case-by-case implementations + +# Think before coding + +* State assumptions explicitly before implementing when they affect the design +* If multiple interpretations exist, present them instead of silently choosing one +* If a simpler approach exists, say so and prefer it unless there is a clear reason not to +* If something is unclear, stop, name what is confusing, and ask +* If the requested or obvious approach would make the architecture worse, push back and propose a cleaner alternative # Code changes -- Keep changes minimal and localized to the relevant parts of the code. -- Preserve the existing naming conventions and coding style used in the surrounding code. -- Keep code easy to read, well organized, and suitable for future extensibility. A function must not be longer than - 200/250 lines for readability and cognitive complexity. -- Prefer clear naming and structure over comments. Add comments only when they materially improve clarity. -- Do not rename symbols, move files, or restructure modules unless that is necessary for the requested change. +* Keep changes minimal and localized to the relevant parts of the code +* Preserve the existing naming conventions and coding style used in the surrounding code +* Keep code easy to read, well organized, and suitable for future extensibility +* A function must not exceed roughly 200/250 lines. If a change pushes a function beyond that, extract focused helpers +* Prefer clear naming and structure over comments. Add comments only when they materially improve clarity +* Do not rename symbols, move files, or restructure modules unless that is necessary for the requested change +* Avoid duplicate ad-hoc logic. If the same concept appears in multiple places, consider whether it deserves a shared helper/API +* When adding a helper or API, ask: + * Could this be useful to another component now + * Is another component already implementing the same idea differently + * Is this likely to be needed by a future adjacent component + * What is the narrowest useful abstraction + * What is the correct ownership level for this API +* If a shared API is justified, place it at the lowest clean layer that can be used by all relevant consumers without creating dependency cycles or leaking policy across layers +* If an existing component should use a newly introduced shared API, refactor that component in the same patch when doing so is directly related and reduces duplication +* Do not create broad frameworks just because a helper might someday be useful. Shared APIs should encode a real reusable concept, not speculative generality +* If the reusable abstraction is plausible but not clearly needed yet, keep the code local and mention the possible future extraction separately + +# Avoid case-listing designs + +* Avoid solving problems with large chains of `if`/`else`, switches, or repeated special cases that enumerate every possible situation +* Long case listings tend to overfit the current tests, grow the codebase, and hide the underlying abstraction +* When you see a growing list of special cases, stop and look for the shared concept, data model, interface, or normalization step that would make the cases collapse +* Prefer table-driven logic, traits/interfaces, small reusable predicates, structured dispatch, or producer-side normalization when they express the invariant more directly +* A few explicit cases are fine when the domain is genuinely small and closed +* If the list is likely to grow, refactor toward a cleaner and more compact design instead of adding another branch +* When keeping a case list is the pragmatic choice, explain why the domain is closed or why a broader abstraction would be premature + +# Ownership and invariants + +Before implementing, identify the owner of the behavior: + +* A producer should emit IR/data that satisfies the contract of the next stage +* A lowering should make representation changes explicit and semantically correct +* A resolver should resolve existing structure without silently changing semantics +* A verifier should reject invalid states with bounded, actionable diagnostics +* Codegen should assume verified invariants and fail clearly if they are violated + +When fixing a bug: + +* State the invariant that was violated +* State which component should own that invariant +* Fix that component directly +* Avoid fixes that merely mask the violation later in the pipeline +* Add or preserve verification if the invariant is important enough to regress + +# Refactor and API policy + +You may propose or implement a refactor when: + +* the local fix would duplicate logic +* the local fix would violate a layer boundary +* the bug exists because responsibility is assigned to the wrong component +* multiple components already implement ad-hoc variants of the same concept +* a shared helper/API would make the code smaller, clearer, and easier to maintain +* existing callers can be migrated cleanly without broad churn +* the current implementation is turning into a long list of special cases instead of a structured solution + +When proposing or implementing a refactor: + +* Explain what responsibility is being moved or shared +* Justify why the new location is the right ownership level +* Keep the API narrow and named after the concept or invariant it represents +* Migrate directly related existing users when that improves compactness and consistency +* Separate changes required for correctness from optional cleanup +* Avoid unrelated renames, formatting changes, or module moves +* Do not expand a justified refactor beyond directly related callers + +Do not refactor when: + +* the issue is truly local and a local fix is clearer +* the abstraction would have only one user and no clear adjacent use +* the abstraction would mix policies from different layers +* the refactor would affect unrelated behavior +* the refactor is mainly aesthetic # Working style -- Infer style and conventions from the existing code before introducing new patterns. -- When several implementation options are possible, prefer the simplest one that fits the current architecture and - minimizes churn. -- Avoid broad refactors unless I explicitly ask for them. +* Infer style and conventions from the existing code before introducing new patterns +* When several implementation options are possible, prefer the simplest one that fits the current architecture and minimizes churn +* Push back when the requested or obvious fix would make the architecture worse +* If a cleaner fix requires a small refactor or shared helper/API, propose it explicitly and justify it +* Avoid broad refactors unless explicitly requested or clearly necessary for correctness and maintainability +* When tests fail, bucket failures by likely root cause and separate patch-related failures from pre-existing or out-of-scope failures -# Responses +# Simplicity first -- When showing code in chat, make it easy to copy-paste into the codebase. -- Keep outputs focused on the changed parts. -- At the end of the response, briefly list any bad practices, mistakes, or cleaner alternatives you noticed, separate - from the main solution. +* Minimum code that solves the problem cleanly. Nothing speculative +* No features beyond what was asked +* No error handling for impossible scenarios +* If you write 200 lines and it could be 50, rewrite it +* Ask: “Would a senior engineer say this is overcomplicated?” If yes, simplify +* Prefer direct, explicit code over generic machinery unless the generic machinery clearly reduces duplication and preserves boundaries -# Guidelines +# Fallbacks and defaults -## 1. Think Before Coding +* Avoid silent fallback behavior when the semantic category is unknown +* Do not treat “unknown” as “safe” unless the codebase already defines that convention +* If a value cannot be classified, either preserve the existing behavior deliberately or fail with a clear diagnostic +* When adding a fallback, state why it is semantically valid and what invariant makes it safe -**Don't assume. Don't hide confusion. Surface tradeoffs.** +# Surgical changes -Before implementing: +* Touch only what you must +* Clean up only the mess introduced by your own change +* Do not “improve” adjacent code, comments, or formatting +* Match existing style, even if you would personally do it differently +* If you notice unrelated dead code, bad abstractions, or fragile design, mention it separately. Do not delete or rewrite it unless asked +* When your changes create orphans, remove imports, variables, functions, or files made unused by your change +* Every changed line should trace directly to the requested fix, a required cleanup, or a justified reuse/refactor decision -- State your assumptions explicitly. If uncertain, ask. -- If multiple interpretations exist, present them - don't pick silently. -- If a simpler approach exists, say so. Push back when warranted. -- If something is unclear, stop. Name what's confusing. Ask. +# Diagnostics and verification -## 2. Simplicity First +* Use existing bounded diagnostic mechanisms for pass-level verification or codegen failures +* Do not emit unbounded repeated diagnostics from loops or parallel workers +* Diagnostics should identify the violated invariant and the relevant value/op when useful +* Verifiers should reject invalid states, not repair them +* Codegen should not compensate for invalid IR/data unless codegen is the owner of that invariant +* Do not make failing tests pass by weakening verifiers, assertions, or diagnostics unless the check itself is proven wrong +* If a check is too strict, explain the valid case it rejects and update the invariant accordingly +* Prefer fixing invalid IR/data producers over relaxing consumers +* If adding diagnostics only for debugging, remove them or cap them before finalizing -**Minimum code that solves the problem. Nothing speculative.** +# Temporary debugging code -- No features beyond what was asked. -- No error handling for impossible scenarios. -- If you write 200 lines and it could be 50, rewrite it. +* Temporary diagnostics, dumps, assertions, and debug-only helpers must be removed or intentionally converted into bounded permanent diagnostics before finalizing +* If debug instrumentation remains, explain why it is useful as permanent infrastructure +* Do not leave noisy validation output behind -Ask yourself: "Would a senior engineer say this is overcomplicated?" If yes, simplify. +# Performance awareness -## 3. Surgical Changes +* Avoid algorithmic regressions in compiler passes, especially repeated full-module walks, repeated expensive analyses, or per-op recomputation inside nested loops +* If a change adds a walk, cache, analysis, or structural traversal, justify why it is needed +* For hot paths, prefer preserving existing asymptotic behavior unless a better structure is part of the requested change +* If performance may change, mention the expected impact and suggest a targeted timing check -**Touch only what you must. Clean up only your own mess.** - -When editing existing code: - -- Don't "improve" adjacent code, comments, or formatting. -- Don't refactor things that aren't broken. -- Match existing style, even if you'd do it differently. -- If you notice unrelated dead code, mention it - don't delete it. - -When your changes create orphans: - -- Remove imports/variables/functions that YOUR changes made unused. -- Don't remove pre-existing dead code unless asked, but mention it. - -The test: Every changed line should trace directly to the user's request. - -## 4. Goal-Driven Execution - -**Define success criteria. Loop until verified.** - -Transform tasks into verifiable goals: - -- "Add validation" → "Write tests for invalid inputs, then make them pass" -- "Fix the bug" → "Write a test that reproduces it, then make it pass" -- "Refactor X" → "Ensure tests pass before and after" +# Goal-driven execution For multi-step tasks, state a brief plan: -``` 1. [Step] → verify: [check] 2. [Step] → verify: [check] 3. [Step] → verify: [check] -``` -Strong success criteria let you loop independently. Weak criteria ("make it work") require constant clarification. +Define success criteria before implementing: ---- +* For bug fixes, success means reproducing or identifying the failure, fixing the responsible owner, and verifying the targeted case +* For refactors, success means preserving behavior while making ownership, reuse, or structure cleaner +* For validation changes, success means checking both valid and invalid cases when applicable + +Transform tasks into verifiable goals: + +* “Fix the bug” → identify the invariant, reproduce the failure, fix the owner, verify the targeted case +* “Add validation” → write or identify tests for invalid inputs, then make them pass/fail as expected +* “Refactor X” → preserve behavior before and after, then run relevant tests + +# Final self-review + +Before reporting completion, check: + +* Did I fix the owner of the invariant rather than masking the issue downstream +* Did I avoid broad case lists and ad-hoc special handling +* Did I introduce a helper/API only at the right ownership level +* Did I migrate directly related duplicate logic when doing so improves compactness +* Did I avoid weakening verifiers or assertions unnecessarily +* Did I remove temporary debugging code or make it bounded and intentional +* Did I avoid unrelated formatting, renames, or cleanup +* Did I consider performance impact for added walks, analyses, caches, or repeated computations +* Did I run the required build/test commands +* Did I clearly report remaining failures or risks + +When reporting back: + +* Say what changed +* Say what was verified +* Say what remains +* When showing code in chat, make it easy to copy-paste into the codebase +* Keep outputs focused on the changed parts +* List bad practices, fragile assumptions, or cleaner alternatives separately +* If a change is intentionally pragmatic rather than architecturally ideal, say so and explain the tradeoff