@sylphx/flow 1.0.1 → 1.0.3

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 (229) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/package.json +10 -9
  3. package/src/commands/codebase-command.ts +168 -0
  4. package/src/commands/flow-command.ts +1137 -0
  5. package/src/commands/flow-orchestrator.ts +296 -0
  6. package/src/commands/hook-command.ts +444 -0
  7. package/src/commands/init-command.ts +92 -0
  8. package/src/commands/init-core.ts +322 -0
  9. package/src/commands/knowledge-command.ts +161 -0
  10. package/src/commands/run-command.ts +120 -0
  11. package/src/components/benchmark-monitor.tsx +331 -0
  12. package/src/components/reindex-progress.tsx +261 -0
  13. package/src/composables/functional/index.ts +14 -0
  14. package/src/composables/functional/useEnvironment.ts +171 -0
  15. package/src/composables/functional/useFileSystem.ts +139 -0
  16. package/src/composables/index.ts +5 -0
  17. package/src/composables/useEnv.ts +13 -0
  18. package/src/composables/useRuntimeConfig.ts +27 -0
  19. package/src/composables/useTargetConfig.ts +45 -0
  20. package/src/config/ai-config.ts +376 -0
  21. package/src/config/constants.ts +35 -0
  22. package/src/config/index.ts +27 -0
  23. package/src/config/rules.ts +43 -0
  24. package/src/config/servers.ts +371 -0
  25. package/src/config/targets.ts +126 -0
  26. package/src/core/agent-loader.ts +141 -0
  27. package/src/core/agent-manager.ts +174 -0
  28. package/src/core/ai-sdk.ts +603 -0
  29. package/src/core/app-factory.ts +381 -0
  30. package/src/core/builtin-agents.ts +9 -0
  31. package/src/core/command-system.ts +550 -0
  32. package/src/core/config-system.ts +550 -0
  33. package/src/core/connection-pool.ts +390 -0
  34. package/src/core/di-container.ts +155 -0
  35. package/src/core/error-handling.ts +519 -0
  36. package/src/core/formatting/bytes.test.ts +115 -0
  37. package/src/core/formatting/bytes.ts +64 -0
  38. package/src/core/functional/async.ts +313 -0
  39. package/src/core/functional/either.ts +109 -0
  40. package/src/core/functional/error-handler.ts +135 -0
  41. package/src/core/functional/error-types.ts +311 -0
  42. package/src/core/functional/index.ts +19 -0
  43. package/src/core/functional/option.ts +142 -0
  44. package/src/core/functional/pipe.ts +189 -0
  45. package/src/core/functional/result.ts +204 -0
  46. package/src/core/functional/validation.ts +138 -0
  47. package/src/core/headless-display.ts +96 -0
  48. package/src/core/index.ts +6 -0
  49. package/src/core/installers/file-installer.ts +303 -0
  50. package/src/core/installers/mcp-installer.ts +213 -0
  51. package/src/core/interfaces/index.ts +22 -0
  52. package/src/core/interfaces/repository.interface.ts +91 -0
  53. package/src/core/interfaces/service.interface.ts +133 -0
  54. package/src/core/interfaces.ts +129 -0
  55. package/src/core/loop-controller.ts +200 -0
  56. package/src/core/result.ts +351 -0
  57. package/src/core/rule-loader.ts +147 -0
  58. package/src/core/rule-manager.ts +240 -0
  59. package/src/core/service-config.ts +252 -0
  60. package/src/core/session-service.ts +121 -0
  61. package/src/core/state-detector.ts +389 -0
  62. package/src/core/storage-factory.ts +115 -0
  63. package/src/core/stream-handler.ts +288 -0
  64. package/src/core/target-manager.ts +161 -0
  65. package/src/core/type-utils.ts +427 -0
  66. package/src/core/unified-storage.ts +456 -0
  67. package/src/core/upgrade-manager.ts +300 -0
  68. package/src/core/validation/limit.test.ts +155 -0
  69. package/src/core/validation/limit.ts +46 -0
  70. package/src/core/validation/query.test.ts +44 -0
  71. package/src/core/validation/query.ts +20 -0
  72. package/src/db/auto-migrate.ts +322 -0
  73. package/src/db/base-database-client.ts +144 -0
  74. package/src/db/cache-db.ts +218 -0
  75. package/src/db/cache-schema.ts +75 -0
  76. package/src/db/database.ts +70 -0
  77. package/src/db/index.ts +252 -0
  78. package/src/db/memory-db.ts +153 -0
  79. package/src/db/memory-schema.ts +29 -0
  80. package/src/db/schema.ts +289 -0
  81. package/src/db/session-repository.ts +733 -0
  82. package/src/domains/codebase/index.ts +5 -0
  83. package/src/domains/codebase/tools.ts +139 -0
  84. package/src/domains/index.ts +8 -0
  85. package/src/domains/knowledge/index.ts +10 -0
  86. package/src/domains/knowledge/resources.ts +537 -0
  87. package/src/domains/knowledge/tools.ts +174 -0
  88. package/src/domains/utilities/index.ts +6 -0
  89. package/src/domains/utilities/time/index.ts +5 -0
  90. package/src/domains/utilities/time/tools.ts +291 -0
  91. package/src/index.ts +211 -0
  92. package/src/services/agent-service.ts +273 -0
  93. package/src/services/claude-config-service.ts +252 -0
  94. package/src/services/config-service.ts +258 -0
  95. package/src/services/evaluation-service.ts +271 -0
  96. package/src/services/functional/evaluation-logic.ts +296 -0
  97. package/src/services/functional/file-processor.ts +273 -0
  98. package/src/services/functional/index.ts +12 -0
  99. package/src/services/index.ts +13 -0
  100. package/src/services/mcp-service.ts +432 -0
  101. package/src/services/memory.service.ts +476 -0
  102. package/src/services/search/base-indexer.ts +156 -0
  103. package/src/services/search/codebase-indexer-types.ts +38 -0
  104. package/src/services/search/codebase-indexer.ts +647 -0
  105. package/src/services/search/embeddings-provider.ts +455 -0
  106. package/src/services/search/embeddings.ts +316 -0
  107. package/src/services/search/functional-indexer.ts +323 -0
  108. package/src/services/search/index.ts +27 -0
  109. package/src/services/search/indexer.ts +380 -0
  110. package/src/services/search/knowledge-indexer.ts +422 -0
  111. package/src/services/search/semantic-search.ts +244 -0
  112. package/src/services/search/tfidf.ts +559 -0
  113. package/src/services/search/unified-search-service.ts +888 -0
  114. package/src/services/smart-config-service.ts +385 -0
  115. package/src/services/storage/cache-storage.ts +487 -0
  116. package/src/services/storage/drizzle-storage.ts +581 -0
  117. package/src/services/storage/index.ts +15 -0
  118. package/src/services/storage/lancedb-vector-storage.ts +494 -0
  119. package/src/services/storage/memory-storage.ts +268 -0
  120. package/src/services/storage/separated-storage.ts +467 -0
  121. package/src/services/storage/vector-storage.ts +13 -0
  122. package/src/shared/agents/index.ts +63 -0
  123. package/src/shared/files/index.ts +99 -0
  124. package/src/shared/index.ts +32 -0
  125. package/src/shared/logging/index.ts +24 -0
  126. package/src/shared/processing/index.ts +153 -0
  127. package/src/shared/types/index.ts +25 -0
  128. package/src/targets/claude-code.ts +574 -0
  129. package/src/targets/functional/claude-code-logic.ts +185 -0
  130. package/src/targets/functional/index.ts +6 -0
  131. package/src/targets/opencode.ts +529 -0
  132. package/src/types/agent.types.ts +32 -0
  133. package/src/types/api/batch.ts +108 -0
  134. package/src/types/api/errors.ts +118 -0
  135. package/src/types/api/index.ts +55 -0
  136. package/src/types/api/requests.ts +76 -0
  137. package/src/types/api/responses.ts +180 -0
  138. package/src/types/api/websockets.ts +85 -0
  139. package/src/types/api.types.ts +9 -0
  140. package/src/types/benchmark.ts +49 -0
  141. package/src/types/cli.types.ts +87 -0
  142. package/src/types/common.types.ts +35 -0
  143. package/src/types/database.types.ts +510 -0
  144. package/src/types/mcp-config.types.ts +448 -0
  145. package/src/types/mcp.types.ts +69 -0
  146. package/src/types/memory-types.ts +63 -0
  147. package/src/types/provider.types.ts +28 -0
  148. package/src/types/rule.types.ts +24 -0
  149. package/src/types/session.types.ts +214 -0
  150. package/src/types/target-config.types.ts +295 -0
  151. package/src/types/target.types.ts +140 -0
  152. package/src/types/todo.types.ts +25 -0
  153. package/src/types.ts +40 -0
  154. package/src/utils/advanced-tokenizer.ts +191 -0
  155. package/src/utils/agent-enhancer.ts +114 -0
  156. package/src/utils/ai-model-fetcher.ts +19 -0
  157. package/src/utils/async-file-operations.ts +516 -0
  158. package/src/utils/audio-player.ts +345 -0
  159. package/src/utils/cli-output.ts +266 -0
  160. package/src/utils/codebase-helpers.ts +211 -0
  161. package/src/utils/console-ui.ts +79 -0
  162. package/src/utils/database-errors.ts +140 -0
  163. package/src/utils/debug-logger.ts +49 -0
  164. package/src/utils/error-handler.ts +53 -0
  165. package/src/utils/file-operations.ts +310 -0
  166. package/src/utils/file-scanner.ts +259 -0
  167. package/src/utils/functional/array.ts +355 -0
  168. package/src/utils/functional/index.ts +15 -0
  169. package/src/utils/functional/object.ts +279 -0
  170. package/src/utils/functional/string.ts +281 -0
  171. package/src/utils/functional.ts +543 -0
  172. package/src/utils/help.ts +20 -0
  173. package/src/utils/immutable-cache.ts +106 -0
  174. package/src/utils/index.ts +78 -0
  175. package/src/utils/jsonc.ts +158 -0
  176. package/src/utils/logger.ts +396 -0
  177. package/src/utils/mcp-config.ts +249 -0
  178. package/src/utils/memory-tui.ts +414 -0
  179. package/src/utils/models-dev.ts +91 -0
  180. package/src/utils/notifications.ts +169 -0
  181. package/src/utils/object-utils.ts +51 -0
  182. package/src/utils/parallel-operations.ts +487 -0
  183. package/src/utils/paths.ts +143 -0
  184. package/src/utils/process-manager.ts +155 -0
  185. package/src/utils/prompts.ts +120 -0
  186. package/src/utils/search-tool-builder.ts +214 -0
  187. package/src/utils/secret-utils.ts +179 -0
  188. package/src/utils/security.ts +537 -0
  189. package/src/utils/session-manager.ts +168 -0
  190. package/src/utils/session-title.ts +87 -0
  191. package/src/utils/settings.ts +182 -0
  192. package/src/utils/simplified-errors.ts +410 -0
  193. package/src/utils/sync-utils.ts +159 -0
  194. package/src/utils/target-config.ts +570 -0
  195. package/src/utils/target-utils.ts +394 -0
  196. package/src/utils/template-engine.ts +94 -0
  197. package/src/utils/test-audio.ts +71 -0
  198. package/src/utils/todo-context.ts +46 -0
  199. package/src/utils/token-counter.ts +288 -0
  200. package/dist/index.d.ts +0 -10
  201. package/dist/index.js +0 -59554
  202. package/dist/lancedb.linux-x64-gnu-b7f0jgsz.node +0 -0
  203. package/dist/lancedb.linux-x64-musl-tgcv22rx.node +0 -0
  204. package/dist/shared/chunk-25dwp0dp.js +0 -89
  205. package/dist/shared/chunk-3pjb6063.js +0 -208
  206. package/dist/shared/chunk-4d6ydpw7.js +0 -2854
  207. package/dist/shared/chunk-4wjcadjk.js +0 -225
  208. package/dist/shared/chunk-5j4w74t6.js +0 -30
  209. package/dist/shared/chunk-5j8m3dh3.js +0 -58
  210. package/dist/shared/chunk-5thh3qem.js +0 -91
  211. package/dist/shared/chunk-6g9xy73m.js +0 -252
  212. package/dist/shared/chunk-7eq34c42.js +0 -23
  213. package/dist/shared/chunk-c2gwgx3r.js +0 -115
  214. package/dist/shared/chunk-cjd3mk4c.js +0 -1320
  215. package/dist/shared/chunk-g5cv6703.js +0 -368
  216. package/dist/shared/chunk-hpkhykhq.js +0 -574
  217. package/dist/shared/chunk-m2322pdk.js +0 -122
  218. package/dist/shared/chunk-nd5fdvaq.js +0 -26
  219. package/dist/shared/chunk-pgd3m6zf.js +0 -108
  220. package/dist/shared/chunk-qk8n91hw.js +0 -494
  221. package/dist/shared/chunk-rkkn8szp.js +0 -16855
  222. package/dist/shared/chunk-t16rfxh0.js +0 -61
  223. package/dist/shared/chunk-t4fbfa5v.js +0 -19
  224. package/dist/shared/chunk-t77h86w6.js +0 -276
  225. package/dist/shared/chunk-v0ez4aef.js +0 -71
  226. package/dist/shared/chunk-v29j2r3s.js +0 -32051
  227. package/dist/shared/chunk-vfbc6ew5.js +0 -765
  228. package/dist/shared/chunk-vmeqwm1c.js +0 -204
  229. package/dist/shared/chunk-x66eh37x.js +0 -137
