feat: add clean-code, clean-architecture and helm skills; fix .gitignore

New skill files:
- clean-code.instructions.md — naming, functions, classes, error handling,
  formatting, DRY/KISS/SOLID, Helm YAML conventions
- clean-architecture.instructions.md — dependency rule, layers, boundaries,
  SOLID foundation, Helm as outermost layer
- helm.instructions.md — resource ownership, values hygiene, required vs
  defaults, umbrella chart pattern, two-file values layering, KISS principle,
  hook ordering, config files pattern, dependency caching, template testing

Register all three in ai-root-instructions.md skills list and routing table.

Remove .ai from .gitignore — .ai/ is the product in this repo and must be
tracked; the apply.sh skip-by-marker mechanism prevents changes to other repos.
This commit is contained in:
moilanik 2026-03-04 09:54:07 +02:00
parent 172bfa78e9
commit a85c7dc0fb
5 changed files with 390 additions and 3 deletions

View File

@ -1,6 +1,6 @@
# AI Assistant Guidelines
**Updated**: 2026-03-02
**Updated**: 2026-03-04
---
@ -57,6 +57,10 @@ This main file references specialized instruction files. **Load relevant files b
- [Mermaid](instructions/skills/mermaid.instructions.md) - Diagram types, color contrast rules, sizing
- [Analysis](instructions/skills/analysis.instructions.md) - Where to write analysis, tmp/ convention, full-pass writing allowed
- [Helm](instructions/skills/helm.instructions.md) - Resource ownership, values hygiene, required vs defaults, dependency caching
- [Clean Code](instructions/skills/clean-code.instructions.md) - Naming, functions, classes, error handling, formatting, DRY/KISS/SOLID, Helm YAML
- [Clean Architecture](instructions/skills/clean-architecture.instructions.md) - Dependency rule, layers, boundaries, SOLID, Helm as outermost layer
### constraints/ — Load When Needed
- [Agent Capabilities](instructions/constraints/agent-capabilities.instructions.md) - AI limitations, user responsibilities, debugging workflows
- [Kubernetes Access](instructions/constraints/kubernetes-access.instructions.md) - kubectl/helm restrictions, port-forwarding patterns
@ -77,11 +81,15 @@ User asks for analysis/comparison → instructions/skills/analysis.instructio
User asks about project → instructions/behavior/project-context.instructions.md → docs/ai-context.md
User needs to debug cluster → instructions/constraints/agent-capabilities.instructions.md + instructions/constraints/kubernetes-access.instructions.md
User reports pod issues → instructions/constraints/container-limitations.instructions.md
User works with Helm charts → instructions/skills/helm.instructions.md
User writes or reviews code → instructions/skills/clean-code.instructions.md
User designs a system or service → instructions/skills/clean-architecture.instructions.md
User creates Helm charts or YAML → instructions/skills/clean-code.instructions.md + instructions/skills/helm.instructions.md + instructions/skills/clean-architecture.instructions.md
Always active → instructions/behavior/core-principles.instructions.md
```
---
**Last Updated**: 2026-03-02
**Last Updated**: 2026-03-04
**Maintained By**: Project Owner
**AI Assistants**: Follow these guidelines strictly - no exceptions

View File

@ -0,0 +1,84 @@
# Clean Architecture
Based on Robert C. Martin's *Clean Architecture*. Apply these principles when designing systems, services, and infrastructure — including Helm chart structure and Kubernetes deployments.
---
## The Dependency Rule
**Source code dependencies point only inward.** Inner layers define interfaces; outer layers implement them.
```
[ Frameworks & Drivers ] ← outermost: UI, DB, APIs, Helm, K8s
[ Interface Adapters ] ← controllers, presenters, gateways
[ Use Cases ] ← application business rules
[ Entities ] ← enterprise business rules (innermost)
```
- Inner layers know nothing about outer layers
- Data crossing a boundary is always in the form the inner layer prefers — never a raw DB row or HTTP request object
- If an outer layer changes (swap MySQL for Postgres, REST for gRPC), the inner layers are untouched
## Layers
### Entities — Core Business Rules
- Enterprise-wide business objects with their validation and invariants
- Pure domain models: no framework imports, no DB annotations, no HTTP concepts
- Stable — change only if fundamental business rules change
### Use Cases — Application Business Rules
- Orchestrate entities to fulfil one actor's goal: `TransferMoneyUseCase`, `ProvisionClusterUseCase`
- Define input/output via interfaces owned by this layer — no knowledge of controllers or DB implementations
- One use case = one reason to exist
### Interface Adapters
- Convert data between use cases and the outside world
- Controllers translate HTTP/CLI input → use case input models
- Gateways implement repository interfaces defined by use cases
- Presenters format use case output → UI/API response format
### Frameworks & Drivers
- The outermost ring: web frameworks, ORMs, message brokers, Kubernetes, Helm
- These are details — they plug into the system via the adapters; the core doesn't know they exist
## SOLID as the Foundation
- **SRP** — one module, one actor, one reason to change
- **OCP** — extend behaviour through new implementations, not modification of existing code
- **LSP** — subtypes must honour the contracts of their interfaces
- **ISP** — prefer narrow, focused interfaces over fat ones
- **DIP** — high-level policy depends on abstractions; low-level detail implements them
## Boundaries
- Boundaries are where the power lives — they isolate components so that one side can change without affecting the other
- Define boundaries at the most volatile points: DB, UI, external services
- Cross a boundary only via an interface; never pass a concrete type across
## What This Means in Practice
- Business rules have no imports from frameworks, ORMs, or infrastructure libraries
- Tests of use cases and entities need no running database, no HTTP server, no cluster
- Swapping the database or the delivery mechanism (REST → gRPC, monolith → microservices) touches only the outer rings
- "Screaming architecture" — the top-level structure should reflect the use cases, not the framework: `invoice/`, `user/`, `payment/` not `controllers/`, `models/`, `views/`
---
## In Helm and Kubernetes
Clean Architecture applies to how infrastructure is organised, not just application code.
**Helm charts are the outermost layer — they are the delivery mechanism, not the system.**
- A chart should deploy exactly one service or one cohesive unit — not a bundle of unrelated things
- Business rules belong in application config (`values.yaml` keys mapped to env vars); they must not be hardcoded in templates
- Separate concerns across charts: one chart for the app, separate charts (or dependencies) for databases, queues, and shared infrastructure
- A chart change (resource limits, replica count, ingress rules) must never require touching application business logic
- Use `_helpers.tpl` as the adapter layer — it translates chart-level abstractions into Kubernetes primitives; templates themselves stay thin
- Environments (`values-dev.yaml`, `values-prod.yaml`) are driver-layer configuration — they override defaults without touching templates
- Test charts with `helm template` + schema validation; business logic is tested independently of the chart
**Dependency Rule for Helm:**
- App charts depend on library charts, not the reverse
- Library charts define interfaces (helpers, common labels); app charts implement them
- Avoid circular chart dependencies — they indicate a missing layer or a misplaced responsibility

View File

@ -0,0 +1,78 @@
# Clean Code
Based on Robert C. Martin's *Clean Code*. Apply these principles to all code and configuration, including Helm charts, YAML, and infrastructure-as-code.
---
## Naming
- Use intention-revealing names: `daysSinceLastPayment` not `d`
- Don't lie with names: `accounts` not `accountList` if it isn't a list
- Pronounceable, searchable names — avoid encodings like `a2dp`, `strName`
- One word per concept across the codebase: pick `fetch`, `get`, or `retrieve` — not all three
- Names should read like prose: `if user.isEligibleForRefund()` not `if user.check()`
## Functions
- **Do one thing** — if you can extract a meaningful function from it, it's doing two things
- **One level of abstraction per function** — don't mix high-level orchestration with low-level detail in the same function
- Keep short — aim for under 20 lines; use extract method aggressively
- 02 arguments preferred; use a parameter object when more are needed
- **No side effects** — a function named `checkPassword` must not also start a session
- No flag arguments: `delete(file, true)` means the function already does two things — split it
## Classes
- **Single Responsibility Principle** — one reason to change
- Small and cohesive — methods should use the class's fields, not act as utilities
- Hide internals: public API simple, private implementation complex
- No god classes — prefer `InvoiceCalculator` over `BusinessLogic`
## Error Handling
- Throw exceptions, don't return error codes — let callers handle what they understand
- Descriptive messages: `throw new UnauthorizedAccess("Admin role required for this action")`
- Define custom exceptions close to where they're used
- Clean up resources: use try/finally or RAII patterns; never leak
## Comments
- Good code needs no comments — if you need to explain *what*, rename instead
- Comments explain *why*, not *what*: acceptable for non-obvious decisions, legal notices
- TODOs are temporary; remove before merging
- Never commit commented-out code
## Formatting
- Related code stays close — declarations near first use
- Consistent indentation throughout the codebase — agree once, automate with linting
- No deep nesting — if you're 4 levels deep, extract a function
- Team standard beats personal preference every time
## General Principles
- **DRY** — every piece of knowledge has one representation; duplication is a maintenance liability
- **KISS** — the simplest solution that works; complexity must earn its place
- **Boy Scout Rule** — leave the code cleaner than you found it; small improvements compound
- **SOLID** — especially SRP (above) and Open-Closed: open for extension, closed for modification
- **Boundaries** — wrap third-party code in adapters; don't let external APIs bleed through the codebase
## Testing
- Tests must be **FAST, Independent, Repeatable, Self-Validating**
- One concept per test — a test that asserts many things is a test that hides failures
- Keep test code as clean as production code — it will be maintained just as long
- Write code testable from day one; retrofitting testability is expensive
---
## In Helm Charts and YAML
The same principles apply to configuration:
- **Naming**: value keys should be intention-revealing — `replicaCount` not `rc`, `service.port` not `p`
- **Single responsibility**: one chart does one thing; don't bundle unrelated services
- **No magic values**: named values in `values.yaml`, never hardcoded in templates
- **DRY**: use template helpers (`_helpers.tpl`) to avoid repeating label blocks and name patterns
- **Comments**: explain *why* a value is set a certain way, not what it does — the YAML speaks for itself
- **Defaults that make sense**: a default of `false` or `""` that always needs overriding is not a useful default

View File

@ -0,0 +1,218 @@
# Helm Chart Development Guidelines
## 🔍 Before Creating Anything New
**ALWAYS search the workspace for existing implementations first.**
Before writing a new template, config, or resource:
1. Search the workspace for similar existing files
2. If found, migrate/adapt it — don't invent a new structure
3. Only create from scratch if nothing exists
---
## 🏠 Resource Ownership
**Every Kubernetes resource must have exactly one owner. Never duplicate.**
- If a chart creates a resource (e.g. Traefik creates IngressClass), don't create it elsewhere
- Before adding a template for a resource, verify no other chart/subchart already manages it
```
❌ Wrong: subchart A creates IngressClass AND subchart B creates IngressClass
✅ Right: one chart owns IngressClass, others do not touch it
```
---
## 🧹 values.yaml Hygiene
**values.yaml must only contain values that templates actually use.**
Before adding a value:
- Verify at least one template references `.Values.<key>`
- If no template uses it → don't add it
- Empty string defaults (`key: ""`) that only serve as documentation are forbidden — use README instead
**Umbrella chart values.yaml:**
- Contains only subchart defaults that override upstream chart defaults
- All environment-specific values belong in the deployment repo
- Must NOT contain empty placeholders
**Subchart values.yaml:**
- Contains only values the subchart's own templates reference
- Required values (validated with `required`) must NOT have empty defaults — absence should trigger the error
---
## ✅ required vs defaults
Use `required` for values that MUST come from the deployer. Do NOT put empty defaults in values.yaml for these.
```yaml
# Good: fails loudly at install if not provided
name: {{ required "ingress.className is required" .Values.ingress.className }}
# Bad: silently passes empty string, fails later in obscure way
name: {{ .Values.ingress.className | default "" }}
```
---
## 📦 Subchart Dependency Conditions
Use `condition:` in `Chart.yaml` to enable/disable components. Use flat booleans, not nested `.enabled`:
```yaml
# Chart.yaml
dependencies:
- name: keycloak
repository: "file://../components/keycloak"
version: "0.2.0"
condition: components.keycloak
# values (Good)
components:
keycloak: true
traefik: false
# values (Avoid)
keycloak:
enabled: true
```
---
## ⏱️ Hook Ordering
Resources that depend on other resources must use `helm.sh/hook` with `hook-weight` to ensure correct install order.
Established ordering convention:
- `hook-weight: "10"` — infrastructure prerequisites (e.g. ClusterIssuer, IngressClass)
- `hook-weight: "15"` — resources that depend on prerequisites (e.g. Certificate)
```yaml
metadata:
annotations:
"helm.sh/hook": post-install,post-upgrade
"helm.sh/hook-weight": "10"
```
---
## 📄 Configuration Files over values.yaml
**Prefer `files/` + ConfigMap over defining configuration in values.yaml.**
When a component needs configuration (CASC, realm config, ini files, etc.):
1. Put the config file in `files/` directory
2. Create a ConfigMap that reads it with `.AsConfig` + `tpl`
3. Use `{{ .Values.global.xxx }}` expressions inside the config file for dynamic values
```yaml
# templates/configmap.yaml
kind: ConfigMap
apiVersion: v1
metadata:
name: my-component-config
data: {{ (tpl (.Files.Glob "files/*").AsConfig . ) | nindent 2 }}
```
```yaml
# files/config.yaml — contains Helm template expressions
server:
url: https://{{ .Values.global.domain }}
env: {{ .Values.global.environment }}
```
**Why:** Config files are readable, diffable, and version-controlled as real files.
Large configs in values.yaml become unmaintainable and hard to review.
**When `.AsConfig` + `tpl` is required:**
- Config file contains `{{ }}` template expressions → always use `.AsConfig` + `tpl`
- Config file contains special characters (`*`, `{`, `}`) → `.AsConfig` handles escaping safely
```yaml
# Never use plain Files.Get on files with template expressions or special chars:
{{ tpl (.Files.Get "files/config.json") . }} # ❌ breaks on * characters
# Always use AsConfig:
{{ tpl (.Files.Glob "files/config.json").AsConfig . }} # ✅
```
---
## 🔄 Dependency Caching
After editing any subchart template or values, cached charts in `charts/` will be stale:
```bash
rm -rf charts/ Chart.lock
helm dependency update .
```
Always rebuild before `helm template` testing when subchart files have changed.
---
## 🎂 Umbrella Chart Pattern
The standard structure is an umbrella chart that integrates multiple component charts.
```
my-umbrella/
├── Chart.yaml ← declares component charts as dependencies
├── values.yaml ← static baseline: shared across all installations
├── templates/ ← only cross-cutting resources that no subchart owns
└── charts/ ← built by helm dependency update
```
**Two-file values layering:**
| File | Where it lives | Purpose |
|---|---|---|
| `values.yaml` | umbrella chart repo | static baseline, same across all IAC installations |
| `values.<installation>.yaml` | IAC / deployment repo | installation-specific overrides and required parameters |
- `values.yaml` contains defaults valid for every installation; it never contains installation-specific data
- `values.<installation>.yaml` provides what that specific installation requires — credentials, hostnames, sizing, feature flags
- Required values (no sensible default) use `required` in templates and have **no entry** in `values.yaml` — their absence triggers a clear error at install time
---
## ⚡ KISS — Start Minimal, Add Only When Asked
**Do not add knobs, flags, or options that arent needed right now.**
- Build the minimum that meets the stated functional requirement
- Do not anticipate future needs with extra values, conditions, or template logic
- Complexity is easy to add; it is hard to remove once deployed
- AI may **suggest** what might be needed later — but must not implement it without explicit instruction
```yaml
# ❌ Wrong: adding a flag “just in case”
features:
enableFutureThing: false # not needed yet, adding anyway
# ✅ Right: dont add it. If its needed, the developer will ask.
```
**When in doubt, leave it out.** A value that exists but is never used is noise. A template condition that is never toggled is dead code. Raise the question with the developer instead of silently implementing it.
---
## 🧪 Testing Templates
Standard verification command:
```bash
helm template <release-name> . \
--set global.key=value \
... \
2>&1 | grep "^kind:" | sort | uniq -c
```
Verify:
- Expected resource kinds appear
- No `Error:` lines
- `required` validation fires when values are missing

1
.gitignore vendored
View File

@ -1,3 +1,2 @@
.ai-superpower.version
tmp/
.ai