sdg-agents 1.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +15 -0
- package/README.md +161 -0
- package/package.json +88 -0
- package/src/assets/dev-guides/agent-deep-flow.md +139 -0
- package/src/assets/dev-guides/prompt-tracks/00-lite-mode/01-project-scope-and-mvp.md +45 -0
- package/src/assets/dev-guides/prompt-tracks/00-lite-mode/02-stack-and-data-model.md +56 -0
- package/src/assets/dev-guides/prompt-tracks/00-lite-mode/03-external-integrations.md +44 -0
- package/src/assets/dev-guides/prompt-tracks/00-lite-mode/04-launch-checklist.md +26 -0
- package/src/assets/dev-guides/prompt-tracks/01-new-evolution/00-foundation/01-project-vision.md +77 -0
- package/src/assets/dev-guides/prompt-tracks/01-new-evolution/00-foundation/02-problem-definition.md +63 -0
- package/src/assets/dev-guides/prompt-tracks/01-new-evolution/00-foundation/03-success-metrics.md +48 -0
- package/src/assets/dev-guides/prompt-tracks/01-new-evolution/00-foundation/05-engineering-culture-and-silos.md +41 -0
- package/src/assets/dev-guides/prompt-tracks/01-new-evolution/00-foundation/06-foundation-approval.md +57 -0
- package/src/assets/dev-guides/prompt-tracks/01-new-evolution/01-setup/01-tech-stack.md +62 -0
- package/src/assets/dev-guides/prompt-tracks/01-new-evolution/01-setup/02-local-environment.md +50 -0
- package/src/assets/dev-guides/prompt-tracks/01-new-evolution/01-setup/03-version-control-and-tracking.md +65 -0
- package/src/assets/dev-guides/prompt-tracks/01-new-evolution/01-setup/04-hello-world-validation.md +37 -0
- package/src/assets/dev-guides/prompt-tracks/01-new-evolution/01-setup/05-setup-approval.md +61 -0
- package/src/assets/dev-guides/prompt-tracks/01-new-evolution/02-architecture/01-folder-structure.md +49 -0
- package/src/assets/dev-guides/prompt-tracks/01-new-evolution/02-architecture/02-design-patterns.md +59 -0
- package/src/assets/dev-guides/prompt-tracks/01-new-evolution/02-architecture/03-apis-and-communication.md +55 -0
- package/src/assets/dev-guides/prompt-tracks/01-new-evolution/02-architecture/04-security-and-access.md +63 -0
- package/src/assets/dev-guides/prompt-tracks/01-new-evolution/02-architecture/05-security-audit.md +64 -0
- package/src/assets/dev-guides/prompt-tracks/01-new-evolution/02-architecture/06-design-system-ux.md +72 -0
- package/src/assets/dev-guides/prompt-tracks/01-new-evolution/02-architecture/07-ai-strategy.md +72 -0
- package/src/assets/dev-guides/prompt-tracks/01-new-evolution/02-architecture/08-observability.md +65 -0
- package/src/assets/dev-guides/prompt-tracks/01-new-evolution/02-architecture/09-technical-documentation.md +64 -0
- package/src/assets/dev-guides/prompt-tracks/01-new-evolution/02-architecture/10-infrastructure-and-costs.md +40 -0
- package/src/assets/dev-guides/prompt-tracks/01-new-evolution/02-architecture/11-data-privacy-compliance.md +41 -0
- package/src/assets/dev-guides/prompt-tracks/01-new-evolution/02-architecture/12-performance-and-scale.md +46 -0
- package/src/assets/dev-guides/prompt-tracks/01-new-evolution/02-architecture/13-disaster-recovery.md +39 -0
- package/src/assets/dev-guides/prompt-tracks/01-new-evolution/02-architecture/14-architecture-approval.md +81 -0
- package/src/assets/dev-guides/prompt-tracks/01-new-evolution/03-delivery/01-ci-cd-pipeline.md +49 -0
- package/src/assets/dev-guides/prompt-tracks/01-new-evolution/03-delivery/02-quality-standards.md +46 -0
- package/src/assets/dev-guides/prompt-tracks/01-new-evolution/03-delivery/03-delivery-approval.md +58 -0
- package/src/assets/dev-guides/prompt-tracks/01-new-evolution/04-features/00-feature-template.md +30 -0
- package/src/assets/dev-guides/prompt-tracks/01-new-evolution/04-features/01-context-and-scope.md +46 -0
- package/src/assets/dev-guides/prompt-tracks/01-new-evolution/04-features/02-business-rules.md +39 -0
- package/src/assets/dev-guides/prompt-tracks/01-new-evolution/04-features/03-architecture-and-design.md +50 -0
- package/src/assets/dev-guides/prompt-tracks/01-new-evolution/04-features/04-testing-strategy.md +48 -0
- package/src/assets/dev-guides/prompt-tracks/01-new-evolution/04-features/05-implementation-plan.md +45 -0
- package/src/assets/dev-guides/prompt-tracks/01-new-evolution/04-features/06-feature-approval.md +46 -0
- package/src/assets/dev-guides/prompt-tracks/01-new-evolution/05-evolution/01-rfcs-and-changes.md +63 -0
- package/src/assets/dev-guides/prompt-tracks/01-new-evolution/05-evolution/02-incident-postmortems.md +64 -0
- package/src/assets/dev-guides/prompt-tracks/01-new-evolution/05-evolution/03-adr-template.md +66 -0
- package/src/assets/dev-guides/prompt-tracks/02-legacy-modernization/00-foundation/01-legacy-vision.md +73 -0
- package/src/assets/dev-guides/prompt-tracks/02-legacy-modernization/00-foundation/02-foundation-approval.md +53 -0
- package/src/assets/dev-guides/prompt-tracks/02-legacy-modernization/01-analysis/01-tech-debt-inventory.md +55 -0
- package/src/assets/dev-guides/prompt-tracks/02-legacy-modernization/01-analysis/02-security-audit.md +63 -0
- package/src/assets/dev-guides/prompt-tracks/02-legacy-modernization/01-analysis/03-regression-baseline.md +49 -0
- package/src/assets/dev-guides/prompt-tracks/02-legacy-modernization/01-analysis/04-analysis-approval.md +65 -0
- package/src/assets/dev-guides/prompt-tracks/02-legacy-modernization/02-strategy/01-modernization-approach.md +60 -0
- package/src/assets/dev-guides/prompt-tracks/02-legacy-modernization/02-strategy/02-versioning-and-coexistence.md +57 -0
- package/src/assets/dev-guides/prompt-tracks/02-legacy-modernization/02-strategy/03-new-demands-triage.md +41 -0
- package/src/assets/dev-guides/prompt-tracks/02-legacy-modernization/02-strategy/04-strategy-approval.md +56 -0
- package/src/assets/dev-guides/prompt-tracks/02-legacy-modernization/03-refactor/01-cleanup-backlog.md +45 -0
- package/src/assets/dev-guides/prompt-tracks/02-legacy-modernization/03-refactor/02-refactor-approval.md +53 -0
- package/src/assets/dev-guides/prompt-tracks/02-legacy-modernization/04-migration/01-migration-roadmap.md +47 -0
- package/src/assets/dev-guides/prompt-tracks/02-legacy-modernization/04-migration/02-data-sync-strategy.md +56 -0
- package/src/assets/dev-guides/prompt-tracks/02-legacy-modernization/04-migration/03-migration-approval.md +55 -0
- package/src/assets/dev-guides/prompt-tracks/02-legacy-modernization/05-sunset/01-decommission-plan.md +47 -0
- package/src/assets/dev-guides/prompt-tracks/02-legacy-modernization/05-sunset/02-sunset-approval.md +60 -0
- package/src/assets/dev-guides/prompt-tracks/README.md +144 -0
- package/src/assets/dev-guides/software-development-lifecycle-sdlc.md +147 -0
- package/src/assets/dev-guides/spec-driven-dev-guide.md +81 -0
- package/src/assets/dev-guides/ui-prompt-guide.md +181 -0
- package/src/assets/img/sdg-agents-icon-dark.svg +55 -0
- package/src/assets/img/sdg-agents-icon-light.svg +55 -0
- package/src/assets/img/sdg-agents-menu-v1.png +0 -0
- package/src/assets/img/sdg-icon.svg +110 -0
- package/src/assets/instructions/commands/sdg-docs.md +69 -0
- package/src/assets/instructions/commands/sdg-feat.md +45 -0
- package/src/assets/instructions/commands/sdg-fix.md +41 -0
- package/src/assets/instructions/commands/sdg-land.md +128 -0
- package/src/assets/instructions/competencies/backend.md +353 -0
- package/src/assets/instructions/competencies/frontend.md +439 -0
- package/src/assets/instructions/core/agent-roles.md +104 -0
- package/src/assets/instructions/core/api-design.md +65 -0
- package/src/assets/instructions/core/ci-cd.md +144 -0
- package/src/assets/instructions/core/cloud.md +63 -0
- package/src/assets/instructions/core/code-style.md +144 -0
- package/src/assets/instructions/core/containers.md +115 -0
- package/src/assets/instructions/core/data-access.md +119 -0
- package/src/assets/instructions/core/engineering-standards.md +502 -0
- package/src/assets/instructions/core/naming.md +136 -0
- package/src/assets/instructions/core/observability.md +73 -0
- package/src/assets/instructions/core/security-pipeline.md +209 -0
- package/src/assets/instructions/core/security.md +45 -0
- package/src/assets/instructions/core/sql-style.md +138 -0
- package/src/assets/instructions/core/staff-dna.md +72 -0
- package/src/assets/instructions/core/testing-principles.md +212 -0
- package/src/assets/instructions/core/ui/architecture.md +171 -0
- package/src/assets/instructions/core/ui/design-thinking.md +319 -0
- package/src/assets/instructions/core/ui/presets.md +200 -0
- package/src/assets/instructions/core/ui/standards.md +144 -0
- package/src/assets/instructions/core/writing-soul.md +82 -0
- package/src/assets/instructions/flavors/legacy/principles.md +64 -0
- package/src/assets/instructions/flavors/lite/principles.md +39 -0
- package/src/assets/instructions/flavors/mvc/principles.md +124 -0
- package/src/assets/instructions/flavors/vertical-slice/principles.md +178 -0
- package/src/assets/instructions/idioms/csharp/patterns.md +367 -0
- package/src/assets/instructions/idioms/flutter/patterns.md +97 -0
- package/src/assets/instructions/idioms/go/patterns.md +193 -0
- package/src/assets/instructions/idioms/java/patterns.md +233 -0
- package/src/assets/instructions/idioms/javascript/patterns.md +223 -0
- package/src/assets/instructions/idioms/kotlin/patterns.md +94 -0
- package/src/assets/instructions/idioms/python/patterns.md +185 -0
- package/src/assets/instructions/idioms/rust/patterns.md +189 -0
- package/src/assets/instructions/idioms/scripts/patterns.md +81 -0
- package/src/assets/instructions/idioms/sql/patterns.md +109 -0
- package/src/assets/instructions/idioms/swift/patterns.md +97 -0
- package/src/assets/instructions/idioms/typescript/patterns.md +304 -0
- package/src/assets/instructions/idioms/vbnet/patterns.md +96 -0
- package/src/assets/instructions/idioms/vbnet-legacy/patterns.md +104 -0
- package/src/assets/instructions/templates/workflow.md +244 -0
- package/src/assets/instructions/workflows/governance.md +162 -0
- package/src/engine/bin/add-idiom.mjs +186 -0
- package/src/engine/bin/index.mjs +226 -0
- package/src/engine/config/stack-display.mjs +75 -0
- package/src/engine/config/stack-versions.mjs +76 -0
- package/src/engine/lib/cli-parser.mjs +80 -0
- package/src/engine/lib/display-utils.mjs +20 -0
- package/src/engine/lib/fs-utils.mjs +99 -0
- package/src/engine/lib/instruction-assembler.mjs +487 -0
- package/src/engine/lib/manifest-utils.mjs +128 -0
- package/src/engine/lib/prompt-utils.mjs +137 -0
- package/src/engine/lib/result-utils.mjs +14 -0
- package/src/engine/lib/ruleset-injector.mjs +183 -0
- package/src/engine/lib/ui-utils.mjs +216 -0
- package/src/engine/lib/wizard.mjs +472 -0
|
@@ -0,0 +1,502 @@
|
|
|
1
|
+
# Engineering Quality Standards — Resilience, Clean Code & DoD
|
|
2
|
+
|
|
3
|
+
<ruleset name="GlobalEngineeringTactical">
|
|
4
|
+
|
|
5
|
+
> [!NOTE]
|
|
6
|
+
> Advanced topics for Staff-level Agent operations. This ruleset defines the **tactical execution** of Clean Code, Reliability, and Delivery.
|
|
7
|
+
|
|
8
|
+
## Rule: Clean Code Essentials (The Art of Simplicity)
|
|
9
|
+
|
|
10
|
+
<rule name="CleanCodeTactical">
|
|
11
|
+
|
|
12
|
+
> [!IMPORTANT]
|
|
13
|
+
> **Simplicity is the goal. Balance is the discipline.** Write only what the problem requires — neither under-engineered nor over-abstracted. Code must be readable, stack-agnostic, and proportional to its purpose.
|
|
14
|
+
|
|
15
|
+
#### Complexity Management
|
|
16
|
+
|
|
17
|
+
- **Rule of Three:** Abstract ONLY after the pattern repeats 3 times.
|
|
18
|
+
- **Guard Clauses:** Kill the "Arrow Antipattern". Early returns and guard clauses are mandatory.
|
|
19
|
+
- **Functional Pipeline (Array Composition over String Mutation):** When building composite output, accumulate parts as typed arrays and compose with an explicit filter + join. Never mutate a string variable across conditional branches. `const parts = [a, b, c].filter(section => section !== null); const result = parts.join('\n');` is always clearer than `let s = ''; if (a) s += a; if (b) s += b;`. Avoid `.filter(Boolean)` — it silently removes `0`, `''`, and `false`, which may be legitimate values. Filter only what you intend to exclude.
|
|
20
|
+
- **Prefer `for...of` over `reduce` for accumulation; `map` is valid for 1-to-1 transforms:** For loops that accumulate a result (sum, total, count), `for...of` with named intermediate variables (Explaining Variables) is clearer, debuggable, more performant, and portable across languages. `map` is valid for **pure 1-to-1 transformation** of collections where no complex logic or multi-step filtering is involved. `reduce` should be avoided as its accumulator pattern obscures intent and hinders step-by-step debugging. While the `for...of` loop is more verbose, it is the default choice for Staff Engineers when accumulation or complex logic is required. When in doubt, prefer the form that reads closest to plain English:
|
|
21
|
+
- ❌ `const total = items.reduce((acc, item) => acc + item.qty * item.price, 0);`
|
|
22
|
+
- ✅ `let total = 0; for (const item of items) { const lineAmount = item.qty * item.price; total += lineAmount; }`
|
|
23
|
+
- ✅ `const labels = items.map(i => i.name); // valid 1-to-1 transform`
|
|
24
|
+
- **Conditional Array Expansion:** When a block of items is conditionally included in a pipeline, produce an empty array instead of nothing: `const refs = hasBackend ? [...backendItems] : [];`. This keeps the spread composition uniform — no special-casing required at the join site.
|
|
25
|
+
- **Vertical Density (Para-Logical Grouping):** Use exactly one blank line to separate logical blocks (e.g., State/Hooks -> Styles/Classes -> Rendered JSX parts). This reduces cognitive load by creating visual "paragraphes" of intent.
|
|
26
|
+
- **Cohesion within Groups:** No blank lines are permitted _within_ a block. All variables that belong together stay together.
|
|
27
|
+
- **UI & Styling (Tailwind + cn):**
|
|
28
|
+
- **Named `cn` constant:** MANDATORY for elements with **10 or more** Tailwind classes. Do not use raw `className` in JSX for complex styling.
|
|
29
|
+
- **Line-Breaking:** Break lines within `cn` arguments every **10 classes** (or per responsibility group: Layout, Typography, Visuals, States) to eliminate horizontal scrolling.
|
|
30
|
+
- **Immediate Context:** Comments must be placed on the line immediately preceding the code they describe (zero blank lines between comment and code).
|
|
31
|
+
- **No Alignment Padding:** Never add extra spaces to align `=` or `:` across adjacent declarations. `const x = foo` is correct; `const x = foo` to match a longer neighbor is not. It creates noisy diffs and breaks the moment any name changes length. Prettier enforces this automatically — never fight it.
|
|
32
|
+
- **Inconsistent Parameter Contract:** Choose one strategy per flow and stick to it. **Rich Object Flow** — pass the full domain object (`buildUser(user)`, `validateUser(user)`, `formatUser(user)`); destructure inside if needed. **Primitive Flow** — pass only the scalar value (`fetchUserById(userId)`, `deleteUserById(userId)`); prefer this at boundaries (DB, API, cache). Rule of thumb: domain → object, boundary (I/O) → primitive. Naming convention enforces the contract: functions receiving objects use the entity name (`user`), functions receiving IDs append `ById` (`fetchUserById`). Mixing both strategies across sibling functions in the same flow is the anti-pattern — it forces callers to know which extraction to perform at each call site.
|
|
33
|
+
- **Separation of Concerns — Data First, Presentation Later:** Never couple business logic with formatting inside the same expression. Anti-pattern name: **Logic-in-Formatting**. The pattern: `const data = computeX(input); const formatted = formatX(data);` — compute step produces pure data structures, format step produces strings. A ternary that returns a template literal (`order.discount ? \`DISCOUNT ${order.discount.code}\` : ''`) is the canonical violation — rule and presentation are fused and neither can be tested independently.
|
|
34
|
+
- **Shallow Boundaries (No Deep Navigation Hell):** Maximum 3 levels of property traversal per expression. `order.customer.address.city` (3 levels) is the limit. `order.customer.address.location.city` (4+ levels) is a violation — extract a named slice first: `const address = order.customer.address; const city = address.city;`. Destructure **inside** the function body, never in the parameter signature (exception: very small, stable utility functions). This prevents coupling your logic to the deep shape of objects you don't own.
|
|
35
|
+
- **Encapsulate Until It Hurts, Extract When Proven:** Functions start nested (Lexical Scoping). Promote to module level or a separate file only when at least one is true: (1) used in 2+ unrelated call sites, (2) exceeds ~15 lines, (3) carries a strong intent name (`normalize`, `validate`, `mapToDto`), or (4) needs isolated testing. Maximum 2 nesting levels inside a file — a third level signals the parent has grown beyond its responsibility and the inner function should move to a sibling module (`order.compute.js`).
|
|
36
|
+
- **No God Utility Modules:** Never create `helpers.js`, `utils.js`, or `common.js`. These names carry no responsibility and evolve into dumping grounds. When extraction is needed, name files by domain and operation: `order.compute.js`, `order.format.js`, `order.normalize.js`. Shared utilities are only valid when (1) truly domain-agnostic, (2) reused in ≥2 real contexts, and (3) named by intent — `currency.normalize.js`, `string.format.js`. Test: _"Does this function make sense without knowing what an `order` is?"_ Yes → it can be shared. No → it stays in the domain module.
|
|
37
|
+
- **Explaining Returns at Boundaries:** Never return the result of a complex computation, pattern call, or framework send directly. Always assign to a named `const` first — the variable name provides declarative meaning to the output. This rule has no framework exception:
|
|
38
|
+
- ❌ `return ok(orderResponse);` → ✅ `const creationResult = ok(orderResponse); return creationResult;`
|
|
39
|
+
- ❌ `return fail('ORDER_EMPTY');` → ✅ `const validationFailure = fail('ORDER_EMPTY'); return validationFailure;`
|
|
40
|
+
- ❌ `res.status(201).json(envelope);` → ✅ `const httpResponse = response.status(201).json(envelope); return httpResponse;`
|
|
41
|
+
- **Narrative Logic:** Mandatory for all languages — see [Code Style Guide](.ai/instructions/core/code-style.md) for Narrative Cascade principles and examples.
|
|
42
|
+
|
|
43
|
+
#### Interface Design (Interfaces at Boundaries)
|
|
44
|
+
|
|
45
|
+
> **Rule:** Start concrete. Extract an interface when the second implementation appears — not before.
|
|
46
|
+
|
|
47
|
+
Interfaces are **variation points**, not a default pattern. Ask before creating one: _"Will I need to swap this without breaking the caller?"_ No → stay concrete. Yes → interface.
|
|
48
|
+
|
|
49
|
+
**Use interface when you have:**
|
|
50
|
+
|
|
51
|
+
- **Strategy** — multiple behaviors for the same contract (`DiscountPolicy`: `NoDiscount`, `SeasonalDiscount`)
|
|
52
|
+
- **Boundary** — infra, DB, external API (`OrderRepository`: `SqlOrderRepository`, `MemoryOrderRepository`)
|
|
53
|
+
- **Output variation** — multiple formats or targets (`OrderFormatter`: `TextFormatter`, `HtmlFormatter`)
|
|
54
|
+
- **Testing** — a `FakeOrderRepository` that replaces the real one in tests
|
|
55
|
+
|
|
56
|
+
```ts
|
|
57
|
+
// Canonical examples — each has multiple real implementations
|
|
58
|
+
interface PaymentPolicy {
|
|
59
|
+
calculate(order: Order): PaymentResult;
|
|
60
|
+
} // Strategy: Pix, Installment, Subscription
|
|
61
|
+
interface OrderFormatter {
|
|
62
|
+
format(data: SummaryData): string;
|
|
63
|
+
} // Variation: Text, HTML, MDX
|
|
64
|
+
interface OrderExporter {
|
|
65
|
+
export(data: SummaryData): FileResult;
|
|
66
|
+
} // Boundary: PDF, XLS, JSON
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
**Never use interface for:**
|
|
70
|
+
|
|
71
|
+
- Pure functions (`computeOrderSummary`, `formatExtras`) — they have no variation point
|
|
72
|
+
- Internal helpers scoped to a single parent — Lexical Scoping already encapsulates them
|
|
73
|
+
- A class with a single implementation that will never be swapped
|
|
74
|
+
|
|
75
|
+
**Natural evolution — never jump to step 3 directly:**
|
|
76
|
+
|
|
77
|
+
```ts
|
|
78
|
+
// Step 1: start concrete
|
|
79
|
+
function formatOrderSummary(data: SummaryData): string { ... }
|
|
80
|
+
|
|
81
|
+
// Step 2: a second format appears — now extract the interface
|
|
82
|
+
interface OrderFormatter {
|
|
83
|
+
format(data: SummaryData): string;
|
|
84
|
+
}
|
|
85
|
+
class TextOrderFormatter implements OrderFormatter { ... }
|
|
86
|
+
class HtmlOrderFormatter implements OrderFormatter { ... }
|
|
87
|
+
|
|
88
|
+
// ❌ premature: interface with a single DefaultOrderFormatter is pure ceremony
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
**DIP in practice — inject the interface, keep the caller ignorant:**
|
|
92
|
+
|
|
93
|
+
```ts
|
|
94
|
+
function buildOrderSummary(order: Order, formatter: OrderFormatter): string {
|
|
95
|
+
const data = computeOrderSummary(order); // Core: pure, no interface needed
|
|
96
|
+
const output = formatter.format(data); // Presentation: pluggable via interface
|
|
97
|
+
return output;
|
|
98
|
+
}
|
|
99
|
+
// Caller swaps formatter without touching buildOrderSummary — that is the point.
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
> **DIP does not require interfaces — it requires low coupling.** Separating `computeOrderSummary` from `formatOrderSummary` already satisfies DIP for 70% of cases. An interface only enters when the _format step itself_ needs to vary across callers.
|
|
103
|
+
|
|
104
|
+
**Three-layer heuristic:**
|
|
105
|
+
|
|
106
|
+
```
|
|
107
|
+
Core (compute) → never needs interface — pure, deterministic, no dependencies
|
|
108
|
+
Presentation (format) → candidate when a second format appears
|
|
109
|
+
Boundary (repo, exporter, payment) → interface almost always required
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
#### Linter Configuration (when setting up ESLint for a project)
|
|
113
|
+
|
|
114
|
+
Always include these rules — they enforce the structural standards above at tooling level:
|
|
115
|
+
|
|
116
|
+
```js
|
|
117
|
+
// eslint.config.mjs — minimum required rules for SDG projects
|
|
118
|
+
rules: {
|
|
119
|
+
'no-multi-spaces': 'error', // No Alignment Padding — collapses const x = foo
|
|
120
|
+
'no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
|
|
121
|
+
'prettier/prettier': 'error', // Delegates whitespace, quotes, trailing commas to Prettier
|
|
122
|
+
}
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
Prettier (`eslint-plugin-prettier/recommended`) already handles line length, indentation, and quote style. `no-multi-spaces` closes the gap Prettier does not cover — multiple spaces used for visual column alignment inside declarations.
|
|
126
|
+
</rule>
|
|
127
|
+
|
|
128
|
+
## Rule: Narrative Cascade — SLA Enforcement
|
|
129
|
+
|
|
130
|
+
<rule name="NarrativeSLA">
|
|
131
|
+
|
|
132
|
+
> [!IMPORTANT]
|
|
133
|
+
> **Single Level of Abstraction is the hardest rule to follow and the most valuable.** For visual patterns and examples, see [Code Style Guide](.ai/instructions/core/code-style.md).
|
|
134
|
+
|
|
135
|
+
- **SLA (Single Level of Abstraction):** Each function body either **orchestrates** (calls other named functions) or **implements** (computes, transforms, formats) — never both in the same body. A function that loops AND formats AND queries a DB is three functions pretending to be one.
|
|
136
|
+
- **Top-Down File Structure:** The highest-level function is defined first. Readers should never scroll down to understand what a file does.
|
|
137
|
+
- **No Orphan Logic:** Any expression that doesn't fit the current abstraction level must be extracted to a named function — either lexically (nested inside its parent) or as a sibling if reused.
|
|
138
|
+
- **Comments explain "why", never "what".** If naming is right, comments about what the code does should disappear. A comment that restates the code is a naming failure.
|
|
139
|
+
|
|
140
|
+
````carousel
|
|
141
|
+
```js
|
|
142
|
+
// ❌ THE ANTI-PATTERN — "The Sprawling Procedural"
|
|
143
|
+
// Violations: Logic-in-Formatting, Mixed Altitudes (SLA), Deep Navigation, Mutation Accumulation
|
|
144
|
+
function buildOrderSummary(order) {
|
|
145
|
+
let result = `ORDER #${order.id} — ${order.customer.name}\n\n`; // mutation + formatting fused
|
|
146
|
+
|
|
147
|
+
result += order.items.map((i) => ` ${i.name} ×${i.qty} $${i.price}`).join('\n'); // SLA: map inside orchestrator
|
|
148
|
+
|
|
149
|
+
if (order.discount) {
|
|
150
|
+
result += `\n\nDISCOUNT ${order.discount.code} –$${order.discount.amount}`; // Logic-in-Formatting
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if (order.shipping.method !== 'pickup') {
|
|
154
|
+
result += `\n\nSHIP TO: ${order.shipping.address}`; // deep navigation + mutation
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return result; // anonymous mutation returned
|
|
158
|
+
}
|
|
159
|
+
```
|
|
160
|
+
<!-- slide -->
|
|
161
|
+
```js
|
|
162
|
+
// ✅ THE STAFF PATTERN — "The Narrative Cascade"
|
|
163
|
+
// Stepdown Rule: Orchestrator sits at top. Lexical Scoping: helpers nested inside their parent.
|
|
164
|
+
// Shallow Boundaries: max 2 levels. Data-first, Presentation-later (SLA).
|
|
165
|
+
function buildOrderSummary(order) {
|
|
166
|
+
const header = buildHeader(order);
|
|
167
|
+
const lineItems = buildLineItems(order);
|
|
168
|
+
const extras = buildExtrasSection(order);
|
|
169
|
+
|
|
170
|
+
const sections = [header, lineItems, extras].filter((section) => section !== null);
|
|
171
|
+
const summary = sections.join('\n\n');
|
|
172
|
+
return summary;
|
|
173
|
+
|
|
174
|
+
function buildHeader(order) {
|
|
175
|
+
const { id } = order;
|
|
176
|
+
const { name } = order.customer; // Shallow Boundary: level 2
|
|
177
|
+
|
|
178
|
+
const headerLine = `ORDER #${id} — ${name}`;
|
|
179
|
+
return headerLine;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function buildLineItems(order) {
|
|
183
|
+
const lines = order.items.map((item) => ` ${item.name} ×${item.qty} $${item.price}`);
|
|
184
|
+
const lineItemsBlock = lines.join('\n');
|
|
185
|
+
return lineItemsBlock;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function buildExtrasSection(order) {
|
|
189
|
+
const extraData = computeExtras(order); // Decision Layer — pure data
|
|
190
|
+
if (extraData.length === 0) return null;
|
|
191
|
+
|
|
192
|
+
const extrasBlock = formatExtras(extraData); // Presentation Layer — pure formatting
|
|
193
|
+
return extrasBlock;
|
|
194
|
+
|
|
195
|
+
function computeExtras(order) {
|
|
196
|
+
const { discount, shipping } = order; // Shallow Boundaries: destructure inside body
|
|
197
|
+
const discountItem = discount ? [{ code: discount.code, amount: discount.amount }] : [];
|
|
198
|
+
const shippingItem = shipping.method !== 'pickup' ? [{ address: shipping.address }] : [];
|
|
199
|
+
const extraData = [...discountItem, ...shippingItem];
|
|
200
|
+
return extraData;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function formatExtras(items) {
|
|
204
|
+
const lines = items.map((item) =>
|
|
205
|
+
item.code ? `DISCOUNT ${item.code} –$${item.amount}` : `SHIP TO: ${item.address}`
|
|
206
|
+
);
|
|
207
|
+
const formatted = lines.join('\n');
|
|
208
|
+
return formatted;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
```
|
|
213
|
+
````
|
|
214
|
+
|
|
215
|
+
</rule>
|
|
216
|
+
|
|
217
|
+
## Rule: Reliability Protocols
|
|
218
|
+
|
|
219
|
+
<rule name="OperationalResilience">
|
|
220
|
+
|
|
221
|
+
> [!IMPORTANT]
|
|
222
|
+
> **Defensive dominance.** Software must withstand failure and repetition. Tactical implementation of **Law 2 (Resilience)**.
|
|
223
|
+
|
|
224
|
+
#### Instructions
|
|
225
|
+
|
|
226
|
+
- **Idempotency:** Operations involving money, state changes, or side-effects must use **Idempotency Keys (UUIDs)**.
|
|
227
|
+
- **Graceful Degradation:** Use **Error Boundaries** and defensive type checking (`data?.user?.name`) to prevent UI collapses.
|
|
228
|
+
- **Failure Narrative:** Prefer typed error objects (or Result objects when they clarify the happy/failure path) over magic strings or raw exceptions. Do not force the pattern where idiomatic error handling is already clear.
|
|
229
|
+
</rule>
|
|
230
|
+
|
|
231
|
+
## Rule: Result Pattern & HTTP Envelope
|
|
232
|
+
|
|
233
|
+
<rule name="ResultPatternAndEnvelope">
|
|
234
|
+
|
|
235
|
+
> [!IMPORTANT]
|
|
236
|
+
> **Result organizes the flow. Envelope organizes the response. Adapter connects the two.**
|
|
237
|
+
> Never mix domain Result with HTTP concerns.
|
|
238
|
+
|
|
239
|
+
#### Result\<T\> — Domain Flow
|
|
240
|
+
|
|
241
|
+
```ts
|
|
242
|
+
type Result<T> = {
|
|
243
|
+
ok: boolean;
|
|
244
|
+
data?: T;
|
|
245
|
+
error?: string;
|
|
246
|
+
readonly isSuccess: boolean;
|
|
247
|
+
readonly isFailure: boolean;
|
|
248
|
+
};
|
|
249
|
+
|
|
250
|
+
function ok<T>(data: T): Result<T> {
|
|
251
|
+
const result = {
|
|
252
|
+
ok: true,
|
|
253
|
+
data,
|
|
254
|
+
get isSuccess() {
|
|
255
|
+
return this.ok;
|
|
256
|
+
},
|
|
257
|
+
get isFailure() {
|
|
258
|
+
return !this.ok;
|
|
259
|
+
},
|
|
260
|
+
};
|
|
261
|
+
return result;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
function fail<T = never>(error: string): Result<T> {
|
|
265
|
+
const result = {
|
|
266
|
+
ok: false,
|
|
267
|
+
error,
|
|
268
|
+
get isSuccess() {
|
|
269
|
+
return this.ok;
|
|
270
|
+
},
|
|
271
|
+
get isFailure() {
|
|
272
|
+
return !this.ok;
|
|
273
|
+
},
|
|
274
|
+
};
|
|
275
|
+
return result;
|
|
276
|
+
}
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
**Rules:**
|
|
280
|
+
|
|
281
|
+
- `ok` is the single source of truth — `isSuccess`/`isFailure` are derived getters, never stored
|
|
282
|
+
- Success → has `data`. Failure → has `error`. Never both
|
|
283
|
+
- Always return via named variable (Explaining Returns): `const result = ok(data); return result;`
|
|
284
|
+
- Result stays in the domain — no HTTP status, no meta, no envelope inside it
|
|
285
|
+
|
|
286
|
+
#### HTTP Envelope — API Contract
|
|
287
|
+
|
|
288
|
+
`success` is the discriminator — consumers branch on it, never on HTTP status alone.
|
|
289
|
+
|
|
290
|
+
```json
|
|
291
|
+
// Success — single resource
|
|
292
|
+
{ "success": true, "error": null, "data": { } }
|
|
293
|
+
|
|
294
|
+
// Success — with BFF action directive
|
|
295
|
+
{ "success": true, "error": null, "meta": { "action": "SHOW_TOAST" }, "data": { } }
|
|
296
|
+
|
|
297
|
+
// Success — collection with pagination
|
|
298
|
+
{ "success": true, "error": null, "meta": { "nextCursor": "abc", "hasMore": true }, "data": [] }
|
|
299
|
+
|
|
300
|
+
// Failure
|
|
301
|
+
{ "success": false, "error": { "code": "ORDER_EMPTY", "message": "Pedido sem itens" }, "data": null }
|
|
302
|
+
|
|
303
|
+
// Failure with BFF action directive
|
|
304
|
+
{ "success": false, "error": { "code": "UNAUTHORIZED", "message": "Session expired" }, "meta": { "action": "REDIRECT_TO_LOGIN" }, "data": null }
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
**Rules:**
|
|
308
|
+
|
|
309
|
+
- `success` always present — the single discriminator
|
|
310
|
+
- `error` always present — `null` on success, `{ code, message }` on failure
|
|
311
|
+
- `meta` optional — carries `action`, pagination, and observability context
|
|
312
|
+
- `meta.action` optional — BFF directive when server must guide the client (`REDIRECT_TO_LOGIN`, `SHOW_TOAST`, `SILENT_RETRY`, `REFRESH_TOKEN`)
|
|
313
|
+
- `data` always last — it is the payload; envelope headers are read first
|
|
314
|
+
|
|
315
|
+
#### Meta — Context, Not Content
|
|
316
|
+
|
|
317
|
+
```ts
|
|
318
|
+
type Meta = {
|
|
319
|
+
action?: string; // BFF directive — guides client behavior (REDIRECT_TO_LOGIN, SHOW_TOAST, SILENT_RETRY, REFRESH_TOKEN)
|
|
320
|
+
traceId?: string; // distributed trace ID — correlates request across services
|
|
321
|
+
requestId?: string; // per-request unique ID — correlates logs within this service
|
|
322
|
+
timestamp?: string; // ISO 8601 — when the response was generated
|
|
323
|
+
duration?: number; // response time in ms
|
|
324
|
+
pagination?:
|
|
325
|
+
| { nextCursor: string | null; hasMore: boolean }
|
|
326
|
+
| { page: number; pageSize: number; total: number };
|
|
327
|
+
};
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
**Rules:**
|
|
331
|
+
|
|
332
|
+
- `meta` is flat — `pagination` is the only valid nested object
|
|
333
|
+
- Allowed: `action`, `traceId`, `requestId`, `timestamp`, `duration`, `pagination`
|
|
334
|
+
- Forbidden: business data, domain payloads, debug dumps
|
|
335
|
+
- Test: _"Does this help debug, navigate, or guide the client without knowing the domain?"_ Yes → meta. No → `data` or drop it
|
|
336
|
+
|
|
337
|
+
```json
|
|
338
|
+
// ❌ meta as data dump
|
|
339
|
+
{ "meta": { "user": { ... }, "order": { ... }, "debug": "..." } }
|
|
340
|
+
|
|
341
|
+
// ✅ meta as context
|
|
342
|
+
{ "meta": { "traceId": "abc-123", "requestId": "req-456", "duration": 42 } }
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
#### Error Codes — Centralized Constants
|
|
346
|
+
|
|
347
|
+
```ts
|
|
348
|
+
const Errors = {
|
|
349
|
+
ORDER_EMPTY: 'ORDER_EMPTY',
|
|
350
|
+
INVALID_EMAIL: 'INVALID_EMAIL',
|
|
351
|
+
} as const;
|
|
352
|
+
|
|
353
|
+
// Usage
|
|
354
|
+
const result = fail(Errors.ORDER_EMPTY);
|
|
355
|
+
return result;
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
Eliminates typos, standardizes backend + frontend, and makes every error code a valid translation/log key. Never use raw strings inside `fail()`.
|
|
359
|
+
|
|
360
|
+
#### Boundary Rule — Result Lives Until the Edge
|
|
361
|
+
|
|
362
|
+
```
|
|
363
|
+
Domain → Result<T> ✔ propagates failures up the call stack
|
|
364
|
+
Controller → HTTP Response ✔ Adapter converts here — Result stops
|
|
365
|
+
UI / State → never Result ✔ translate to UI state before crossing
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
Result is a domain contract. It must not cross into HTTP responses, DTOs, or frontend state. The Adapter is the only crossing point.
|
|
369
|
+
|
|
370
|
+
#### Command vs Query (light CQRS)
|
|
371
|
+
|
|
372
|
+
```ts
|
|
373
|
+
createOrder(): Result<void> // Command — mutates state, returns no data
|
|
374
|
+
getOrder(): Result<Order> // Query — reads state, returns data, no side-effects
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
Commands and Queries never mix. A function that both mutates and returns domain data is a violation.
|
|
378
|
+
|
|
379
|
+
#### DTO — Never Leak Internal Structure
|
|
380
|
+
|
|
381
|
+
```ts
|
|
382
|
+
// Domain object stays internal
|
|
383
|
+
const dto = { id: order.id, total: order.total }; // only expose what the API contract needs
|
|
384
|
+
```
|
|
385
|
+
|
|
386
|
+
The Adapter produces the DTO. The domain object never goes out raw.
|
|
387
|
+
|
|
388
|
+
#### Adapter — the Bridge
|
|
389
|
+
|
|
390
|
+
The Adapter translates `Result<T>` → HTTP status + Envelope and domain object → DTO. It is the only layer allowed to know both. Domain code never imports HTTP; HTTP layer never imports domain logic.
|
|
391
|
+
|
|
392
|
+
#### Anti-patterns
|
|
393
|
+
|
|
394
|
+
| ❌ Result | ❌ Envelope | ❌ Architecture |
|
|
395
|
+
| -------------------------------- | ------------------------------------------- | --------------------------------------------- |
|
|
396
|
+
| `boolean` instead of `Result<T>` | `action` at envelope root instead of `meta` | Returning `Result` directly from controller |
|
|
397
|
+
| `ok` + `error` both set | `meta` as data dump | Exception as normal control flow |
|
|
398
|
+
| Storing `isFailure` as a field | Empty fields sent (`"data": undefined`) | God Result with HTTP concerns inside |
|
|
399
|
+
| Raw string error without code | `errors[]` array instead of `error` object | Branching on HTTP status instead of `success` |
|
|
400
|
+
| **Pure Boolean** (success/error) | **Redundant Action** (CRUD / NONE) | **Coupled Cache** to core/domain |
|
|
401
|
+
| Generic names (`handle`, `do`) | Missing envelope (`{ success: true }`) | **Structure Duplication** across layers |
|
|
402
|
+
|
|
403
|
+
> **Consistency over perfection.** One simple pattern followed everywhere beats five perfect patterns used inconsistently. Add `meta`, `Errors`, builders, and adapters only when the pain is real — not preemptively. The moment you notice you are building a framework instead of a feature, stop and apply Rule of Three.
|
|
404
|
+
|
|
405
|
+
</rule>
|
|
406
|
+
|
|
407
|
+
## Rule: Enforcement Checklist (Pre-Finish Gate)
|
|
408
|
+
|
|
409
|
+
<rule name="EnforcementChecklist">
|
|
410
|
+
|
|
411
|
+
> [!IMPORTANT]
|
|
412
|
+
> Before finishing any file, verify every item. Binary pass/fail — no partial credit.
|
|
413
|
+
|
|
414
|
+
- [ ] Entry point is the **first function** after imports/constants (Stepdown Rule)
|
|
415
|
+
- [ ] **SLA applied**: every function either orchestrates or implements — never both
|
|
416
|
+
- [ ] **Lexical Scoping**: every helper used by only one function is declared _inside_ that function
|
|
417
|
+
- [ ] **Explaining Returns**: no bare `return ok(...)`, `return fail(...)`, or `response.status(...).json(...)` — named `const` above every return
|
|
418
|
+
- [ ] **No framework abbreviations**: `req` → `request`, `res` → `response`; No Abbreviations rule has no framework exception
|
|
419
|
+
- [ ] **Vertical Density applied**: logical parts grouped by "Paragraphs of Intent" (single blank line between groups, none within)
|
|
420
|
+
- [ ] **Revealing Module Pattern**: public contract defined via named object + named export at file footer
|
|
421
|
+
- [ ] **Shallow Boundaries**: no property chain deeper than 3 levels — slice into a named `const` first
|
|
422
|
+
- [ ] Destructuring happens inside the function body, not in the parameter signature
|
|
423
|
+
- [ ] No `helpers.js`, `utils.js`, or `common.js` — files named by domain + operation
|
|
424
|
+
- [ ] Every name reveals its role without needing a comment
|
|
425
|
+
- [ ] No explanatory comments — only `// why:` for non-obvious constraints or deliberate trade-offs
|
|
426
|
+
- [ ] A new reader understands the module's purpose without scrolling past the first screen
|
|
427
|
+
- [ ] **Boolean names carry a prefix**: no bare `loading`, `error`, `active` — use `isLoading`, `hasError`, `isActive`
|
|
428
|
+
- [ ] No raw entities in responses — OutputFilter (DTO) applied at every API boundary
|
|
429
|
+
- [ ] **Config Contract defined**: all environment variables listed in the SPEC with abstract naming
|
|
430
|
+
- [ ] **No config templates**: confirmed no `.env.example` or similar committed to the repository
|
|
431
|
+
|
|
432
|
+
</rule>
|
|
433
|
+
|
|
434
|
+
## Rule: Abstract Configuration Management (The Hardened Setup)
|
|
435
|
+
|
|
436
|
+
<rule name="AbstractConfig">
|
|
437
|
+
|
|
438
|
+
> [!IMPORTANT]
|
|
439
|
+
> **Configuration is a contract, not a template.** We prohibit `.env.example` to reduce information disclosure.
|
|
440
|
+
|
|
441
|
+
1. **Phase (SPEC)**: Mandatory definition of the "Configuration Contract". List keys and their purpose (e.g., `PAYMENT_SECRET`).
|
|
442
|
+
2. **Abstract Naming**: Use keys that hide the specific vendor or internal infrastructure details.
|
|
443
|
+
3. **Runtime Validation**: Regardless of the language, implement a validation step at boot (Fail-Fast) that checks for required variables. Canonical pattern:
|
|
444
|
+
|
|
445
|
+
```ts
|
|
446
|
+
// config.ts — called once at application boot
|
|
447
|
+
const REQUIRED_VARS = ['PAYMENT_SECRET', 'AUTH_PROVIDER_SECRET', 'DB_URL'] as const;
|
|
448
|
+
|
|
449
|
+
function validateConfig() {
|
|
450
|
+
const missing = REQUIRED_VARS.filter((key) => !process.env[key]);
|
|
451
|
+
if (missing.length > 0) {
|
|
452
|
+
const missingList = missing.join(', ');
|
|
453
|
+
throw new Error(`Missing required environment variables: ${missingList}`);
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
validateConfig();
|
|
458
|
+
```
|
|
459
|
+
|
|
460
|
+
```js
|
|
461
|
+
// config.mjs — JavaScript equivalent (ESM)
|
|
462
|
+
const REQUIRED_VARS = ['PAYMENT_SECRET', 'AUTH_PROVIDER_SECRET', 'DB_URL'];
|
|
463
|
+
|
|
464
|
+
function validateConfig() {
|
|
465
|
+
const missing = REQUIRED_VARS.filter((key) => !process.env[key]);
|
|
466
|
+
if (missing.length > 0) {
|
|
467
|
+
const missingList = missing.join(', ');
|
|
468
|
+
throw new Error(`Missing required environment variables: ${missingList}`);
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
validateConfig();
|
|
473
|
+
```
|
|
474
|
+
|
|
475
|
+
4. **Setup Guide**: Instead of committed templates, provide an initialization command or a "Setup" section in the SPEC that guides the developer on which values to obtain.
|
|
476
|
+
|
|
477
|
+
</rule>
|
|
478
|
+
|
|
479
|
+
## Rule: Staff-grade Version Control
|
|
480
|
+
|
|
481
|
+
<rule name="StaffGradeVCS">
|
|
482
|
+
|
|
483
|
+
- **Conventional Commits:** Mandatory `type(scope): message`. Types: `feat`, `fix`, `refactor`, `docs`, `test`, `chore`, `perf`.
|
|
484
|
+
- **Narrative Subject:** Imperative, lowercase, describes WHAT the commit achieves — not HOW. `feat(order): add idempotency key to checkout` ✅ — `fix: changed the if condition` ❌.
|
|
485
|
+
- **Atomic Commits:** One logical change per commit. A commit that adds a feature AND fixes an unrelated bug is two commits.
|
|
486
|
+
- **No WIP commits on shared branches:** Squash or rebase before merging. `fix: typo` stacked on `fix: another typo` must be squashed.
|
|
487
|
+
- **Branch naming:** `feat/`, `fix/`, `chore/` prefix + kebab-case description. e.g. `feat/order-idempotency`, `fix/cart-total-rounding`.
|
|
488
|
+
|
|
489
|
+
</rule>
|
|
490
|
+
|
|
491
|
+
## Rule: Definition of Done (DoD)
|
|
492
|
+
|
|
493
|
+
<rule name="DefinitionOfDone">
|
|
494
|
+
- Code follows project conventions.
|
|
495
|
+
- Tests cover main flows and error scenarios.
|
|
496
|
+
- Logs are meaningful and structured.
|
|
497
|
+
- No TODOs in critical paths.
|
|
498
|
+
- No raw entities in responses — OutputFilter (Response DTO) applied at every endpoint.
|
|
499
|
+
- [Security Strategy](.ai/instructions/core/security.md) rules passed.
|
|
500
|
+
</rule>
|
|
501
|
+
|
|
502
|
+
</ruleset>
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
# Naming Discipline — Domain Language & Expressivity
|
|
2
|
+
|
|
3
|
+
<ruleset name="NamingDiscipline">
|
|
4
|
+
|
|
5
|
+
> [!IMPORTANT]
|
|
6
|
+
> Names are the primary documentation of code. A name that requires a comment to be understood is a naming failure.
|
|
7
|
+
> This ruleset applies universally — every language, every layer, every file.
|
|
8
|
+
|
|
9
|
+
## Rule: Domain First
|
|
10
|
+
|
|
11
|
+
<rule name="DomainFirst">
|
|
12
|
+
|
|
13
|
+
Name by business intent, not technical implementation or storage detail.
|
|
14
|
+
|
|
15
|
+
- `processOrder` ✅ — `callStripe` ❌ (implementation detail)
|
|
16
|
+
- `fetchUserProfile` ✅ — `getUserFromDB` ❌ (storage detail)
|
|
17
|
+
- `isNonInteractiveMode` ✅ — `hasFlags` ❌ (too abstract)
|
|
18
|
+
- `flagsThatConsumeNextArg` ✅ — `flagsWithValues` ❌ (not expressive)
|
|
19
|
+
|
|
20
|
+
A reader must understand a variable's domain role without tracing its origin. If the name only makes sense after reading the surrounding code, rename it.
|
|
21
|
+
|
|
22
|
+
</rule>
|
|
23
|
+
|
|
24
|
+
## Rule: SDG Taboos (Banned Generic Names)
|
|
25
|
+
|
|
26
|
+
<rule name="SDGTaboos">
|
|
27
|
+
|
|
28
|
+
> [!IMPORTANT]
|
|
29
|
+
> These names are banned because they carry no information. They force the reader to look elsewhere to understand the code.
|
|
30
|
+
|
|
31
|
+
#### Banned Verbs
|
|
32
|
+
|
|
33
|
+
- **NO `handle...`** as a verb prefix: ambiguous. Replace with `process`, `validate`, `persist`, `navigate`, `submit`, `dispatch`.
|
|
34
|
+
- **NO `do`, `run`, `execute`, `perform`** without a domain subject: `doStuff` ❌ — `executeOrderCheckout` ✅.
|
|
35
|
+
- **NO `get` for computations**: `getTotal` ❌ when it computes — use `computeTotal` or `calculateTotal` ✅.
|
|
36
|
+
|
|
37
|
+
#### Banned Nouns
|
|
38
|
+
|
|
39
|
+
- **NO `data`, `info`, `obj`, `item`, `thing`**: meaningless. Use the domain name — `order`, `userPayload`, `productCatalog`.
|
|
40
|
+
- **NO Single-Letter Vars**: banned even in loops. Use `index`, `item`, `orderItem`, `userId`.
|
|
41
|
+
|
|
42
|
+
#### Banned Abbreviations
|
|
43
|
+
|
|
44
|
+
`idx`, `prev`, `arr`, `val`, `tmp`, `res`, `cb`, `fn`, `mgr`, `ctrl`, `svc` are banned.
|
|
45
|
+
If you need to abbreviate, the name is wrong. **Framework parameters are not exempt**: `req` → `request`, `res` → `response`, `ctx` → `context`.
|
|
46
|
+
|
|
47
|
+
</rule>
|
|
48
|
+
|
|
49
|
+
## Rule: Expressive Booleans
|
|
50
|
+
|
|
51
|
+
<rule name="ExpressiveBooleans">
|
|
52
|
+
|
|
53
|
+
> [!IMPORTANT]
|
|
54
|
+
> Every boolean must carry a semantic prefix. A bare noun (`loading`, `error`, `active`) is banned.
|
|
55
|
+
|
|
56
|
+
| Prefix | Meaning | Examples |
|
|
57
|
+
| :--------- | :------------------------------------------ | :-------------------------------------------- |
|
|
58
|
+
| `is` | Current state or condition | `isLoading`, `isError`, `isOpen`, `isValid` |
|
|
59
|
+
| `has` | Presence or possession | `hasContent`, `hasError`, `hasSelection` |
|
|
60
|
+
| `can` | Active capability or permission (dynamic) | `canSubmit`, `canDelete`, `canRetry` |
|
|
61
|
+
| `should` | Behavioral directive (agent decides) | `shouldRedirect`, `shouldRetry`, `shouldSkip` |
|
|
62
|
+
| `did` | Past action completed (one-time events) | `didFetch`, `didMount`, `didAccept` |
|
|
63
|
+
| `needs` | Dependency or requirement not yet satisfied | `needsConfirmation`, `needsRefresh` |
|
|
64
|
+
| `supports` | Capability of an external system or feature | `supportsTouch`, `supportsWebP` |
|
|
65
|
+
| `allows` | Passive permission (policy/config driven) | `allowsMultiSelect`, `allowsGuests` |
|
|
66
|
+
|
|
67
|
+
**`can` vs `allows`:** `can` = the current actor has the ability right now (computed, dynamic). `allows` = the system or policy permits it (config, role, feature flag — static from the caller's perspective).
|
|
68
|
+
|
|
69
|
+
**Negation rule:** Define both positive and negative forms only when both improve readability at the call site. `isSuccess` and `isFailure` coexist because guard clauses read better with `isFailure` than `!isSuccess`. Never define a negative-only boolean — `isNotReady` ❌ → use `!isReady` or rename to `isPending` ✅.
|
|
70
|
+
|
|
71
|
+
</rule>
|
|
72
|
+
|
|
73
|
+
## Rule: Verb Taxonomy (Operations by Intent)
|
|
74
|
+
|
|
75
|
+
<rule name="VerbTaxonomy">
|
|
76
|
+
|
|
77
|
+
> [!NOTE]
|
|
78
|
+
> Choose the verb that describes the operation's intent, not its mechanism.
|
|
79
|
+
|
|
80
|
+
| Intent | Preferred Verbs | Avoid |
|
|
81
|
+
| :---------------------------------- | :---------------------------------------- | :----------------- |
|
|
82
|
+
| Read from storage / external system | `fetch`, `load`, `find`, `get` | `retrieve`, `pull` |
|
|
83
|
+
| Write / persist state | `save`, `persist`, `create`, `insert` | `put`, `push` |
|
|
84
|
+
| Compute / derive a value | `compute`, `calculate`, `derive`, `build` | `get`, `do` |
|
|
85
|
+
| Transform / map shape | `map`, `transform`, `convert`, `format` | `process`, `parse` |
|
|
86
|
+
| Validate / assert constraints | `validate`, `check`, `assert`, `verify` | `handle`, `test` |
|
|
87
|
+
| Send / dispatch / notify | `send`, `dispatch`, `notify`, `emit` | `fire`, `trigger` |
|
|
88
|
+
| Remove / clean up | `delete`, `remove`, `purge`, `clear` | `destroy`, `kill` |
|
|
89
|
+
|
|
90
|
+
**UI-specific verb prefixes** (React / component functions):
|
|
91
|
+
|
|
92
|
+
| Prefix | Use |
|
|
93
|
+
| :-------- | :------------------------------------- |
|
|
94
|
+
| `load` | Initial data fetch on mount |
|
|
95
|
+
| `refresh` | Reload triggered by user action |
|
|
96
|
+
| `submit` | Form submission or mutation |
|
|
97
|
+
| `handle` | DOM event handler (click, change, etc) |
|
|
98
|
+
|
|
99
|
+
Note: `handle` is **only permitted as a UI event handler prefix** — not as a general-purpose verb in business logic or services.
|
|
100
|
+
|
|
101
|
+
</rule>
|
|
102
|
+
|
|
103
|
+
## Rule: File & Module Naming
|
|
104
|
+
|
|
105
|
+
<rule name="FileModuleNaming">
|
|
106
|
+
|
|
107
|
+
> [!IMPORTANT]
|
|
108
|
+
> File names must describe the domain and operation. Generic names are a dumping ground waiting to happen.
|
|
109
|
+
|
|
110
|
+
#### Pattern: `domain.operation.ext`
|
|
111
|
+
|
|
112
|
+
- `order.compute.js` ✅ — `helpers.js` ❌
|
|
113
|
+
- `order.format.ts` ✅ — `utils.ts` ❌
|
|
114
|
+
- `currency.normalize.js` ✅ — `common.js` ❌
|
|
115
|
+
- `user.validate.ts` ✅ — `shared.ts` ❌
|
|
116
|
+
|
|
117
|
+
**Test:** _"Does this function make sense without knowing what an `order` is?"_
|
|
118
|
+
|
|
119
|
+
- Yes → it can live in a shared, domain-agnostic module named by intent.
|
|
120
|
+
- No → it stays in the domain module.
|
|
121
|
+
|
|
122
|
+
#### Shared utilities — 3 conditions required
|
|
123
|
+
|
|
124
|
+
1. Truly domain-agnostic (works without knowing the business entity)
|
|
125
|
+
2. Reused in ≥ 2 real, unrelated contexts
|
|
126
|
+
3. Named by intent — `currency.normalize.js`, not `formatters.js`
|
|
127
|
+
|
|
128
|
+
If any condition fails, keep it in the domain module.
|
|
129
|
+
|
|
130
|
+
#### Never create
|
|
131
|
+
|
|
132
|
+
`helpers.js`, `utils.js`, `common.js`, `shared.js`, `misc.js` — these names carry no responsibility and evolve into dumping grounds.
|
|
133
|
+
|
|
134
|
+
</rule>
|
|
135
|
+
|
|
136
|
+
</ruleset>
|