211 lines
12 KiB
Markdown
211 lines
12 KiB
Markdown
* 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 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
|
|
* 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
|
|
|
|
# Simplicity first
|
|
|
|
* 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
|
|
|
|
# Fallbacks and defaults
|
|
|
|
* 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
|
|
|
|
# Surgical changes
|
|
|
|
* 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
|
|
|
|
# Diagnostics and verification
|
|
|
|
* 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
|
|
|
|
# Temporary debugging code
|
|
|
|
* 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
|
|
|
|
# Performance awareness
|
|
|
|
* 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
|
|
|
|
# Goal-driven execution
|
|
|
|
For multi-step tasks, state a brief plan:
|
|
|
|
1. [Step] → verify: [check]
|
|
2. [Step] → verify: [check]
|
|
3. [Step] → verify: [check]
|
|
|
|
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
|