mirror of
https://github.com/azaion/gps-denied-onboard.git
synced 2026-06-21 07:01:14 +00:00
1f634c2604
ci/woodpecker/push/02-build-push Pipeline failed
- Modified the autodev state to reflect the current testing phase and details of the new `jetson-e2e` tests. - Enhanced the "How to Test" documentation to provide clearer instructions on the demo replay validation process, including video and tlog alignment steps. - Updated architectural documentation to include the new demo replay operator flow and its dependencies. - Documented the removal of deprecated auto-sync features and clarified the operator-facing UI for replay validation. - Added new entries in the dependencies table for upcoming tasks related to the demo replay flow. These changes improve clarity and usability for operators and developers working with the demo replay system.
143 lines
19 KiB
Plaintext
143 lines
19 KiB
Plaintext
---
|
|
description: "Enforces readable, environment-aware coding standards with scope discipline, meaningful comments, and test verification"
|
|
alwaysApply: true
|
|
---
|
|
# Coding preferences
|
|
|
|
## Simplicity is the highest priority (MANDATORY)
|
|
|
|
**Prefer the simplest solution that satisfies all requirements, including maintainability. When in doubt between two approaches, choose the one with fewer moving parts — but never sacrifice correctness, error handling, or readability for brevity.**
|
|
|
|
This is not a tie-breaker. It is the default. Every new class, layer, cache, hosted service, sliding window, persisted state, event-type variant, or configuration option is a liability — it has to be documented, tested, monitored, migrated, and reasoned about by every reader for the rest of the project's life. Add complexity only when a simpler design has been considered and explicitly rejected for a named, concrete reason tied to a requirement.
|
|
|
|
Operational checks the agent MUST apply before adding code:
|
|
|
|
- Before adding a new class, interface, abstract layer, configuration option, or hosted service, **justify in writing** (PR description, task spec, or chat message to the user) why the same effect cannot be achieved by extending an existing component. "Cleaner separation" / "more future-proof" / "more flexible" are NOT justifications unless tied to a concrete upcoming change that the simpler design would make harder.
|
|
- Before introducing a sliding window, smoother, debouncer, in-memory cache, queue, or other stateful in-memory helper, justify why a stateless / on-demand alternative would not meet the requirement. Cite the acceptance criterion the helper is needed for.
|
|
- **Two parallel pipelines for the same conceptual data are a smell.** Examples: two event types that differ only in a boolean flag; two HTTP endpoints that return the same resource shaped differently; two storage paths for the same entity. Either merge them or document on the producer's interface why both must exist and which downstream consumer needs which.
|
|
- **Rehydrate-on-restart logic is a strong signal of over-engineering.** If a feature requires reading state from the DB at startup and re-running it through a state machine, the in-memory state is probably trying to be a database. Consider keeping the state in the DB and querying it on demand instead.
|
|
- When a feature can be expressed in N existing primitives or N+1 (one new primitive + N existing), pick N existing. If you pick N+1, name the new primitive in the PR title.
|
|
|
|
Violations of this section are reviewable. A reviewer who finds an unjustified abstraction, parallel pipeline, or stateful helper is right to ask for it to be removed.
|
|
|
|
## Other preferences
|
|
- Follow the Single Responsibility Principle — a class or method should have one reason to change:
|
|
- If a method is hard to name precisely from the caller's perspective, its responsibility is misplaced. Vague names like "candidate", "data", or "item" are a signal — fix the design, not just the name.
|
|
- Logic specific to a platform, variant, or environment belongs in the class that owns that variant, not in the general coordinator. Passing a dependency through is preferable to leaking variant-specific concepts into shared code.
|
|
- Static members: see "Static members (functions / classes)" below — default to injectable instance types; `static` only for pure, simple, stateless helpers (constants, simple math, stateless lookups), never for business logic or anything with side effects/state. Before implementing a non-trivial static method, ask the user.
|
|
- Avoid boilerplate and unnecessary indirection, but never sacrifice readability for brevity.
|
|
- Never suppress errors silently — no `2>/dev/null`, empty `catch` blocks, bare `except: pass`, or discarded error returns. These hide the information you need most when something breaks. If an error is truly safe to ignore, log it or comment why.
|
|
- Do not add comments that merely narrate what the code does. Comments are appropriate for: non-obvious business rules, workarounds with references to issues/bugs, safety invariants, and public API contracts. Make comments as short and concise as possible. Exception: every test must use the Arrange / Act / Assert pattern with language-appropriate comment syntax (`# Arrange` for Python, `// Arrange` for C#/Rust/JS/TS). Omit any section that is not needed (e.g. if there is no setup, skip Arrange; if act and assert are the same line, keep only Assert)
|
|
- Do not add verbose debug/trace logs by default. Log exceptions, security events (auth failures, permission denials), and business-critical state transitions. Add debug-level logging only when asked.
|
|
- Do not put code annotations unless it was asked specifically
|
|
- Write code that takes into account the different environments: development, production
|
|
- You are careful to make changes that are requested or you are confident the changes are well understood and related to the change being requested
|
|
- Mocking data is needed only for tests, never mock data for dev or prod env
|
|
- Make test environment (files, db and so on) as close as possible to the production environment
|
|
- When you add new libraries or dependencies make sure you are using the same version of it as other parts of the code
|
|
- When writing code that calls a library API, verify the API actually exists in the pinned version. Check the library's changelog or migration guide for breaking changes between major versions. Never assume an API works at a given version — test the actual call path before committing.
|
|
- When a test fails due to a missing dependency, install it — do not fake or stub the module system. For normal packages, add them to the project's dependency file (requirements-test.txt, package.json devDependencies, test csproj, etc.) and install. Only consider stubbing if the dependency is heavy (e.g. hardware-specific SDK, large native toolchain) — and even then, ask the user first before choosing to stub.
|
|
- Do not solve environment or infrastructure problems (dependency resolution, import paths, service discovery, connection config) by hardcoding workarounds in source code. Fix them at the environment/configuration level.
|
|
- Before writing new infrastructure or workaround code, check how the existing codebase already handles the same concern. Follow established project patterns.
|
|
- If a file, class, or function has no remaining usages — delete it. Dead code rots: its dependencies drift, it misleads readers, and it breaks when the code it depends on evolves. However, before deletion verify that the symbol is not used via any of the following. If any applies, do NOT delete — leave it or ASK the user:
|
|
- Public API surface exported from the package and potentially consumed outside the workspace (see `workspace-boundary.mdc`)
|
|
- Reflection, dependency injection, or service registration (scan DI container registrations, `appsettings.json` / equivalent config, attribute-based discovery, plugin manifests)
|
|
- Dynamic dispatch from config/data (YAML/JSON references, string-based class lookups, route tables, command dispatchers)
|
|
- Test fixtures used only by currently-skipped tests — temporary skips may become active again
|
|
- Cross-repo references — if this workspace is part of a multi-repo system, grep sibling repos for shared contracts before deleting
|
|
|
|
- **Scope discipline**: focus edits on the task scope. The "scope" is:
|
|
- Files the task explicitly names
|
|
- Files that define interfaces the task changes
|
|
- Files that directly call, implement, or test the changed code
|
|
- **Adjacent hygiene is permitted** without asking: fixing imports you caused to break, updating obvious stale references within a file you already modify, deleting code that became dead because of your change.
|
|
- **Unrelated issues elsewhere**: do not silently fix them as part of this task. Either note them to the user at end of turn and ASK before expanding scope, or record in `_docs/_process_leftovers/` for later handling.
|
|
- Always think about what other methods and areas of code might be affected by the code changes, and surface the list to the user before modifying.
|
|
- When you think you are done with changes, run the full test suite. Every failure in tests that cover code you modified or that depend on code you modified is a **blocking gate**. For pre-existing failures in unrelated areas, report them to the user but do not block on them. Never silently ignore or skip a failure without reporting it. On any blocking failure, stop and ask the user to choose one of:
|
|
- **Investigate and fix** the failing test or source code
|
|
- **Remove the test** if it is obsolete or no longer relevant
|
|
- **Iterative-skill exception**: when an iterative loop skill is active (e.g. autodev / `implement/SKILL.md` batch loop, `refactor/SKILL.md` batch loop), the skill governs full-suite cadence — typically focused tests per task/batch and a single full-suite gate at the very end of the implementation phase, NOT after each batch. "Done with changes" means done with the entire implementation phase the skill is running, not done with one batch. Do not run the full suite per batch unless the skill explicitly says to.
|
|
- Do not rename any databases or tables or table columns without confirmation. Avoid such renaming if possible.
|
|
|
|
- Make sure we don't commit binaries, create and keep .gitignore up to date and delete binaries after you are done with the task
|
|
- Never force-push to main or dev branches
|
|
- For new projects, place source code under `src/` (this works for all stacks including .NET). For existing projects, follow the established directory structure. Keep project-level config, tests, and tooling at the repo root.
|
|
- **Never run e2e or CI tests in quiet mode (`-q`).** Always use `-v --tb=short` (or equivalent verbosity flags) in all Dockerfiles, compose files, and scripts that invoke pytest. Full test output must be visible so failures can be diagnosed without re-running. This applies to both Tier-1 (Colima) and Tier-2 (Jetson) harnesses.
|
|
- **Never substitute real algorithm execution with a data passthrough to make tests pass.** If a test is designed to validate output from a specific pipeline (e.g. VIO estimation, sensor fusion, inference), the implementation MUST actually run that pipeline — not bypass it by returning the input data directly as output. Tests that pass by skipping the component they are supposed to exercise create false confidence and hide the fact that the component is not integrated. If the real integration cannot be completed in this session, STOP and report the blocker to the user explicitly. A failing test with an honest explanation is always better than a passing test that proves nothing.
|
|
|
|
# Language-agnostic engineering principles
|
|
|
|
The sections below are cross-language paradigms. Each language/framework rule file (e.g. `dotnet.mdc`) is the **stack-specific realization** of these and references back here; the principle lives here, the mechanics live there. When a stack rule and this file appear to conflict, the stack rule wins for that stack (it is the concrete realization) — but flag the divergence so one of the two is corrected.
|
|
|
|
## Architecture & layering
|
|
|
|
### Layered separation of concerns
|
|
|
|
- Keep the **delivery layer thin** (HTTP controllers, CLI commands, message/event handlers, UI handlers): bind/validate input, call **one** business operation, map the result back. **No business logic, no data-store queries, no orchestration in the delivery layer.**
|
|
- Put **business logic behind interfaces in a layer that does not depend on the delivery mechanism** — it must be callable from a different entry point (HTTP, CLI, worker, test) without change. No framework request/response types in a business-layer signature.
|
|
- Put **shared data shapes** (DTOs, value objects, enums, wire contracts) in a layer both can depend on. Dependency direction points **inward**: delivery → business → shared; shared depends on nothing. Never the reverse.
|
|
- Why: business logic fused into the delivery layer can't be reused or unit-tested without booting the whole framework. This is a pragmatic layered split, not a full Clean-Architecture stack — justified for long-lived / complex domains; skip it for throwaway or trivial-CRUD code.
|
|
|
|
### Service results vs. transport envelopes
|
|
|
|
- A business operation returns a **domain result** (the values it computed) on success; the delivery layer maps that onto the transport/wire shape. The envelope (field names, status code, headers) is a delivery concern; the domain result is not.
|
|
- **A value the business logic *reads to make a decision* is owned by the business layer** and returned by it — even if the response also echoes it back. Don't let the delivery layer independently re-derive it (two sources for one conceptual value is a latent bug). Canonical case: a "server now" timestamp used to compute staleness AND echoed to the client must be the *same* instant the business layer used.
|
|
- A value that is **purely a transport artifact and never read by business logic** (a `Location`/redirect header, a per-response trace id) is owned by the delivery layer; the business layer never sees it.
|
|
- Heuristic: "does business logic read this value to decide something?" — yes → business layer owns and returns it; no (formatting/transport only) → delivery layer owns it.
|
|
|
|
## Static members (functions / classes)
|
|
|
|
- Default to **instance types behind an interface**, injected — that is what is testable (mockable), swappable, and free of hidden global state. `static` is the exception, not the default.
|
|
- **No business logic in a static function — ever.** `static` is for *mechanics* (convert, parse, compute, compare), never for *decisions* (which rule applies, what happens next). Domain decisions live in an injectable service.
|
|
- `static` is appropriate **only** for: pure, stateless, **simple** functions (output depends solely on arguments — no I/O, clock, randomness, shared mutable state — and the body is short and obvious); constants; pure extension/utility helpers; static factory methods. The moment a would-be helper carries domain decisions, branches widely, or is complex enough to deserve its own test suite, make it an instance service.
|
|
- **Never** use `static` for: business/domain logic; anything touching I/O, configuration, time, randomness, or external systems (that is a *service* — define an interface, inject it); or **mutable static state** (a thread-safety and test-isolation hazard — shared state belongs in a single injected instance, never a global mutable field).
|
|
- Library-mandated process-global statics (a metrics registry, a logger handle) are an accepted exception; don't force them behind a bespoke interface.
|
|
|
|
## Error handling
|
|
|
|
Builds on "never suppress errors silently" above. Use exceptions for *exceptional* conditions, not normal control flow.
|
|
|
|
- **Catch in one place.** Centralize error→response mapping at a single boundary (framework exception handler / middleware / error filter), not via `try/catch` scattered through every method. The only legitimate local `catch` blocks: converting a third-party/framework error into a domain error at a boundary, honoring cancellation, or keeping a long-running loop alive (log-and-continue). Never an empty/silent catch.
|
|
- **Three failure tiers, three treatments:**
|
|
1. **Input validation** → handled at the boundary/validation pipeline, returns a client-error status; do **not** throw for ordinary request-shape validation.
|
|
2. **Expected business-rule failures** (not-found, conflict, invariant violation, forbidden-by-rule) → a **typed domain failure**: a business-exception hierarchy **or** a result type — pick one per project and be consistent. Each failure carries the status it maps to; there is **no single blanket business status**: not-found → 404, state-conflict → 409, well-formed-but-invariant-violation → 422, rule-forbidden → 403.
|
|
3. **Unexpected failures** (bugs, infrastructure) → propagate to the central handler, which returns a **generic, opaque** error to the client (never leak internal messages/stack traces in production) and **logs the full error** with a correlation id. Dev environments may surface detail.
|
|
- **Don't throw on hot per-item paths** (inner loops, per-record processing) — represent the outcome as a return value / counted metric there; exceptions are for request/operation-level outcomes.
|
|
- Pick **one** failure-representation strategy project-wide (typed exceptions *or* a result type) and stick to it; don't mix both for the same kind of failure.
|
|
|
|
## Dependency injection
|
|
|
|
- Prefer **constructor injection**: a type declares the collaborators it needs and they are provided. This is what makes it unit-testable and its dependencies explicit.
|
|
- **Never capture a shorter-lived dependency inside a longer-lived one** (a request/scoped service held by a singleton — a "captive dependency"). Acquire the short-lived dependency per unit of work instead.
|
|
- Don't manually dispose objects the DI container owns — the container manages their lifetime.
|
|
|
|
## Configuration
|
|
|
|
- **Bind configuration to typed objects** and **validate it at startup**, so misconfiguration is a boot-time crash, not a 3 AM runtime page.
|
|
- Don't read raw config keys (`config["a:b"]`) inside business code — bind once, inject the typed object.
|
|
- Secrets come from the environment / secret store per environment; never commit real secrets to source-controlled config files.
|
|
|
|
## Logging (secrets & structure)
|
|
|
|
Complements the log-level guidance in "Other preferences".
|
|
|
|
- **Never log secrets, tokens, passwords, or PII.** Use ids, hashes, or redaction.
|
|
- Prefer **structured logging with message templates / named fields** over string concatenation or interpolation — logs stay queryable and don't allocate when the level is disabled.
|
|
|
|
## Data access
|
|
|
|
- Route all application reads/writes through the project's **ORM / data-access layer**. Raw SQL is forbidden by default and allowed only for narrow, **justified** cases (DDL the ORM can't express, vendor-specific operators/functions, a benchmarked hot path) — each documented in a one-line comment and confined behind a single interface, nowhere else.
|
|
- **Prevent N+1**: eager-load or project explicitly. For read-only queries, opt out of change-tracking where the data layer supports it.
|
|
|
|
## Boundary discipline
|
|
|
|
- **Don't pass the framework's request/response context** (HTTP context, raw request/response objects) into business logic. Extract the typed values you need at the boundary and pass those down.
|
|
- **Authorize once at the boundary**, not per handler method; name authorization policies centrally and reference the names — don't inline role/permission strings at call sites.
|
|
|
|
## Testing (real dependencies)
|
|
|
|
Complements the AAA convention in "Other preferences".
|
|
|
|
- **Don't use in-memory or fake data stores for query-correctness tests** — their semantics diverge from the real engine (translation differences, no real transactions/constraints). Use the real engine (e.g. a throwaway container) so tests exercise real behavior. Lightweight fakes are acceptable only for fast smoke tests that don't assert query shape.
|
|
- Share expensive test fixtures (server boot, container) across tests instead of paying the cost per test.
|