@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,300 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { exec } from 'node:child_process';
|
|
4
|
+
import { promisify } from 'node:util';
|
|
5
|
+
import chalk from 'chalk';
|
|
6
|
+
import ora from 'ora';
|
|
7
|
+
import type { ProjectState } from './state-detector.js';
|
|
8
|
+
import { CLIError } from '../utils/error-handler.js';
|
|
9
|
+
import { ConfigService } from '../services/config-service.js';
|
|
10
|
+
import { getProjectSettingsFile } from '../config/constants.js';
|
|
11
|
+
|
|
12
|
+
const execAsync = promisify(exec);
|
|
13
|
+
|
|
14
|
+
export interface UpgradeOptions {
|
|
15
|
+
verbose?: boolean;
|
|
16
|
+
dryRun?: boolean;
|
|
17
|
+
skipBackup?: boolean;
|
|
18
|
+
backupPath?: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface UpgradeResult {
|
|
22
|
+
success: boolean;
|
|
23
|
+
upgrades: {
|
|
24
|
+
flow?: { from: string; to: string };
|
|
25
|
+
target?: { from: string; to: string };
|
|
26
|
+
components?: string[];
|
|
27
|
+
};
|
|
28
|
+
message: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export class UpgradeManager {
|
|
32
|
+
private projectPath: string;
|
|
33
|
+
private options: UpgradeOptions;
|
|
34
|
+
|
|
35
|
+
constructor(projectPath: string = process.cwd(), options: UpgradeOptions = {}) {
|
|
36
|
+
this.projectPath = projectPath;
|
|
37
|
+
this.options = options;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async checkUpdates(): Promise<{
|
|
41
|
+
flowUpdate: boolean;
|
|
42
|
+
targetUpdate: boolean;
|
|
43
|
+
flowVersion: { current: string; latest: string } | null;
|
|
44
|
+
targetVersion: { current: string; latest: string } | null;
|
|
45
|
+
}> {
|
|
46
|
+
if (this.options.verbose) {
|
|
47
|
+
console.log('检查更新...');
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const [flowLatest, targetLatest] = await Promise.all([
|
|
51
|
+
this.getLatestFlowVersion(),
|
|
52
|
+
this.getLatestTargetVersion(),
|
|
53
|
+
]);
|
|
54
|
+
|
|
55
|
+
let flowVersion: { current: string; latest: string } | null = null;
|
|
56
|
+
let targetVersion: { current: string; latest: string } | null = null;
|
|
57
|
+
|
|
58
|
+
// 当前 Flow 版本
|
|
59
|
+
const flowCurrent = await this.getCurrentFlowVersion();
|
|
60
|
+
|
|
61
|
+
if (flowLatest && flowCurrent && flowLatest !== flowCurrent) {
|
|
62
|
+
flowVersion = { current: flowCurrent, latest: flowLatest };
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// 当前 Target 版本
|
|
66
|
+
const configPath = path.join(this.projectPath, getProjectSettingsFile());
|
|
67
|
+
try {
|
|
68
|
+
const config = JSON.parse(await fs.readFile(configPath, 'utf-8'));
|
|
69
|
+
const target = config.target;
|
|
70
|
+
|
|
71
|
+
if (target) {
|
|
72
|
+
const targetCurrent = await this.getCurrentTargetVersion(target);
|
|
73
|
+
|
|
74
|
+
if (targetLatest && targetCurrent && targetLatest !== targetCurrent) {
|
|
75
|
+
targetVersion = { current: targetCurrent, latest: targetLatest };
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
} catch {
|
|
79
|
+
// 无法读取配置
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return {
|
|
83
|
+
flowUpdate: !!flowVersion,
|
|
84
|
+
targetUpdate: !!targetVersion,
|
|
85
|
+
flowVersion,
|
|
86
|
+
targetVersion,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
async upgradeFlow(state: ProjectState): Promise<boolean> {
|
|
91
|
+
if (!state.outdated || !state.latestVersion) {
|
|
92
|
+
return false;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const spinner = ora('升级 Sylphx Flow...').start();
|
|
96
|
+
|
|
97
|
+
try {
|
|
98
|
+
// 备份当前配置
|
|
99
|
+
if (!this.options.skipBackup) {
|
|
100
|
+
await this.backupConfig();
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (this.options.dryRun) {
|
|
104
|
+
spinner.succeed(`模拟升级: ${state.version} → ${state.latestVersion}`);
|
|
105
|
+
return true;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// sym link 方式 - 实际需要重新安装
|
|
109
|
+
// 这里假设用户会通过 git pull 或 npm update 更新
|
|
110
|
+
// 我们只需要更新配置文件和 component
|
|
111
|
+
|
|
112
|
+
// 更新配置文件中的版本号
|
|
113
|
+
const configPath = path.join(this.projectPath, getProjectSettingsFile());
|
|
114
|
+
try {
|
|
115
|
+
const config = JSON.parse(await fs.readFile(configPath, 'utf-8'));
|
|
116
|
+
config.version = state.latestVersion;
|
|
117
|
+
config.lastUpdated = new Date().toISOString();
|
|
118
|
+
await fs.writeFile(configPath, JSON.stringify(config, null, 2));
|
|
119
|
+
} catch {
|
|
120
|
+
// 无法更新配置
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
spinner.succeed(`已升级到 ${state.latestVersion}`);
|
|
124
|
+
return true;
|
|
125
|
+
} catch (error) {
|
|
126
|
+
spinner.fail('升级失败');
|
|
127
|
+
throw new CLIError(
|
|
128
|
+
`升级 Sylphx Flow 失败: ${error instanceof Error ? error.message : String(error)}`,
|
|
129
|
+
'UPGRADE_FAILED'
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
async upgradeTarget(state: ProjectState): Promise<boolean> {
|
|
135
|
+
if (!state.target || !state.targetLatestVersion) {
|
|
136
|
+
return false;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const spinner = ora(`升级 ${state.target}...`).start();
|
|
140
|
+
|
|
141
|
+
try {
|
|
142
|
+
if (state.target === 'claude-code') {
|
|
143
|
+
await this.upgradeClaudeCode();
|
|
144
|
+
} else if (state.target === 'opencode') {
|
|
145
|
+
await this.upgradeOpenCode();
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
spinner.succeed(`${state.target} 已升级到最新版本`);
|
|
149
|
+
return true;
|
|
150
|
+
} catch (error) {
|
|
151
|
+
spinner.fail(`${state.target} 升级失败`);
|
|
152
|
+
throw new CLIError(
|
|
153
|
+
`升级 ${state.target} 失败: ${error instanceof Error ? error.message : String(error)}`,
|
|
154
|
+
'TARGET_UPGRADE_FAILED'
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
private async upgradeClaudeCode(): Promise<void> {
|
|
160
|
+
if (this.options.dryRun) {
|
|
161
|
+
console.log('模拟: claude update');
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Claude Code has built-in update command
|
|
166
|
+
const { stdout } = await execAsync('claude update');
|
|
167
|
+
|
|
168
|
+
if (this.options.verbose) {
|
|
169
|
+
console.log(stdout);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
private async upgradeOpenCode(): Promise<void> {
|
|
174
|
+
if (this.options.dryRun) {
|
|
175
|
+
console.log('模拟: opencode upgrade');
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// OpenCode has built-in upgrade command
|
|
180
|
+
const { stdout } = await execAsync('opencode upgrade');
|
|
181
|
+
|
|
182
|
+
if (this.options.verbose) {
|
|
183
|
+
console.log(stdout);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
async upgradeComponents(components: string[]): Promise<string[]> {
|
|
188
|
+
const upgraded: string[] = [];
|
|
189
|
+
|
|
190
|
+
for (const component of components) {
|
|
191
|
+
const spinner = ora(`升级 ${component}...`).start();
|
|
192
|
+
|
|
193
|
+
try {
|
|
194
|
+
await this.upgradeComponent(component);
|
|
195
|
+
spinner.succeed(`${component} 已升级`);
|
|
196
|
+
upgraded.push(component);
|
|
197
|
+
} catch (error) {
|
|
198
|
+
spinner.fail(`${component} 升级失败`);
|
|
199
|
+
if (this.options.verbose) {
|
|
200
|
+
console.error(error);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
return upgraded;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
private async upgradeComponent(component: string): Promise<void> {
|
|
209
|
+
// 删除旧版本
|
|
210
|
+
const componentPath = path.join(this.projectPath, '.claude', component);
|
|
211
|
+
await fs.rm(componentPath, { recursive: true, force: true });
|
|
212
|
+
|
|
213
|
+
// 重新安装最新版本
|
|
214
|
+
// 实际实现会调用相应的 installer
|
|
215
|
+
// 这里用 dry-run 模式模拟
|
|
216
|
+
if (this.options.dryRun) {
|
|
217
|
+
console.log(`模拟: 重新安装 ${component}`);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
private async backupConfig(): Promise<string> {
|
|
222
|
+
const backupDir = this.options.backupPath || path.join(this.projectPath, '.claude-backup');
|
|
223
|
+
await fs.mkdir(backupDir, { recursive: true });
|
|
224
|
+
|
|
225
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
226
|
+
const backupPath = path.join(backupDir, `backup-${timestamp}`);
|
|
227
|
+
|
|
228
|
+
// 备份 .claude 目录
|
|
229
|
+
const claudePath = path.join(this.projectPath, '.claude');
|
|
230
|
+
try {
|
|
231
|
+
await fs.cp(claudePath, path.join(backupPath, '.claude'), { recursive: true });
|
|
232
|
+
} catch {
|
|
233
|
+
// .claude 目录可能不存在
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// 备份配置文件
|
|
237
|
+
const configPath = path.join(this.projectPath, getProjectSettingsFile());
|
|
238
|
+
try {
|
|
239
|
+
await fs.cp(configPath, path.join(backupPath, getProjectSettingsFile()));
|
|
240
|
+
} catch {
|
|
241
|
+
// 配置文件可能不存在
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
return backupPath;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
private async getCurrentFlowVersion(): Promise<string | null> {
|
|
248
|
+
try {
|
|
249
|
+
const configPath = path.join(this.projectPath, getProjectSettingsFile());
|
|
250
|
+
const config = JSON.parse(await fs.readFile(configPath, 'utf-8'));
|
|
251
|
+
return config.version || null;
|
|
252
|
+
} catch {
|
|
253
|
+
return null;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
private async getLatestFlowVersion(): Promise<string | null> {
|
|
258
|
+
try {
|
|
259
|
+
// 从当前 package.json 获取(假设是当前开发版本)
|
|
260
|
+
const packagePath = path.join(__dirname, '..', '..', 'package.json');
|
|
261
|
+
const packageJson = JSON.parse(await fs.readFile(packagePath, 'utf-8'));
|
|
262
|
+
return packageJson.version || null;
|
|
263
|
+
} catch {
|
|
264
|
+
return null;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
private async getCurrentTargetVersion(target: string): Promise<string | null> {
|
|
269
|
+
if (target === 'claude-code') {
|
|
270
|
+
try {
|
|
271
|
+
const { stdout } = await execAsync('claude --version');
|
|
272
|
+
const match = stdout.match(/v?(\d+\.\d+\.\d+)/);
|
|
273
|
+
return match ? match[1] : null;
|
|
274
|
+
} catch {
|
|
275
|
+
return null;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
return null;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
private async getLatestTargetVersion(target: string): Promise<string | null> {
|
|
283
|
+
if (target === 'claude-code') {
|
|
284
|
+
try {
|
|
285
|
+
const { stdout } = await execAsync('npm view @anthropic-ai/claude-code version');
|
|
286
|
+
return stdout.trim();
|
|
287
|
+
} catch {
|
|
288
|
+
return null;
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
return null;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
static async isUpgradeAvailable(): Promise<boolean> {
|
|
296
|
+
const manager = new UpgradeManager();
|
|
297
|
+
const updates = await manager.checkUpdates();
|
|
298
|
+
return updates.flowUpdate || updates.targetUpdate;
|
|
299
|
+
}
|
|
300
|
+
}
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for Limit Validation Utilities
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { describe, expect, it } from 'bun:test';
|
|
6
|
+
import { validateLimit } from './limit.js';
|
|
7
|
+
|
|
8
|
+
describe('validateLimit', () => {
|
|
9
|
+
describe('with default parameters (defaultLimit=50, maxLimit=1000)', () => {
|
|
10
|
+
it('returns default limit when undefined', () => {
|
|
11
|
+
const result = validateLimit(undefined);
|
|
12
|
+
expect(result._tag).toBe('Success');
|
|
13
|
+
expect(result._tag === 'Success' && result.value).toBe(50);
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it('accepts valid number', () => {
|
|
17
|
+
const result = validateLimit(100);
|
|
18
|
+
expect(result._tag).toBe('Success');
|
|
19
|
+
expect(result._tag === 'Success' && result.value).toBe(100);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it('accepts valid string number', () => {
|
|
23
|
+
const result = validateLimit('500');
|
|
24
|
+
expect(result._tag).toBe('Success');
|
|
25
|
+
expect(result._tag === 'Success' && result.value).toBe(500);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('accepts limit at max', () => {
|
|
29
|
+
const result = validateLimit(1000);
|
|
30
|
+
expect(result._tag).toBe('Success');
|
|
31
|
+
expect(result._tag === 'Success' && result.value).toBe(1000);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('rejects limit above max', () => {
|
|
35
|
+
const result = validateLimit(1001);
|
|
36
|
+
expect(result._tag).toBe('Failure');
|
|
37
|
+
expect(result._tag === 'Failure' && result.error.message).toBe('Limit cannot exceed 1000');
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it('rejects zero', () => {
|
|
41
|
+
const result = validateLimit(0);
|
|
42
|
+
expect(result._tag).toBe('Failure');
|
|
43
|
+
expect(result._tag === 'Failure' && result.error.message).toBe(
|
|
44
|
+
'Limit must be a positive number'
|
|
45
|
+
);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('rejects negative number', () => {
|
|
49
|
+
const result = validateLimit(-10);
|
|
50
|
+
expect(result._tag).toBe('Failure');
|
|
51
|
+
expect(result._tag === 'Failure' && result.error.message).toBe(
|
|
52
|
+
'Limit must be a positive number'
|
|
53
|
+
);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it('rejects invalid string', () => {
|
|
57
|
+
const result = validateLimit('invalid');
|
|
58
|
+
expect(result._tag).toBe('Failure');
|
|
59
|
+
expect(result._tag === 'Failure' && result.error.message).toBe(
|
|
60
|
+
'Limit must be a positive number'
|
|
61
|
+
);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('rejects empty string', () => {
|
|
65
|
+
const result = validateLimit('');
|
|
66
|
+
expect(result._tag).toBe('Failure');
|
|
67
|
+
expect(result._tag === 'Failure' && result.error.message).toBe(
|
|
68
|
+
'Limit must be a positive number'
|
|
69
|
+
);
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
describe('with custom default (defaultLimit=10)', () => {
|
|
74
|
+
it('returns custom default when undefined', () => {
|
|
75
|
+
const result = validateLimit(undefined, 10);
|
|
76
|
+
expect(result._tag).toBe('Success');
|
|
77
|
+
expect(result._tag === 'Success' && result.value).toBe(10);
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
describe('with custom max (maxLimit=100)', () => {
|
|
82
|
+
it('accepts limit at custom max', () => {
|
|
83
|
+
const result = validateLimit(100, 10, 100);
|
|
84
|
+
expect(result._tag).toBe('Success');
|
|
85
|
+
expect(result._tag === 'Success' && result.value).toBe(100);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it('rejects limit above custom max', () => {
|
|
89
|
+
const result = validateLimit(101, 10, 100);
|
|
90
|
+
expect(result._tag).toBe('Failure');
|
|
91
|
+
expect(result._tag === 'Failure' && result.error.message).toBe('Limit cannot exceed 100');
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
describe('knowledge feature defaults (defaultLimit=10, maxLimit=100)', () => {
|
|
96
|
+
it('returns 10 when undefined', () => {
|
|
97
|
+
const result = validateLimit(undefined, 10, 100);
|
|
98
|
+
expect(result._tag).toBe('Success');
|
|
99
|
+
expect(result._tag === 'Success' && result.value).toBe(10);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it('accepts 50', () => {
|
|
103
|
+
const result = validateLimit(50, 10, 100);
|
|
104
|
+
expect(result._tag).toBe('Success');
|
|
105
|
+
expect(result._tag === 'Success' && result.value).toBe(50);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it('rejects 101', () => {
|
|
109
|
+
const result = validateLimit(101, 10, 100);
|
|
110
|
+
expect(result._tag).toBe('Failure');
|
|
111
|
+
expect(result._tag === 'Failure' && result.error.message).toBe('Limit cannot exceed 100');
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
describe('memory feature defaults (defaultLimit=50, maxLimit=1000)', () => {
|
|
116
|
+
it('returns 50 when undefined', () => {
|
|
117
|
+
const result = validateLimit(undefined, 50, 1000);
|
|
118
|
+
expect(result._tag).toBe('Success');
|
|
119
|
+
expect(result._tag === 'Success' && result.value).toBe(50);
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it('accepts 500', () => {
|
|
123
|
+
const result = validateLimit(500, 50, 1000);
|
|
124
|
+
expect(result._tag).toBe('Success');
|
|
125
|
+
expect(result._tag === 'Success' && result.value).toBe(500);
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it('rejects 1001', () => {
|
|
129
|
+
const result = validateLimit(1001, 50, 1000);
|
|
130
|
+
expect(result._tag).toBe('Failure');
|
|
131
|
+
expect(result._tag === 'Failure' && result.error.message).toBe('Limit cannot exceed 1000');
|
|
132
|
+
});
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
describe('edge cases', () => {
|
|
136
|
+
it('accepts 1 (minimum valid)', () => {
|
|
137
|
+
const result = validateLimit(1);
|
|
138
|
+
expect(result._tag).toBe('Success');
|
|
139
|
+
expect(result._tag === 'Success' && result.value).toBe(1);
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
it('handles string with whitespace', () => {
|
|
143
|
+
const result = validateLimit(' 100 ');
|
|
144
|
+
expect(result._tag).toBe('Success');
|
|
145
|
+
expect(result._tag === 'Success' && result.value).toBe(100);
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
it('rejects decimal numbers', () => {
|
|
149
|
+
const result = validateLimit('10.5');
|
|
150
|
+
expect(result._tag).toBe('Success');
|
|
151
|
+
// parseInt truncates to 10
|
|
152
|
+
expect(result._tag === 'Success' && result.value).toBe(10);
|
|
153
|
+
});
|
|
154
|
+
});
|
|
155
|
+
});
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Limit Validation Utilities
|
|
3
|
+
* Shared validation for limit parameters across features
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { Result } from '../functional/result.js';
|
|
7
|
+
import { success, failure } from '../functional/result.js';
|
|
8
|
+
import type { AppError } from '../functional/error-types.js';
|
|
9
|
+
import { validationError } from '../functional/error-types.js';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Validate limit parameter with configurable defaults and max
|
|
13
|
+
* Pure - validation with explicit parameters
|
|
14
|
+
*
|
|
15
|
+
* @param limit - The limit value to validate (string, number, or undefined)
|
|
16
|
+
* @param defaultLimit - Default limit if undefined (default: 50)
|
|
17
|
+
* @param maxLimit - Maximum allowed limit (default: 1000)
|
|
18
|
+
* @returns Result with validated limit number or validation error
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* validateLimit(undefined, 10, 100) // success(10)
|
|
22
|
+
* validateLimit('50', 10, 100) // success(50)
|
|
23
|
+
* validateLimit('150', 10, 100) // failure("Limit cannot exceed 100")
|
|
24
|
+
* validateLimit('invalid', 10, 100) // failure("Limit must be a positive number")
|
|
25
|
+
*/
|
|
26
|
+
export function validateLimit(
|
|
27
|
+
limit: string | number | undefined,
|
|
28
|
+
defaultLimit: number = 50,
|
|
29
|
+
maxLimit: number = 1000
|
|
30
|
+
): Result<number, AppError> {
|
|
31
|
+
if (limit === undefined) {
|
|
32
|
+
return success(defaultLimit);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const numLimit = typeof limit === 'string' ? Number.parseInt(limit, 10) : limit;
|
|
36
|
+
|
|
37
|
+
if (Number.isNaN(numLimit) || numLimit < 1) {
|
|
38
|
+
return failure(validationError('Limit must be a positive number', 'limit', limit));
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (numLimit > maxLimit) {
|
|
42
|
+
return failure(validationError(`Limit cannot exceed ${maxLimit}`, 'limit', limit));
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return success(numLimit);
|
|
46
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for Query Validation Utilities
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { describe, expect, it } from 'bun:test';
|
|
6
|
+
import { normalizeQuery } from './query.js';
|
|
7
|
+
|
|
8
|
+
describe('normalizeQuery', () => {
|
|
9
|
+
it('trims leading whitespace', () => {
|
|
10
|
+
expect(normalizeQuery(' hello')).toBe('hello');
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
it('trims trailing whitespace', () => {
|
|
14
|
+
expect(normalizeQuery('hello ')).toBe('hello');
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it('trims both leading and trailing whitespace', () => {
|
|
18
|
+
expect(normalizeQuery(' hello ')).toBe('hello');
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it('preserves internal whitespace', () => {
|
|
22
|
+
expect(normalizeQuery('hello world')).toBe('hello world');
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('handles empty string', () => {
|
|
26
|
+
expect(normalizeQuery('')).toBe('');
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('handles string with only whitespace', () => {
|
|
30
|
+
expect(normalizeQuery(' ')).toBe('');
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('handles tabs and newlines', () => {
|
|
34
|
+
expect(normalizeQuery('\t\nhello\t\n')).toBe('hello');
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('handles already trimmed string', () => {
|
|
38
|
+
expect(normalizeQuery('hello')).toBe('hello');
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('handles multi-line queries', () => {
|
|
42
|
+
expect(normalizeQuery(' line1\nline2 ')).toBe('line1\nline2');
|
|
43
|
+
});
|
|
44
|
+
});
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Query Validation Utilities
|
|
3
|
+
* Shared validation and normalization for query parameters
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Normalize query string by trimming whitespace
|
|
8
|
+
* Pure - string transformation
|
|
9
|
+
*
|
|
10
|
+
* @param query - The query string to normalize
|
|
11
|
+
* @returns Trimmed query string
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* normalizeQuery(' hello ') // 'hello'
|
|
15
|
+
* normalizeQuery('test') // 'test'
|
|
16
|
+
* normalizeQuery('') // ''
|
|
17
|
+
*/
|
|
18
|
+
export function normalizeQuery(query: string): string {
|
|
19
|
+
return query.trim();
|
|
20
|
+
}
|