@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.
- 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 +389 -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,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session Title Generation Utility
|
|
3
|
+
* Re-exports pure functions from feature and adds streaming functionality
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { createAIStream } from '../core/ai-sdk.js';
|
|
7
|
+
import type { ProviderId } from '../types/config.types.js';
|
|
8
|
+
|
|
9
|
+
// Re-export pure functions from feature
|
|
10
|
+
export {
|
|
11
|
+
generateSessionTitle,
|
|
12
|
+
formatSessionDisplay,
|
|
13
|
+
formatRelativeTime,
|
|
14
|
+
cleanTitle,
|
|
15
|
+
truncateTitle,
|
|
16
|
+
} from '../features/session/utils/title.js';
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Generate a session title using LLM with streaming
|
|
20
|
+
*/
|
|
21
|
+
export async function generateSessionTitleWithStreaming(
|
|
22
|
+
firstMessage: string,
|
|
23
|
+
provider: ProviderId,
|
|
24
|
+
modelName: string,
|
|
25
|
+
providerConfig: any,
|
|
26
|
+
onChunk: (chunk: string) => void
|
|
27
|
+
): Promise<string> {
|
|
28
|
+
if (!firstMessage || firstMessage.trim().length === 0) {
|
|
29
|
+
return 'New Chat';
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
try {
|
|
33
|
+
// Get the provider instance and create the model
|
|
34
|
+
const { getProvider } = await import('../providers/index.js');
|
|
35
|
+
const providerInstance = getProvider(provider);
|
|
36
|
+
const model = providerInstance.createClient(providerConfig, modelName);
|
|
37
|
+
|
|
38
|
+
const streamGenerator = createAIStream({
|
|
39
|
+
model,
|
|
40
|
+
messages: [
|
|
41
|
+
{
|
|
42
|
+
role: 'user',
|
|
43
|
+
content: `You need to generate a SHORT, DESCRIPTIVE title (maximum 50 characters) for a chat conversation.
|
|
44
|
+
|
|
45
|
+
User's first message: "${firstMessage}"
|
|
46
|
+
|
|
47
|
+
Requirements:
|
|
48
|
+
- Summarize the TOPIC or INTENT, don't just copy the message
|
|
49
|
+
- Be concise and descriptive
|
|
50
|
+
- Maximum 50 characters
|
|
51
|
+
- Output ONLY the title, nothing else
|
|
52
|
+
|
|
53
|
+
Examples:
|
|
54
|
+
- Message: "How do I implement authentication?" → Title: "Authentication Implementation"
|
|
55
|
+
- Message: "你好,请帮我修复这个 bug" → Title: "Bug 修复请求"
|
|
56
|
+
- Message: "Can you help me with React hooks?" → Title: "React Hooks Help"
|
|
57
|
+
|
|
58
|
+
Now generate the title:`,
|
|
59
|
+
},
|
|
60
|
+
],
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
let fullTitle = '';
|
|
64
|
+
|
|
65
|
+
// Iterate the async generator and stream to UI
|
|
66
|
+
for await (const chunk of streamGenerator) {
|
|
67
|
+
if (chunk.type === 'text-delta' && chunk.textDelta) {
|
|
68
|
+
fullTitle += chunk.textDelta;
|
|
69
|
+
onChunk(chunk.textDelta);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Clean up title
|
|
74
|
+
let cleaned = fullTitle.trim();
|
|
75
|
+
cleaned = cleaned.replace(/^["'「『]+|["'」』]+$/g, ''); // Remove quotes
|
|
76
|
+
cleaned = cleaned.replace(/^(Title:|标题:)\s*/i, ''); // Remove "Title:" prefix
|
|
77
|
+
cleaned = cleaned.replace(/\n+/g, ' '); // Replace newlines with spaces
|
|
78
|
+
cleaned = cleaned.trim();
|
|
79
|
+
|
|
80
|
+
// Return truncated if needed
|
|
81
|
+
return cleaned.length > 50 ? cleaned.substring(0, 50) + '...' : cleaned;
|
|
82
|
+
} catch (error) {
|
|
83
|
+
// Fallback to simple title generation on any error
|
|
84
|
+
return generateSessionTitle(firstMessage);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Project settings manager - functional implementation
|
|
3
|
+
* Pure functions for managing uncommitted project-specific settings
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import fs from 'node:fs/promises';
|
|
7
|
+
import path from 'node:path';
|
|
8
|
+
import { type Result, success, tryCatchAsync } from '../core/functional/result.js';
|
|
9
|
+
|
|
10
|
+
export interface ProjectSettings {
|
|
11
|
+
/** Default target for the project */
|
|
12
|
+
defaultTarget?: string;
|
|
13
|
+
/** Settings version for migration purposes */
|
|
14
|
+
version?: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const SETTINGS_FILE = '.sylphx-flow/settings.json';
|
|
18
|
+
const CURRENT_VERSION = '1.0.0';
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Get settings file path for a given working directory
|
|
22
|
+
*/
|
|
23
|
+
export const getSettingsPath = (cwd: string = process.cwd()): string =>
|
|
24
|
+
path.join(cwd, SETTINGS_FILE);
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Check if settings file exists
|
|
28
|
+
*/
|
|
29
|
+
export const settingsExists = async (cwd: string = process.cwd()): Promise<boolean> => {
|
|
30
|
+
try {
|
|
31
|
+
await fs.access(getSettingsPath(cwd));
|
|
32
|
+
return true;
|
|
33
|
+
} catch {
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Load project settings from file
|
|
40
|
+
* Returns Result type for explicit error handling
|
|
41
|
+
*/
|
|
42
|
+
export const loadSettings = async (
|
|
43
|
+
cwd: string = process.cwd()
|
|
44
|
+
): Promise<Result<ProjectSettings, Error>> => {
|
|
45
|
+
const settingsPath = getSettingsPath(cwd);
|
|
46
|
+
|
|
47
|
+
return tryCatchAsync(
|
|
48
|
+
async () => {
|
|
49
|
+
const content = await fs.readFile(settingsPath, 'utf8');
|
|
50
|
+
return JSON.parse(content) as ProjectSettings;
|
|
51
|
+
},
|
|
52
|
+
(error: any) => {
|
|
53
|
+
// File not found is not an error - return empty settings
|
|
54
|
+
if (error.code === 'ENOENT') {
|
|
55
|
+
return new Error('EMPTY_SETTINGS');
|
|
56
|
+
}
|
|
57
|
+
return new Error(`Failed to load settings: ${error.message}`);
|
|
58
|
+
}
|
|
59
|
+
).then((result) => {
|
|
60
|
+
// Convert EMPTY_SETTINGS error to success with empty object
|
|
61
|
+
if (result._tag === 'Failure' && result.error.message === 'EMPTY_SETTINGS') {
|
|
62
|
+
return success({});
|
|
63
|
+
}
|
|
64
|
+
return result;
|
|
65
|
+
});
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Save project settings to file
|
|
70
|
+
* Returns Result type for explicit error handling
|
|
71
|
+
*/
|
|
72
|
+
export const saveSettings = async (
|
|
73
|
+
settings: ProjectSettings,
|
|
74
|
+
cwd: string = process.cwd()
|
|
75
|
+
): Promise<Result<void, Error>> => {
|
|
76
|
+
const settingsPath = getSettingsPath(cwd);
|
|
77
|
+
|
|
78
|
+
return tryCatchAsync(
|
|
79
|
+
async () => {
|
|
80
|
+
// Ensure the directory exists
|
|
81
|
+
await fs.mkdir(path.dirname(settingsPath), { recursive: true });
|
|
82
|
+
|
|
83
|
+
// Add current version if not present
|
|
84
|
+
const settingsWithVersion = {
|
|
85
|
+
...settings,
|
|
86
|
+
version: settings.version || CURRENT_VERSION,
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
// Write settings with proper formatting
|
|
90
|
+
await fs.writeFile(settingsPath, `${JSON.stringify(settingsWithVersion, null, 2)}\n`, 'utf8');
|
|
91
|
+
},
|
|
92
|
+
(error: any) => new Error(`Failed to save settings: ${error.message}`)
|
|
93
|
+
);
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Update specific settings properties
|
|
98
|
+
*/
|
|
99
|
+
export const updateSettings = async (
|
|
100
|
+
updates: Partial<ProjectSettings>,
|
|
101
|
+
cwd: string = process.cwd()
|
|
102
|
+
): Promise<Result<void, Error>> => {
|
|
103
|
+
const currentResult = await loadSettings(cwd);
|
|
104
|
+
|
|
105
|
+
if (currentResult._tag === 'Failure') {
|
|
106
|
+
return currentResult;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const newSettings = { ...currentResult.value, ...updates };
|
|
110
|
+
return saveSettings(newSettings, cwd);
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Get the default target from settings
|
|
115
|
+
*/
|
|
116
|
+
export const getDefaultTarget = async (
|
|
117
|
+
cwd: string = process.cwd()
|
|
118
|
+
): Promise<string | undefined> => {
|
|
119
|
+
const result = await loadSettings(cwd);
|
|
120
|
+
return result._tag === 'Success' ? result.value.defaultTarget : undefined;
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Set the default target in settings
|
|
125
|
+
*/
|
|
126
|
+
export const setDefaultTarget = async (
|
|
127
|
+
target: string,
|
|
128
|
+
cwd: string = process.cwd()
|
|
129
|
+
): Promise<Result<void, Error>> => updateSettings({ defaultTarget: target }, cwd);
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Legacy class-based interface for backward compatibility
|
|
133
|
+
* @deprecated Use functional exports instead (loadSettings, saveSettings, etc.)
|
|
134
|
+
*/
|
|
135
|
+
export class ProjectSettings {
|
|
136
|
+
constructor(private cwd: string = process.cwd()) {
|
|
137
|
+
this.settingsPath = getSettingsPath(cwd);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
async load(): Promise<ProjectSettings> {
|
|
141
|
+
const result = await loadSettings(this.cwd);
|
|
142
|
+
if (result._tag === 'Failure') {
|
|
143
|
+
throw result.error;
|
|
144
|
+
}
|
|
145
|
+
return result.value;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
async save(settings: ProjectSettings): Promise<void> {
|
|
149
|
+
const result = await saveSettings(settings, this.cwd);
|
|
150
|
+
if (result._tag === 'Failure') {
|
|
151
|
+
throw result.error;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
async update(updates: Partial<ProjectSettings>): Promise<void> {
|
|
156
|
+
const result = await updateSettings(updates, this.cwd);
|
|
157
|
+
if (result._tag === 'Failure') {
|
|
158
|
+
throw result.error;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
async getDefaultTarget(): Promise<string | undefined> {
|
|
163
|
+
return getDefaultTarget(this.cwd);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
async setDefaultTarget(target: string): Promise<void> {
|
|
167
|
+
const result = await setDefaultTarget(target, this.cwd);
|
|
168
|
+
if (result._tag === 'Failure') {
|
|
169
|
+
throw result.error;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
async exists(): Promise<boolean> {
|
|
174
|
+
return settingsExists(this.cwd);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Singleton instance for backward compatibility
|
|
180
|
+
* @deprecated Use functional exports with explicit cwd parameter
|
|
181
|
+
*/
|
|
182
|
+
export const projectSettings = new ProjectSettings();
|
|
@@ -0,0 +1,410 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Simplified Error Handling System
|
|
3
|
+
* Reduces complexity from 11+ error types to 3 core types
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { logger } from './logger.js';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Error severity levels
|
|
10
|
+
*/
|
|
11
|
+
export enum ErrorSeverity {
|
|
12
|
+
LOW = 'LOW',
|
|
13
|
+
MEDIUM = 'MEDIUM',
|
|
14
|
+
HIGH = 'HIGH',
|
|
15
|
+
CRITICAL = 'CRITICAL',
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Error categories for classification
|
|
20
|
+
*/
|
|
21
|
+
export enum ErrorCategory {
|
|
22
|
+
VALIDATION = 'VALIDATION',
|
|
23
|
+
CONFIGURATION = 'CONFIGURATION',
|
|
24
|
+
DATABASE = 'DATABASE',
|
|
25
|
+
NETWORK = 'NETWORK',
|
|
26
|
+
FILESYSTEM = 'FILESYSTEM',
|
|
27
|
+
AUTHENTICATION = 'AUTHENTICATION',
|
|
28
|
+
RUNTIME = 'RUNTIME',
|
|
29
|
+
EXTERNAL = 'EXTERNAL',
|
|
30
|
+
INTERNAL = 'INTERNAL',
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Core error interface
|
|
35
|
+
*/
|
|
36
|
+
export interface ErrorInfo {
|
|
37
|
+
code: string;
|
|
38
|
+
message: string;
|
|
39
|
+
category: ErrorCategory;
|
|
40
|
+
severity: ErrorSeverity;
|
|
41
|
+
context?: Record<string, unknown>;
|
|
42
|
+
cause?: Error;
|
|
43
|
+
timestamp: string;
|
|
44
|
+
id: string;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Simplified Error Class - The main error type for most use cases
|
|
49
|
+
*/
|
|
50
|
+
export class AppError extends Error implements ErrorInfo {
|
|
51
|
+
public readonly code: string;
|
|
52
|
+
public readonly category: ErrorCategory;
|
|
53
|
+
public readonly severity: ErrorSeverity;
|
|
54
|
+
public readonly context?: Record<string, unknown>;
|
|
55
|
+
public readonly timestamp: string;
|
|
56
|
+
public readonly id: string;
|
|
57
|
+
|
|
58
|
+
constructor(
|
|
59
|
+
message: string,
|
|
60
|
+
code: string,
|
|
61
|
+
category: ErrorCategory = ErrorCategory.RUNTIME,
|
|
62
|
+
severity: ErrorSeverity = ErrorSeverity.MEDIUM,
|
|
63
|
+
context?: Record<string, unknown>,
|
|
64
|
+
cause?: Error
|
|
65
|
+
) {
|
|
66
|
+
super(message, { cause });
|
|
67
|
+
this.name = 'AppError';
|
|
68
|
+
this.code = code;
|
|
69
|
+
this.category = category;
|
|
70
|
+
this.severity = severity;
|
|
71
|
+
this.context = context;
|
|
72
|
+
this.timestamp = new Date().toISOString();
|
|
73
|
+
this.id = this.generateErrorId();
|
|
74
|
+
|
|
75
|
+
// Log the error
|
|
76
|
+
this.logError();
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
private generateErrorId(): string {
|
|
80
|
+
return `err_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
private logError(): void {
|
|
84
|
+
const logLevel = this.getLogLevel();
|
|
85
|
+
logger[logLevel]('AppError occurred', {
|
|
86
|
+
id: this.id,
|
|
87
|
+
code: this.code,
|
|
88
|
+
message: this.message,
|
|
89
|
+
category: this.category,
|
|
90
|
+
severity: this.severity,
|
|
91
|
+
context: this.context,
|
|
92
|
+
stack: this.stack,
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
private getLogLevel(): 'error' | 'warn' | 'info' | 'debug' {
|
|
97
|
+
switch (this.severity) {
|
|
98
|
+
case ErrorSeverity.CRITICAL:
|
|
99
|
+
case ErrorSeverity.HIGH:
|
|
100
|
+
return 'error';
|
|
101
|
+
case ErrorSeverity.MEDIUM:
|
|
102
|
+
return 'warn';
|
|
103
|
+
case ErrorSeverity.LOW:
|
|
104
|
+
return 'info';
|
|
105
|
+
default:
|
|
106
|
+
return 'debug';
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Convert to JSON for serialization
|
|
112
|
+
*/
|
|
113
|
+
toJSON(): ErrorInfo {
|
|
114
|
+
return {
|
|
115
|
+
code: this.code,
|
|
116
|
+
message: this.message,
|
|
117
|
+
category: this.category,
|
|
118
|
+
severity: this.severity,
|
|
119
|
+
context: this.context,
|
|
120
|
+
cause: this.cause?.message,
|
|
121
|
+
timestamp: this.timestamp,
|
|
122
|
+
id: this.id,
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Get user-friendly message
|
|
128
|
+
*/
|
|
129
|
+
getUserMessage(): string {
|
|
130
|
+
// For validation errors, be more specific
|
|
131
|
+
if (this.category === ErrorCategory.VALIDATION) {
|
|
132
|
+
return `Validation failed: ${this.message}`;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// For configuration errors, provide actionable guidance
|
|
136
|
+
if (this.category === ErrorCategory.CONFIGURATION) {
|
|
137
|
+
return `Configuration error: ${this.message}. Please check your settings.`;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// For database errors, be more generic
|
|
141
|
+
if (this.category === ErrorCategory.DATABASE) {
|
|
142
|
+
return 'Database operation failed. Please try again later.';
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// For network/external errors
|
|
146
|
+
if (this.category === ErrorCategory.NETWORK || this.category === ErrorCategory.EXTERNAL) {
|
|
147
|
+
return 'External service unavailable. Please try again later.';
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Default message
|
|
151
|
+
return this.message;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Validation Error - For input validation failures
|
|
157
|
+
*/
|
|
158
|
+
export class ValidationError extends AppError {
|
|
159
|
+
constructor(message: string, field?: string, value?: unknown) {
|
|
160
|
+
const context = field ? { field, value } : undefined;
|
|
161
|
+
super(message, 'VALIDATION_ERROR', ErrorCategory.VALIDATION, ErrorSeverity.LOW, context);
|
|
162
|
+
this.name = 'ValidationError';
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Configuration Error - For configuration issues
|
|
168
|
+
*/
|
|
169
|
+
export class ConfigurationError extends AppError {
|
|
170
|
+
constructor(message: string, configKey?: string) {
|
|
171
|
+
const context = configKey ? { configKey } : undefined;
|
|
172
|
+
super(message, 'CONFIG_ERROR', ErrorCategory.CONFIGURATION, ErrorSeverity.HIGH, context);
|
|
173
|
+
this.name = 'ConfigurationError';
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Database Error - For database operation failures
|
|
179
|
+
*/
|
|
180
|
+
export class DatabaseError extends AppError {
|
|
181
|
+
constructor(message: string, operation?: string, query?: string) {
|
|
182
|
+
const context = operation ? { operation, query } : undefined;
|
|
183
|
+
super(message, 'DATABASE_ERROR', ErrorCategory.DATABASE, ErrorSeverity.HIGH, context);
|
|
184
|
+
this.name = 'DatabaseError';
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Network Error - For network-related failures
|
|
190
|
+
*/
|
|
191
|
+
export class NetworkError extends AppError {
|
|
192
|
+
constructor(message: string, url?: string, statusCode?: number) {
|
|
193
|
+
const context = url ? { url, statusCode } : undefined;
|
|
194
|
+
super(message, 'NETWORK_ERROR', ErrorCategory.NETWORK, ErrorSeverity.MEDIUM, context);
|
|
195
|
+
this.name = 'NetworkError';
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Filesystem Error - For file system operations
|
|
201
|
+
*/
|
|
202
|
+
export class FilesystemError extends AppError {
|
|
203
|
+
constructor(message: string, path?: string, operation?: string) {
|
|
204
|
+
const context = path ? { path, operation } : undefined;
|
|
205
|
+
super(message, 'FILESYSTEM_ERROR', ErrorCategory.FILESYSTEM, ErrorSeverity.MEDIUM, context);
|
|
206
|
+
this.name = 'FilesystemError';
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Authentication Error - For auth failures
|
|
212
|
+
*/
|
|
213
|
+
export class AuthenticationError extends AppError {
|
|
214
|
+
constructor(message = 'Authentication failed') {
|
|
215
|
+
super(message, 'AUTH_ERROR', ErrorCategory.AUTHENTICATION, ErrorSeverity.HIGH);
|
|
216
|
+
this.name = 'AuthenticationError';
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Error factory for convenient error creation
|
|
222
|
+
*/
|
|
223
|
+
export class ErrorFactory {
|
|
224
|
+
/**
|
|
225
|
+
* Create validation error
|
|
226
|
+
*/
|
|
227
|
+
static validation(message: string, field?: string, value?: unknown): ValidationError {
|
|
228
|
+
return new ValidationError(message, field, value);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Create configuration error
|
|
233
|
+
*/
|
|
234
|
+
static configuration(message: string, configKey?: string): ConfigurationError {
|
|
235
|
+
return new ConfigurationError(message, configKey);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Create database error
|
|
240
|
+
*/
|
|
241
|
+
static database(message: string, operation?: string, query?: string): DatabaseError {
|
|
242
|
+
return new DatabaseError(message, operation, query);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Create network error
|
|
247
|
+
*/
|
|
248
|
+
static network(message: string, url?: string, statusCode?: number): NetworkError {
|
|
249
|
+
return new NetworkError(message, url, statusCode);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Create filesystem error
|
|
254
|
+
*/
|
|
255
|
+
static filesystem(message: string, path?: string, operation?: string): FilesystemError {
|
|
256
|
+
return new FilesystemError(message, path, operation);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Create authentication error
|
|
261
|
+
*/
|
|
262
|
+
static authentication(message?: string): AuthenticationError {
|
|
263
|
+
return new AuthenticationError(message);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Create generic app error
|
|
268
|
+
*/
|
|
269
|
+
static app(
|
|
270
|
+
message: string,
|
|
271
|
+
code: string,
|
|
272
|
+
category: ErrorCategory = ErrorCategory.RUNTIME,
|
|
273
|
+
severity: ErrorSeverity = ErrorSeverity.MEDIUM,
|
|
274
|
+
context?: Record<string, unknown>
|
|
275
|
+
): AppError {
|
|
276
|
+
return new AppError(message, code, category, severity, context);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* Create error from unknown error type
|
|
281
|
+
*/
|
|
282
|
+
static fromUnknown(error: unknown, defaultMessage = 'Unknown error occurred'): AppError {
|
|
283
|
+
if (error instanceof AppError) {
|
|
284
|
+
return error;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
if (error instanceof Error) {
|
|
288
|
+
return new AppError(
|
|
289
|
+
error.message,
|
|
290
|
+
'UNKNOWN_ERROR',
|
|
291
|
+
ErrorCategory.INTERNAL,
|
|
292
|
+
ErrorSeverity.MEDIUM,
|
|
293
|
+
{ originalError: error.name },
|
|
294
|
+
error
|
|
295
|
+
);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
if (typeof error === 'string') {
|
|
299
|
+
return new AppError(error, 'STRING_ERROR', ErrorCategory.INTERNAL, ErrorSeverity.LOW);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
return new AppError(
|
|
303
|
+
defaultMessage,
|
|
304
|
+
'UNKNOWN_ERROR',
|
|
305
|
+
ErrorCategory.INTERNAL,
|
|
306
|
+
ErrorSeverity.MEDIUM,
|
|
307
|
+
{ originalError: error }
|
|
308
|
+
);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* Error handler utility class
|
|
314
|
+
*/
|
|
315
|
+
export class ErrorHandler {
|
|
316
|
+
/**
|
|
317
|
+
* Handle error and convert to user-friendly response
|
|
318
|
+
*/
|
|
319
|
+
static handle(error: unknown): { message: string; code: string; severity: ErrorSeverity } {
|
|
320
|
+
const appError = ErrorFactory.fromUnknown(error);
|
|
321
|
+
|
|
322
|
+
return {
|
|
323
|
+
message: appError.getUserMessage(),
|
|
324
|
+
code: appError.code,
|
|
325
|
+
severity: appError.severity,
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
* Execute operation with error handling
|
|
331
|
+
*/
|
|
332
|
+
static async execute<T>(
|
|
333
|
+
operation: () => Promise<T>,
|
|
334
|
+
errorContext?: Record<string, unknown>
|
|
335
|
+
): Promise<{ success: true; data: T } | { success: false; error: AppError }> {
|
|
336
|
+
try {
|
|
337
|
+
const data = await operation();
|
|
338
|
+
return { success: true, data };
|
|
339
|
+
} catch (error) {
|
|
340
|
+
const appError = ErrorFactory.fromUnknown(error);
|
|
341
|
+
|
|
342
|
+
// Add context if provided
|
|
343
|
+
if (errorContext) {
|
|
344
|
+
appError.context = { ...appError.context, ...errorContext };
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
return { success: false, error: appError };
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
/**
|
|
352
|
+
* Check if error is recoverable
|
|
353
|
+
*/
|
|
354
|
+
static isRecoverable(error: Error): boolean {
|
|
355
|
+
if (error instanceof AppError) {
|
|
356
|
+
return error.severity !== ErrorSeverity.CRITICAL;
|
|
357
|
+
}
|
|
358
|
+
return true; // Assume unknown errors are recoverable
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
/**
|
|
362
|
+
* Get retry delay for error
|
|
363
|
+
*/
|
|
364
|
+
static getRetryDelay(error: Error, attempt: number): number {
|
|
365
|
+
if (error instanceof AppError) {
|
|
366
|
+
switch (error.category) {
|
|
367
|
+
case ErrorCategory.NETWORK:
|
|
368
|
+
case ErrorCategory.EXTERNAL:
|
|
369
|
+
return Math.min(1000 * 2 ** attempt, 30000); // Exponential backoff
|
|
370
|
+
case ErrorCategory.DATABASE:
|
|
371
|
+
return Math.min(500 * 2 ** attempt, 5000);
|
|
372
|
+
default:
|
|
373
|
+
return 1000;
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
return 1000;
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
/**
|
|
381
|
+
* Convenience functions
|
|
382
|
+
*/
|
|
383
|
+
export const createValidationError = (message: string, field?: string, value?: unknown) =>
|
|
384
|
+
ErrorFactory.validation(message, field, value);
|
|
385
|
+
|
|
386
|
+
export const createConfigurationError = (message: string, configKey?: string) =>
|
|
387
|
+
ErrorFactory.configuration(message, configKey);
|
|
388
|
+
|
|
389
|
+
export const createDatabaseError = (message: string, operation?: string, query?: string) =>
|
|
390
|
+
ErrorFactory.database(message, operation, query);
|
|
391
|
+
|
|
392
|
+
export const createNetworkError = (message: string, url?: string, statusCode?: number) =>
|
|
393
|
+
ErrorFactory.network(message, url, statusCode);
|
|
394
|
+
|
|
395
|
+
export const createFilesystemError = (message: string, path?: string, operation?: string) =>
|
|
396
|
+
ErrorFactory.filesystem(message, path, operation);
|
|
397
|
+
|
|
398
|
+
export const createAuthenticationError = (message?: string) => ErrorFactory.authentication(message);
|
|
399
|
+
|
|
400
|
+
export const createError = (
|
|
401
|
+
message: string,
|
|
402
|
+
code: string,
|
|
403
|
+
category: ErrorCategory = ErrorCategory.RUNTIME,
|
|
404
|
+
severity: ErrorSeverity = ErrorSeverity.MEDIUM,
|
|
405
|
+
context?: Record<string, unknown>
|
|
406
|
+
) => ErrorFactory.app(message, code, category, severity, context);
|
|
407
|
+
|
|
408
|
+
// Legacy exports for backward compatibility
|
|
409
|
+
export { AppError as BaseError };
|
|
410
|
+
export type { ErrorInfo };
|