@garethdaine/agentops 0.9.0
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/.claude-plugin/plugin.json +10 -0
- package/LICENSE +21 -0
- package/README.md +410 -0
- package/agents/architecture-researcher.md +115 -0
- package/agents/code-critic.md +190 -0
- package/agents/delegation-router.md +40 -0
- package/agents/feature-researcher.md +117 -0
- package/agents/interrogator.md +11 -0
- package/agents/pitfalls-researcher.md +112 -0
- package/agents/plan-validator.md +173 -0
- package/agents/proposer.md +61 -0
- package/agents/security-reviewer.md +189 -0
- package/agents/skill-builder.md +43 -0
- package/agents/spec-compliance-reviewer.md +154 -0
- package/agents/stack-researcher.md +89 -0
- package/commands/build.md +766 -0
- package/commands/code-analysis.md +39 -0
- package/commands/code-field.md +22 -0
- package/commands/compliance-check.md +34 -0
- package/commands/configure.md +178 -0
- package/commands/cost-report.md +17 -0
- package/commands/enterprise/adr.md +78 -0
- package/commands/enterprise/brainstorm.md +461 -0
- package/commands/enterprise/design.md +203 -0
- package/commands/enterprise/dev-setup.md +136 -0
- package/commands/enterprise/docker-dev.md +229 -0
- package/commands/enterprise/e2e.md +233 -0
- package/commands/enterprise/feature.md +218 -0
- package/commands/enterprise/gap-analysis.md +204 -0
- package/commands/enterprise/handover.md +195 -0
- package/commands/enterprise/herd.md +152 -0
- package/commands/enterprise/knowledge.md +173 -0
- package/commands/enterprise/onboard.md +86 -0
- package/commands/enterprise/qa-check.md +80 -0
- package/commands/enterprise/reason.md +196 -0
- package/commands/enterprise/review.md +177 -0
- package/commands/enterprise/scaffold.md +153 -0
- package/commands/enterprise/status-report.md +101 -0
- package/commands/enterprise/tech-catalog.md +170 -0
- package/commands/enterprise/test-gen.md +138 -0
- package/commands/evolve.md +39 -0
- package/commands/flags.md +44 -0
- package/commands/interrogate.md +263 -0
- package/commands/lesson.md +15 -0
- package/commands/lessons.md +10 -0
- package/commands/plan.md +44 -0
- package/commands/prune.md +27 -0
- package/commands/star.md +17 -0
- package/commands/supply-chain-scan.md +44 -0
- package/commands/unicode-scan.md +63 -0
- package/commands/verify.md +41 -0
- package/commands/workflow.md +436 -0
- package/hooks/ai-guardrails.sh +114 -0
- package/hooks/audit-log.sh +26 -0
- package/hooks/auto-delegate.sh +45 -0
- package/hooks/auto-evolve.sh +22 -0
- package/hooks/auto-lesson.sh +26 -0
- package/hooks/auto-plan.sh +59 -0
- package/hooks/auto-test.sh +46 -0
- package/hooks/auto-verify.sh +30 -0
- package/hooks/budget-check.sh +24 -0
- package/hooks/code-field-preamble.sh +30 -0
- package/hooks/compliance-gate.sh +50 -0
- package/hooks/content-trust.sh +22 -0
- package/hooks/credential-redact.sh +23 -0
- package/hooks/delegation-trust.sh +15 -0
- package/hooks/detect-test-run.sh +19 -0
- package/hooks/enforcement-lib.sh +60 -0
- package/hooks/evolve-gate.sh +32 -0
- package/hooks/evolve-lib.sh +32 -0
- package/hooks/exfiltration-check.sh +67 -0
- package/hooks/failure-collector.sh +27 -0
- package/hooks/feature-flags.sh +67 -0
- package/hooks/file-provenance.sh +31 -0
- package/hooks/flag-utils.sh +36 -0
- package/hooks/hooks.json +145 -0
- package/hooks/injection-scan.sh +58 -0
- package/hooks/integrity-verify.sh +91 -0
- package/hooks/lessons-check.sh +17 -0
- package/hooks/lockfile-audit.sh +109 -0
- package/hooks/patterns-lib.sh +22 -0
- package/hooks/plan-gate.sh +18 -0
- package/hooks/redact-lib.sh +15 -0
- package/hooks/runtime-mode.sh +56 -0
- package/hooks/session-cleanup.sh +74 -0
- package/hooks/skill-validator.sh +28 -0
- package/hooks/standards-enforce.sh +106 -0
- package/hooks/star-gate.sh +93 -0
- package/hooks/star-preamble.sh +10 -0
- package/hooks/telemetry.sh +33 -0
- package/hooks/todo-prune.sh +84 -0
- package/hooks/unicode-firewall.sh +122 -0
- package/hooks/unicode-lib.sh +66 -0
- package/hooks/unicode-scan-session.sh +96 -0
- package/hooks/validate-command.sh +103 -0
- package/hooks/validate-env.sh +51 -0
- package/hooks/validate-path.sh +81 -0
- package/package.json +40 -0
- package/settings.json +6 -0
- package/templates/ai-config/tool-standards.md +56 -0
- package/templates/architecture/api-first.md +192 -0
- package/templates/architecture/auth-patterns.md +302 -0
- package/templates/architecture/caching-strategy.md +359 -0
- package/templates/architecture/database-patterns.md +347 -0
- package/templates/architecture/event-driven.md +252 -0
- package/templates/architecture/integration-patterns.md +185 -0
- package/templates/architecture/multi-tenancy.md +104 -0
- package/templates/architecture/service-boundaries.md +200 -0
- package/templates/build/brief-template.md +86 -0
- package/templates/build/summary-template.md +100 -0
- package/templates/build/task-plan-template.md +133 -0
- package/templates/communication/effort-estimate.md +54 -0
- package/templates/communication/incident-response.md +59 -0
- package/templates/communication/post-mortem.md +109 -0
- package/templates/communication/risk-register.md +43 -0
- package/templates/communication/sprint-demo-checklist.md +64 -0
- package/templates/communication/stakeholder-presentation-outline.md +84 -0
- package/templates/communication/technical-proposal.md +77 -0
- package/templates/delivery/deployment/deployment-checklist.md +49 -0
- package/templates/delivery/design/solution-design-checklist.md +37 -0
- package/templates/delivery/discovery/stakeholder-questions.md +33 -0
- package/templates/delivery/handover/knowledge-transfer-checklist.md +75 -0
- package/templates/delivery/handover/operational-runbook.md +117 -0
- package/templates/delivery/handover/support-escalation-matrix.md +56 -0
- package/templates/delivery/implementation/blocker-escalation-template.md +55 -0
- package/templates/delivery/implementation/sprint-planning-template.md +49 -0
- package/templates/delivery/implementation/task-decomposition-guide.md +59 -0
- package/templates/delivery/qa/test-plan-template.md +76 -0
- package/templates/delivery/qa/test-results-template.md +55 -0
- package/templates/delivery/qa/uat-signoff-template.md +44 -0
- package/templates/governance/codeowners.md +60 -0
- package/templates/integration/adapter-pattern.md +160 -0
- package/templates/scaffolds/env-validation.md +85 -0
- package/templates/scaffolds/error-handling.md +171 -0
- package/templates/scaffolds/graceful-shutdown.md +139 -0
- package/templates/scaffolds/health-check.md +109 -0
- package/templates/scaffolds/structured-logging.md +134 -0
- package/templates/standards/engineering-standards.md +413 -0
- package/templates/standards/standards-checklist.md +125 -0
- package/templates/tech-catalog.json +663 -0
- package/templates/utilities/project-detection.md +75 -0
- package/templates/utilities/requirements-collection.md +68 -0
- package/templates/utilities/template-rendering.md +81 -0
- package/templates/workflows/architecture-decision.md +90 -0
- package/templates/workflows/bug-investigation.md +83 -0
- package/templates/workflows/feature-implementation.md +80 -0
- package/templates/workflows/refactoring.md +83 -0
- package/templates/workflows/spike-exploration.md +82 -0
|
@@ -0,0 +1,413 @@
|
|
|
1
|
+
# Engineering Standards
|
|
2
|
+
|
|
3
|
+
This document is injected into every execution subagent during a build. All code produced MUST comply with these standards. Deviations are flagged by the spec-compliance reviewer in Phase 6.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## SOLID Principles
|
|
8
|
+
|
|
9
|
+
### Single Responsibility Principle (SRP)
|
|
10
|
+
|
|
11
|
+
Every class, module, and function should have exactly one reason to change.
|
|
12
|
+
|
|
13
|
+
**Heuristics:**
|
|
14
|
+
- Functions: ≤30 lines. If you exceed this, split into smaller named functions.
|
|
15
|
+
- Classes: ≤200 lines. If you exceed this, extract a collaborator.
|
|
16
|
+
- Files: one primary export per file. Do not combine unrelated classes or functions in a single file.
|
|
17
|
+
- If you find yourself saying "and" when describing what a function does, it has two responsibilities.
|
|
18
|
+
|
|
19
|
+
```typescript
|
|
20
|
+
// BAD: Two responsibilities — fetching AND formatting
|
|
21
|
+
async function getUserReport(userId: string): Promise<string> {
|
|
22
|
+
const user = await db.users.findById(userId);
|
|
23
|
+
const orders = await db.orders.findByUser(userId);
|
|
24
|
+
return `${user.name}: ${orders.length} orders, total: ${orders.reduce(...)}`;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// GOOD: Separate concerns
|
|
28
|
+
async function getUserWithOrders(userId: string): Promise<UserWithOrders> {
|
|
29
|
+
const user = await userRepository.findById(userId);
|
|
30
|
+
const orders = await orderRepository.findByUserId(userId);
|
|
31
|
+
return { user, orders };
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function formatUserReport(data: UserWithOrders): string {
|
|
35
|
+
return `${data.user.name}: ${data.orders.length} orders, total: ${calculateTotal(data.orders)}`;
|
|
36
|
+
}
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### Open/Closed Principle (OCP)
|
|
40
|
+
|
|
41
|
+
Code should be open for extension, closed for modification.
|
|
42
|
+
|
|
43
|
+
**Heuristics:**
|
|
44
|
+
- Use the Strategy pattern or dependency injection instead of `switch` chains on type.
|
|
45
|
+
- Adding new behaviour means adding a new class/function — not modifying existing ones.
|
|
46
|
+
- When you find yourself adding `else if` to an existing block, consider whether a new strategy/handler is the right move.
|
|
47
|
+
|
|
48
|
+
```typescript
|
|
49
|
+
// BAD: Every new payment type requires modifying this function
|
|
50
|
+
function processPayment(type: string, amount: number) {
|
|
51
|
+
if (type === 'stripe') { ... }
|
|
52
|
+
else if (type === 'paypal') { ... }
|
|
53
|
+
else if (type === 'crypto') { ... } // New type = modify existing code
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// GOOD: New payment type = new class, zero changes to existing code
|
|
57
|
+
interface PaymentProcessor {
|
|
58
|
+
process(amount: number): Promise<PaymentResult>;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
class StripeProcessor implements PaymentProcessor { ... }
|
|
62
|
+
class PayPalProcessor implements PaymentProcessor { ... }
|
|
63
|
+
class CryptoProcessor implements PaymentProcessor { ... }
|
|
64
|
+
|
|
65
|
+
function processPayment(processor: PaymentProcessor, amount: number) {
|
|
66
|
+
return processor.process(amount);
|
|
67
|
+
}
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### Liskov Substitution Principle (LSP)
|
|
71
|
+
|
|
72
|
+
Subtypes must be substitutable for their base types without altering program correctness.
|
|
73
|
+
|
|
74
|
+
**Heuristics:**
|
|
75
|
+
- Do NOT strengthen preconditions in a subclass (e.g., adding null checks the base doesn't require).
|
|
76
|
+
- Do NOT weaken postconditions (e.g., returning a narrower type or throwing where the base doesn't).
|
|
77
|
+
- If a subclass overrides a method and throws `UnsupportedOperationException`, it violates LSP — extract a separate interface instead.
|
|
78
|
+
- Test by asking: "If I swap this subtype for its base type, do all existing tests still pass?"
|
|
79
|
+
|
|
80
|
+
### Interface Segregation Principle (ISP)
|
|
81
|
+
|
|
82
|
+
Clients should not be forced to depend on interfaces they do not use.
|
|
83
|
+
|
|
84
|
+
**Heuristics:**
|
|
85
|
+
- Small, focused interfaces over one fat interface.
|
|
86
|
+
- Multiple specific interfaces are better than one generic interface with optional methods.
|
|
87
|
+
- If a class implementing an interface must provide a stub/no-op for any method, split the interface.
|
|
88
|
+
|
|
89
|
+
```typescript
|
|
90
|
+
// BAD: Everything in one interface
|
|
91
|
+
interface Repository<T> {
|
|
92
|
+
findById(id: string): Promise<T>;
|
|
93
|
+
findAll(): Promise<T[]>;
|
|
94
|
+
save(entity: T): Promise<T>;
|
|
95
|
+
delete(id: string): Promise<void>;
|
|
96
|
+
search(query: string): Promise<T[]>; // Not all repos need this
|
|
97
|
+
exportToCsv(): string; // Definitely not all repos
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// GOOD: Segregated interfaces composed as needed
|
|
101
|
+
interface Readable<T> {
|
|
102
|
+
findById(id: string): Promise<T>;
|
|
103
|
+
findAll(): Promise<T[]>;
|
|
104
|
+
}
|
|
105
|
+
interface Writable<T> {
|
|
106
|
+
save(entity: T): Promise<T>;
|
|
107
|
+
delete(id: string): Promise<void>;
|
|
108
|
+
}
|
|
109
|
+
interface Searchable<T> {
|
|
110
|
+
search(query: string): Promise<T[]>;
|
|
111
|
+
}
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### Dependency Inversion Principle (DIP)
|
|
115
|
+
|
|
116
|
+
High-level modules should not depend on low-level modules. Both should depend on abstractions.
|
|
117
|
+
|
|
118
|
+
**Heuristics:**
|
|
119
|
+
- Use constructor injection. Do NOT instantiate concrete dependencies inside a class.
|
|
120
|
+
- No `new ConcreteClass()` in business logic — dependencies are injected.
|
|
121
|
+
- Depend on interfaces/abstract types, not concrete implementations.
|
|
122
|
+
- Repositories, services, and external clients are always injected.
|
|
123
|
+
|
|
124
|
+
```typescript
|
|
125
|
+
// BAD: Hard-coupled to Stripe
|
|
126
|
+
class OrderService {
|
|
127
|
+
private stripe = new StripeClient(process.env.STRIPE_KEY); // Concrete dep, untestable
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// GOOD: Depends on abstraction, injected
|
|
131
|
+
class OrderService {
|
|
132
|
+
constructor(private readonly paymentProcessor: PaymentProcessor) {}
|
|
133
|
+
}
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
---
|
|
137
|
+
|
|
138
|
+
## Clean Code
|
|
139
|
+
|
|
140
|
+
### Naming
|
|
141
|
+
|
|
142
|
+
- **Reveal intent.** Names should explain WHY, not HOW.
|
|
143
|
+
- **Functions:** verb phrases (`calculateTotal`, `sendWelcomeEmail`, `validateUserInput`)
|
|
144
|
+
- **Booleans:** question form (`isActive`, `hasPermission`, `canAccessResource`, `shouldRetry`)
|
|
145
|
+
- **Classes:** noun phrases, no "Manager", "Handler", "Processor" suffixes unless genuinely appropriate (`UserRepository`, not `UserManager`)
|
|
146
|
+
- **Constants:** SCREAMING_SNAKE_CASE for module-level constants (`MAX_RETRY_ATTEMPTS`)
|
|
147
|
+
- Avoid single-letter variables outside of math-heavy code or trivially-scoped loops.
|
|
148
|
+
- Avoid abbreviations unless universally understood (`url`, `id`, `db` are fine; `usrMgr` is not).
|
|
149
|
+
|
|
150
|
+
### Functions
|
|
151
|
+
|
|
152
|
+
- **One abstraction level per function.** A function should either orchestrate OR compute, not both.
|
|
153
|
+
- **Command-query separation:** Functions either DO something (commands) or RETURN something (queries) — not both. Functions that both mutate state and return a value are confusing.
|
|
154
|
+
- **Stepdown rule:** Functions call only functions one level below them in abstraction.
|
|
155
|
+
- **Max 3 parameters.** More than 3: introduce a parameter object.
|
|
156
|
+
- **No flag parameters.** A boolean argument is a sign the function should be split in two.
|
|
157
|
+
|
|
158
|
+
```typescript
|
|
159
|
+
// BAD: Does and returns, flag parameter, mixed abstraction
|
|
160
|
+
async function processUser(userId: string, sendEmail: boolean): Promise<User> {
|
|
161
|
+
const raw = await db.query(`SELECT * FROM users WHERE id = ?`, [userId]);
|
|
162
|
+
const user = { id: raw.id, name: raw.name, email: raw.email };
|
|
163
|
+
await db.query(`UPDATE users SET last_seen = NOW() WHERE id = ?`, [userId]);
|
|
164
|
+
if (sendEmail) await mailer.send(user.email, 'Welcome back');
|
|
165
|
+
return user;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// GOOD: Single responsibility, no flag parameter
|
|
169
|
+
async function findUser(userId: string): Promise<User> {
|
|
170
|
+
return userRepository.findById(userId);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
async function recordUserLogin(userId: string): Promise<void> {
|
|
174
|
+
await userRepository.updateLastSeen(userId, new Date());
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
async function sendLoginEmail(user: User): Promise<void> {
|
|
178
|
+
await emailService.send(user.email, 'Welcome back');
|
|
179
|
+
}
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
### Error Handling
|
|
183
|
+
|
|
184
|
+
- **Typed error hierarchy.** Define domain-specific error classes. Never throw raw `Error` with a string message from business logic.
|
|
185
|
+
- **Never swallow errors.** Empty `catch` blocks are forbidden. Log at minimum.
|
|
186
|
+
- **Correlation IDs.** Every request should have a correlation ID propagated through all log entries and error objects.
|
|
187
|
+
- **Fail fast at boundaries.** Validate all external input at the point of entry. Do not let invalid data propagate into the domain.
|
|
188
|
+
|
|
189
|
+
```typescript
|
|
190
|
+
// BAD
|
|
191
|
+
try {
|
|
192
|
+
await processOrder(order);
|
|
193
|
+
} catch (e) {} // Swallowed
|
|
194
|
+
|
|
195
|
+
// GOOD
|
|
196
|
+
class OrderProcessingError extends AppError {
|
|
197
|
+
constructor(message: string, public readonly orderId: string, cause?: Error) {
|
|
198
|
+
super(message, 'ORDER_PROCESSING_FAILED', { orderId }, cause);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
try {
|
|
203
|
+
await processOrder(order);
|
|
204
|
+
} catch (error) {
|
|
205
|
+
logger.error('Order processing failed', { orderId: order.id, correlationId, error });
|
|
206
|
+
throw new OrderProcessingError('Failed to process order', order.id, error as Error);
|
|
207
|
+
}
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
---
|
|
211
|
+
|
|
212
|
+
## DRY — Don't Repeat Yourself
|
|
213
|
+
|
|
214
|
+
- **2+ locations = extract.** If the same logic appears in two places, extract it. Three is a certainty.
|
|
215
|
+
- **Knowledge duplication = single source of truth.** The same business rule should live in exactly one place. If you update it, you should only need to update it in one file.
|
|
216
|
+
- **DRY applies to knowledge, not syntax.** Two `for` loops that happen to look similar are not a DRY violation if they represent different concepts.
|
|
217
|
+
|
|
218
|
+
---
|
|
219
|
+
|
|
220
|
+
## KISS / YAGNI
|
|
221
|
+
|
|
222
|
+
- **Simplest correct solution first.** Before adding abstraction, ask: does the simpler version solve the problem?
|
|
223
|
+
- **YAGNI (You Aren't Gonna Need It).** Do not implement features or abstractions "in case we need them later." Implement what is required now.
|
|
224
|
+
- **No speculative generality.** Hooks, extension points, and plugin systems not required by current features must not be added.
|
|
225
|
+
- **Three similar lines are better than a premature abstraction.** Only extract when the pattern is stable and has 3+ concrete uses.
|
|
226
|
+
|
|
227
|
+
---
|
|
228
|
+
|
|
229
|
+
## Design Patterns
|
|
230
|
+
|
|
231
|
+
Apply patterns when NOT applying them creates a named problem. Do not apply patterns speculatively.
|
|
232
|
+
|
|
233
|
+
Reference: [refactoring.guru](https://refactoring.guru/design-patterns)
|
|
234
|
+
|
|
235
|
+
### Creational
|
|
236
|
+
|
|
237
|
+
| Pattern | Use when |
|
|
238
|
+
|---------|----------|
|
|
239
|
+
| **Factory Method** | Object creation logic is complex or varies by subtype; callers shouldn't know the concrete class |
|
|
240
|
+
| **Builder** | Object requires many optional parameters or a multi-step construction process |
|
|
241
|
+
| **Singleton** | Prefer Dependency Injection. Use Singleton only for stateless utilities (loggers, config readers) where a single instance is provably correct |
|
|
242
|
+
|
|
243
|
+
### Structural
|
|
244
|
+
|
|
245
|
+
| Pattern | Use when |
|
|
246
|
+
|---------|----------|
|
|
247
|
+
| **Adapter** | Integrating a third-party library whose interface doesn't match your domain's needs |
|
|
248
|
+
| **Decorator** | Adding cross-cutting behaviour (logging, caching, validation) to an existing object without subclassing |
|
|
249
|
+
| **Facade** | Simplifying a complex subsystem behind a clean, high-level interface for callers |
|
|
250
|
+
| **Proxy** | Controlling access to a resource — lazy loading, access control, logging |
|
|
251
|
+
|
|
252
|
+
### Behavioral
|
|
253
|
+
|
|
254
|
+
| Pattern | Use when |
|
|
255
|
+
|---------|----------|
|
|
256
|
+
| **Strategy** | Interchangeable algorithms or behaviours; replaces switch chains on type |
|
|
257
|
+
| **Observer** | One-to-many event propagation; decouples producers from consumers |
|
|
258
|
+
| **Command** | Encapsulating a request as an object — enables undo, queuing, logging |
|
|
259
|
+
| **Template Method** | Invariant algorithm structure with variable steps; base class defines the skeleton |
|
|
260
|
+
| **Chain of Responsibility** | A request passes through a chain of handlers; each decides to handle or pass on |
|
|
261
|
+
|
|
262
|
+
**Selection heuristic:** Before applying a pattern, name the problem it solves. If you can't name the problem, you don't need the pattern.
|
|
263
|
+
|
|
264
|
+
---
|
|
265
|
+
|
|
266
|
+
## Action-Based Architecture
|
|
267
|
+
|
|
268
|
+
For business logic: use single-purpose action classes.
|
|
269
|
+
|
|
270
|
+
- **One public method** (`execute` or `handle`).
|
|
271
|
+
- **One responsibility** — the class name describes exactly what it does.
|
|
272
|
+
- **Fully injectable** — all dependencies injected via constructor.
|
|
273
|
+
- **No side effects beyond the stated purpose.**
|
|
274
|
+
|
|
275
|
+
```typescript
|
|
276
|
+
// GOOD: Action class
|
|
277
|
+
class CreateOrderAction {
|
|
278
|
+
constructor(
|
|
279
|
+
private readonly orderRepository: OrderRepository,
|
|
280
|
+
private readonly inventoryService: InventoryService,
|
|
281
|
+
private readonly eventBus: EventBus,
|
|
282
|
+
) {}
|
|
283
|
+
|
|
284
|
+
async execute(input: CreateOrderInput): Promise<Order> {
|
|
285
|
+
await this.inventoryService.reserve(input.items);
|
|
286
|
+
const order = Order.create(input);
|
|
287
|
+
await this.orderRepository.save(order);
|
|
288
|
+
await this.eventBus.publish(new OrderCreatedEvent(order));
|
|
289
|
+
return order;
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
Use action classes for:
|
|
295
|
+
- Every user-initiated state change (create, update, delete, transition)
|
|
296
|
+
- Complex business operations with multiple steps
|
|
297
|
+
- Operations that publish domain events
|
|
298
|
+
|
|
299
|
+
---
|
|
300
|
+
|
|
301
|
+
## Domain-Driven Design (DDD)
|
|
302
|
+
|
|
303
|
+
Apply DDD where domain complexity warrants it. For simple CRUD, DDD adds unnecessary ceremony.
|
|
304
|
+
|
|
305
|
+
| Concept | Rule |
|
|
306
|
+
|---------|------|
|
|
307
|
+
| **Aggregate** | Cluster of related objects with a single entry point (Aggregate Root). Only the root is referenced from outside. Enforce invariants at the aggregate root. |
|
|
308
|
+
| **Entity** | Has identity (`id`). Two entities are equal if their IDs match, regardless of other field values. |
|
|
309
|
+
| **Value Object** | No identity. Immutable. Two value objects are equal if all their fields match. |
|
|
310
|
+
| **Domain Service** | Stateless logic that doesn't naturally belong to any entity or value object. |
|
|
311
|
+
| **Application Service / Action** | Orchestrates use cases. Has no domain logic itself — calls domain objects and infrastructure. |
|
|
312
|
+
| **Repository** | Interface defined in the domain layer. Implementation in the infrastructure layer. |
|
|
313
|
+
| **Ubiquitous Language** | Use the business's language in code. Class names, method names, and variable names should match how domain experts talk about the problem. |
|
|
314
|
+
|
|
315
|
+
---
|
|
316
|
+
|
|
317
|
+
## Layered Architecture
|
|
318
|
+
|
|
319
|
+
**Layer order (no skipping):**
|
|
320
|
+
|
|
321
|
+
```
|
|
322
|
+
Controller (HTTP/GraphQL/CLI)
|
|
323
|
+
└── Application Service / Action
|
|
324
|
+
└── Domain (Entities, Aggregates, Domain Services)
|
|
325
|
+
└── Repository Interface
|
|
326
|
+
└── Repository Implementation (Infrastructure)
|
|
327
|
+
└── Database / External Services
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
- A controller must not contain business logic — it delegates to an application service or action.
|
|
331
|
+
- An application service must not contain SQL or ORM calls — it uses repository interfaces.
|
|
332
|
+
- Domain objects must not depend on infrastructure (no `import prisma from '...'` in domain code).
|
|
333
|
+
- Violation of layering is a CRITICAL finding in code review.
|
|
334
|
+
|
|
335
|
+
---
|
|
336
|
+
|
|
337
|
+
## Modular Structure
|
|
338
|
+
|
|
339
|
+
- **Feature-based modules.** Group by business capability, not technical layer.
|
|
340
|
+
- **Co-located tests.** Test files live next to the code they test (`foo.service.ts` → `foo.service.test.ts`).
|
|
341
|
+
- **Barrel exports.** Each module exposes a deliberate public API via `index.ts`. Internal implementation details are not exported.
|
|
342
|
+
- **Shared kernel.** Cross-cutting concerns (errors, logging, HTTP utilities, config) live in `shared/` or `lib/`. This directory must NOT contain domain logic.
|
|
343
|
+
- **No circular dependencies.** Module A may not import from Module B if Module B imports from Module A.
|
|
344
|
+
|
|
345
|
+
---
|
|
346
|
+
|
|
347
|
+
## Testing Standards
|
|
348
|
+
|
|
349
|
+
- **Tests document behaviour, not implementation.** Test descriptions use the form: "should {do something} when {condition}".
|
|
350
|
+
- **Arrange-Act-Assert.** Structure every test with a clear setup, a single action, and explicit assertions.
|
|
351
|
+
- **One assertion concept per test.** Multiple `expect` calls are fine if they all verify the same behaviour.
|
|
352
|
+
- **No magic numbers in assertions.** Use named constants or setup variables.
|
|
353
|
+
- **Test the contract, not the internals.** Do not test private methods directly.
|
|
354
|
+
- **Red first.** In TDD, the test MUST fail before implementation. A test that passes without implementation is a broken test.
|
|
355
|
+
|
|
356
|
+
---
|
|
357
|
+
|
|
358
|
+
## Security Standards
|
|
359
|
+
|
|
360
|
+
Any violation of these rules is a **CRITICAL** finding in code review. No exceptions.
|
|
361
|
+
|
|
362
|
+
### Injection Prevention
|
|
363
|
+
|
|
364
|
+
- **No raw SQL string concatenation.** Always use parameterised queries or the ORM's query builder. Never interpolate user input into SQL strings.
|
|
365
|
+
- **No raw HTML rendering of user input.** All user-controlled values rendered in HTML must be sanitised or escaped. Use the framework's built-in escaping (e.g., React's JSX auto-escaping, Django's template auto-escaping).
|
|
366
|
+
- **No `eval()`, `Function()`, or equivalent dynamic code execution** with user-controlled input.
|
|
367
|
+
|
|
368
|
+
```typescript
|
|
369
|
+
// BAD: SQL injection via string interpolation
|
|
370
|
+
const users = await db.query(`SELECT * FROM users WHERE name = '${name}'`);
|
|
371
|
+
|
|
372
|
+
// GOOD: Parameterised query
|
|
373
|
+
const users = await db.query('SELECT * FROM users WHERE name = $1', [name]);
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
### Secrets and Credentials
|
|
377
|
+
|
|
378
|
+
- **No hardcoded secrets.** API keys, passwords, tokens, and connection strings must come from environment variables or a secrets manager. Never commit them to source code.
|
|
379
|
+
- **No secrets in URL query parameters.** Tokens and keys go in headers (`Authorization`, `X-API-Key`), never in URLs (they appear in logs, browser history, and referrer headers).
|
|
380
|
+
- **No secrets in client-side code.** Frontend bundles are public. Server-side secrets must stay server-side.
|
|
381
|
+
|
|
382
|
+
### Authentication and Authorisation
|
|
383
|
+
|
|
384
|
+
- **Auth middleware on every route handling user data.** No route that reads or writes user-specific data should be accessible without authentication.
|
|
385
|
+
- **Authorisation checks at the resource level.** Verifying that a user is logged in is not enough — verify they have access to the specific resource they're requesting (prevents IDOR vulnerabilities).
|
|
386
|
+
- **Session tokens must be httpOnly, secure, sameSite.** Never store session tokens in localStorage (XSS-accessible). Use httpOnly cookies.
|
|
387
|
+
|
|
388
|
+
### Input Validation
|
|
389
|
+
|
|
390
|
+
- **Validate all external input at system boundaries.** Every API endpoint that accepts POST/PUT/PATCH must have an input validation schema (zod, joi, yup, pydantic, or equivalent).
|
|
391
|
+
- **Fail closed.** If validation fails, reject the request. Never proceed with partially-valid data.
|
|
392
|
+
- **Validate types, ranges, and formats.** Not just presence — check that email fields contain emails, IDs are valid formats, numbers are within expected ranges.
|
|
393
|
+
|
|
394
|
+
```typescript
|
|
395
|
+
// GOOD: Schema validation at the boundary
|
|
396
|
+
const CreateUserSchema = z.object({
|
|
397
|
+
email: z.string().email(),
|
|
398
|
+
name: z.string().min(1).max(200),
|
|
399
|
+
role: z.enum(['user', 'admin']),
|
|
400
|
+
});
|
|
401
|
+
|
|
402
|
+
app.post('/users', async (req, res) => {
|
|
403
|
+
const input = CreateUserSchema.parse(req.body); // Throws on invalid input
|
|
404
|
+
const user = await createUserAction.execute(input);
|
|
405
|
+
res.json(user);
|
|
406
|
+
});
|
|
407
|
+
```
|
|
408
|
+
|
|
409
|
+
### Data Protection
|
|
410
|
+
|
|
411
|
+
- **Never log sensitive data.** Passwords, tokens, credit card numbers, and PII must be redacted from log output.
|
|
412
|
+
- **Encrypt sensitive data at rest** when regulatory requirements demand it (GDPR, HIPAA, PCI-DSS).
|
|
413
|
+
- **Use constant-time comparison for secrets.** When comparing tokens, hashes, or API keys, use `crypto.timingSafeEqual` or equivalent to prevent timing attacks.
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
# Engineering Standards — Quick-Reference Checklist
|
|
2
|
+
|
|
3
|
+
Use this checklist during Phase 6 spec-compliance review. One rule per line. Every item is checkable.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## SOLID
|
|
8
|
+
|
|
9
|
+
### Single Responsibility Principle
|
|
10
|
+
- [ ] No function exceeds 30 lines
|
|
11
|
+
- [ ] No class exceeds 200 lines
|
|
12
|
+
- [ ] Every file has exactly one primary export
|
|
13
|
+
- [ ] No function name contains "and" or "or" indicating dual responsibility
|
|
14
|
+
|
|
15
|
+
### Open/Closed Principle
|
|
16
|
+
- [ ] No `switch` or long `if/else if` chains on type — use Strategy/polymorphism
|
|
17
|
+
- [ ] Adding new behaviour does not require modifying existing classes
|
|
18
|
+
- [ ] Extension points use interfaces, not concrete base classes
|
|
19
|
+
|
|
20
|
+
### Liskov Substitution Principle
|
|
21
|
+
- [ ] Subclasses do not strengthen preconditions
|
|
22
|
+
- [ ] Subclasses do not weaken postconditions
|
|
23
|
+
- [ ] No subclass method throws `UnsupportedOperationException` / equivalent
|
|
24
|
+
|
|
25
|
+
### Interface Segregation Principle
|
|
26
|
+
- [ ] No interface has methods that implementors must stub or no-op
|
|
27
|
+
- [ ] Interfaces are small and focused on a single concern
|
|
28
|
+
- [ ] Multiple specific interfaces preferred over one fat interface
|
|
29
|
+
|
|
30
|
+
### Dependency Inversion Principle
|
|
31
|
+
- [ ] No `new ConcreteClass()` instantiation inside business logic
|
|
32
|
+
- [ ] All external dependencies injected via constructor
|
|
33
|
+
- [ ] Business logic depends on interfaces, not concrete implementations
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## Clean Code
|
|
38
|
+
|
|
39
|
+
### Naming
|
|
40
|
+
- [ ] All function names are verb phrases
|
|
41
|
+
- [ ] All boolean variables/props use question form (`is`, `has`, `can`, `should`)
|
|
42
|
+
- [ ] No single-letter variable names outside trivial loops
|
|
43
|
+
- [ ] No unexplained abbreviations
|
|
44
|
+
|
|
45
|
+
### Functions
|
|
46
|
+
- [ ] Each function operates at a single level of abstraction
|
|
47
|
+
- [ ] No function has more than 3 parameters (or uses a parameter object)
|
|
48
|
+
- [ ] No boolean flag parameters
|
|
49
|
+
- [ ] Functions either do something OR return something (command-query separation)
|
|
50
|
+
|
|
51
|
+
### Error Handling
|
|
52
|
+
- [ ] No empty `catch` blocks
|
|
53
|
+
- [ ] All errors are typed (domain-specific error classes)
|
|
54
|
+
- [ ] Correlation IDs propagated in all log entries
|
|
55
|
+
- [ ] Input validated at system boundaries (HTTP handlers, CLI args, webhook payloads)
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
## DRY / KISS / YAGNI
|
|
60
|
+
- [ ] No logic duplicated in 2+ locations
|
|
61
|
+
- [ ] No speculative features or extension points not required by current scope
|
|
62
|
+
- [ ] No abstractions with fewer than 3 concrete uses
|
|
63
|
+
- [ ] No "Manager", "Helper", or "Util" classes without a clear bounded purpose
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
## Design Patterns
|
|
68
|
+
- [ ] Patterns applied only where the problem they solve exists
|
|
69
|
+
- [ ] No strategy pattern applied speculatively
|
|
70
|
+
- [ ] Factory/Builder used where construction complexity warrants it
|
|
71
|
+
- [ ] Adapter used when integrating third-party interfaces with mismatched contracts
|
|
72
|
+
|
|
73
|
+
---
|
|
74
|
+
|
|
75
|
+
## Action-Based Architecture
|
|
76
|
+
- [ ] Every user-initiated state change is an Action class
|
|
77
|
+
- [ ] Action classes have exactly one public method
|
|
78
|
+
- [ ] Action classes are fully injectable (no `new` inside)
|
|
79
|
+
- [ ] Actions do not contain repository SQL — they call repository interfaces
|
|
80
|
+
|
|
81
|
+
---
|
|
82
|
+
|
|
83
|
+
## Layered Architecture
|
|
84
|
+
- [ ] Controllers contain no business logic
|
|
85
|
+
- [ ] Application services / actions contain no SQL or ORM calls
|
|
86
|
+
- [ ] Domain objects do not import from infrastructure layer
|
|
87
|
+
- [ ] No layer is skipped (controller → service → domain → repository)
|
|
88
|
+
- [ ] No circular imports between layers
|
|
89
|
+
|
|
90
|
+
---
|
|
91
|
+
|
|
92
|
+
## Modular Structure
|
|
93
|
+
- [ ] Code is organised by feature/domain, not technical layer
|
|
94
|
+
- [ ] Each module has a barrel `index.ts` exporting its public API only
|
|
95
|
+
- [ ] Test files co-located with source files
|
|
96
|
+
- [ ] `shared/` contains only cross-cutting utilities, no domain logic
|
|
97
|
+
- [ ] No circular imports between modules
|
|
98
|
+
|
|
99
|
+
---
|
|
100
|
+
|
|
101
|
+
## Testing
|
|
102
|
+
- [ ] All test descriptions use "should {behaviour} when {condition}" form
|
|
103
|
+
- [ ] Every test follows Arrange-Act-Assert structure
|
|
104
|
+
- [ ] No tests pass before the implementation is written (TDD RED phase confirmed)
|
|
105
|
+
- [ ] No test uses `toBeDefined()` or `toBeTruthy()` as the only assertion
|
|
106
|
+
- [ ] All mocked dependencies are verified with `.toHaveBeenCalledWith(...)`
|
|
107
|
+
- [ ] No `it.skip` or `describe.skip` on critical paths
|
|
108
|
+
|
|
109
|
+
---
|
|
110
|
+
|
|
111
|
+
## Security (Critical — any violation is CRITICAL severity)
|
|
112
|
+
- [ ] No raw SQL string concatenation
|
|
113
|
+
- [ ] No hardcoded secrets (API keys, passwords, tokens)
|
|
114
|
+
- [ ] All API POST/PUT/PATCH handlers have input validation schemas (zod/joi/yup)
|
|
115
|
+
- [ ] Auth middleware applied to all routes handling user data
|
|
116
|
+
- [ ] No user-controlled values rendered in HTML without sanitisation
|
|
117
|
+
- [ ] No sensitive data in URL query parameters
|
|
118
|
+
|
|
119
|
+
---
|
|
120
|
+
|
|
121
|
+
## Performance
|
|
122
|
+
- [ ] No database calls inside loops (N+1 pattern)
|
|
123
|
+
- [ ] No `findMany()` / `SELECT *` without `limit`/`take`
|
|
124
|
+
- [ ] All list endpoints have pagination
|
|
125
|
+
- [ ] No synchronous file I/O in request handlers
|