dialectic 0.1.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.
Files changed (119) hide show
  1. package/.cursor/commands/setup-test.mdc +175 -0
  2. package/.cursor/rules/basic-code-cleanup.mdc +1110 -0
  3. package/.cursor/rules/riper5.mdc +96 -0
  4. package/.env.example +6 -0
  5. package/AGENTS.md +1052 -0
  6. package/LICENSE +21 -0
  7. package/README.md +93 -0
  8. package/WARP.md +113 -0
  9. package/dialectic-1.0.0.tgz +0 -0
  10. package/dialectic.js +10 -0
  11. package/docs/commands.md +375 -0
  12. package/docs/configuration.md +882 -0
  13. package/docs/context_summarization.md +1023 -0
  14. package/docs/debate_flow.md +1127 -0
  15. package/docs/eval_flow.md +795 -0
  16. package/docs/evaluator.md +141 -0
  17. package/examples/debate-config-openrouter.json +48 -0
  18. package/examples/debate_config1.json +48 -0
  19. package/examples/eval/eval1/eval_config1.json +13 -0
  20. package/examples/eval/eval1/result1.json +62 -0
  21. package/examples/eval/eval1/result2.json +97 -0
  22. package/examples/eval_summary_format.md +11 -0
  23. package/examples/example3/debate-config.json +64 -0
  24. package/examples/example3/eval_config2.json +25 -0
  25. package/examples/example3/problem.md +17 -0
  26. package/examples/example3/rounds_test/eval_run.sh +16 -0
  27. package/examples/example3/rounds_test/run_test.sh +16 -0
  28. package/examples/kata1/architect-only-solution_2-rounds.json +121 -0
  29. package/examples/kata1/architect-perf-solution_2-rounds.json +234 -0
  30. package/examples/kata1/debate-config-kata1.json +54 -0
  31. package/examples/kata1/eval_architect-only_2-rounds.json +97 -0
  32. package/examples/kata1/eval_architect-perf_2-rounds.json +97 -0
  33. package/examples/kata1/kata1-report.md +12224 -0
  34. package/examples/kata1/kata1-report_temps-01_01_01_07.md +2451 -0
  35. package/examples/kata1/kata1.md +5 -0
  36. package/examples/kata1/meta.txt +1 -0
  37. package/examples/kata2/debate-config.json +54 -0
  38. package/examples/kata2/eval_config1.json +21 -0
  39. package/examples/kata2/eval_config2.json +25 -0
  40. package/examples/kata2/kata2.md +5 -0
  41. package/examples/kata2/only_architect/debate-config.json +45 -0
  42. package/examples/kata2/only_architect/eval_run.sh +11 -0
  43. package/examples/kata2/only_architect/run_test.sh +5 -0
  44. package/examples/kata2/rounds_test/eval_run.sh +11 -0
  45. package/examples/kata2/rounds_test/run_test.sh +5 -0
  46. package/examples/kata2/summary_length_test/eval_run.sh +11 -0
  47. package/examples/kata2/summary_length_test/eval_run_w_clarify.sh +7 -0
  48. package/examples/kata2/summary_length_test/run_test.sh +5 -0
  49. package/examples/task-queue/debate-config.json +76 -0
  50. package/examples/task-queue/debate_report.md +566 -0
  51. package/examples/task-queue/task-queue-system.md +25 -0
  52. package/jest.config.ts +13 -0
  53. package/multi_agent_debate_spec.md +2980 -0
  54. package/package.json +38 -0
  55. package/sanity-check-problem.txt +9 -0
  56. package/src/agents/prompts/architect-prompts.ts +203 -0
  57. package/src/agents/prompts/generalist-prompts.ts +157 -0
  58. package/src/agents/prompts/index.ts +41 -0
  59. package/src/agents/prompts/judge-prompts.ts +19 -0
  60. package/src/agents/prompts/kiss-prompts.ts +230 -0
  61. package/src/agents/prompts/performance-prompts.ts +142 -0
  62. package/src/agents/prompts/prompt-types.ts +68 -0
  63. package/src/agents/prompts/security-prompts.ts +149 -0
  64. package/src/agents/prompts/shared.ts +144 -0
  65. package/src/agents/prompts/testing-prompts.ts +149 -0
  66. package/src/agents/role-based-agent.ts +386 -0
  67. package/src/cli/commands/debate.ts +761 -0
  68. package/src/cli/commands/eval.ts +475 -0
  69. package/src/cli/commands/report.ts +265 -0
  70. package/src/cli/index.ts +79 -0
  71. package/src/core/agent.ts +198 -0
  72. package/src/core/clarifications.ts +34 -0
  73. package/src/core/judge.ts +257 -0
  74. package/src/core/orchestrator.ts +432 -0
  75. package/src/core/state-manager.ts +322 -0
  76. package/src/eval/evaluator-agent.ts +130 -0
  77. package/src/eval/prompts/system.md +41 -0
  78. package/src/eval/prompts/user.md +64 -0
  79. package/src/providers/llm-provider.ts +25 -0
  80. package/src/providers/openai-provider.ts +84 -0
  81. package/src/providers/openrouter-provider.ts +122 -0
  82. package/src/providers/provider-factory.ts +64 -0
  83. package/src/types/agent.types.ts +141 -0
  84. package/src/types/config.types.ts +47 -0
  85. package/src/types/debate.types.ts +237 -0
  86. package/src/types/eval.types.ts +85 -0
  87. package/src/utils/common.ts +104 -0
  88. package/src/utils/context-formatter.ts +102 -0
  89. package/src/utils/context-summarizer.ts +143 -0
  90. package/src/utils/env-loader.ts +46 -0
  91. package/src/utils/exit-codes.ts +5 -0
  92. package/src/utils/id.ts +11 -0
  93. package/src/utils/logger.ts +48 -0
  94. package/src/utils/paths.ts +10 -0
  95. package/src/utils/progress-ui.ts +313 -0
  96. package/src/utils/prompt-loader.ts +79 -0
  97. package/src/utils/report-generator.ts +301 -0
  98. package/tests/clarifications.spec.ts +128 -0
  99. package/tests/cli.debate.spec.ts +144 -0
  100. package/tests/config-loading.spec.ts +206 -0
  101. package/tests/context-summarizer.spec.ts +131 -0
  102. package/tests/debate-config-custom.json +38 -0
  103. package/tests/env-loader.spec.ts +149 -0
  104. package/tests/eval.command.spec.ts +1191 -0
  105. package/tests/logger.spec.ts +19 -0
  106. package/tests/openai-provider.spec.ts +26 -0
  107. package/tests/openrouter-provider.spec.ts +279 -0
  108. package/tests/orchestrator-summary.spec.ts +386 -0
  109. package/tests/orchestrator.spec.ts +207 -0
  110. package/tests/prompt-loader.spec.ts +52 -0
  111. package/tests/prompts/architect.md +16 -0
  112. package/tests/provider-factory.spec.ts +150 -0
  113. package/tests/report.command.spec.ts +546 -0
  114. package/tests/role-based-agent-summary.spec.ts +476 -0
  115. package/tests/security-agent.spec.ts +221 -0
  116. package/tests/shared-prompts.spec.ts +318 -0
  117. package/tests/state-manager.spec.ts +251 -0
  118. package/tests/summary-prompts.spec.ts +153 -0
  119. package/tsconfig.json +49 -0
