@sylphx/flow 1.0.2 → 1.0.4
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/CHANGELOG.md +12 -0
- package/package.json +10 -9
- package/src/commands/codebase-command.ts +168 -0
- package/src/commands/flow-command.ts +1137 -0
- package/src/commands/flow-orchestrator.ts +296 -0
- package/src/commands/hook-command.ts +444 -0
- package/src/commands/init-command.ts +92 -0
- package/src/commands/init-core.ts +322 -0
- package/src/commands/knowledge-command.ts +161 -0
- package/src/commands/run-command.ts +120 -0
- package/src/components/benchmark-monitor.tsx +331 -0
- package/src/components/reindex-progress.tsx +261 -0
- package/src/composables/functional/index.ts +14 -0
- package/src/composables/functional/useEnvironment.ts +171 -0
- package/src/composables/functional/useFileSystem.ts +139 -0
- package/src/composables/index.ts +5 -0
- package/src/composables/useEnv.ts +13 -0
- package/src/composables/useRuntimeConfig.ts +27 -0
- package/src/composables/useTargetConfig.ts +45 -0
- package/src/config/ai-config.ts +376 -0
- package/src/config/constants.ts +35 -0
- package/src/config/index.ts +27 -0
- package/src/config/rules.ts +43 -0
- package/src/config/servers.ts +371 -0
- package/src/config/targets.ts +126 -0
- package/src/core/agent-loader.ts +141 -0
- package/src/core/agent-manager.ts +174 -0
- package/src/core/ai-sdk.ts +603 -0
- package/src/core/app-factory.ts +381 -0
- package/src/core/builtin-agents.ts +9 -0
- package/src/core/command-system.ts +550 -0
- package/src/core/config-system.ts +550 -0
- package/src/core/connection-pool.ts +390 -0
- package/src/core/di-container.ts +155 -0
- package/src/core/error-handling.ts +519 -0
- package/src/core/formatting/bytes.test.ts +115 -0
- package/src/core/formatting/bytes.ts +64 -0
- package/src/core/functional/async.ts +313 -0
- package/src/core/functional/either.ts +109 -0
- package/src/core/functional/error-handler.ts +135 -0
- package/src/core/functional/error-types.ts +311 -0
- package/src/core/functional/index.ts +19 -0
- package/src/core/functional/option.ts +142 -0
- package/src/core/functional/pipe.ts +189 -0
- package/src/core/functional/result.ts +204 -0
- package/src/core/functional/validation.ts +138 -0
- package/src/core/headless-display.ts +96 -0
- package/src/core/index.ts +6 -0
- package/src/core/installers/file-installer.ts +303 -0
- package/src/core/installers/mcp-installer.ts +213 -0
- package/src/core/interfaces/index.ts +22 -0
- package/src/core/interfaces/repository.interface.ts +91 -0
- package/src/core/interfaces/service.interface.ts +133 -0
- package/src/core/interfaces.ts +129 -0
- package/src/core/loop-controller.ts +200 -0
- package/src/core/result.ts +351 -0
- package/src/core/rule-loader.ts +147 -0
- package/src/core/rule-manager.ts +240 -0
- package/src/core/service-config.ts +252 -0
- package/src/core/session-service.ts +121 -0
- package/src/core/state-detector.ts +393 -0
- package/src/core/storage-factory.ts +115 -0
- package/src/core/stream-handler.ts +288 -0
- package/src/core/target-manager.ts +161 -0
- package/src/core/type-utils.ts +427 -0
- package/src/core/unified-storage.ts +456 -0
- package/src/core/upgrade-manager.ts +300 -0
- package/src/core/validation/limit.test.ts +155 -0
- package/src/core/validation/limit.ts +46 -0
- package/src/core/validation/query.test.ts +44 -0
- package/src/core/validation/query.ts +20 -0
- package/src/db/auto-migrate.ts +322 -0
- package/src/db/base-database-client.ts +144 -0
- package/src/db/cache-db.ts +218 -0
- package/src/db/cache-schema.ts +75 -0
- package/src/db/database.ts +70 -0
- package/src/db/index.ts +252 -0
- package/src/db/memory-db.ts +153 -0
- package/src/db/memory-schema.ts +29 -0
- package/src/db/schema.ts +289 -0
- package/src/db/session-repository.ts +733 -0
- package/src/domains/codebase/index.ts +5 -0
- package/src/domains/codebase/tools.ts +139 -0
- package/src/domains/index.ts +8 -0
- package/src/domains/knowledge/index.ts +10 -0
- package/src/domains/knowledge/resources.ts +537 -0
- package/src/domains/knowledge/tools.ts +174 -0
- package/src/domains/utilities/index.ts +6 -0
- package/src/domains/utilities/time/index.ts +5 -0
- package/src/domains/utilities/time/tools.ts +291 -0
- package/src/index.ts +211 -0
- package/src/services/agent-service.ts +273 -0
- package/src/services/claude-config-service.ts +252 -0
- package/src/services/config-service.ts +258 -0
- package/src/services/evaluation-service.ts +271 -0
- package/src/services/functional/evaluation-logic.ts +296 -0
- package/src/services/functional/file-processor.ts +273 -0
- package/src/services/functional/index.ts +12 -0
- package/src/services/index.ts +13 -0
- package/src/services/mcp-service.ts +432 -0
- package/src/services/memory.service.ts +476 -0
- package/src/services/search/base-indexer.ts +156 -0
- package/src/services/search/codebase-indexer-types.ts +38 -0
- package/src/services/search/codebase-indexer.ts +647 -0
- package/src/services/search/embeddings-provider.ts +455 -0
- package/src/services/search/embeddings.ts +316 -0
- package/src/services/search/functional-indexer.ts +323 -0
- package/src/services/search/index.ts +27 -0
- package/src/services/search/indexer.ts +380 -0
- package/src/services/search/knowledge-indexer.ts +422 -0
- package/src/services/search/semantic-search.ts +244 -0
- package/src/services/search/tfidf.ts +559 -0
- package/src/services/search/unified-search-service.ts +888 -0
- package/src/services/smart-config-service.ts +385 -0
- package/src/services/storage/cache-storage.ts +487 -0
- package/src/services/storage/drizzle-storage.ts +581 -0
- package/src/services/storage/index.ts +15 -0
- package/src/services/storage/lancedb-vector-storage.ts +494 -0
- package/src/services/storage/memory-storage.ts +268 -0
- package/src/services/storage/separated-storage.ts +467 -0
- package/src/services/storage/vector-storage.ts +13 -0
- package/src/shared/agents/index.ts +63 -0
- package/src/shared/files/index.ts +99 -0
- package/src/shared/index.ts +32 -0
- package/src/shared/logging/index.ts +24 -0
- package/src/shared/processing/index.ts +153 -0
- package/src/shared/types/index.ts +25 -0
- package/src/targets/claude-code.ts +574 -0
- package/src/targets/functional/claude-code-logic.ts +185 -0
- package/src/targets/functional/index.ts +6 -0
- package/src/targets/opencode.ts +529 -0
- package/src/types/agent.types.ts +32 -0
- package/src/types/api/batch.ts +108 -0
- package/src/types/api/errors.ts +118 -0
- package/src/types/api/index.ts +55 -0
- package/src/types/api/requests.ts +76 -0
- package/src/types/api/responses.ts +180 -0
- package/src/types/api/websockets.ts +85 -0
- package/src/types/api.types.ts +9 -0
- package/src/types/benchmark.ts +49 -0
- package/src/types/cli.types.ts +87 -0
- package/src/types/common.types.ts +35 -0
- package/src/types/database.types.ts +510 -0
- package/src/types/mcp-config.types.ts +448 -0
- package/src/types/mcp.types.ts +69 -0
- package/src/types/memory-types.ts +63 -0
- package/src/types/provider.types.ts +28 -0
- package/src/types/rule.types.ts +24 -0
- package/src/types/session.types.ts +214 -0
- package/src/types/target-config.types.ts +295 -0
- package/src/types/target.types.ts +140 -0
- package/src/types/todo.types.ts +25 -0
- package/src/types.ts +40 -0
- package/src/utils/advanced-tokenizer.ts +191 -0
- package/src/utils/agent-enhancer.ts +114 -0
- package/src/utils/ai-model-fetcher.ts +19 -0
- package/src/utils/async-file-operations.ts +516 -0
- package/src/utils/audio-player.ts +345 -0
- package/src/utils/cli-output.ts +266 -0
- package/src/utils/codebase-helpers.ts +211 -0
- package/src/utils/console-ui.ts +79 -0
- package/src/utils/database-errors.ts +140 -0
- package/src/utils/debug-logger.ts +49 -0
- package/src/utils/error-handler.ts +53 -0
- package/src/utils/file-operations.ts +310 -0
- package/src/utils/file-scanner.ts +259 -0
- package/src/utils/functional/array.ts +355 -0
- package/src/utils/functional/index.ts +15 -0
- package/src/utils/functional/object.ts +279 -0
- package/src/utils/functional/string.ts +281 -0
- package/src/utils/functional.ts +543 -0
- package/src/utils/help.ts +20 -0
- package/src/utils/immutable-cache.ts +106 -0
- package/src/utils/index.ts +78 -0
- package/src/utils/jsonc.ts +158 -0
- package/src/utils/logger.ts +396 -0
- package/src/utils/mcp-config.ts +249 -0
- package/src/utils/memory-tui.ts +414 -0
- package/src/utils/models-dev.ts +91 -0
- package/src/utils/notifications.ts +169 -0
- package/src/utils/object-utils.ts +51 -0
- package/src/utils/parallel-operations.ts +487 -0
- package/src/utils/paths.ts +143 -0
- package/src/utils/process-manager.ts +155 -0
- package/src/utils/prompts.ts +120 -0
- package/src/utils/search-tool-builder.ts +214 -0
- package/src/utils/secret-utils.ts +179 -0
- package/src/utils/security.ts +537 -0
- package/src/utils/session-manager.ts +168 -0
- package/src/utils/session-title.ts +87 -0
- package/src/utils/settings.ts +182 -0
- package/src/utils/simplified-errors.ts +410 -0
- package/src/utils/sync-utils.ts +159 -0
- package/src/utils/target-config.ts +570 -0
- package/src/utils/target-utils.ts +394 -0
- package/src/utils/template-engine.ts +94 -0
- package/src/utils/test-audio.ts +71 -0
- package/src/utils/todo-context.ts +46 -0
- package/src/utils/token-counter.ts +288 -0
- package/dist/index.d.ts +0 -10
- package/dist/index.js +0 -59554
- package/dist/lancedb.linux-x64-gnu-b7f0jgsz.node +0 -0
- package/dist/lancedb.linux-x64-musl-tgcv22rx.node +0 -0
- package/dist/shared/chunk-25dwp0dp.js +0 -89
- package/dist/shared/chunk-3pjb6063.js +0 -208
- package/dist/shared/chunk-4d6ydpw7.js +0 -2854
- package/dist/shared/chunk-4wjcadjk.js +0 -225
- package/dist/shared/chunk-5j4w74t6.js +0 -30
- package/dist/shared/chunk-5j8m3dh3.js +0 -58
- package/dist/shared/chunk-5thh3qem.js +0 -91
- package/dist/shared/chunk-6g9xy73m.js +0 -252
- package/dist/shared/chunk-7eq34c42.js +0 -23
- package/dist/shared/chunk-c2gwgx3r.js +0 -115
- package/dist/shared/chunk-cjd3mk4c.js +0 -1320
- package/dist/shared/chunk-g5cv6703.js +0 -368
- package/dist/shared/chunk-hpkhykhq.js +0 -574
- package/dist/shared/chunk-m2322pdk.js +0 -122
- package/dist/shared/chunk-nd5fdvaq.js +0 -26
- package/dist/shared/chunk-pgd3m6zf.js +0 -108
- package/dist/shared/chunk-qk8n91hw.js +0 -494
- package/dist/shared/chunk-rkkn8szp.js +0 -16855
- package/dist/shared/chunk-t16rfxh0.js +0 -61
- package/dist/shared/chunk-t4fbfa5v.js +0 -19
- package/dist/shared/chunk-t77h86w6.js +0 -276
- package/dist/shared/chunk-v0ez4aef.js +0 -71
- package/dist/shared/chunk-v29j2r3s.js +0 -32051
- package/dist/shared/chunk-vfbc6ew5.js +0 -765
- package/dist/shared/chunk-vmeqwm1c.js +0 -204
- 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
|
+
}
|