@@ -0,0 +1,258 @@
1
+ /**
2
+ * Configuration service - handles layered configuration loading
3
+ * Priority: local > project > home
4
+ */
5
+
6
+ import fs from 'node:fs/promises';
7
+ import path from 'node:path';
8
+ import os from 'node:os';
9
+ import {
10
+ CONFIG_DIR,
11
+ USER_SETTINGS_FILE,
12
+ getProjectSettingsFile,
13
+ getProjectLocalSettingsFile
14
+ } from '../config/constants.js';
15
+
16
+ /**
17
+ * User configuration (sensitive data, saved to home directory)
18
+ * This is set up once and reused across projects
19
+ */
20
+ export interface UserSettings {
21
+ claudeProvider?: string;
22
+ claudeProviderConfig?: {
23
+ ANTHROPIC_BASE_URL: string;
24
+ description: string;
25
+ };
26
+ claudeApiKey?: string;
27
+
28
+ // API keys for providers
29
+ apiKeys?: {
30
+ kimi?: string; // Kimi provider
31
+ 'z.ai'?: string; // Z.ai proxy
32
+ };
33
+
34
+ // User preferences (can be changed anytime)
35
+ defaultProvider?: string;
36
+ defaultAgent?: string;
37
+ hasCompletedSetup?: boolean;
38
+
39
+ [key: string]: unknown;
40
+ }
41
+
42
+ /**
43
+ * Project configuration (shareable, saved to project directory)
44
+ */
45
+ export interface ProjectSettings {
46
+ target?: string;
47
+ version?: string;
48
+ defaultAgent?: string; // Can override user default per project
49
+
50
+ [key: string]: unknown;
51
+ }
52
+
53
+ /**
54
+ * Runtime choices (temporary, not saved)
55
+ * These are selected each run but can be overridden by CLI flags
56
+ */
57
+ export interface RuntimeChoices {
58
+ provider?: string; // Selected for this run
59
+ agent?: string; // Selected for this run
60
+ prompt?: string; // User prompt for this run
61
+
62
+ [key: string]: unknown;
63
+ }
64
+
65
+ export class ConfigService {
66
+ /**
67
+ * Load complete configuration (user + project + local)
68
+ */
69
+ static async loadConfiguration(cwd: string = process.cwd()): Promise<{
70
+ user: UserSettings;
71
+ project: ProjectSettings;
72
+ choices: RuntimeChoices;
73
+ }> {
74
+ const userSettings = await this.loadHomeSettings();
75
+ const projectSettings = await this.loadProjectSettings(cwd);
76
+ const localSettings = await this.loadLocalSettings(cwd);
77
+
78
+ // Runtime choices merge: local > project > user defaults
79
+ const choices: RuntimeChoices = {
80
+ provider: localSettings.provider || userSettings.defaultProvider,
81
+ agent: localSettings.agent || projectSettings.defaultAgent || userSettings.defaultAgent,
82
+ };
83
+
84
+ return {
85
+ user: userSettings,
86
+ project: projectSettings,
87
+ choices,
88
+ };
89
+ }
90
+
91
+ /**
92
+ * Legacy method for backward compatibility
93
+ */
94
+ static async loadSettings(cwd: string = process.cwd()): Promise<any> {
95
+ const config = await this.loadConfiguration(cwd);
96
+ return {
97
+ ...config.user,
98
+ ...config.project,
99
+ ...config.choices,
100
+ };
101
+ }
102
+
103
+ /**
104
+ * Load user global settings (mainly for API keys)
105
+ */
106
+ static async loadHomeSettings(): Promise<UserSettings> {
107
+ try {
108
+ const content = await fs.readFile(USER_SETTINGS_FILE, 'utf-8');
109
+ return JSON.parse(content);
110
+ } catch {
111
+ return {};
112
+ }
113
+ }
114
+
115
+ /**
116
+ * Save user global settings
117
+ */
118
+ static async saveHomeSettings(settings: UserSettings): Promise<void> {
119
+ // Ensure directory exists
120
+ await fs.mkdir(USER_SETTINGS_FILE.replace('/settings.json', ''), { recursive: true });
121
+
122
+ // Merge with existing settings and save
123
+ const existing = await this.loadHomeSettings();
124
+ const merged = { ...existing, ...settings };
125
+ await fs.writeFile(USER_SETTINGS_FILE, JSON.stringify(merged, null, 2) + '\n');
126
+ }
127
+
128
+ /**
129
+ * Check if user has completed initial setup (API keys configured)
130
+ */
131
+ static async hasInitialSetup(): Promise<boolean> {
132
+ const userSettings = await this.loadHomeSettings();
133
+ // Check if user has completed setup (either has API keys OR has explicitly chosen default)
134
+ return !!(userSettings.hasCompletedSetup || (userSettings.apiKeys && Object.keys(userSettings.apiKeys).length > 0));
135
+ }
136
+
137
+ /**
138
+ * Get available providers (those with API keys configured)
139
+ * Always includes 'default' (no override)
140
+ */
141
+ static getAvailableProviders(userSettings: UserSettings): string[] {
142
+ const providers: string[] = ['default']; // Always available
143
+ if (userSettings.apiKeys?.kimi) providers.push('kimi');
144
+ if (userSettings.apiKeys?.['z.ai']) providers.push('z.ai');
145
+ return providers;
146
+ }
147
+
148
+ /**
149
+ * Load project-level settings
150
+ */
151
+ static async loadProjectSettings(cwd: string = process.cwd()): Promise<ProjectSettings> {
152
+ try {
153
+ const configPath = getProjectSettingsFile(cwd);
154
+ const content = await fs.readFile(configPath, 'utf-8');
155
+ return JSON.parse(content);
156
+ } catch {
157
+ return {};
158
+ }
159
+ }
160
+
161
+ /**
162
+ * Save project-level settings
163
+ */
164
+ static async saveProjectSettings(settings: ProjectSettings, cwd: string = process.cwd()): Promise<void> {
165
+ // Ensure directory exists
166
+ const configDir = path.join(cwd, CONFIG_DIR);
167
+ await fs.mkdir(configDir, { recursive: true });
168
+
169
+ // Merge with existing settings and save
170
+ const existing = await this.loadProjectSettings(cwd);
171
+ const merged = { ...existing, ...settings };
172
+
173
+ const configPath = getProjectSettingsFile(cwd);
174
+ await fs.writeFile(configPath, JSON.stringify(merged, null, 2) + '\n');
175
+ }
176
+
177
+ /**
178
+ * Load project-local settings (overrides everything)
179
+ */
180
+ static async loadLocalSettings(cwd: string = process.cwd()): Promise<RuntimeChoices> {
181
+ try {
182
+ const configPath = getProjectLocalSettingsFile(cwd);
183
+ const content = await fs.readFile(configPath, 'utf-8');
184
+ return JSON.parse(content);
185
+ } catch {
186
+ return {};
187
+ }
188
+ }
189
+
190
+ /**
191
+ * Save project-local settings
192
+ */
193
+ static async saveLocalSettings(settings: RuntimeChoices, cwd: string = process.cwd()): Promise<void> {
194
+ // Ensure directory exists
195
+ const configDir = path.join(cwd, CONFIG_DIR);
196
+ await fs.mkdir(configDir, { recursive: true });
197
+
198
+ const configPath = getProjectLocalSettingsFile(cwd);
199
+ await fs.writeFile(configPath, JSON.stringify(settings, null, 2) + '\n');
200
+ }
201
+
202
+ /**
203
+ * Convenient method: save Claude configuration (API keys to home, other settings to project)
204
+ */
205
+ static async saveClaudeConfig(
206
+ projectConfig: ProjectSettings,
207
+ userConfig: UserSettings,
208
+ cwd: string = process.cwd()
209
+ ): Promise<void> {
210
+ // Save API keys to home directory
211
+ if (userConfig.claudeApiKey || userConfig.claudeProvider || userConfig.claudeProviderConfig) {
212
+ await this.saveHomeSettings(userConfig);
213
+ }
214
+
215
+ // Save other settings to project
216
+ await this.saveProjectSettings(projectConfig, cwd);
217
+
218
+ // Create .gitignore pattern file if it doesn't exist (excluding .local.json)
219
+ await this.addGitignore(cwd);
220
+ }
221
+
222
+ /**
223
+ * Add .sylphx-flow/ to .gitignore with proper patterns
224
+ */
225
+ private static async addGitignore(cwd: string): Promise<void> {
226
+ const gitignorePath = path.join(cwd, '.gitignore');
227
+ const patterns = [
228
+ '',
229
+ '# Sylphx Flow - local settings (never commit)',
230
+ '.sylphx-flow/*.local.json',
231
+ ];
232
+
233
+ try {
234
+ const content = await fs.readFile(gitignorePath, 'utf-8');
235
+
236
+ // Check if pattern already exists
237
+ if (!content.includes('.sylphx-flow/*.local.json')) {
238
+ await fs.appendFile(gitignorePath, patterns.join('\n') + '\n');
239
+ }
240
+ } catch {
241
+ // .gitignore doesn't exist - create it
242
+ await fs.writeFile(gitignorePath, patterns.join('\n').trim() + '\n');
243
+ }
244
+ }
245
+
246
+ /**
247
+ * Check if config directory exists
248
+ */
249
+ static async isInitialized(cwd: string = process.cwd()): Promise<boolean> {
250
+ try {
251
+ const configDir = path.join(cwd, CONFIG_DIR);
252
+ await fs.access(configDir);
253
+ return true;
254
+ } catch {
255
+ return false;
256
+ }
257
+ }
258
+ }
@@ -0,0 +1,271 @@
1
+ import { spawn } from 'node:child_process';
2
+ import fs from 'node:fs/promises';
3
+ import path from 'node:path';
4
+ import type { InkMonitor } from '../components/benchmark-monitor.js';
5
+ import { DEFAULT_AGENTS, PERFORMANCE_SCORE_RANGES } from '../constants/benchmark-constants.js';
6
+ import type { AgentTimings, AgentWork } from '../types/benchmark.js';
7
+ import { ProcessManager } from '../utils/process-manager.js';
8
+
9
+ /**
10
+ * Evaluate agent results by running Claude to analyze agent work
11
+ * Pure function (with side effects: file I/O, process spawning)
12
+ */
13
+ export async function evaluateResults(
14
+ outputDir: string,
15
+ reportDir: string | undefined,
16
+ monitor?: InkMonitor
17
+ ): Promise<void> {
18
+ // First, collect actual timing information for each agent
19
+ const agentTimings: AgentTimings = {};
20
+ const agentDirs = DEFAULT_AGENTS.map((agent) => path.join(outputDir, agent));
21
+
22
+ for (const agentDir of agentDirs) {
23
+ const agentName = path.basename(agentDir);
24
+ try {
25
+ // Try to read the execution-time.txt file first
26
+ const timingFile = path.join(agentDir, 'execution-time.txt');
27
+ const timingContent = await fs.readFile(timingFile, 'utf-8');
28
+
29
+ // Parse the timing information
30
+ const durationMatch = timingContent.match(/Duration:\s*(\d+)\s*seconds/);
31
+ const duration = durationMatch ? Number.parseInt(durationMatch[1], 10) : 0;
32
+
33
+ agentTimings[agentName] = { duration };
34
+ } catch (_error) {
35
+ // Fallback: try to read from timing.json
36
+ try {
37
+ const timingJsonFile = path.join(agentDir, 'timing.json');
38
+ const timingContent = await fs.readFile(timingJsonFile, 'utf-8');
39
+ const _timingData = JSON.parse(timingContent);
40
+
41
+ // If we have timing data but no duration, estimate it
42
+ agentTimings[agentName] = { duration: 0 }; // Unknown duration
43
+ } catch (_fallbackError) {
44
+ agentTimings[agentName] = { duration: 0 }; // No timing data available
45
+ }
46
+ }
47
+ }
48
+
49
+ const evaluatorPrompt = await buildEvaluationPrompt(agentTimings);
50
+
51
+ // Collect all agent work by reading their created files
52
+ const agentWork: AgentWork = {};
53
+
54
+ for (const agentDir of agentDirs) {
55
+ const agentName = path.basename(agentDir);
56
+ try {
57
+ // Read all files in agent directory
58
+ const files = await fs.readdir(agentDir);
59
+
60
+ // FUNCTIONAL: Build file content array instead of string accumulation
61
+ const fileContents: string[] = [];
62
+ for (const file of files) {
63
+ const filePath = path.join(agentDir, file);
64
+ const stat = await fs.stat(filePath);
65
+
66
+ if (stat.isFile()) {
67
+ const content = await fs.readFile(filePath, 'utf-8');
68
+ fileContents.push(`\n--- File: ${file} ---\n${content}\n`);
69
+ }
70
+ }
71
+
72
+ // Join at the end
73
+ agentWork[agentName] = `=== ${agentName} WORK ===\n\n${fileContents.join('')}`;
74
+ } catch (error) {
75
+ agentWork[agentName] = `=== ${agentName} WORK ===\n\nERROR: Could not read files - ${error}`;
76
+ }
77
+ }
78
+
79
+ // Combine all agent work into input for evaluator
80
+ const allWork = Object.values(agentWork).join(`\n${'='.repeat(80)}\n`);
81
+ const fullInput = `${evaluatorPrompt}\n\nAGENT WORK TO EVALUATE:\n${allWork}`;
82
+
83
+ // Write evaluation prompt to temp file
84
+ const tempEvalFile = path.join(outputDir, '.evaluation-prompt.md');
85
+ await fs.writeFile(tempEvalFile, fullInput);
86
+
87
+ // Add evaluation agent to monitor if available
88
+ if (monitor) {
89
+ monitor.addAgent('evaluator');
90
+ monitor.updateAgentStatus('evaluator', 'running');
91
+ }
92
+
93
+ // Run evaluation with Claude
94
+ const evaluationProcess = spawn(
95
+ 'claude',
96
+ [
97
+ '--system-prompt',
98
+ `@${tempEvalFile}`,
99
+ '--dangerously-skip-permissions',
100
+ '--output-format',
101
+ 'stream-json',
102
+ '--verbose',
103
+ 'Please evaluate the agent work as described in the system prompt.',
104
+ ],
105
+ {
106
+ cwd: outputDir,
107
+ stdio: ['inherit', 'pipe', 'pipe'],
108
+ env: {
109
+ ...process.env,
110
+ FORCE_NO_PROGRESS: '1',
111
+ CI: '1',
112
+ PYTHONUNBUFFERED: '1',
113
+ },
114
+ }
115
+ );
116
+
117
+ // Track evaluation process for cleanup
118
+ ProcessManager.getInstance().trackChildProcess(evaluationProcess);
119
+
120
+ // FUNCTIONAL: Use arrays for immutable buffer accumulation
121
+ const evaluationOutputChunks: string[] = [];
122
+ let incompleteStdoutLine = '';
123
+
124
+ evaluationProcess.stdout?.on('data', (data) => {
125
+ const output = data.toString();
126
+
127
+ // Process complete lines only - keep incomplete data in buffer
128
+ const combined = incompleteStdoutLine + output;
129
+ const lines = combined.split('\n');
130
+ incompleteStdoutLine = lines.pop() || ''; // Keep last incomplete line
131
+
132
+ for (const line of lines) {
133
+ if (!line.trim()) {
134
+ continue;
135
+ }
136
+
137
+ try {
138
+ const jsonData = JSON.parse(line);
139
+
140
+ if (jsonData.type === 'assistant' && jsonData.message?.content) {
141
+ // Extract text content from assistant message
142
+ for (const content of jsonData.message.content) {
143
+ if (content.type === 'text') {
144
+ const textContent = content.text.trim();
145
+ if (textContent) {
146
+ evaluationOutputChunks.push(`${textContent}\n`);
147
+ // Add to monitor if available
148
+ monitor?.addAgentOutput('evaluator', textContent);
149
+ }
150
+ }
151
+ }
152
+ }
153
+ } catch (_e) {
154
+ // Skip invalid JSON (shouldn't happen with stream-json)
155
+ // For non-JSON output, add to evaluation output
156
+ evaluationOutputChunks.push(`${line}\n`);
157
+ monitor?.addAgentOutput('evaluator', line);
158
+ }
159
+ }
160
+ });
161
+
162
+ return new Promise((resolve, reject) => {
163
+ evaluationProcess.on('close', async (code) => {
164
+ // Update evaluator status
165
+ monitor?.updateAgentStatus('evaluator', code === 0 ? 'completed' : 'error');
166
+
167
+ if (code === 0) {
168
+ // Save report to both temp directory and optionally to project directory
169
+ // FUNCTIONAL: Join output chunks at the end
170
+ const evaluationOutput = evaluationOutputChunks.join('');
171
+ const tempReportPath = path.join(outputDir, 'evaluation-report.md');
172
+ await fs.writeFile(tempReportPath, evaluationOutput);
173
+
174
+ // Save summary of what each agent created
175
+ const summary = Object.entries(agentWork)
176
+ .map(([agent, content]) => {
177
+ const fileCount = (content.match(/--- File: /g) || []).length;
178
+ return `${agent}: ${fileCount} files created`;
179
+ })
180
+ .join('\n');
181
+
182
+ const tempSummaryPath = path.join(outputDir, 'summary.txt');
183
+ await fs.writeFile(tempSummaryPath, summary);
184
+
185
+ // Show completion message and display the full LLM output
186
+ if (monitor) {
187
+ monitor?.addAgentOutput('evaluator', '📊 Evaluation completed!');
188
+ monitor?.addAgentOutput('evaluator', `📁 Report saved to: ${tempReportPath}`);
189
+ monitor?.addAgentOutput('evaluator', '');
190
+ monitor?.addAgentOutput('evaluator', '🏆 EVALUATION RESULTS:');
191
+ monitor?.addAgentOutput('evaluator', '');
192
+
193
+ // Display the complete LLM evaluation output directly
194
+ const lines = evaluationOutput.split('\n');
195
+ lines.forEach((line, _index) => {
196
+ if (line.trim()) {
197
+ monitor?.addAgentOutput('evaluator', line);
198
+ }
199
+ });
200
+
201
+ monitor?.addAgentOutput('evaluator', '');
202
+ monitor?.addAgentOutput('evaluator', '✅ End of evaluation report');
203
+ }
204
+
205
+ // Clean up evaluation temp file
206
+ try {
207
+ await fs.unlink(tempEvalFile);
208
+ } catch (_error) {
209
+ // Ignore cleanup errors
210
+ }
211
+
212
+ // Also save to project directory if report option is provided
213
+ if (reportDir) {
214
+ const projectReportPath = path.join(process.cwd(), reportDir, 'evaluation-report.md');
215
+ const projectSummaryPath = path.join(process.cwd(), reportDir, 'summary.txt');
216
+
217
+ // Ensure report directory exists
218
+ await fs.mkdir(path.dirname(projectReportPath), { recursive: true });
219
+
220
+ await fs.writeFile(projectReportPath, evaluationOutput);
221
+ await fs.writeFile(projectSummaryPath, summary);
222
+
223
+ if (monitor) {
224
+ monitor?.addAgentOutput(
225
+ 'evaluator',
226
+ `📁 Project report saved to: ${projectReportPath}`
227
+ );
228
+ }
229
+ }
230
+
231
+ resolve();
232
+ } else {
233
+ monitor?.addAgentOutput('evaluator', `❌ Evaluation failed with exit code ${code}`);
234
+ reject(new Error(`Evaluation failed with code ${code}`));
235
+ }
236
+ });
237
+
238
+ evaluationProcess.on('error', (error) => {
239
+ reject(error);
240
+ });
241
+ });
242
+ }
243
+
244
+ /**
245
+ * Internal helper: Build evaluation prompt from agent timings
246
+ */
247
+ async function buildEvaluationPrompt(agentTimings: AgentTimings): Promise<string> {
248
+ // Load template from file - required file, no fallback
249
+ const templatePath = path.join(process.cwd(), 'templates', 'evaluation-prompt.md');
250
+
251
+ try {
252
+ const template = await fs.readFile(templatePath, 'utf-8');
253
+
254
+ // Generate agent performance data section
255
+ const performanceData = Object.entries(agentTimings)
256
+ .map(([agent, timing]) => {
257
+ const duration = timing.duration || 0;
258
+ const scoreRange = PERFORMANCE_SCORE_RANGES.find((range) => duration <= range.max)!;
259
+ return `- ${agent}: ${duration}s execution time (Performance: ${scoreRange.score}/10)`;
260
+ })
261
+ .join('\n');
262
+
263
+ // Replace template variables
264
+ return template.replace('{{AGENT_PERFORMANCE_DATA}}', performanceData);
265
+ } catch (error) {
266
+ const errorMessage = error instanceof Error ? error.message : String(error);
267
+ throw new Error(
268
+ `Failed to load evaluation template from ${templatePath}. Error: ${errorMessage}\n\nPlease ensure:\n1. The file exists at: ${templatePath}\n2. The file is readable (check permissions)\n3. The file contains valid markdown content`
269
+ );
270
+ }
271
+ }