tlc-claude-code 1.4.9 → 1.5.2
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.md +23 -0
- package/CODING-STANDARDS.md +408 -0
- package/bin/install.js +2 -0
- package/dashboard/dist/components/QualityGatePane.d.ts +38 -0
- package/dashboard/dist/components/QualityGatePane.js +31 -0
- package/dashboard/dist/components/QualityGatePane.test.d.ts +1 -0
- package/dashboard/dist/components/QualityGatePane.test.js +147 -0
- package/dashboard/dist/components/orchestration/AgentCard.d.ts +26 -0
- package/dashboard/dist/components/orchestration/AgentCard.js +60 -0
- package/dashboard/dist/components/orchestration/AgentCard.test.d.ts +1 -0
- package/dashboard/dist/components/orchestration/AgentCard.test.js +63 -0
- package/dashboard/dist/components/orchestration/AgentControls.d.ts +11 -0
- package/dashboard/dist/components/orchestration/AgentControls.js +20 -0
- package/dashboard/dist/components/orchestration/AgentControls.test.d.ts +1 -0
- package/dashboard/dist/components/orchestration/AgentControls.test.js +52 -0
- package/dashboard/dist/components/orchestration/AgentDetail.d.ts +35 -0
- package/dashboard/dist/components/orchestration/AgentDetail.js +37 -0
- package/dashboard/dist/components/orchestration/AgentDetail.test.d.ts +1 -0
- package/dashboard/dist/components/orchestration/AgentDetail.test.js +79 -0
- package/dashboard/dist/components/orchestration/AgentList.d.ts +31 -0
- package/dashboard/dist/components/orchestration/AgentList.js +47 -0
- package/dashboard/dist/components/orchestration/AgentList.test.d.ts +1 -0
- package/dashboard/dist/components/orchestration/AgentList.test.js +64 -0
- package/dashboard/dist/components/orchestration/CostMeter.d.ts +11 -0
- package/dashboard/dist/components/orchestration/CostMeter.js +28 -0
- package/dashboard/dist/components/orchestration/CostMeter.test.d.ts +1 -0
- package/dashboard/dist/components/orchestration/CostMeter.test.js +50 -0
- package/dashboard/dist/components/orchestration/ModelSelector.d.ts +20 -0
- package/dashboard/dist/components/orchestration/ModelSelector.js +12 -0
- package/dashboard/dist/components/orchestration/ModelSelector.test.d.ts +1 -0
- package/dashboard/dist/components/orchestration/ModelSelector.test.js +56 -0
- package/dashboard/dist/components/orchestration/OrchestrationDashboard.d.ts +28 -0
- package/dashboard/dist/components/orchestration/OrchestrationDashboard.js +28 -0
- package/dashboard/dist/components/orchestration/OrchestrationDashboard.test.d.ts +1 -0
- package/dashboard/dist/components/orchestration/OrchestrationDashboard.test.js +56 -0
- package/dashboard/dist/components/orchestration/QualityIndicator.d.ts +11 -0
- package/dashboard/dist/components/orchestration/QualityIndicator.js +37 -0
- package/dashboard/dist/components/orchestration/QualityIndicator.test.d.ts +1 -0
- package/dashboard/dist/components/orchestration/QualityIndicator.test.js +52 -0
- package/dashboard/dist/components/orchestration/index.d.ts +8 -0
- package/dashboard/dist/components/orchestration/index.js +8 -0
- package/package.json +1 -1
- package/server/lib/access-control.js +352 -0
- package/server/lib/access-control.test.js +322 -0
- package/server/lib/agents-cancel-command.js +139 -0
- package/server/lib/agents-cancel-command.test.js +180 -0
- package/server/lib/agents-get-command.js +159 -0
- package/server/lib/agents-get-command.test.js +167 -0
- package/server/lib/agents-list-command.js +150 -0
- package/server/lib/agents-list-command.test.js +149 -0
- package/server/lib/agents-logs-command.js +126 -0
- package/server/lib/agents-logs-command.test.js +198 -0
- package/server/lib/agents-retry-command.js +117 -0
- package/server/lib/agents-retry-command.test.js +192 -0
- package/server/lib/budget-limits.js +222 -0
- package/server/lib/budget-limits.test.js +214 -0
- package/server/lib/code-generator.js +291 -0
- package/server/lib/code-generator.test.js +307 -0
- package/server/lib/cost-command.js +290 -0
- package/server/lib/cost-command.test.js +202 -0
- package/server/lib/cost-optimizer.js +404 -0
- package/server/lib/cost-optimizer.test.js +232 -0
- package/server/lib/cost-projections.js +302 -0
- package/server/lib/cost-projections.test.js +217 -0
- package/server/lib/cost-reports.js +277 -0
- package/server/lib/cost-reports.test.js +254 -0
- package/server/lib/cost-tracker.js +216 -0
- package/server/lib/cost-tracker.test.js +302 -0
- package/server/lib/crypto-patterns.js +433 -0
- package/server/lib/crypto-patterns.test.js +346 -0
- package/server/lib/design-command.js +385 -0
- package/server/lib/design-command.test.js +249 -0
- package/server/lib/design-parser.js +237 -0
- package/server/lib/design-parser.test.js +290 -0
- package/server/lib/gemini-vision.js +377 -0
- package/server/lib/gemini-vision.test.js +282 -0
- package/server/lib/input-validator.js +360 -0
- package/server/lib/input-validator.test.js +295 -0
- package/server/lib/litellm-client.js +232 -0
- package/server/lib/litellm-client.test.js +267 -0
- package/server/lib/litellm-command.js +291 -0
- package/server/lib/litellm-command.test.js +260 -0
- package/server/lib/litellm-config.js +273 -0
- package/server/lib/litellm-config.test.js +212 -0
- package/server/lib/model-pricing.js +189 -0
- package/server/lib/model-pricing.test.js +178 -0
- package/server/lib/models-command.js +223 -0
- package/server/lib/models-command.test.js +193 -0
- package/server/lib/optimize-command.js +197 -0
- package/server/lib/optimize-command.test.js +193 -0
- package/server/lib/orchestration-integration.js +206 -0
- package/server/lib/orchestration-integration.test.js +235 -0
- package/server/lib/output-encoder.js +308 -0
- package/server/lib/output-encoder.test.js +312 -0
- package/server/lib/quality-evaluator.js +396 -0
- package/server/lib/quality-evaluator.test.js +337 -0
- package/server/lib/quality-gate-command.js +340 -0
- package/server/lib/quality-gate-command.test.js +321 -0
- package/server/lib/quality-gate-scorer.js +378 -0
- package/server/lib/quality-gate-scorer.test.js +376 -0
- package/server/lib/quality-history.js +265 -0
- package/server/lib/quality-history.test.js +359 -0
- package/server/lib/quality-presets.js +288 -0
- package/server/lib/quality-presets.test.js +269 -0
- package/server/lib/quality-retry.js +323 -0
- package/server/lib/quality-retry.test.js +325 -0
- package/server/lib/quality-thresholds.js +255 -0
- package/server/lib/quality-thresholds.test.js +237 -0
- package/server/lib/secure-auth.js +333 -0
- package/server/lib/secure-auth.test.js +288 -0
- package/server/lib/secure-code-command.js +540 -0
- package/server/lib/secure-code-command.test.js +309 -0
- package/server/lib/secure-errors.js +521 -0
- package/server/lib/secure-errors.test.js +298 -0
- package/server/lib/vision-command.js +372 -0
- package/server/lib/vision-command.test.js +255 -0
- package/server/lib/visual-command.js +350 -0
- package/server/lib/visual-command.test.js +256 -0
- package/server/lib/visual-testing.js +315 -0
- package/server/lib/visual-testing.test.js +357 -0
- package/server/package-lock.json +2 -2
- package/server/package.json +1 -1
package/CLAUDE.md
CHANGED
|
@@ -112,3 +112,26 @@ When working with teammates:
|
|
|
112
112
|
- The USER is the author. Claude is a tool, not an author.
|
|
113
113
|
|
|
114
114
|
**ALWAYS ask before `git push`.** Never push to remote without explicit user approval.
|
|
115
|
+
|
|
116
|
+
---
|
|
117
|
+
|
|
118
|
+
<!-- TLC-STANDARDS -->
|
|
119
|
+
|
|
120
|
+
## Code Quality (TLC)
|
|
121
|
+
|
|
122
|
+
This project follows TLC (Test-Led Coding) code quality standards. See [CODING-STANDARDS.md](./CODING-STANDARDS.md) for detailed guidelines.
|
|
123
|
+
|
|
124
|
+
### Quick Reference
|
|
125
|
+
|
|
126
|
+
**Module Structure:** Code lives in `server/lib/` - each module is a self-contained `.js` file with corresponding `.test.js` test file.
|
|
127
|
+
|
|
128
|
+
### Key Rules
|
|
129
|
+
|
|
130
|
+
1. **Test-first development** - Tests are written BEFORE implementation
|
|
131
|
+
2. **No hardcoded URLs or config** - Use environment variables
|
|
132
|
+
3. **JSDoc required** - Document all exported functions
|
|
133
|
+
4. **Paired test files** - Every `module.js` has a `module.test.js`
|
|
134
|
+
|
|
135
|
+
### Standards Reference
|
|
136
|
+
|
|
137
|
+
For complete standards including file naming, import rules, error handling patterns, and service design guidelines, see [CODING-STANDARDS.md](./CODING-STANDARDS.md).
|
|
@@ -0,0 +1,408 @@
|
|
|
1
|
+
# TLC Coding Standards
|
|
2
|
+
|
|
3
|
+
This document defines the coding standards for projects using TLC (Test-Led Coding).
|
|
4
|
+
|
|
5
|
+
## Module Structure
|
|
6
|
+
|
|
7
|
+
Organize code by **entity**, not by type. Each entity gets its own folder with consistent substructure.
|
|
8
|
+
|
|
9
|
+
### Correct Structure
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
src/
|
|
13
|
+
{entity}/
|
|
14
|
+
types/ # TypeScript interfaces and types
|
|
15
|
+
schemas/ # Validation schemas (Zod, Joi, etc.)
|
|
16
|
+
constants/ # Entity-specific constants
|
|
17
|
+
{entity}.service.js
|
|
18
|
+
{entity}.controller.js
|
|
19
|
+
{entity}.repository.js
|
|
20
|
+
{entity}.seed.js
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
### Examples
|
|
24
|
+
|
|
25
|
+
```
|
|
26
|
+
src/
|
|
27
|
+
user/
|
|
28
|
+
types/
|
|
29
|
+
user.types.ts
|
|
30
|
+
user-response.types.ts
|
|
31
|
+
schemas/
|
|
32
|
+
create-user.schema.ts
|
|
33
|
+
update-user.schema.ts
|
|
34
|
+
constants/
|
|
35
|
+
user.constants.ts
|
|
36
|
+
user.service.ts
|
|
37
|
+
user.controller.ts
|
|
38
|
+
user.repository.ts
|
|
39
|
+
user.seed.ts
|
|
40
|
+
order/
|
|
41
|
+
types/
|
|
42
|
+
order.types.ts
|
|
43
|
+
schemas/
|
|
44
|
+
create-order.schema.ts
|
|
45
|
+
constants/
|
|
46
|
+
order-status.constants.ts
|
|
47
|
+
order.service.ts
|
|
48
|
+
order.controller.ts
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### Never Do This
|
|
52
|
+
|
|
53
|
+
```
|
|
54
|
+
src/
|
|
55
|
+
services/ # NO - flat service folder
|
|
56
|
+
user.service.ts
|
|
57
|
+
order.service.ts
|
|
58
|
+
interfaces/ # NO - flat interface folder
|
|
59
|
+
user.interface.ts
|
|
60
|
+
order.interface.ts
|
|
61
|
+
types/ # NO - flat types folder
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## Type Definitions
|
|
65
|
+
|
|
66
|
+
### Types in Separate Files
|
|
67
|
+
|
|
68
|
+
All types must be in dedicated files under `types/` folder.
|
|
69
|
+
|
|
70
|
+
```typescript
|
|
71
|
+
// CORRECT: src/user/types/user.types.ts
|
|
72
|
+
export interface User {
|
|
73
|
+
id: string;
|
|
74
|
+
email: string;
|
|
75
|
+
name: string;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export interface CreateUserInput {
|
|
79
|
+
email: string;
|
|
80
|
+
name: string;
|
|
81
|
+
}
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### Never Inline Types in Services
|
|
85
|
+
|
|
86
|
+
```typescript
|
|
87
|
+
// WRONG: Inline interface in service
|
|
88
|
+
export class UserService {
|
|
89
|
+
async createUser(input: { email: string; name: string }): Promise<{ id: string; email: string }> {
|
|
90
|
+
// ...
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// CORRECT: Import types from types folder
|
|
95
|
+
import { CreateUserInput, User } from './types/user.types.js';
|
|
96
|
+
|
|
97
|
+
export class UserService {
|
|
98
|
+
async createUser(input: CreateUserInput): Promise<User> {
|
|
99
|
+
// ...
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
## Validation Schemas
|
|
105
|
+
|
|
106
|
+
Place all validation schemas in the `schemas/` folder.
|
|
107
|
+
|
|
108
|
+
```typescript
|
|
109
|
+
// src/user/schemas/create-user.schema.ts
|
|
110
|
+
import { z } from 'zod';
|
|
111
|
+
|
|
112
|
+
export const createUserSchema = z.object({
|
|
113
|
+
email: z.string().email(),
|
|
114
|
+
name: z.string().min(1).max(100),
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
export type CreateUserInput = z.infer<typeof createUserSchema>;
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
## Constants
|
|
121
|
+
|
|
122
|
+
### No Magic Strings
|
|
123
|
+
|
|
124
|
+
All repeated values must be defined as constants.
|
|
125
|
+
|
|
126
|
+
```typescript
|
|
127
|
+
// WRONG: Magic strings
|
|
128
|
+
if (user.role === 'admin') { ... }
|
|
129
|
+
if (status === 'pending') { ... }
|
|
130
|
+
|
|
131
|
+
// CORRECT: Use constants
|
|
132
|
+
import { UserRole } from './constants/user.constants.js';
|
|
133
|
+
import { OrderStatus } from '../order/constants/order-status.constants.js';
|
|
134
|
+
|
|
135
|
+
if (user.role === UserRole.ADMIN) { ... }
|
|
136
|
+
if (status === OrderStatus.PENDING) { ... }
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
### Constants File Structure
|
|
140
|
+
|
|
141
|
+
```typescript
|
|
142
|
+
// src/user/constants/user.constants.ts
|
|
143
|
+
export const UserRole = {
|
|
144
|
+
ADMIN: 'admin',
|
|
145
|
+
USER: 'user',
|
|
146
|
+
GUEST: 'guest',
|
|
147
|
+
} as const;
|
|
148
|
+
|
|
149
|
+
export type UserRole = typeof UserRole[keyof typeof UserRole];
|
|
150
|
+
|
|
151
|
+
export const USER_DEFAULTS = {
|
|
152
|
+
MAX_LOGIN_ATTEMPTS: 5,
|
|
153
|
+
SESSION_TIMEOUT_MS: 3600000,
|
|
154
|
+
} as const;
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
## Configuration
|
|
158
|
+
|
|
159
|
+
### No Hardcoded Values
|
|
160
|
+
|
|
161
|
+
Configuration values must come from environment variables.
|
|
162
|
+
|
|
163
|
+
```typescript
|
|
164
|
+
// WRONG: Hardcoded URL
|
|
165
|
+
const API_URL = 'https://api.example.com';
|
|
166
|
+
|
|
167
|
+
// CORRECT: From environment
|
|
168
|
+
const API_URL = process.env.API_URL;
|
|
169
|
+
|
|
170
|
+
// BETTER: Validated config module
|
|
171
|
+
import { config } from './config.js';
|
|
172
|
+
const apiUrl = config.apiUrl; // Validated at startup
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
### Config Module Pattern
|
|
176
|
+
|
|
177
|
+
```typescript
|
|
178
|
+
// src/config.ts
|
|
179
|
+
import { z } from 'zod';
|
|
180
|
+
|
|
181
|
+
const configSchema = z.object({
|
|
182
|
+
apiUrl: z.string().url(),
|
|
183
|
+
port: z.coerce.number().default(3000),
|
|
184
|
+
nodeEnv: z.enum(['development', 'production', 'test']),
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
export const config = configSchema.parse({
|
|
188
|
+
apiUrl: process.env.API_URL,
|
|
189
|
+
port: process.env.PORT,
|
|
190
|
+
nodeEnv: process.env.NODE_ENV,
|
|
191
|
+
});
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
## Seed Files
|
|
195
|
+
|
|
196
|
+
Each entity should have its own seed file for test and development data.
|
|
197
|
+
|
|
198
|
+
```typescript
|
|
199
|
+
// src/user/user.seed.ts
|
|
200
|
+
import { User } from './types/user.types.js';
|
|
201
|
+
|
|
202
|
+
export const userSeeds: User[] = [
|
|
203
|
+
{ id: '1', email: 'admin@example.com', name: 'Admin User' },
|
|
204
|
+
{ id: '2', email: 'user@example.com', name: 'Regular User' },
|
|
205
|
+
];
|
|
206
|
+
|
|
207
|
+
export async function seedUsers(repository: UserRepository): Promise<void> {
|
|
208
|
+
for (const user of userSeeds) {
|
|
209
|
+
await repository.create(user);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
## JSDoc Requirements
|
|
215
|
+
|
|
216
|
+
All public members must have JSDoc documentation.
|
|
217
|
+
|
|
218
|
+
### Functions
|
|
219
|
+
|
|
220
|
+
```typescript
|
|
221
|
+
/**
|
|
222
|
+
* Creates a new user in the system.
|
|
223
|
+
* @param input - The user creation input data
|
|
224
|
+
* @returns The created user with generated ID
|
|
225
|
+
* @throws {ValidationError} If input fails validation
|
|
226
|
+
* @throws {DuplicateError} If email already exists
|
|
227
|
+
*/
|
|
228
|
+
export async function createUser(input: CreateUserInput): Promise<User> {
|
|
229
|
+
// ...
|
|
230
|
+
}
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
### Classes
|
|
234
|
+
|
|
235
|
+
```typescript
|
|
236
|
+
/**
|
|
237
|
+
* Service for managing user operations.
|
|
238
|
+
* Handles user CRUD operations and authentication.
|
|
239
|
+
*/
|
|
240
|
+
export class UserService {
|
|
241
|
+
/**
|
|
242
|
+
* Creates a new UserService instance.
|
|
243
|
+
* @param repository - The user repository for data access
|
|
244
|
+
* @param eventBus - Event bus for publishing user events
|
|
245
|
+
*/
|
|
246
|
+
constructor(
|
|
247
|
+
private repository: UserRepository,
|
|
248
|
+
private eventBus: EventBus
|
|
249
|
+
) {}
|
|
250
|
+
}
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
### Interfaces
|
|
254
|
+
|
|
255
|
+
```typescript
|
|
256
|
+
/**
|
|
257
|
+
* Represents a user in the system.
|
|
258
|
+
*/
|
|
259
|
+
export interface User {
|
|
260
|
+
/** Unique identifier */
|
|
261
|
+
id: string;
|
|
262
|
+
/** User's email address (unique) */
|
|
263
|
+
email: string;
|
|
264
|
+
/** User's display name */
|
|
265
|
+
name: string;
|
|
266
|
+
/** Account creation timestamp */
|
|
267
|
+
createdAt: Date;
|
|
268
|
+
}
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
## File Naming Conventions
|
|
272
|
+
|
|
273
|
+
| Type | Pattern | Example |
|
|
274
|
+
|------|---------|---------|
|
|
275
|
+
| Service | `{entity}.service.ts` | `user.service.ts` |
|
|
276
|
+
| Controller | `{entity}.controller.ts` | `user.controller.ts` |
|
|
277
|
+
| Repository | `{entity}.repository.ts` | `user.repository.ts` |
|
|
278
|
+
| Types | `{entity}.types.ts` | `user.types.ts` |
|
|
279
|
+
| Schema | `{action}-{entity}.schema.ts` | `create-user.schema.ts` |
|
|
280
|
+
| Constants | `{entity}.constants.ts` | `user.constants.ts` |
|
|
281
|
+
| Seed | `{entity}.seed.ts` | `user.seed.ts` |
|
|
282
|
+
| Tests | `{file}.test.ts` | `user.service.test.ts` |
|
|
283
|
+
|
|
284
|
+
## Import Rules
|
|
285
|
+
|
|
286
|
+
### Use Path Aliases
|
|
287
|
+
|
|
288
|
+
```typescript
|
|
289
|
+
// CORRECT: Path alias
|
|
290
|
+
import { User } from '@/user/types/user.types.js';
|
|
291
|
+
|
|
292
|
+
// AVOID: Deep relative paths
|
|
293
|
+
import { User } from '../../../user/types/user.types.js';
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
### Import Order
|
|
297
|
+
|
|
298
|
+
1. Node built-ins (`fs`, `path`, `crypto`)
|
|
299
|
+
2. External packages (`express`, `zod`)
|
|
300
|
+
3. Internal modules using path aliases
|
|
301
|
+
4. Relative imports (same module only)
|
|
302
|
+
|
|
303
|
+
```typescript
|
|
304
|
+
import { readFile } from 'fs/promises';
|
|
305
|
+
|
|
306
|
+
import express from 'express';
|
|
307
|
+
import { z } from 'zod';
|
|
308
|
+
|
|
309
|
+
import { logger } from '@/lib/logger.js';
|
|
310
|
+
import { config } from '@/config.js';
|
|
311
|
+
|
|
312
|
+
import { UserService } from './user.service.js';
|
|
313
|
+
import { User } from './types/user.types.js';
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
## Service Design
|
|
317
|
+
|
|
318
|
+
### Single Responsibility
|
|
319
|
+
|
|
320
|
+
Each service handles one entity or domain concept.
|
|
321
|
+
|
|
322
|
+
```typescript
|
|
323
|
+
// CORRECT: Single responsibility
|
|
324
|
+
class UserService { /* user operations only */ }
|
|
325
|
+
class AuthService { /* authentication only */ }
|
|
326
|
+
class EmailService { /* email sending only */ }
|
|
327
|
+
|
|
328
|
+
// WRONG: Mixed responsibilities
|
|
329
|
+
class UserService {
|
|
330
|
+
sendEmail() { /* NO */ }
|
|
331
|
+
authenticate() { /* NO */ }
|
|
332
|
+
}
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
### Dependency Injection
|
|
336
|
+
|
|
337
|
+
Services receive dependencies through constructor.
|
|
338
|
+
|
|
339
|
+
```typescript
|
|
340
|
+
export class UserService {
|
|
341
|
+
constructor(
|
|
342
|
+
private repository: UserRepository,
|
|
343
|
+
private emailService: EmailService,
|
|
344
|
+
private eventBus: EventBus
|
|
345
|
+
) {}
|
|
346
|
+
}
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
## Error Handling
|
|
350
|
+
|
|
351
|
+
### Custom Error Classes
|
|
352
|
+
|
|
353
|
+
```typescript
|
|
354
|
+
// src/lib/errors/index.ts
|
|
355
|
+
export class AppError extends Error {
|
|
356
|
+
constructor(
|
|
357
|
+
message: string,
|
|
358
|
+
public code: string,
|
|
359
|
+
public statusCode: number = 500
|
|
360
|
+
) {
|
|
361
|
+
super(message);
|
|
362
|
+
this.name = this.constructor.name;
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
export class NotFoundError extends AppError {
|
|
367
|
+
constructor(entity: string, id: string) {
|
|
368
|
+
super(`${entity} not found: ${id}`, 'NOT_FOUND', 404);
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
export class ValidationError extends AppError {
|
|
373
|
+
constructor(message: string, public details?: unknown) {
|
|
374
|
+
super(message, 'VALIDATION_ERROR', 400);
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
### Error Handling in Services
|
|
380
|
+
|
|
381
|
+
```typescript
|
|
382
|
+
async function getUser(id: string): Promise<User> {
|
|
383
|
+
const user = await repository.findById(id);
|
|
384
|
+
if (!user) {
|
|
385
|
+
throw new NotFoundError('User', id);
|
|
386
|
+
}
|
|
387
|
+
return user;
|
|
388
|
+
}
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
### Error Handling in Controllers
|
|
392
|
+
|
|
393
|
+
```typescript
|
|
394
|
+
app.use((err: Error, req: Request, res: Response, next: NextFunction) => {
|
|
395
|
+
if (err instanceof AppError) {
|
|
396
|
+
return res.status(err.statusCode).json({
|
|
397
|
+
error: err.code,
|
|
398
|
+
message: err.message,
|
|
399
|
+
});
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
logger.error('Unhandled error', { error: err });
|
|
403
|
+
return res.status(500).json({
|
|
404
|
+
error: 'INTERNAL_ERROR',
|
|
405
|
+
message: 'An unexpected error occurred',
|
|
406
|
+
});
|
|
407
|
+
});
|
|
408
|
+
```
|
package/bin/install.js
CHANGED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
interface ThresholdConfig {
|
|
2
|
+
default: number;
|
|
3
|
+
dimensions?: Record<string, number>;
|
|
4
|
+
}
|
|
5
|
+
interface QualityGateConfig {
|
|
6
|
+
preset: string;
|
|
7
|
+
thresholds: ThresholdConfig;
|
|
8
|
+
}
|
|
9
|
+
interface EvaluationResult {
|
|
10
|
+
file: string;
|
|
11
|
+
pass: boolean;
|
|
12
|
+
composite: number;
|
|
13
|
+
scores?: Record<string, number>;
|
|
14
|
+
failed?: string[];
|
|
15
|
+
}
|
|
16
|
+
interface TrendData {
|
|
17
|
+
direction: 'improving' | 'declining' | 'stable';
|
|
18
|
+
slope: number;
|
|
19
|
+
}
|
|
20
|
+
interface HistoryRecord {
|
|
21
|
+
composite: number;
|
|
22
|
+
timestamp?: Date;
|
|
23
|
+
}
|
|
24
|
+
interface QualityGatePaneProps {
|
|
25
|
+
config?: QualityGateConfig;
|
|
26
|
+
evaluations?: EvaluationResult[];
|
|
27
|
+
currentEvaluation?: EvaluationResult;
|
|
28
|
+
history?: HistoryRecord[];
|
|
29
|
+
trend?: TrendData;
|
|
30
|
+
presets?: string[];
|
|
31
|
+
loading?: boolean;
|
|
32
|
+
error?: string;
|
|
33
|
+
onConfigure?: () => void;
|
|
34
|
+
onChangePreset?: (preset: string) => void;
|
|
35
|
+
onRetry?: () => void;
|
|
36
|
+
}
|
|
37
|
+
export declare function QualityGatePane({ config, evaluations, currentEvaluation, history, trend, presets, loading, error, onConfigure, onChangePreset, onRetry, }: QualityGatePaneProps): import("react/jsx-runtime").JSX.Element;
|
|
38
|
+
export {};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Box, Text } from 'ink';
|
|
3
|
+
export function QualityGatePane({ config, evaluations = [], currentEvaluation, history = [], trend, presets = ['fast', 'balanced', 'thorough', 'critical'], loading, error, onConfigure, onChangePreset, onRetry, }) {
|
|
4
|
+
if (loading) {
|
|
5
|
+
return (_jsxs(Box, { padding: 1, flexDirection: "column", children: [_jsx(Text, { bold: true, children: "Quality Gate" }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: "gray", children: "Loading..." }) })] }));
|
|
6
|
+
}
|
|
7
|
+
if (error) {
|
|
8
|
+
return (_jsxs(Box, { padding: 1, flexDirection: "column", children: [_jsx(Text, { bold: true, children: "Quality Gate" }), _jsx(Box, { marginTop: 1, children: _jsxs(Text, { color: "red", children: ["Error: ", error] }) })] }));
|
|
9
|
+
}
|
|
10
|
+
const getScoreColor = (score, threshold = 70) => {
|
|
11
|
+
if (score >= threshold)
|
|
12
|
+
return 'green';
|
|
13
|
+
if (score >= threshold - 15)
|
|
14
|
+
return 'yellow';
|
|
15
|
+
return 'red';
|
|
16
|
+
};
|
|
17
|
+
const getTrendArrow = (direction) => {
|
|
18
|
+
switch (direction) {
|
|
19
|
+
case 'improving':
|
|
20
|
+
return '↑';
|
|
21
|
+
case 'declining':
|
|
22
|
+
return '↓';
|
|
23
|
+
default:
|
|
24
|
+
return '→';
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
return (_jsxs(Box, { padding: 1, flexDirection: "column", children: [_jsx(Text, { bold: true, children: "Quality Gate" }), config && (_jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsxs(Box, { children: [_jsx(Text, { dimColor: true, children: "Preset: " }), _jsx(Text, { color: "cyan", children: config.preset })] }), _jsxs(Box, { children: [_jsx(Text, { dimColor: true, children: "Threshold: " }), _jsxs(Text, { children: [config.thresholds.default, "%"] })] })] })), config?.thresholds?.dimensions && (_jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsx(Text, { bold: true, dimColor: true, children: "Dimension Thresholds:" }), Object.entries(config.thresholds.dimensions).map(([dim, threshold]) => (_jsxs(Box, { children: [_jsxs(Text, { children: [" ", dim, ": "] }), _jsxs(Text, { color: "gray", children: [threshold, "%"] })] }, dim)))] })), currentEvaluation && (_jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsx(Text, { bold: true, dimColor: true, children: "Current Evaluation:" }), _jsxs(Box, { children: [_jsxs(Text, { children: [" ", currentEvaluation.file, ": "] }), _jsxs(Text, { color: currentEvaluation.pass ? 'green' : 'red', children: [currentEvaluation.composite, "% ", currentEvaluation.pass ? '✓' : '✗'] })] }), currentEvaluation.scores && (_jsx(Box, { flexDirection: "column", marginLeft: 2, children: Object.entries(currentEvaluation.scores).map(([dim, score]) => {
|
|
28
|
+
const isFailing = currentEvaluation.failed?.includes(dim);
|
|
29
|
+
return (_jsx(Box, { children: _jsxs(Text, { color: isFailing ? 'red' : 'gray', children: [dim, ": ", score, "%"] }) }, dim));
|
|
30
|
+
}) })), !currentEvaluation.pass && onRetry && (_jsx(Box, { marginTop: 1, children: _jsx(Text, { color: "yellow", children: "[Retry] \u21BB" }) }))] })), evaluations.length > 0 && (_jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsx(Text, { bold: true, dimColor: true, children: "Recent Evaluations:" }), evaluations.slice(0, 5).map((evalResult, idx) => (_jsxs(Box, { children: [_jsx(Text, { color: evalResult.pass ? 'green' : 'red', children: evalResult.pass ? '✓' : '✗' }), _jsxs(Text, { children: [" ", evalResult.file.split('/').pop(), ": "] }), _jsxs(Text, { color: getScoreColor(evalResult.composite, config?.thresholds?.default || 70), children: [evalResult.composite, "%"] })] }, idx)))] })), trend && (_jsxs(Box, { marginTop: 1, children: [_jsx(Text, { dimColor: true, children: "Trend: " }), _jsxs(Text, { color: trend.direction === 'improving' ? 'green' : trend.direction === 'declining' ? 'red' : 'gray', children: [getTrendArrow(trend.direction), " ", trend.direction] })] })), _jsx(Box, { marginTop: 1, children: onConfigure && (_jsx(Text, { color: "blue", children: "[Configure]" })) })] }));
|
|
31
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { describe, it, expect, vi } from 'vitest';
|
|
3
|
+
import { render } from 'ink-testing-library';
|
|
4
|
+
import { QualityGatePane } from './QualityGatePane.js';
|
|
5
|
+
describe('QualityGatePane', () => {
|
|
6
|
+
it('renders without error', () => {
|
|
7
|
+
const { lastFrame } = render(_jsx(QualityGatePane, {}));
|
|
8
|
+
expect(lastFrame()).toBeDefined();
|
|
9
|
+
});
|
|
10
|
+
it('renders current preset', () => {
|
|
11
|
+
const config = {
|
|
12
|
+
preset: 'balanced',
|
|
13
|
+
thresholds: { default: 70 },
|
|
14
|
+
};
|
|
15
|
+
const { lastFrame } = render(_jsx(QualityGatePane, { config: config }));
|
|
16
|
+
const output = lastFrame();
|
|
17
|
+
expect(output).toContain('balanced');
|
|
18
|
+
});
|
|
19
|
+
it('renders threshold bars', () => {
|
|
20
|
+
const config = {
|
|
21
|
+
preset: 'balanced',
|
|
22
|
+
thresholds: {
|
|
23
|
+
default: 70,
|
|
24
|
+
dimensions: {
|
|
25
|
+
style: 80,
|
|
26
|
+
correctness: 90,
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
const { lastFrame } = render(_jsx(QualityGatePane, { config: config }));
|
|
31
|
+
const output = lastFrame();
|
|
32
|
+
expect(output).toContain('70');
|
|
33
|
+
expect(output).toContain('80');
|
|
34
|
+
expect(output).toContain('90');
|
|
35
|
+
});
|
|
36
|
+
it('renders trend chart with history', () => {
|
|
37
|
+
const config = { preset: 'balanced', thresholds: { default: 70 } };
|
|
38
|
+
const history = [
|
|
39
|
+
{ composite: 65, timestamp: new Date('2024-01-01') },
|
|
40
|
+
{ composite: 70, timestamp: new Date('2024-01-02') },
|
|
41
|
+
{ composite: 75, timestamp: new Date('2024-01-03') },
|
|
42
|
+
];
|
|
43
|
+
const { lastFrame } = render(_jsx(QualityGatePane, { config: config, history: history }));
|
|
44
|
+
const output = lastFrame();
|
|
45
|
+
// Should show some trend visualization
|
|
46
|
+
expect(output).toBeDefined();
|
|
47
|
+
});
|
|
48
|
+
it('renders evaluations list with recent items', () => {
|
|
49
|
+
const config = { preset: 'balanced', thresholds: { default: 70 } };
|
|
50
|
+
const evaluations = [
|
|
51
|
+
{ file: 'src/index.js', pass: true, composite: 85 },
|
|
52
|
+
{ file: 'src/utils.js', pass: false, composite: 60 },
|
|
53
|
+
];
|
|
54
|
+
const { lastFrame } = render(_jsx(QualityGatePane, { config: config, evaluations: evaluations }));
|
|
55
|
+
const output = lastFrame();
|
|
56
|
+
expect(output).toContain('index.js');
|
|
57
|
+
expect(output).toContain('utils.js');
|
|
58
|
+
});
|
|
59
|
+
it('renders configure button', () => {
|
|
60
|
+
const onConfigure = vi.fn();
|
|
61
|
+
const { lastFrame } = render(_jsx(QualityGatePane, { config: { preset: 'fast', thresholds: { default: 60 } }, onConfigure: onConfigure }));
|
|
62
|
+
const output = lastFrame();
|
|
63
|
+
expect(output).toBeDefined();
|
|
64
|
+
});
|
|
65
|
+
it('renders preset selector', () => {
|
|
66
|
+
const onChangePreset = vi.fn();
|
|
67
|
+
const { lastFrame } = render(_jsx(QualityGatePane, { config: { preset: 'balanced', thresholds: { default: 70 } }, presets: ['fast', 'balanced', 'thorough', 'critical'], onChangePreset: onChangePreset }));
|
|
68
|
+
const output = lastFrame();
|
|
69
|
+
// Should show preset options or current preset
|
|
70
|
+
expect(output).toContain('balanced');
|
|
71
|
+
});
|
|
72
|
+
it('renders retry button when evaluation fails', () => {
|
|
73
|
+
const onRetry = vi.fn();
|
|
74
|
+
const evaluation = { file: 'test.js', pass: false, composite: 50 };
|
|
75
|
+
const { lastFrame } = render(_jsx(QualityGatePane, { config: { preset: 'balanced', thresholds: { default: 70 } }, currentEvaluation: evaluation, onRetry: onRetry }));
|
|
76
|
+
const output = lastFrame();
|
|
77
|
+
expect(output).toBeDefined();
|
|
78
|
+
});
|
|
79
|
+
it('handles loading state', () => {
|
|
80
|
+
const { lastFrame } = render(_jsx(QualityGatePane, { config: { preset: 'balanced', thresholds: { default: 70 } }, loading: true }));
|
|
81
|
+
const output = lastFrame();
|
|
82
|
+
expect(output).toBeDefined();
|
|
83
|
+
});
|
|
84
|
+
it('handles error state', () => {
|
|
85
|
+
const { lastFrame } = render(_jsx(QualityGatePane, { config: { preset: 'balanced', thresholds: { default: 70 } }, error: "Failed to load quality data" }));
|
|
86
|
+
const output = lastFrame();
|
|
87
|
+
expect(output).toBeDefined();
|
|
88
|
+
});
|
|
89
|
+
it('handles empty history', () => {
|
|
90
|
+
const { lastFrame } = render(_jsx(QualityGatePane, { config: { preset: 'balanced', thresholds: { default: 70 } }, history: [] }));
|
|
91
|
+
const output = lastFrame();
|
|
92
|
+
expect(output).toBeDefined();
|
|
93
|
+
// Should show empty state or placeholder
|
|
94
|
+
});
|
|
95
|
+
it('shows pass/fail indicator for evaluations', () => {
|
|
96
|
+
const evaluations = [
|
|
97
|
+
{ file: 'pass.js', pass: true, composite: 85 },
|
|
98
|
+
{ file: 'fail.js', pass: false, composite: 55 },
|
|
99
|
+
];
|
|
100
|
+
const { lastFrame } = render(_jsx(QualityGatePane, { config: { preset: 'balanced', thresholds: { default: 70 } }, evaluations: evaluations }));
|
|
101
|
+
const output = lastFrame();
|
|
102
|
+
expect(output).toBeDefined();
|
|
103
|
+
});
|
|
104
|
+
it('shows dimension breakdown', () => {
|
|
105
|
+
const evaluation = {
|
|
106
|
+
file: 'test.js',
|
|
107
|
+
pass: true,
|
|
108
|
+
composite: 82,
|
|
109
|
+
scores: {
|
|
110
|
+
style: 80,
|
|
111
|
+
completeness: 85,
|
|
112
|
+
correctness: 90,
|
|
113
|
+
documentation: 75,
|
|
114
|
+
},
|
|
115
|
+
};
|
|
116
|
+
const { lastFrame } = render(_jsx(QualityGatePane, { config: { preset: 'balanced', thresholds: { default: 70 } }, currentEvaluation: evaluation }));
|
|
117
|
+
const output = lastFrame();
|
|
118
|
+
expect(output).toBeDefined();
|
|
119
|
+
});
|
|
120
|
+
it('highlights failing dimensions', () => {
|
|
121
|
+
const evaluation = {
|
|
122
|
+
file: 'test.js',
|
|
123
|
+
pass: false,
|
|
124
|
+
composite: 65,
|
|
125
|
+
scores: {
|
|
126
|
+
style: 50,
|
|
127
|
+
correctness: 80,
|
|
128
|
+
},
|
|
129
|
+
failed: ['style'],
|
|
130
|
+
};
|
|
131
|
+
const { lastFrame } = render(_jsx(QualityGatePane, { config: { preset: 'balanced', thresholds: { default: 70 } }, currentEvaluation: evaluation }));
|
|
132
|
+
const output = lastFrame();
|
|
133
|
+
// Should highlight the failing dimension
|
|
134
|
+
expect(output).toBeDefined();
|
|
135
|
+
});
|
|
136
|
+
it('shows trend direction', () => {
|
|
137
|
+
const history = [
|
|
138
|
+
{ composite: 60 },
|
|
139
|
+
{ composite: 65 },
|
|
140
|
+
{ composite: 70 },
|
|
141
|
+
{ composite: 75 },
|
|
142
|
+
];
|
|
143
|
+
const { lastFrame } = render(_jsx(QualityGatePane, { config: { preset: 'balanced', thresholds: { default: 70 } }, history: history, trend: { direction: 'improving', slope: 5 } }));
|
|
144
|
+
const output = lastFrame();
|
|
145
|
+
expect(output).toBeDefined();
|
|
146
|
+
});
|
|
147
|
+
});
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
type AgentStatus = 'running' | 'queued' | 'completed' | 'failed' | 'paused' | 'cancelled';
|
|
2
|
+
interface AgentTokens {
|
|
3
|
+
input: number;
|
|
4
|
+
output: number;
|
|
5
|
+
}
|
|
6
|
+
interface QualityInfo {
|
|
7
|
+
score: number;
|
|
8
|
+
pass: boolean;
|
|
9
|
+
}
|
|
10
|
+
interface Agent {
|
|
11
|
+
id: string;
|
|
12
|
+
name?: string;
|
|
13
|
+
model: string;
|
|
14
|
+
status: AgentStatus;
|
|
15
|
+
startTime: Date;
|
|
16
|
+
tokens: AgentTokens;
|
|
17
|
+
cost: number;
|
|
18
|
+
quality?: QualityInfo;
|
|
19
|
+
}
|
|
20
|
+
interface AgentCardProps {
|
|
21
|
+
agent: Agent;
|
|
22
|
+
onClick?: () => void;
|
|
23
|
+
selected?: boolean;
|
|
24
|
+
}
|
|
25
|
+
export declare function AgentCard({ agent, onClick, selected }: AgentCardProps): import("react/jsx-runtime").JSX.Element;
|
|
26
|
+
export {};
|