@@ -0,0 +1,1110 @@
1
+ ---
2
+ alwaysApply: false
3
+ ---
4
+ # Code Cleanup and Refactoring Rules for TypeScript Projects
5
+
6
+ ## Overview
7
+ This guide explains systematic code cleanup procedures focusing on three key areas: template method refactoring, removing magic numbers, and proper documentation. Follow these rules to improve code maintainability, readability, and testability.
8
+
9
+ ---
10
+
11
+ ## 1. Template Method Refactoring
12
+
13
+ ### What is it?
14
+ When multiple subclasses have similar methods with duplicate logic, extract the common parts into a template method in the base class. Subclasses then only provide the unique parts (like prompts or specific data) and delegate the execution to the template method.
15
+
16
+ ### When to use it?
17
+ - You see the same code structure repeated across subclasses
18
+ - Only specific values (prompts, parameters) differ between implementations
19
+ - Methods follow the same execution pattern (call API, build metadata, return result)
20
+
21
+ ### Step-by-Step Process
22
+
23
+ **Before - Duplicate code in subclasses:**
24
+
25
+ ```typescript
26
+ // ArchitectAgent
27
+ async propose(problem: string, context: DebateContext): Promise<Proposal> {
28
+ const system = this.config.systemPrompt || ARCHITECT_SYSTEM_PROMPT;
29
+ const user = `Problem: ${problem}\n\nProvide architectural solution...`;
30
+ const { text, usage, latencyMs } = await this.callLLM(system, user);
31
+ const metadata: ContributionMetadata = { latencyMs, model: this.config.model };
32
+ if (usage?.totalTokens != null) metadata.tokensUsed = usage.totalTokens;
33
+ return { content: text, metadata };
34
+ }
35
+
36
+ // PerformanceAgent - SAME LOGIC, different prompts
37
+ async propose(problem: string, context: DebateContext): Promise<Proposal> {
38
+ const system = this.config.systemPrompt || PERFORMANCE_SYSTEM_PROMPT;
39
+ const user = `Problem: ${problem}\n\nProvide performance solution...`;
40
+ const { text, usage, latencyMs } = await this.callLLM(system, user);
41
+ const metadata: ContributionMetadata = { latencyMs, model: this.config.model };
42
+ if (usage?.totalTokens != null) metadata.tokensUsed = usage.totalTokens;
43
+ return { content: text, metadata };
44
+ }
45
+ ```
46
+
47
+ **After - Template method in base class:**
48
+
49
+ ```typescript
50
+ // Base Agent class
51
+ /**
52
+ * Template method for generating proposals.
53
+ * Subclasses should call this method from their `propose` implementation after preparing prompts.
54
+ *
55
+ * @final
56
+ * @param _context - The current debate context (unused in base implementation).
57
+ * @param systemPrompt - The system prompt to use for the LLM.
58
+ * @param userPrompt - The user prompt to use for the LLM.
59
+ * @returns A Promise resolving to a Proposal object containing the agent's solution and metadata.
60
+ */
61
+ protected async proposeImpl(
62
+ _context: DebateContext,
63
+ systemPrompt: string,
64
+ userPrompt: string
65
+ ): Promise<Proposal> {
66
+ const { text, usage, latencyMs } = await this.callLLM(systemPrompt, userPrompt);
67
+ const metadata: ContributionMetadata = { latencyMs, model: this.config.model };
68
+ if (usage?.totalTokens != null) metadata.tokensUsed = usage.totalTokens;
69
+ return { content: text, metadata };
70
+ }
71
+
72
+ // ArchitectAgent - only prompts, no duplicate logic
73
+ async propose(problem: string, context: DebateContext): Promise<Proposal> {
74
+ const system = this.config.systemPrompt || ARCHITECT_SYSTEM_PROMPT;
75
+ const user = `Problem: ${problem}\n\nProvide architectural solution...`;
76
+ return this.proposeImpl(context, system, user);
77
+ }
78
+
79
+ // PerformanceAgent - clean and simple
80
+ async propose(problem: string, context: DebateContext): Promise<Proposal> {
81
+ const system = this.config.systemPrompt || PERFORMANCE_SYSTEM_PROMPT;
82
+ const user = `Problem: ${problem}\n\nProvide performance solution...`;
83
+ return this.proposeImpl(context, system, user);
84
+ }
85
+ ```
86
+
87
+ ### Benefits
88
+ ✅ **No code duplication** - Common logic exists in one place
89
+ ✅ **Easy to maintain** - Fix bugs once, applies to all subclasses
90
+ ✅ **Clear separation** - Subclasses focus on their unique concerns
91
+ ✅ **Consistent behavior** - All agents handle metadata the same way
92
+
93
+ ---
94
+
95
+ ## 2. Removing Magic Numbers and Hardcoded Strings
96
+
97
+ ### What are they?
98
+ Magic numbers/strings are literal values hardcoded in your code without explanation. They make code harder to understand and maintain.
99
+
100
+ ### Step-by-Step Process
101
+
102
+ **Bad - Magic numbers and hardcoded strings:**
103
+
104
+ ```typescript
105
+ function buildAgents(configs: AgentConfig[], provider: Provider) {
106
+ return configs.map((cfg) => {
107
+ if (cfg.role === 'architect') return new ArchitectAgent(cfg, provider);
108
+ if (cfg.role === 'performance') return new PerformanceAgent(cfg, provider);
109
+ return new ArchitectAgent(cfg, provider);
110
+ });
111
+ }
112
+
113
+ const debateConfig = {
114
+ rounds: options.rounds || config.rounds || 3, // What is 3?
115
+ timeout: 300000 // What is 300000?
116
+ };
117
+ ```
118
+
119
+ **Good - Named constants:**
120
+
121
+ ```typescript
122
+ // File-level constants with clear names
123
+ const DEFAULT_ROUNDS = 3;
124
+ const DEFAULT_TIMEOUT_MS = 300000;
125
+
126
+ export const AGENT_ROLES = {
127
+ ARCHITECT: "architect",
128
+ SECURITY: "security",
129
+ PERFORMANCE: "performance",
130
+ TESTING: "testing",
131
+ GENERALIST: "generalist",
132
+ } as const;
133
+
134
+ export type AgentRole = (typeof AGENT_ROLES)[keyof typeof AGENT_ROLES];
135
+
136
+ // Now the code is self-documenting
137
+ function buildAgents(configs: AgentConfig[], provider: Provider) {
138
+ return configs.map((cfg) => {
139
+ if (cfg.role === AGENT_ROLES.ARCHITECT) return new ArchitectAgent(cfg, provider);
140
+ if (cfg.role === AGENT_ROLES.PERFORMANCE) return new PerformanceAgent(cfg, provider);
141
+ return new ArchitectAgent(cfg, provider);
142
+ });
143
+ }
144
+
145
+ const debateConfig = {
146
+ rounds: options.rounds || config.rounds || DEFAULT_ROUNDS,
147
+ timeout: DEFAULT_TIMEOUT_MS
148
+ };
149
+ ```
150
+
151
+ ### Where to define constants
152
+
153
+ **File-level constants** - Use when only one file needs them:
154
+ ```typescript
155
+ // At top of file, after imports
156
+ const DEFAULT_ROUNDS = 3;
157
+ const MAX_RETRIES = 5;
158
+ ```
159
+
160
+ **Exported constants** - Use when multiple files need them:
161
+ ```typescript
162
+ // In types/constants file
163
+ export const AGENT_ROLES = {
164
+ ARCHITECT: "architect",
165
+ PERFORMANCE: "performance",
166
+ } as const;
167
+
168
+ export const LLM_PROVIDERS = {
169
+ OPENAI: "openai",
170
+ } as const;
171
+ ```
172
+
173
+ ### Benefits
174
+ ✅ **Self-documenting** - Name explains what the value means
175
+ ✅ **Single source of truth** - Change value in one place
176
+ ✅ **Type safety** - TypeScript can help catch errors
177
+ ✅ **Easier to find** - Search for constant name, not a number
178
+
179
+ ---
180
+
181
+ ## 3. Documentation Standards
182
+
183
+ ### JSDoc Comments
184
+
185
+ Every public function, class, and complex method should have JSDoc documentation.
186
+
187
+ **Template for functions:**
188
+
189
+ ```typescript
190
+ /**
191
+ * Brief one-line description of what the function does.
192
+ *
193
+ * Optional longer description explaining the function's behavior,
194
+ * edge cases, or important details.
195
+ *
196
+ * @param paramName - Description of the parameter.
197
+ * @param options - Command-line options containing optional settings.
198
+ * @returns Description of what the function returns.
199
+ * @throws {ErrorType} When and why this error is thrown.
200
+ */
201
+ function myFunction(paramName: string, options: any): ReturnType {
202
+ // implementation
203
+ }
204
+ ```
205
+
206
+ **Example - Well-documented function:**
207
+
208
+ ```typescript
209
+ /**
210
+ * Creates a DebateConfig from the system configuration and command-line options.
211
+ * Validates that the number of rounds is at least 1.
212
+ *
213
+ * @param sysConfig - The system configuration.
214
+ * @param options - Command-line options containing optional rounds override.
215
+ * @returns The debate configuration.
216
+ * @throws {Error} If rounds is less than 1.
217
+ */
218
+ function debateConfigFromSysConfig(sysConfig: SystemConfig, options: any): DebateConfig {
219
+ const debateCfg: DebateConfig = {
220
+ ...sysConfig.debate!,
221
+ rounds: options.rounds ? parseInt(options.rounds, 10) : (sysConfig.debate?.rounds ?? DEFAULT_ROUNDS),
222
+ } as DebateConfig;
223
+
224
+ if (!debateCfg.rounds || debateCfg.rounds < 1) {
225
+ const err: any = new Error('Invalid arguments: --rounds must be >= 1');
226
+ err.code = EXIT_INVALID_ARGS;
227
+ throw err;
228
+ }
229
+
230
+ return debateCfg;
231
+ }
232
+ ```
233
+
234
+ ### Special Tags
235
+
236
+ **@final** - Indicates a method should not be overridden:
237
+
238
+ ```typescript
239
+ /**
240
+ * Template method for generating proposals.
241
+ *
242
+ * @final
243
+ * @param systemPrompt - The system prompt to use for the LLM.
244
+ * @param userPrompt - The user prompt to use for the LLM.
245
+ * @returns A Promise resolving to a Proposal.
246
+ */
247
+ protected async proposeImpl(systemPrompt: string, userPrompt: string): Promise<Proposal> {
248
+ // implementation
249
+ }
250
+ ```
251
+
252
+ ### Inline Comments for Non-Obvious Choices
253
+
254
+ When you make a non-obvious technical choice, explain why:
255
+
256
+ ```typescript
257
+ // Use process.stderr.write for immediate, unbuffered output with precise newline control (CLI best practice)
258
+ process.stderr.write(color('yellow', 'Config missing agents. Using built-in defaults.') + '\n');
259
+
260
+ // NOT console.error() because:
261
+ // - process.stderr.write gives precise control over newlines
262
+ // - It's unbuffered for immediate output
263
+ // - Standard practice for professional CLI tools
264
+ ```
265
+
266
+ ### What NOT to document
267
+
268
+ Don't document obvious things:
269
+
270
+ ```typescript
271
+ // ❌ BAD - Obvious from code
272
+ // Increment counter by one
273
+ counter++;
274
+
275
+ // ✅ GOOD - Explains WHY
276
+ // Skip first element as it's the header row
277
+ for (let i = 1; i < rows.length; i++) {
278
+ processRow(rows[i]);
279
+ }
280
+ ```
281
+
282
+ ---
283
+
284
+ ## 4. Complete Example: Before and After
285
+
286
+ ### Before - Needs cleanup:
287
+
288
+ ```typescript
289
+ class PerformanceAgent extends Agent {
290
+ async propose(problem: string, context: DebateContext): Promise<Proposal> {
291
+ const system = this.config.systemPrompt || "You are a performance engineer...";
292
+ const user = `Problem: ${problem}\n\nPropose solution...`;
293
+ const { text, usage, latencyMs } = await this.callLLM(system, user);
294
+ const metadata: any = { latencyMs, model: this.config.model };
295
+ if (usage?.totalTokens != null) metadata.tokensUsed = usage.totalTokens;
296
+ return { content: text, metadata };
297
+ }
298
+
299
+ async critique(proposal: Proposal, context: DebateContext): Promise<Critique> {
300
+ const system = this.config.systemPrompt || "You are a performance engineer...";
301
+ const user = `Review: ${proposal.content}`;
302
+ const { text, usage, latencyMs } = await this.callLLM(system, user);
303
+ const metadata: any = { latencyMs, model: this.config.model };
304
+ if (usage?.totalTokens != null) metadata.tokensUsed = usage.totalTokens;
305
+ return { content: text, metadata };
306
+ }
307
+ }
308
+ ```
309
+
310
+ ### After - Clean and maintainable:
311
+
312
+ ```typescript
313
+ // Constants at file level
314
+ const DEFAULT_PERFORMANCE_SYSTEM_PROMPT = `You are a performance engineer specializing in system optimization, profiling, and resource management.`;
315
+
316
+ /**
317
+ * PerformanceAgent is an AI agent specializing in system performance optimization.
318
+ *
319
+ * Focuses on latency, throughput, resource utilization, caching strategies,
320
+ * and algorithmic complexity.
321
+ *
322
+ * Note: This class cannot be extended. Use the static `create` factory method to instantiate.
323
+ */
324
+ export class PerformanceAgent extends Agent {
325
+ /**
326
+ * Private constructor to prevent direct instantiation and extension.
327
+ * Use the static `create` method instead.
328
+ */
329
+ private constructor(config: AgentConfig, provider: LLMProvider) {
330
+ super(config, provider);
331
+ }
332
+
333
+ /**
334
+ * Factory method to create a new PerformanceAgent instance.
335
+ */
336
+ static create(config: AgentConfig, provider: LLMProvider): PerformanceAgent {
337
+ return new PerformanceAgent(config, provider);
338
+ }
339
+
340
+ /**
341
+ * Generates a performance-focused proposal for the given problem.
342
+ * @param problem - The software/system design problem to solve.
343
+ * @param context - The current debate context.
344
+ * @returns A Promise resolving to a Proposal containing the solution and metadata.
345
+ */
346
+ async propose(problem: string, context: DebateContext): Promise<Proposal> {
347
+ const system = this.config.systemPrompt || DEFAULT_PERFORMANCE_SYSTEM_PROMPT;
348
+ const user = `Problem: ${problem}\n\nAs a performance engineer, propose a solution focusing on latency, throughput, and resource efficiency.`;
349
+ return this.proposeImpl(context, system, user);
350
+ }
351
+
352
+ /**
353
+ * Critiques a given proposal from a performance engineering perspective.
354
+ * @param proposal - The proposal to critique.
355
+ * @param context - The current debate context.
356
+ * @returns A Promise resolving to a Critique containing the review and metadata.
357
+ */
358
+ async critique(proposal: Proposal, context: DebateContext): Promise<Critique> {
359
+ const system = this.config.systemPrompt || DEFAULT_PERFORMANCE_SYSTEM_PROMPT;
360
+ const user = `Review this proposal as a performance engineer. Identify bottlenecks and improvements.\n\nProposal:\n${proposal.content}`;
361
+ return this.critiqueImpl(proposal, context, system, user);
362
+ }
363
+ }
364
+ ```
365
+
366
+ ---
367
+
368
+ ## 5. Checklist for Code Cleanup
369
+
370
+ When reviewing code, check:
371
+
372
+ - [ ] **No duplicate logic** - Extract common patterns to base classes or utility functions
373
+ - [ ] **No magic numbers** - All literal numbers replaced with named constants
374
+ - [ ] **No hardcoded strings** - Especially for types, roles, statuses - use constants
375
+ - [ ] **All public APIs documented** - JSDoc for functions, classes, complex methods
376
+ - [ ] **Non-obvious choices explained** - Inline comments for "why" not "what"
377
+ - [ ] **Constants properly scoped** - File-level for local, exported for shared
378
+ - [ ] **Template methods marked @final** - When they shouldn't be overridden
379
+ - [ ] **Function extraction** - Large functions broken into smaller, focused ones
380
+ - [ ] **Separation of concerns** - Each function has a single, clear purpose
381
+
382
+ ---
383
+
384
+ ## Quick Reference
385
+
386
+ | Problem | Solution | Example |
387
+ |---------|----------|---------|
388
+ | Duplicate method logic | Template method pattern | `proposeImpl()` in base class |
389
+ | Magic number `3` | Named constant | `DEFAULT_ROUNDS = 3` |
390
+ | String `"architect"` | Constant object | `AGENT_ROLES.ARCHITECT` |
391
+ | Long complex function | Extract helper functions | `debateConfigFromSysConfig()` |
392
+ | Unclear technical choice | Inline comment | `// Use stderr.write for unbuffered output` |
393
+ | Public function | JSDoc with @param/@returns | See examples above |
394
+ ---
395
+
396
+ ## When to Use Type Assertions (`as Type`)
397
+
398
+ Type assertions should be used **sparingly** and only in specific scenarios where you have information that TypeScript cannot infer.
399
+
400
+ ### ✅ Valid Use Cases
401
+
402
+ #### 1. Working with Third-Party Libraries
403
+ When dealing with `any` types from external libraries where you know the actual type:
404
+
405
+ ```typescript
406
+ // Third-party library returns 'any'
407
+ const data = externalAPI.fetchData() as UserData;
408
+ ```
409
+
410
+ #### 2. Type Narrowing After Runtime Validation
411
+ When you've validated the type at runtime but TypeScript can't track it:
412
+
413
+ ```typescript
414
+ function processData(data: unknown) {
415
+ if (isValidUser(data)) {
416
+ // We've validated it's a User, but TypeScript doesn't know
417
+ const user = data as User;
418
+ return user.name;
419
+ }
420
+ }
421
+ ```
422
+
423
+ #### 3. Complex Type Scenarios TypeScript Cannot Infer
424
+ When working with advanced type transformations that are correct but TypeScript cannot prove:
425
+
426
+ ```typescript
427
+ // Complex mapped type that TypeScript struggles with
428
+ const result = transformData(input) as TransformedType;
429
+ ```
430
+
431
+ ### ❌ Invalid Use Cases
432
+
433
+ #### 1. Hiding Legitimate Type Errors
434
+ **NEVER** use type assertions to bypass valid TypeScript errors:
435
+
436
+ ```typescript
437
+ // ❌ BAD: Hiding a real type mismatch
438
+ const state: DebateState = {
439
+ context: undefined, // Type error!
440
+ } as DebateState;
441
+
442
+ // ✅ GOOD: Fix the actual issue
443
+ const state: DebateState = {
444
+ ...(context !== undefined && { context }),
445
+ };
446
+ ```
447
+
448
+ #### 2. Working Around Optional Properties
449
+ **NEVER** use assertions to force undefined into optional properties:
450
+
451
+ ```typescript
452
+ // ❌ BAD: Using assertion to hide strictness issue
453
+ const obj = { value: undefined } as MyType;
454
+
455
+ // ✅ GOOD: Conditionally include the property
456
+ const obj = { ...(value !== undefined && { value }) };
457
+ ```
458
+
459
+ #### 3. Forcing Incompatible Types
460
+ **NEVER** use assertions to force incompatible types:
461
+
462
+ ```typescript
463
+ // ❌ BAD: These types are fundamentally incompatible
464
+ const num = "string" as number;
465
+
466
+ // ✅ GOOD: Convert properly
467
+ const num = parseInt("string", 10);
468
+ ```
469
+
470
+ ## Key Principle
471
+
472
+ > **Type assertions are for saying "trust me, I know better" — not "ignore this error."**
473
+
474
+ If you find yourself using `as` to silence an error, stop and ask:
475
+ 1. Is this a real type incompatibility I should fix?
476
+ 2. Can I narrow the type properly with guards or validation?
477
+ 3. Am I hiding a bug that will surface at runtime?
478
+
479
+ ## Alternative Solutions
480
+
481
+ Before using type assertions, consider:
482
+
483
+ - **Type guards**: `if (typeof x === 'string')`
484
+ - **Discriminated unions**: Use `type` fields to narrow types
485
+ - **Conditional spreads**: `...(value !== undefined && { value })`
486
+ - **Proper typing**: Fix the type definitions rather than casting
487
+ - **Validation libraries**: Use runtime validators like Zod or io-ts
488
+
489
+ ## Summary
490
+
491
+ ✅ **Use `as Type` when**: You have runtime knowledge TypeScript lacks
492
+ ❌ **Don't use `as Type` when**: TypeScript is catching a real bug
493
+ ---
494
+
495
+ # Code Smells
496
+
497
+ This section provides guidelines for identifying and fixing common code smells, with real examples from code refactoring sessions.
498
+
499
+ ## Table of Contents
500
+
501
+ 1. [Unnecessary Exports](#1-unnecessary-exports)
502
+ 2. [Inline Type Definitions (DRY Violation)](#2-inline-type-definitions-dry-violation)
503
+ 3. [Magic Strings and Hardcoded Values](#3-magic-strings-and-hardcoded-values)
504
+ 4. [Repeated Code Patterns](#4-repeated-code-patterns)
505
+ 5. [Improper stdout/stderr Usage](#5-improper-stdoutstderr-usage)
506
+ 6. [Redundant Function Calls](#6-redundant-function-calls)
507
+ 7. [Complex Nested Logic](#7-complex-nested-logic)
508
+ 8. [Unnecessary Type Casts](#8-unnecessary-type-casts)
509
+
510
+ ---
511
+
512
+ ## 1. Unnecessary Exports
513
+
514
+ ### Problem
515
+ Exporting constants or functions that are only used internally, creating unnecessary public API surface.
516
+
517
+ ### Code Smell
518
+ ```typescript
519
+ // ❌ BAD: Constant is exported but only method should be public
520
+ export const DEFAULT_PERFORMANCE_SYSTEM_PROMPT = `You are a performance engineer...`;
521
+
522
+ export class PerformanceAgent {
523
+ static defaultSystemPrompt(): string {
524
+ return DEFAULT_PERFORMANCE_SYSTEM_PROMPT;
525
+ }
526
+ }
527
+
528
+ // Usage elsewhere
529
+ PerformanceAgent.defaultSystemPrompt(); // Only this is used
530
+ ```
531
+
532
+ ### Solution
533
+ ```typescript
534
+ // ✅ GOOD: Keep constant private, expose through method
535
+ const DEFAULT_PERFORMANCE_SYSTEM_PROMPT = `You are a performance engineer...`;
536
+
537
+ export class PerformanceAgent {
538
+ static defaultSystemPrompt(): string {
539
+ return DEFAULT_PERFORMANCE_SYSTEM_PROMPT;
540
+ }
541
+ }
542
+ ```
543
+
544
+ ### Why It Matters
545
+ - **Encapsulation**: Internal implementation details should not be part of the public API
546
+ - **Maintainability**: Changes to internal constants won't break external consumers
547
+ - **Single Source of Truth**: The static method is the intended public interface
548
+
549
+ ---
550
+
551
+ ## 2. Inline Type Definitions (DRY Violation)
552
+
553
+ ### Problem
554
+ Repeating the same inline type definition multiple times across the codebase.
555
+
556
+ ### Code Smell
557
+ ```typescript
558
+ // ❌ BAD: Same inline type repeated 8+ times
559
+ function createAgent(
560
+ promptSource?: { source: 'built-in' | 'file'; absPath?: string }
561
+ ) { }
562
+
563
+ class JudgeAgent {
564
+ public readonly promptSource?: { source: 'built-in' | 'file'; absPath?: string };
565
+ constructor(
566
+ promptSource?: { source: 'built-in' | 'file'; absPath?: string }
567
+ ) { }
568
+ }
569
+
570
+ // ... repeated 5+ more times
571
+ ```
572
+
573
+ ### Solution
574
+ ```typescript
575
+ // ✅ GOOD: Define once, use everywhere
576
+ export interface PromptSource {
577
+ source: 'built-in' | 'file';
578
+ absPath?: string;
579
+ }
580
+
581
+ function createAgent(promptSource?: PromptSource) { }
582
+
583
+ class JudgeAgent {
584
+ public readonly promptSource?: PromptSource;
585
+ constructor(promptSource?: PromptSource) { }
586
+ }
587
+ ```
588
+
589
+ ### Why It Matters
590
+ - **DRY Principle**: Single source of truth for type definitions
591
+ - **Maintainability**: Changes only need to be made in one place
592
+ - **Documentation**: Named types are self-documenting
593
+ - **Reusability**: Can extend or compose types easily
594
+
595
+ ---
596
+
597
+ ## 3. Magic Strings and Hardcoded Values
598
+
599
+ ### Problem
600
+ Using hardcoded string literals instead of named constants throughout the codebase.
601
+
602
+ ### Code Smell
603
+ ```typescript
604
+ // ❌ BAD: Hardcoded strings scattered everywhere
605
+ process.stderr.write(color('yellow', 'Warning message') + '\n');
606
+ // ... in another file
607
+ process.stderr.write(color('yellow', 'Another warning') + '\n');
608
+ // ... in yet another file
609
+ console.log(color('gray', 'Debug info'));
610
+ ```
611
+
612
+ ### Solution
613
+ ```typescript
614
+ // ✅ GOOD: Define constants once
615
+ // In cli/index.ts
616
+ export const WARNING_COLOR = 'yellow';
617
+ export const INFO_COLOR = 'gray';
618
+
619
+ // Usage
620
+ import { WARNING_COLOR, INFO_COLOR } from '../cli/index';
621
+ process.stderr.write(color(WARNING_COLOR, 'Warning message') + '\n');
622
+ console.log(color(INFO_COLOR, 'Debug info'));
623
+ ```
624
+
625
+ ### Even Better
626
+ Create utility functions:
627
+ ```typescript
628
+ // ✅ BEST: Encapsulate the behavior
629
+ export function warnUser(message: string): void {
630
+ process.stderr.write(color(WARNING_COLOR, message) + '\n');
631
+ }
632
+
633
+ // Usage
634
+ warnUser('Warning message');
635
+ ```
636
+
637
+ ### Why It Matters
638
+ - **Consistency**: All warnings use the same color
639
+ - **Maintainability**: Change color in one place
640
+ - **Discoverability**: Constants appear in autocomplete
641
+ - **Type Safety**: Typos caught at compile time
642
+
643
+ ---
644
+
645
+ ## 4. Repeated Code Patterns
646
+
647
+ ### Problem
648
+ The same logic pattern repeated multiple times, violating DRY principle.
649
+
650
+ ### Code Smell
651
+ ```typescript
652
+ // ❌ BAD: Same 18-line pattern repeated 3 times
653
+ function buildAgents() {
654
+ return configs.map((cfg) => {
655
+ if (cfg.role === ARCHITECT) {
656
+ const defaultText = ArchitectAgent.defaultSystemPrompt();
657
+ const res = resolvePrompt({ label: cfg.name, configDir, defaultText });
658
+ const agent = ArchitectAgent.create(cfg, provider, res.text,
659
+ res.source === 'file' ? { source: 'file', absPath: res.absPath } : { source: 'built-in' }
660
+ );
661
+ collect.agents.push({ agentId: cfg.id, role: cfg.role, source: res.source });
662
+ return agent;
663
+ }
664
+ if (cfg.role === PERFORMANCE) {
665
+ // ... exact same 18 lines with different class name
666
+ }
667
+ // ... same pattern again for default case
668
+ });
669
+ }
670
+ ```
671
+
672
+ ### Solution
673
+ ```typescript
674
+ // ✅ GOOD: Extract to helper function
675
+ function createAgentWithPromptResolution(
676
+ agentClass: typeof ArchitectAgent | typeof PerformanceAgent,
677
+ cfg: AgentConfig,
678
+ provider: OpenAIProvider,
679
+ configDir: string,
680
+ collect: { agents: AgentPromptMetadata[] }
681
+ ): Agent {
682
+ const defaultText = agentClass.defaultSystemPrompt();
683
+ const res = resolvePrompt({ label: cfg.name, configDir, defaultText });
684
+
685
+ const promptSource: PromptSource = res.source === PROMPT_SOURCES.FILE
686
+ ? { source: PROMPT_SOURCES.FILE, absPath: res.absPath }
687
+ : { source: PROMPT_SOURCES.BUILT_IN };
688
+
689
+ const agent = agentClass.create(cfg, provider, res.text, promptSource);
690
+ collect.agents.push({ agentId: cfg.id, role: cfg.role, source: res.source });
691
+
692
+ return agent;
693
+ }
694
+
695
+ // Usage: Clean and simple
696
+ function buildAgents() {
697
+ return configs.map((cfg) => {
698
+ if (cfg.role === ARCHITECT) {
699
+ return createAgentWithPromptResolution(ArchitectAgent, cfg, provider, configDir, collect);
700
+ }
701
+ if (cfg.role === PERFORMANCE) {
702
+ return createAgentWithPromptResolution(PerformanceAgent, cfg, provider, configDir, collect);
703
+ }
704
+ warnUser(`Unknown role '${cfg.role}'. Defaulting to architect.`);
705
+ return createAgentWithPromptResolution(ArchitectAgent, cfg, provider, configDir, collect);
706
+ });
707
+ }
708
+ ```
709
+
710
+ ### Why It Matters
711
+ - **DRY**: Changes made once, applied everywhere
712
+ - **Readability**: Main function is now clear and concise
713
+ - **Testability**: Helper function can be tested independently
714
+ - **Maintainability**: Bugs fixed in one place
715
+
716
+ ---
717
+
718
+ ## 5. Improper stdout/stderr Usage
719
+
720
+ ### Problem
721
+ Writing diagnostic/verbose output to stdout instead of stderr, breaking Unix pipeline conventions.
722
+
723
+ ### Code Smell
724
+ ```typescript
725
+ // ❌ BAD: Mixing diagnostic output with results on stdout
726
+ async function outputResults(result: DebateResult) {
727
+ // Main result (should be stdout)
728
+ process.stdout.write(result.solution.description);
729
+
730
+ // Diagnostic info (should be stderr, but goes to stdout)
731
+ if (options.verbose) {
732
+ process.stdout.write('Running debate (verbose)\n');
733
+ process.stdout.write('Active Agents:\n');
734
+ rounds.forEach(round => {
735
+ process.stdout.write(`Round ${round.number}\n`);
736
+ });
737
+ }
738
+ }
739
+
740
+ // Problem: Can't pipe results without getting verbose output
741
+ // $ debate "problem" > solution.txt (verbose output pollutes file)
742
+ ```
743
+
744
+ ### Solution
745
+ ```typescript
746
+ // ✅ GOOD: Proper separation
747
+ export function writeStderr(message: string): void {
748
+ process.stderr.write(message);
749
+ }
750
+
751
+ async function outputResults(result: DebateResult) {
752
+ // Main result on stdout (pipeable)
753
+ process.stdout.write(result.solution.description);
754
+
755
+ // Diagnostic info on stderr (doesn't interfere with piping)
756
+ if (options.verbose) {
757
+ writeStderr('Running debate (verbose)\n');
758
+ writeStderr('Active Agents:\n');
759
+ rounds.forEach(round => {
760
+ writeStderr(`Round ${round.number}\n`);
761
+ });
762
+ }
763
+ }
764
+
765
+ // Now piping works correctly:
766
+ // $ debate "problem" > solution.txt (only solution goes to file)
767
+ // $ debate "problem" 2>/dev/null (suppress diagnostics)
768
+ ```
769
+
770
+ ### Why It Matters
771
+ - **Unix Philosophy**: stdout = data, stderr = diagnostics
772
+ - **Pipeable Output**: Results can be piped without noise
773
+ - **Standard Compliance**: Follows CLI best practices
774
+ - **User Experience**: Users can redirect output as expected
775
+
776
+ ---
777
+
778
+ ## 6. Redundant Function Calls
779
+
780
+ ### Problem
781
+ Calling the same function multiple times when the result could be reused.
782
+
783
+ ### Code Smell
784
+ ```typescript
785
+ // ❌ BAD: Function called up to 3 times
786
+ export async function loadConfig(configPath?: string): Promise<SystemConfig> {
787
+ if (!fs.existsSync(finalPath)) {
788
+ const defaults = builtInDefaults(); // Call 1
789
+ return defaults;
790
+ }
791
+
792
+ const parsed = JSON.parse(raw);
793
+
794
+ if (!Array.isArray(parsed.agents) || parsed.agents.length === 0) {
795
+ const defaults = builtInDefaults(); // Call 2
796
+ return defaults;
797
+ }
798
+
799
+ if (!parsed.judge) {
800
+ parsed.judge = builtInDefaults().judge; // Call 3
801
+ }
802
+
803
+ if (!parsed.debate) {
804
+ parsed.debate = builtInDefaults().debate; // Call 4
805
+ }
806
+
807
+ return parsed;
808
+ }
809
+ ```
810
+
811
+ ### Solution
812
+ ```typescript
813
+ // ✅ GOOD: Call once, reuse everywhere
814
+ export async function loadConfig(configPath?: string): Promise<SystemConfig> {
815
+ const defaults = builtInDefaults(); // Call once
816
+
817
+ if (!fs.existsSync(finalPath)) {
818
+ defaults.configDir = process.cwd();
819
+ return defaults;
820
+ }
821
+
822
+ const parsed = JSON.parse(raw);
823
+
824
+ if (!Array.isArray(parsed.agents) || parsed.agents.length === 0) {
825
+ defaults.configDir = path.dirname(finalPath);
826
+ return defaults;
827
+ }
828
+
829
+ if (!parsed.judge) {
830
+ parsed.judge = defaults.judge; // Reuse
831
+ }
832
+
833
+ if (!parsed.debate) {
834
+ parsed.debate = defaults.debate; // Reuse
835
+ }
836
+
837
+ return parsed;
838
+ }
839
+ ```
840
+
841
+ ### Why It Matters
842
+ - **Performance**: Avoid redundant computation
843
+ - **Efficiency**: Especially important if function is expensive
844
+ - **Clarity**: Shows defaults are the same throughout function
845
+ - **Maintainability**: Changes to default computation happen once
846
+
847
+ ---
848
+
849
+ ## 7. Complex Nested Logic
850
+
851
+ ### Problem
852
+ Deeply nested loops and conditionals that are hard to read and understand.
853
+
854
+ ### Code Smell
855
+ ```typescript
856
+ // ❌ BAD: 18+ lines of nested logic embedded in function
857
+ async function outputResults(result: DebateResult) {
858
+ if (!outputPath && options.verbose) {
859
+ const debate = await stateManager.getDebate(result.debateId);
860
+ if (debate) {
861
+ writeStderr('\nSummary (verbose)\n');
862
+ debate.rounds.forEach((round) => {
863
+ writeStderr(`Round ${round.roundNumber}\n`);
864
+ const types = [PROPOSAL, CRITIQUE, REFINEMENT] as const;
865
+ types.forEach((t) => {
866
+ const items = round.contributions.filter((c) => c.type === t);
867
+ if (items.length > 0) {
868
+ writeStderr(` ${t}:\n`);
869
+ items.forEach((c) => {
870
+ const firstLine = c.content.split('\n')[0];
871
+ const tokens = c.metadata?.tokensUsed ?? 'N/A';
872
+ const lat = c.metadata?.latencyMs ? `${c.metadata.latencyMs}ms` : 'N/A';
873
+ writeStderr(` [${c.agentRole}] ${firstLine}\n`);
874
+ writeStderr(` (latency=${lat}, tokens=${tokens})\n`);
875
+ });
876
+ }
877
+ });
878
+ });
879
+ // ... more logic
880
+ }
881
+ }
882
+ }
883
+ ```
884
+
885
+ ### Solution
886
+ ```typescript
887
+ // ✅ GOOD: Extract to focused function
888
+ function outputRoundSummary(round: DebateRound): void {
889
+ writeStderr(`Round ${round.roundNumber}\n`);
890
+ const types = [PROPOSAL, CRITIQUE, REFINEMENT] as const;
891
+ types.forEach((t) => {
892
+ const items = round.contributions.filter((c: Contribution) => c.type === t);
893
+ if (items.length > 0) {
894
+ writeStderr(` ${t}:\n`);
895
+ items.forEach((c: Contribution) => {
896
+ const firstLine = c.content.split('\n')[0];
897
+ const tokens = c.metadata?.tokensUsed ?? 'N/A';
898
+ const lat = c.metadata?.latencyMs ? `${c.metadata.latencyMs}ms` : 'N/A';
899
+ writeStderr(` [${c.agentRole}] ${firstLine}\n`);
900
+ writeStderr(` (latency=${lat}, tokens=${tokens})\n`);
901
+ });
902
+ }
903
+ });
904
+ }
905
+
906
+ // Usage: Clean and readable
907
+ async function outputResults(result: DebateResult) {
908
+ if (!outputPath && options.verbose) {
909
+ const debate = await stateManager.getDebate(result.debateId);
910
+ if (debate) {
911
+ writeStderr('\nSummary (verbose)\n');
912
+ debate.rounds.forEach(outputRoundSummary); // Simple and clear
913
+ // ... more logic
914
+ }
915
+ }
916
+ }
917
+ ```
918
+
919
+ ### Why It Matters
920
+ - **Readability**: Each function has single responsibility
921
+ - **Testability**: `outputRoundSummary` can be tested in isolation
922
+ - **Reusability**: Function can be used elsewhere if needed
923
+ - **Cognitive Load**: Easier to understand smaller, focused functions
924
+
925
+ ---
926
+
927
+ ## 8. Unnecessary Type Casts
928
+
929
+ ### Problem
930
+ Using `as any` or other unsafe type casts when proper typing is possible.
931
+
932
+ ### Code Smell 1: Optional Properties
933
+ ```typescript
934
+ // ❌ BAD: Unnecessary cast for optional property
935
+ interface DebateState {
936
+ promptSources?: {
937
+ agents: AgentPromptMetadata[];
938
+ judge: JudgePromptMetadata;
939
+ };
940
+ }
941
+
942
+ async setPromptSources(sources: DebateState['promptSources']): Promise<void> {
943
+ const state = this.debates.get(debateId);
944
+ if (sources) {
945
+ state.promptSources = sources;
946
+ } else {
947
+ delete (state as any).promptSources; // Unnecessary!
948
+ }
949
+ }
950
+ ```
951
+
952
+ **Solution:**
953
+ ```typescript
954
+ // ✅ GOOD: Optional properties can be deleted without cast
955
+ async setPromptSources(sources: DebateState['promptSources']): Promise<void> {
956
+ const state = this.debates.get(debateId);
957
+ if (sources) {
958
+ state.promptSources = sources;
959
+ } else {
960
+ delete state.promptSources; // Works fine!
961
+ }
962
+ }
963
+ ```
964
+
965
+ ### Code Smell 2: Type Mismatches
966
+ ```typescript
967
+ // ❌ BAD: Using 'as any' to bypass type mismatch
968
+ interface Contribution {
969
+ agentId: string;
970
+ agentRole: AgentRole;
971
+ type: ContributionType;
972
+ content: string;
973
+ metadata: { tokensUsed?: number; latencyMs?: number };
974
+ }
975
+
976
+ interface Critique {
977
+ content: string;
978
+ metadata: { tokensUsed?: number; latencyMs?: number };
979
+ }
980
+
981
+ async refinementPhase() {
982
+ const critiques = contributions.filter(c => c.type === 'critique');
983
+
984
+ // Type error: Contribution[] is not Critique[]
985
+ const refined = await agent.refine(original, critiques as any, ctx); // BAD!
986
+ }
987
+ ```
988
+
989
+ **Solution:**
990
+ ```typescript
991
+ // ✅ GOOD: Properly map types
992
+ async refinementPhase() {
993
+ const critiqueContributions = contributions.filter(c => c.type === 'critique');
994
+
995
+ // Map Contribution[] to Critique[] by extracting needed fields
996
+ const critiques: Critique[] = critiqueContributions.map((c) => ({
997
+ content: c.content,
998
+ metadata: c.metadata
999
+ }));
1000
+
1001
+ const refined = await agent.refine(original, critiques, ctx); // Type-safe!
1002
+ }
1003
+ ```
1004
+
1005
+ ### Why It Matters
1006
+ - **Type Safety**: Catch errors at compile time
1007
+ - **Explicit Transformation**: Clear boundary between different layers
1008
+ - **Maintainability**: Type system catches breaking changes
1009
+ - **Documentation**: Types document the expected structure
1010
+
1011
+ ---
1012
+
1013
+ ## Quick Reference Checklist
1014
+
1015
+ When reviewing code, ask:
1016
+
1017
+ - [ ] Are there exported constants/types that are only accessed through methods?
1018
+ - [ ] Are the same inline types repeated multiple times?
1019
+ - [ ] Are there hardcoded strings that should be constants?
1020
+ - [ ] Is the same code pattern repeated 3+ times?
1021
+ - [ ] Is diagnostic output going to stdout instead of stderr?
1022
+ - [ ] Are functions called multiple times with the same result?
1023
+ - [ ] Are there deeply nested loops/conditionals that could be extracted?
1024
+ - [ ] Are there `as any` casts that could be removed with proper typing?
1025
+
1026
+ ---
1027
+
1028
+ ## Additional Best Practices
1029
+
1030
+ ### Consistent Naming
1031
+ ```typescript
1032
+ // ❌ Inconsistent
1033
+ const defaults = getDefaults();
1034
+ const config = loadConfiguration();
1035
+ const opts = parseOptions();
1036
+
1037
+ // ✅ Consistent
1038
+ const defaults = loadDefaults();
1039
+ const config = loadConfig();
1040
+ const options = parseOptions();
1041
+ ```
1042
+
1043
+ ### Single Responsibility
1044
+ ```typescript
1045
+ // ❌ Function does too much
1046
+ function processAndSaveAndNotify(data: Data) {
1047
+ const processed = transform(data);
1048
+ database.save(processed);
1049
+ email.send('Done!');
1050
+ return processed;
1051
+ }
1052
+
1053
+ // ✅ Each function has one job
1054
+ function processData(data: Data): ProcessedData {
1055
+ return transform(data);
1056
+ }
1057
+
1058
+ async function saveData(data: ProcessedData): Promise<void> {
1059
+ await database.save(data);
1060
+ }
1061
+
1062
+ async function notifyComplete(): Promise<void> {
1063
+ await email.send('Done!');
1064
+ }
1065
+ ```
1066
+
1067
+ ### Early Returns
1068
+ ```typescript
1069
+ // ❌ Nested conditions
1070
+ function validate(config: Config): boolean {
1071
+ if (config) {
1072
+ if (config.agents) {
1073
+ if (config.agents.length > 0) {
1074
+ return true;
1075
+ } else {
1076
+ return false;
1077
+ }
1078
+ } else {
1079
+ return false;
1080
+ }
1081
+ } else {
1082
+ return false;
1083
+ }
1084
+ }
1085
+
1086
+ // ✅ Early returns
1087
+ function validate(config: Config): boolean {
1088
+ if (!config) return false;
1089
+ if (!config.agents) return false;
1090
+ if (config.agents.length === 0) return false;
1091
+ return true;
1092
+ }
1093
+ ```
1094
+
1095
+ ---
1096
+
1097
+ ## Summary
1098
+
1099
+ Good code is:
1100
+ - **DRY**: Don't Repeat Yourself
1101
+ - **Type-safe**: Let the compiler help you
1102
+ - **Clear**: Easy to read and understand
1103
+ - **Focused**: Each function does one thing well
1104
+ - **Consistent**: Follows established patterns
1105
+
1106
+ When in doubt, remember: **If you copy-paste code, you're doing it wrong.**
1107
+
1108
+
1109
+
1110
+