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.
- package/.cursor/commands/setup-test.mdc +175 -0
- package/.cursor/rules/basic-code-cleanup.mdc +1110 -0
- package/.cursor/rules/riper5.mdc +96 -0
- package/.env.example +6 -0
- package/AGENTS.md +1052 -0
- package/LICENSE +21 -0
- package/README.md +93 -0
- package/WARP.md +113 -0
- package/dialectic-1.0.0.tgz +0 -0
- package/dialectic.js +10 -0
- package/docs/commands.md +375 -0
- package/docs/configuration.md +882 -0
- package/docs/context_summarization.md +1023 -0
- package/docs/debate_flow.md +1127 -0
- package/docs/eval_flow.md +795 -0
- package/docs/evaluator.md +141 -0
- package/examples/debate-config-openrouter.json +48 -0
- package/examples/debate_config1.json +48 -0
- package/examples/eval/eval1/eval_config1.json +13 -0
- package/examples/eval/eval1/result1.json +62 -0
- package/examples/eval/eval1/result2.json +97 -0
- package/examples/eval_summary_format.md +11 -0
- package/examples/example3/debate-config.json +64 -0
- package/examples/example3/eval_config2.json +25 -0
- package/examples/example3/problem.md +17 -0
- package/examples/example3/rounds_test/eval_run.sh +16 -0
- package/examples/example3/rounds_test/run_test.sh +16 -0
- package/examples/kata1/architect-only-solution_2-rounds.json +121 -0
- package/examples/kata1/architect-perf-solution_2-rounds.json +234 -0
- package/examples/kata1/debate-config-kata1.json +54 -0
- package/examples/kata1/eval_architect-only_2-rounds.json +97 -0
- package/examples/kata1/eval_architect-perf_2-rounds.json +97 -0
- package/examples/kata1/kata1-report.md +12224 -0
- package/examples/kata1/kata1-report_temps-01_01_01_07.md +2451 -0
- package/examples/kata1/kata1.md +5 -0
- package/examples/kata1/meta.txt +1 -0
- package/examples/kata2/debate-config.json +54 -0
- package/examples/kata2/eval_config1.json +21 -0
- package/examples/kata2/eval_config2.json +25 -0
- package/examples/kata2/kata2.md +5 -0
- package/examples/kata2/only_architect/debate-config.json +45 -0
- package/examples/kata2/only_architect/eval_run.sh +11 -0
- package/examples/kata2/only_architect/run_test.sh +5 -0
- package/examples/kata2/rounds_test/eval_run.sh +11 -0
- package/examples/kata2/rounds_test/run_test.sh +5 -0
- package/examples/kata2/summary_length_test/eval_run.sh +11 -0
- package/examples/kata2/summary_length_test/eval_run_w_clarify.sh +7 -0
- package/examples/kata2/summary_length_test/run_test.sh +5 -0
- package/examples/task-queue/debate-config.json +76 -0
- package/examples/task-queue/debate_report.md +566 -0
- package/examples/task-queue/task-queue-system.md +25 -0
- package/jest.config.ts +13 -0
- package/multi_agent_debate_spec.md +2980 -0
- package/package.json +38 -0
- package/sanity-check-problem.txt +9 -0
- package/src/agents/prompts/architect-prompts.ts +203 -0
- package/src/agents/prompts/generalist-prompts.ts +157 -0
- package/src/agents/prompts/index.ts +41 -0
- package/src/agents/prompts/judge-prompts.ts +19 -0
- package/src/agents/prompts/kiss-prompts.ts +230 -0
- package/src/agents/prompts/performance-prompts.ts +142 -0
- package/src/agents/prompts/prompt-types.ts +68 -0
- package/src/agents/prompts/security-prompts.ts +149 -0
- package/src/agents/prompts/shared.ts +144 -0
- package/src/agents/prompts/testing-prompts.ts +149 -0
- package/src/agents/role-based-agent.ts +386 -0
- package/src/cli/commands/debate.ts +761 -0
- package/src/cli/commands/eval.ts +475 -0
- package/src/cli/commands/report.ts +265 -0
- package/src/cli/index.ts +79 -0
- package/src/core/agent.ts +198 -0
- package/src/core/clarifications.ts +34 -0
- package/src/core/judge.ts +257 -0
- package/src/core/orchestrator.ts +432 -0
- package/src/core/state-manager.ts +322 -0
- package/src/eval/evaluator-agent.ts +130 -0
- package/src/eval/prompts/system.md +41 -0
- package/src/eval/prompts/user.md +64 -0
- package/src/providers/llm-provider.ts +25 -0
- package/src/providers/openai-provider.ts +84 -0
- package/src/providers/openrouter-provider.ts +122 -0
- package/src/providers/provider-factory.ts +64 -0
- package/src/types/agent.types.ts +141 -0
- package/src/types/config.types.ts +47 -0
- package/src/types/debate.types.ts +237 -0
- package/src/types/eval.types.ts +85 -0
- package/src/utils/common.ts +104 -0
- package/src/utils/context-formatter.ts +102 -0
- package/src/utils/context-summarizer.ts +143 -0
- package/src/utils/env-loader.ts +46 -0
- package/src/utils/exit-codes.ts +5 -0
- package/src/utils/id.ts +11 -0
- package/src/utils/logger.ts +48 -0
- package/src/utils/paths.ts +10 -0
- package/src/utils/progress-ui.ts +313 -0
- package/src/utils/prompt-loader.ts +79 -0
- package/src/utils/report-generator.ts +301 -0
- package/tests/clarifications.spec.ts +128 -0
- package/tests/cli.debate.spec.ts +144 -0
- package/tests/config-loading.spec.ts +206 -0
- package/tests/context-summarizer.spec.ts +131 -0
- package/tests/debate-config-custom.json +38 -0
- package/tests/env-loader.spec.ts +149 -0
- package/tests/eval.command.spec.ts +1191 -0
- package/tests/logger.spec.ts +19 -0
- package/tests/openai-provider.spec.ts +26 -0
- package/tests/openrouter-provider.spec.ts +279 -0
- package/tests/orchestrator-summary.spec.ts +386 -0
- package/tests/orchestrator.spec.ts +207 -0
- package/tests/prompt-loader.spec.ts +52 -0
- package/tests/prompts/architect.md +16 -0
- package/tests/provider-factory.spec.ts +150 -0
- package/tests/report.command.spec.ts +546 -0
- package/tests/role-based-agent-summary.spec.ts +476 -0
- package/tests/security-agent.spec.ts +221 -0
- package/tests/shared-prompts.spec.ts +318 -0
- package/tests/state-manager.spec.ts +251 -0
- package/tests/summary-prompts.spec.ts +153 -0
- 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
|
+
|