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.
Files changed (122) hide show
  1. package/CLAUDE.md +23 -0
  2. package/CODING-STANDARDS.md +408 -0
  3. package/bin/install.js +2 -0
  4. package/dashboard/dist/components/QualityGatePane.d.ts +38 -0
  5. package/dashboard/dist/components/QualityGatePane.js +31 -0
  6. package/dashboard/dist/components/QualityGatePane.test.d.ts +1 -0
  7. package/dashboard/dist/components/QualityGatePane.test.js +147 -0
  8. package/dashboard/dist/components/orchestration/AgentCard.d.ts +26 -0
  9. package/dashboard/dist/components/orchestration/AgentCard.js +60 -0
  10. package/dashboard/dist/components/orchestration/AgentCard.test.d.ts +1 -0
  11. package/dashboard/dist/components/orchestration/AgentCard.test.js +63 -0
  12. package/dashboard/dist/components/orchestration/AgentControls.d.ts +11 -0
  13. package/dashboard/dist/components/orchestration/AgentControls.js +20 -0
  14. package/dashboard/dist/components/orchestration/AgentControls.test.d.ts +1 -0
  15. package/dashboard/dist/components/orchestration/AgentControls.test.js +52 -0
  16. package/dashboard/dist/components/orchestration/AgentDetail.d.ts +35 -0
  17. package/dashboard/dist/components/orchestration/AgentDetail.js +37 -0
  18. package/dashboard/dist/components/orchestration/AgentDetail.test.d.ts +1 -0
  19. package/dashboard/dist/components/orchestration/AgentDetail.test.js +79 -0
  20. package/dashboard/dist/components/orchestration/AgentList.d.ts +31 -0
  21. package/dashboard/dist/components/orchestration/AgentList.js +47 -0
  22. package/dashboard/dist/components/orchestration/AgentList.test.d.ts +1 -0
  23. package/dashboard/dist/components/orchestration/AgentList.test.js +64 -0
  24. package/dashboard/dist/components/orchestration/CostMeter.d.ts +11 -0
  25. package/dashboard/dist/components/orchestration/CostMeter.js +28 -0
  26. package/dashboard/dist/components/orchestration/CostMeter.test.d.ts +1 -0
  27. package/dashboard/dist/components/orchestration/CostMeter.test.js +50 -0
  28. package/dashboard/dist/components/orchestration/ModelSelector.d.ts +20 -0
  29. package/dashboard/dist/components/orchestration/ModelSelector.js +12 -0
  30. package/dashboard/dist/components/orchestration/ModelSelector.test.d.ts +1 -0
  31. package/dashboard/dist/components/orchestration/ModelSelector.test.js +56 -0
  32. package/dashboard/dist/components/orchestration/OrchestrationDashboard.d.ts +28 -0
  33. package/dashboard/dist/components/orchestration/OrchestrationDashboard.js +28 -0
  34. package/dashboard/dist/components/orchestration/OrchestrationDashboard.test.d.ts +1 -0
  35. package/dashboard/dist/components/orchestration/OrchestrationDashboard.test.js +56 -0
  36. package/dashboard/dist/components/orchestration/QualityIndicator.d.ts +11 -0
  37. package/dashboard/dist/components/orchestration/QualityIndicator.js +37 -0
  38. package/dashboard/dist/components/orchestration/QualityIndicator.test.d.ts +1 -0
  39. package/dashboard/dist/components/orchestration/QualityIndicator.test.js +52 -0
  40. package/dashboard/dist/components/orchestration/index.d.ts +8 -0
  41. package/dashboard/dist/components/orchestration/index.js +8 -0
  42. package/package.json +1 -1
  43. package/server/lib/access-control.js +352 -0
  44. package/server/lib/access-control.test.js +322 -0
  45. package/server/lib/agents-cancel-command.js +139 -0
  46. package/server/lib/agents-cancel-command.test.js +180 -0
  47. package/server/lib/agents-get-command.js +159 -0
  48. package/server/lib/agents-get-command.test.js +167 -0
  49. package/server/lib/agents-list-command.js +150 -0
  50. package/server/lib/agents-list-command.test.js +149 -0
  51. package/server/lib/agents-logs-command.js +126 -0
  52. package/server/lib/agents-logs-command.test.js +198 -0
  53. package/server/lib/agents-retry-command.js +117 -0
  54. package/server/lib/agents-retry-command.test.js +192 -0
  55. package/server/lib/budget-limits.js +222 -0
  56. package/server/lib/budget-limits.test.js +214 -0
  57. package/server/lib/code-generator.js +291 -0
  58. package/server/lib/code-generator.test.js +307 -0
  59. package/server/lib/cost-command.js +290 -0
  60. package/server/lib/cost-command.test.js +202 -0
  61. package/server/lib/cost-optimizer.js +404 -0
  62. package/server/lib/cost-optimizer.test.js +232 -0
  63. package/server/lib/cost-projections.js +302 -0
  64. package/server/lib/cost-projections.test.js +217 -0
  65. package/server/lib/cost-reports.js +277 -0
  66. package/server/lib/cost-reports.test.js +254 -0
  67. package/server/lib/cost-tracker.js +216 -0
  68. package/server/lib/cost-tracker.test.js +302 -0
  69. package/server/lib/crypto-patterns.js +433 -0
  70. package/server/lib/crypto-patterns.test.js +346 -0
  71. package/server/lib/design-command.js +385 -0
  72. package/server/lib/design-command.test.js +249 -0
  73. package/server/lib/design-parser.js +237 -0
  74. package/server/lib/design-parser.test.js +290 -0
  75. package/server/lib/gemini-vision.js +377 -0
  76. package/server/lib/gemini-vision.test.js +282 -0
  77. package/server/lib/input-validator.js +360 -0
  78. package/server/lib/input-validator.test.js +295 -0
  79. package/server/lib/litellm-client.js +232 -0
  80. package/server/lib/litellm-client.test.js +267 -0
  81. package/server/lib/litellm-command.js +291 -0
  82. package/server/lib/litellm-command.test.js +260 -0
  83. package/server/lib/litellm-config.js +273 -0
  84. package/server/lib/litellm-config.test.js +212 -0
  85. package/server/lib/model-pricing.js +189 -0
  86. package/server/lib/model-pricing.test.js +178 -0
  87. package/server/lib/models-command.js +223 -0
  88. package/server/lib/models-command.test.js +193 -0
  89. package/server/lib/optimize-command.js +197 -0
  90. package/server/lib/optimize-command.test.js +193 -0
  91. package/server/lib/orchestration-integration.js +206 -0
  92. package/server/lib/orchestration-integration.test.js +235 -0
  93. package/server/lib/output-encoder.js +308 -0
  94. package/server/lib/output-encoder.test.js +312 -0
  95. package/server/lib/quality-evaluator.js +396 -0
  96. package/server/lib/quality-evaluator.test.js +337 -0
  97. package/server/lib/quality-gate-command.js +340 -0
  98. package/server/lib/quality-gate-command.test.js +321 -0
  99. package/server/lib/quality-gate-scorer.js +378 -0
  100. package/server/lib/quality-gate-scorer.test.js +376 -0
  101. package/server/lib/quality-history.js +265 -0
  102. package/server/lib/quality-history.test.js +359 -0
  103. package/server/lib/quality-presets.js +288 -0
  104. package/server/lib/quality-presets.test.js +269 -0
  105. package/server/lib/quality-retry.js +323 -0
  106. package/server/lib/quality-retry.test.js +325 -0
  107. package/server/lib/quality-thresholds.js +255 -0
  108. package/server/lib/quality-thresholds.test.js +237 -0
  109. package/server/lib/secure-auth.js +333 -0
  110. package/server/lib/secure-auth.test.js +288 -0
  111. package/server/lib/secure-code-command.js +540 -0
  112. package/server/lib/secure-code-command.test.js +309 -0
  113. package/server/lib/secure-errors.js +521 -0
  114. package/server/lib/secure-errors.test.js +298 -0
  115. package/server/lib/vision-command.js +372 -0
  116. package/server/lib/vision-command.test.js +255 -0
  117. package/server/lib/visual-command.js +350 -0
  118. package/server/lib/visual-command.test.js +256 -0
  119. package/server/lib/visual-testing.js +315 -0
  120. package/server/lib/visual-testing.test.js +357 -0
  121. package/server/package-lock.json +2 -2
  122. 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
@@ -65,6 +65,8 @@ const COMMANDS = [
65
65
  // Multi-Tool & Deployment
66
66
  'export.md',
67
67
  'deploy.md',
68
+ // Multi-Model
69
+ 'llm.md',
68
70
  // Help
69
71
  'help.md'
70
72
  ];
@@ -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,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 {};