@sylphx/flow 1.0.1 → 1.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (229) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/package.json +10 -9
  3. package/src/commands/codebase-command.ts +168 -0
  4. package/src/commands/flow-command.ts +1137 -0
  5. package/src/commands/flow-orchestrator.ts +296 -0
  6. package/src/commands/hook-command.ts +444 -0
  7. package/src/commands/init-command.ts +92 -0
  8. package/src/commands/init-core.ts +322 -0
  9. package/src/commands/knowledge-command.ts +161 -0
  10. package/src/commands/run-command.ts +120 -0
  11. package/src/components/benchmark-monitor.tsx +331 -0
  12. package/src/components/reindex-progress.tsx +261 -0
  13. package/src/composables/functional/index.ts +14 -0
  14. package/src/composables/functional/useEnvironment.ts +171 -0
  15. package/src/composables/functional/useFileSystem.ts +139 -0
  16. package/src/composables/index.ts +5 -0
  17. package/src/composables/useEnv.ts +13 -0
  18. package/src/composables/useRuntimeConfig.ts +27 -0
  19. package/src/composables/useTargetConfig.ts +45 -0
  20. package/src/config/ai-config.ts +376 -0
  21. package/src/config/constants.ts +35 -0
  22. package/src/config/index.ts +27 -0
  23. package/src/config/rules.ts +43 -0
  24. package/src/config/servers.ts +371 -0
  25. package/src/config/targets.ts +126 -0
  26. package/src/core/agent-loader.ts +141 -0
  27. package/src/core/agent-manager.ts +174 -0
  28. package/src/core/ai-sdk.ts +603 -0
  29. package/src/core/app-factory.ts +381 -0
  30. package/src/core/builtin-agents.ts +9 -0
  31. package/src/core/command-system.ts +550 -0
  32. package/src/core/config-system.ts +550 -0
  33. package/src/core/connection-pool.ts +390 -0
  34. package/src/core/di-container.ts +155 -0
  35. package/src/core/error-handling.ts +519 -0
  36. package/src/core/formatting/bytes.test.ts +115 -0
  37. package/src/core/formatting/bytes.ts +64 -0
  38. package/src/core/functional/async.ts +313 -0
  39. package/src/core/functional/either.ts +109 -0
  40. package/src/core/functional/error-handler.ts +135 -0
  41. package/src/core/functional/error-types.ts +311 -0
  42. package/src/core/functional/index.ts +19 -0
  43. package/src/core/functional/option.ts +142 -0
  44. package/src/core/functional/pipe.ts +189 -0
  45. package/src/core/functional/result.ts +204 -0
  46. package/src/core/functional/validation.ts +138 -0
  47. package/src/core/headless-display.ts +96 -0
  48. package/src/core/index.ts +6 -0
  49. package/src/core/installers/file-installer.ts +303 -0
  50. package/src/core/installers/mcp-installer.ts +213 -0
  51. package/src/core/interfaces/index.ts +22 -0
  52. package/src/core/interfaces/repository.interface.ts +91 -0
  53. package/src/core/interfaces/service.interface.ts +133 -0
  54. package/src/core/interfaces.ts +129 -0
  55. package/src/core/loop-controller.ts +200 -0
  56. package/src/core/result.ts +351 -0
  57. package/src/core/rule-loader.ts +147 -0
  58. package/src/core/rule-manager.ts +240 -0
  59. package/src/core/service-config.ts +252 -0
  60. package/src/core/session-service.ts +121 -0
  61. package/src/core/state-detector.ts +389 -0
  62. package/src/core/storage-factory.ts +115 -0
  63. package/src/core/stream-handler.ts +288 -0
  64. package/src/core/target-manager.ts +161 -0
  65. package/src/core/type-utils.ts +427 -0
  66. package/src/core/unified-storage.ts +456 -0
  67. package/src/core/upgrade-manager.ts +300 -0
  68. package/src/core/validation/limit.test.ts +155 -0
  69. package/src/core/validation/limit.ts +46 -0
  70. package/src/core/validation/query.test.ts +44 -0
  71. package/src/core/validation/query.ts +20 -0
  72. package/src/db/auto-migrate.ts +322 -0
  73. package/src/db/base-database-client.ts +144 -0
  74. package/src/db/cache-db.ts +218 -0
  75. package/src/db/cache-schema.ts +75 -0
  76. package/src/db/database.ts +70 -0
  77. package/src/db/index.ts +252 -0
  78. package/src/db/memory-db.ts +153 -0
  79. package/src/db/memory-schema.ts +29 -0
  80. package/src/db/schema.ts +289 -0
  81. package/src/db/session-repository.ts +733 -0
  82. package/src/domains/codebase/index.ts +5 -0
  83. package/src/domains/codebase/tools.ts +139 -0
  84. package/src/domains/index.ts +8 -0
  85. package/src/domains/knowledge/index.ts +10 -0
  86. package/src/domains/knowledge/resources.ts +537 -0
  87. package/src/domains/knowledge/tools.ts +174 -0
  88. package/src/domains/utilities/index.ts +6 -0
  89. package/src/domains/utilities/time/index.ts +5 -0
  90. package/src/domains/utilities/time/tools.ts +291 -0
  91. package/src/index.ts +211 -0
  92. package/src/services/agent-service.ts +273 -0
  93. package/src/services/claude-config-service.ts +252 -0
  94. package/src/services/config-service.ts +258 -0
  95. package/src/services/evaluation-service.ts +271 -0
  96. package/src/services/functional/evaluation-logic.ts +296 -0
  97. package/src/services/functional/file-processor.ts +273 -0
  98. package/src/services/functional/index.ts +12 -0
  99. package/src/services/index.ts +13 -0
  100. package/src/services/mcp-service.ts +432 -0
  101. package/src/services/memory.service.ts +476 -0
  102. package/src/services/search/base-indexer.ts +156 -0
  103. package/src/services/search/codebase-indexer-types.ts +38 -0
  104. package/src/services/search/codebase-indexer.ts +647 -0
  105. package/src/services/search/embeddings-provider.ts +455 -0
  106. package/src/services/search/embeddings.ts +316 -0
  107. package/src/services/search/functional-indexer.ts +323 -0
  108. package/src/services/search/index.ts +27 -0
  109. package/src/services/search/indexer.ts +380 -0
  110. package/src/services/search/knowledge-indexer.ts +422 -0
  111. package/src/services/search/semantic-search.ts +244 -0
  112. package/src/services/search/tfidf.ts +559 -0
  113. package/src/services/search/unified-search-service.ts +888 -0
  114. package/src/services/smart-config-service.ts +385 -0
  115. package/src/services/storage/cache-storage.ts +487 -0
  116. package/src/services/storage/drizzle-storage.ts +581 -0
  117. package/src/services/storage/index.ts +15 -0
  118. package/src/services/storage/lancedb-vector-storage.ts +494 -0
  119. package/src/services/storage/memory-storage.ts +268 -0
  120. package/src/services/storage/separated-storage.ts +467 -0
  121. package/src/services/storage/vector-storage.ts +13 -0
  122. package/src/shared/agents/index.ts +63 -0
  123. package/src/shared/files/index.ts +99 -0
  124. package/src/shared/index.ts +32 -0
  125. package/src/shared/logging/index.ts +24 -0
  126. package/src/shared/processing/index.ts +153 -0
  127. package/src/shared/types/index.ts +25 -0
  128. package/src/targets/claude-code.ts +574 -0
  129. package/src/targets/functional/claude-code-logic.ts +185 -0
  130. package/src/targets/functional/index.ts +6 -0
  131. package/src/targets/opencode.ts +529 -0
  132. package/src/types/agent.types.ts +32 -0
  133. package/src/types/api/batch.ts +108 -0
  134. package/src/types/api/errors.ts +118 -0
  135. package/src/types/api/index.ts +55 -0
  136. package/src/types/api/requests.ts +76 -0
  137. package/src/types/api/responses.ts +180 -0
  138. package/src/types/api/websockets.ts +85 -0
  139. package/src/types/api.types.ts +9 -0
  140. package/src/types/benchmark.ts +49 -0
  141. package/src/types/cli.types.ts +87 -0
  142. package/src/types/common.types.ts +35 -0
  143. package/src/types/database.types.ts +510 -0
  144. package/src/types/mcp-config.types.ts +448 -0
  145. package/src/types/mcp.types.ts +69 -0
  146. package/src/types/memory-types.ts +63 -0
  147. package/src/types/provider.types.ts +28 -0
  148. package/src/types/rule.types.ts +24 -0
  149. package/src/types/session.types.ts +214 -0
  150. package/src/types/target-config.types.ts +295 -0
  151. package/src/types/target.types.ts +140 -0
  152. package/src/types/todo.types.ts +25 -0
  153. package/src/types.ts +40 -0
  154. package/src/utils/advanced-tokenizer.ts +191 -0
  155. package/src/utils/agent-enhancer.ts +114 -0
  156. package/src/utils/ai-model-fetcher.ts +19 -0
  157. package/src/utils/async-file-operations.ts +516 -0
  158. package/src/utils/audio-player.ts +345 -0
  159. package/src/utils/cli-output.ts +266 -0
  160. package/src/utils/codebase-helpers.ts +211 -0
  161. package/src/utils/console-ui.ts +79 -0
  162. package/src/utils/database-errors.ts +140 -0
  163. package/src/utils/debug-logger.ts +49 -0
  164. package/src/utils/error-handler.ts +53 -0
  165. package/src/utils/file-operations.ts +310 -0
  166. package/src/utils/file-scanner.ts +259 -0
  167. package/src/utils/functional/array.ts +355 -0
  168. package/src/utils/functional/index.ts +15 -0
  169. package/src/utils/functional/object.ts +279 -0
  170. package/src/utils/functional/string.ts +281 -0
  171. package/src/utils/functional.ts +543 -0
  172. package/src/utils/help.ts +20 -0
  173. package/src/utils/immutable-cache.ts +106 -0
  174. package/src/utils/index.ts +78 -0
  175. package/src/utils/jsonc.ts +158 -0
  176. package/src/utils/logger.ts +396 -0
  177. package/src/utils/mcp-config.ts +249 -0
  178. package/src/utils/memory-tui.ts +414 -0
  179. package/src/utils/models-dev.ts +91 -0
  180. package/src/utils/notifications.ts +169 -0
  181. package/src/utils/object-utils.ts +51 -0
  182. package/src/utils/parallel-operations.ts +487 -0
  183. package/src/utils/paths.ts +143 -0
  184. package/src/utils/process-manager.ts +155 -0
  185. package/src/utils/prompts.ts +120 -0
  186. package/src/utils/search-tool-builder.ts +214 -0
  187. package/src/utils/secret-utils.ts +179 -0
  188. package/src/utils/security.ts +537 -0
  189. package/src/utils/session-manager.ts +168 -0
  190. package/src/utils/session-title.ts +87 -0
  191. package/src/utils/settings.ts +182 -0
  192. package/src/utils/simplified-errors.ts +410 -0
  193. package/src/utils/sync-utils.ts +159 -0
  194. package/src/utils/target-config.ts +570 -0
  195. package/src/utils/target-utils.ts +394 -0
  196. package/src/utils/template-engine.ts +94 -0
  197. package/src/utils/test-audio.ts +71 -0
  198. package/src/utils/todo-context.ts +46 -0
  199. package/src/utils/token-counter.ts +288 -0
  200. package/dist/index.d.ts +0 -10
  201. package/dist/index.js +0 -59554
  202. package/dist/lancedb.linux-x64-gnu-b7f0jgsz.node +0 -0
  203. package/dist/lancedb.linux-x64-musl-tgcv22rx.node +0 -0
  204. package/dist/shared/chunk-25dwp0dp.js +0 -89
  205. package/dist/shared/chunk-3pjb6063.js +0 -208
  206. package/dist/shared/chunk-4d6ydpw7.js +0 -2854
  207. package/dist/shared/chunk-4wjcadjk.js +0 -225
  208. package/dist/shared/chunk-5j4w74t6.js +0 -30
  209. package/dist/shared/chunk-5j8m3dh3.js +0 -58
  210. package/dist/shared/chunk-5thh3qem.js +0 -91
  211. package/dist/shared/chunk-6g9xy73m.js +0 -252
  212. package/dist/shared/chunk-7eq34c42.js +0 -23
  213. package/dist/shared/chunk-c2gwgx3r.js +0 -115
  214. package/dist/shared/chunk-cjd3mk4c.js +0 -1320
  215. package/dist/shared/chunk-g5cv6703.js +0 -368
  216. package/dist/shared/chunk-hpkhykhq.js +0 -574
  217. package/dist/shared/chunk-m2322pdk.js +0 -122
  218. package/dist/shared/chunk-nd5fdvaq.js +0 -26
  219. package/dist/shared/chunk-pgd3m6zf.js +0 -108
  220. package/dist/shared/chunk-qk8n91hw.js +0 -494
  221. package/dist/shared/chunk-rkkn8szp.js +0 -16855
  222. package/dist/shared/chunk-t16rfxh0.js +0 -61
  223. package/dist/shared/chunk-t4fbfa5v.js +0 -19
  224. package/dist/shared/chunk-t77h86w6.js +0 -276
  225. package/dist/shared/chunk-v0ez4aef.js +0 -71
  226. package/dist/shared/chunk-v29j2r3s.js +0 -32051
  227. package/dist/shared/chunk-vfbc6ew5.js +0 -765
  228. package/dist/shared/chunk-vmeqwm1c.js +0 -204
  229. package/dist/shared/chunk-x66eh37x.js +0 -137
@@ -0,0 +1,389 @@
1
+ import fs from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+ import { projectSettings } from '../utils/settings.js';
5
+ import { targetManager } from './target-manager.js';
6
+ import { ConfigService } from '../services/config-service.js';
7
+
8
+ const __filename = fileURLToPath(import.meta.url);
9
+ const __dirname = path.dirname(__filename);
10
+
11
+ export interface ProjectState {
12
+ initialized: boolean;
13
+ version: string | null;
14
+ latestVersion: string | null;
15
+ target: string | null;
16
+ targetVersion: string | null;
17
+ targetLatestVersion: string | null;
18
+ components: {
19
+ agents: { installed: boolean; count: number; version: string | null };
20
+ rules: { installed: boolean; count: number; version: string | null };
21
+ hooks: { installed: boolean; version: string | null };
22
+ mcp: { installed: boolean; serverCount: number; version: string | null };
23
+ outputStyles: { installed: boolean; version: string | null };
24
+ slashCommands: { installed: boolean; count: number; version: string | null };
25
+ };
26
+ corrupted: boolean;
27
+ outdated: boolean;
28
+ lastUpdated: Date | null;
29
+ }
30
+
31
+ export type RecommendedAction = 'FULL_INIT' | 'RUN_ONLY' | 'REPAIR' | 'UPGRADE' | 'UPGRADE_TARGET' | 'CLEAN_INIT';
32
+
33
+ export class StateDetector {
34
+ private projectPath: string;
35
+
36
+ constructor(projectPath: string = process.cwd()) {
37
+ this.projectPath = projectPath;
38
+ }
39
+
40
+ async detect(): Promise<ProjectState> {
41
+ const state: ProjectState = {
42
+ initialized: false,
43
+ version: null,
44
+ latestVersion: await this.getLatestFlowVersion(),
45
+ target: null,
46
+ targetVersion: null,
47
+ targetLatestVersion: null,
48
+ components: {
49
+ agents: { installed: false, count: 0, version: null },
50
+ rules: { installed: false, count: 0, version: null },
51
+ hooks: { installed: false, version: null },
52
+ mcp: { installed: false, serverCount: 0, version: null },
53
+ outputStyles: { installed: false, version: null },
54
+ slashCommands: { installed: false, count: 0, version: null },
55
+ },
56
+ corrupted: false,
57
+ outdated: false,
58
+ lastUpdated: null,
59
+ };
60
+
61
+ try {
62
+ // Check if initialized - use ConfigService for consistency
63
+ state.initialized = await ConfigService.isInitialized(this.projectPath);
64
+
65
+ if (!state.initialized) {
66
+ return state; // Not initialized
67
+ }
68
+
69
+ // Read project settings
70
+ const config = await ConfigService.loadProjectSettings(this.projectPath);
71
+ state.version = config.version || null;
72
+ state.target = config.target || null;
73
+ state.lastUpdated = config.lastUpdated ? new Date(config.lastUpdated) : null;
74
+
75
+ // Check if outdated
76
+ if (state.version && state.latestVersion) {
77
+ state.outdated = this.isVersionOutdated(state.version, state.latestVersion);
78
+ }
79
+
80
+ // Check components based on target
81
+ if (state.target === 'opencode') {
82
+ // OpenCode uses different directory structure
83
+ await this.checkComponent('agents', '.opencode/agent', '*.md', state);
84
+ // OpenCode uses AGENTS.md for rules
85
+ await this.checkFileComponent('rules', 'AGENTS.md', state);
86
+ // OpenCode doesn't have separate hooks directory (hooks config in opencode.jsonc)
87
+ state.components.hooks.installed = false;
88
+ // OpenCode appends output styles to AGENTS.md
89
+ state.components.outputStyles.installed = await this.checkOutputStylesInAGENTS();
90
+ await this.checkComponent('slashCommands', '.opencode/command', '*.md', state);
91
+ } else {
92
+ // Claude Code (default)
93
+ await this.checkComponent('agents', '.claude/agents', '*.md', state);
94
+ await this.checkComponent('rules', '.claude/rules', '*.md', state);
95
+ await this.checkComponent('hooks', '.claude/hooks', '*.js', state);
96
+ await this.checkComponent('outputStyles', '.claude/output-styles', '*.md', state);
97
+ await this.checkComponent('slashCommands', '.claude/commands', '*.md', state);
98
+ }
99
+
100
+ // Check MCP
101
+ const mcpConfig = await this.checkMCPConfig(state.target);
102
+ state.components.mcp.installed = mcpConfig.exists;
103
+ state.components.mcp.serverCount = mcpConfig.serverCount;
104
+ state.components.mcp.version = mcpConfig.version;
105
+
106
+ // Check target version
107
+ if (state.target) {
108
+ const targetInfo = await this.checkTargetVersion(state.target);
109
+ state.targetVersion = targetInfo.version;
110
+ state.targetLatestVersion = targetInfo.latestVersion;
111
+ }
112
+
113
+ // Check corruption
114
+ state.corrupted = await this.checkCorruption(state);
115
+
116
+ } catch (error) {
117
+ state.corrupted = true;
118
+ }
119
+
120
+ return state;
121
+ }
122
+
123
+ recommendAction(state: ProjectState): RecommendedAction {
124
+ if (!state.initialized) {
125
+ return 'FULL_INIT';
126
+ }
127
+
128
+ if (state.corrupted) {
129
+ return 'REPAIR';
130
+ }
131
+
132
+ if (state.outdated && state.version !== state.latestVersion) {
133
+ return 'UPGRADE';
134
+ }
135
+
136
+ if (state.targetVersion && state.targetLatestVersion &&
137
+ this.isVersionOutdated(state.targetVersion, state.targetLatestVersion)) {
138
+ return 'UPGRADE_TARGET';
139
+ }
140
+
141
+ return 'RUN_ONLY';
142
+ }
143
+
144
+ async explainState(state: ProjectState): Promise<string[]> {
145
+ const explanations: string[] = [];
146
+
147
+ if (!state.initialized) {
148
+ explanations.push('Project not initialized yet');
149
+ explanations.push('Run `bun dev:flow` to start initialization');
150
+ return explanations;
151
+ }
152
+
153
+ if (state.corrupted) {
154
+ explanations.push('Configuration corruption detected');
155
+ explanations.push('Run `bun dev:flow --clean` to repair');
156
+ return explanations;
157
+ }
158
+
159
+ if (state.outdated) {
160
+ explanations.push(`Flow version outdated: ${state.version} → ${state.latestVersion}`);
161
+ explanations.push('Run `bun dev:flow upgrade` to upgrade');
162
+ }
163
+
164
+ if (state.targetVersion && state.targetLatestVersion &&
165
+ this.isVersionOutdated(state.targetVersion, state.targetLatestVersion)) {
166
+ explanations.push(`${state.target} update available`);
167
+ explanations.push(`Run \`bun dev:flow upgrade-target\` to upgrade`);
168
+ }
169
+
170
+ // Check components
171
+ Object.entries(state.components).forEach(([name, component]) => {
172
+ if (!component.installed) {
173
+ explanations.push(`Missing ${name}`);
174
+ }
175
+ });
176
+
177
+ if (explanations.length === 0) {
178
+ explanations.push('Project status is normal');
179
+ explanations.push('Run `bun dev:flow` to start Claude Code');
180
+ }
181
+
182
+ return explanations;
183
+ }
184
+
185
+ private async getLatestFlowVersion(): Promise<string | null> {
186
+ try {
187
+ // 从 package.json 获取当前版本
188
+ const packagePath = path.join(__dirname, '..', '..', 'package.json');
189
+ const packageJson = JSON.parse(await fs.readFile(packagePath, 'utf-8'));
190
+ return packageJson.version || null;
191
+ } catch {
192
+ return null;
193
+ }
194
+ }
195
+
196
+ private async checkComponent(
197
+ componentName: keyof ProjectState['components'],
198
+ componentPath: string,
199
+ pattern: string,
200
+ state: ProjectState
201
+ ): Promise<void> {
202
+ try {
203
+ const fullPath = path.join(this.projectPath, componentPath);
204
+ const exists = await fs.access(fullPath).then(() => true).catch(() => false);
205
+
206
+ state.components[componentName].installed = exists;
207
+
208
+ if (exists) {
209
+ // 计算文件数量
210
+ const files = await fs.readdir(fullPath).catch(() => []);
211
+ const count = pattern === '*.js' ? files.filter(f => f.endsWith('.js')).length :
212
+ pattern === '*.md' ? files.filter(f => f.endsWith('.md')).length : files.length;
213
+
214
+ if (componentName === 'agents' || componentName === 'slashCommands' || componentName === 'rules') {
215
+ state.components[componentName].count = count;
216
+ }
217
+
218
+ // 这里可以读取版本信息(如果保存了的话)
219
+ const versionPath = path.join(fullPath, '.version');
220
+ const versionExists = await fs.access(versionPath).then(() => true).catch(() => false);
221
+ if (versionExists) {
222
+ state.components[componentName].version = await fs.readFile(versionPath, 'utf-8');
223
+ }
224
+ }
225
+ } catch {
226
+ state.components[componentName].installed = false;
227
+ }
228
+ }
229
+
230
+ private async checkFileComponent(
231
+ componentName: keyof ProjectState['components'],
232
+ filePath: string,
233
+ state: ProjectState
234
+ ): Promise<void> {
235
+ try {
236
+ const fullPath = path.join(this.projectPath, filePath);
237
+ const exists = await fs.access(fullPath).then(() => true).catch(() => false);
238
+
239
+ state.components[componentName].installed = exists;
240
+
241
+ if (exists && componentName === 'rules') {
242
+ // For AGENTS.md, count is always 1
243
+ state.components[componentName].count = 1;
244
+ }
245
+ } catch {
246
+ state.components[componentName].installed = false;
247
+ }
248
+ }
249
+
250
+ private async checkOutputStylesInAGENTS(): Promise<boolean> {
251
+ try {
252
+ const agentsPath = path.join(this.projectPath, 'AGENTS.md');
253
+ const exists = await fs.access(agentsPath).then(() => true).catch(() => false);
254
+
255
+ if (!exists) {
256
+ return false;
257
+ }
258
+
259
+ // Check if AGENTS.md contains output styles section
260
+ const content = await fs.readFile(agentsPath, 'utf-8');
261
+ return content.includes('# Output Styles');
262
+ } catch {
263
+ return false;
264
+ }
265
+ }
266
+
267
+ private async checkMCPConfig(target?: string | null): Promise<{ exists: boolean; serverCount: number; version: string | null }> {
268
+ try {
269
+ let mcpPath: string;
270
+ let serversKey: string;
271
+
272
+ if (target === 'opencode') {
273
+ // OpenCode uses opencode.jsonc with mcp key
274
+ mcpPath = path.join(this.projectPath, 'opencode.jsonc');
275
+ serversKey = 'mcp';
276
+ } else {
277
+ // Claude Code uses .mcp.json with mcpServers key
278
+ mcpPath = path.join(this.projectPath, '.mcp.json');
279
+ serversKey = 'mcpServers';
280
+ }
281
+
282
+ const exists = await fs.access(mcpPath).then(() => true).catch(() => false);
283
+
284
+ if (!exists) {
285
+ return { exists: false, serverCount: 0, version: null };
286
+ }
287
+
288
+ // Use proper JSONC parser for OpenCode (handles comments)
289
+ let content: any;
290
+ if (target === 'opencode') {
291
+ // Import dynamically to avoid circular dependency
292
+ const { fileUtils } = await import('../utils/target-utils.js');
293
+ const { opencodeTarget } = await import('../targets/opencode.js');
294
+ content = await fileUtils.readConfig(opencodeTarget.config, this.projectPath);
295
+ } else {
296
+ // Claude Code uses plain JSON
297
+ content = JSON.parse(await fs.readFile(mcpPath, 'utf-8'));
298
+ }
299
+
300
+ const servers = content[serversKey] || {};
301
+
302
+ return {
303
+ exists: true,
304
+ serverCount: Object.keys(servers).length,
305
+ version: content.version || null,
306
+ };
307
+ } catch {
308
+ return { exists: false, serverCount: 0, version: null };
309
+ }
310
+ }
311
+
312
+ private async checkTargetVersion(target: string): Promise<{ version: string | null; latestVersion: string | null }> {
313
+ try {
314
+ // 这里可以检查目标平台的版本
315
+ // 例如检查 claude CLI 版本或 opencode 版本
316
+ if (target === 'claude-code') {
317
+ // 检查 claude --version
318
+ const { exec } = await import('node:child_process');
319
+ const { promisify } = await import('node:util');
320
+ const execAsync = promisify(exec);
321
+
322
+ try {
323
+ const { stdout } = await execAsync('claude --version');
324
+ // 解析版本号
325
+ const match = stdout.match(/v?(\d+\.\d+\.\d+)/);
326
+ return {
327
+ version: match ? match[1] : null,
328
+ latestVersion: await this.getLatestClaudeVersion(),
329
+ };
330
+ } catch {
331
+ return { version: null, latestVersion: null };
332
+ }
333
+ }
334
+
335
+ return { version: null, latestVersion: null };
336
+ } catch {
337
+ return { version: null, latestVersion: null };
338
+ }
339
+ }
340
+
341
+ private async getLatestClaudeVersion(): Promise<string | null> {
342
+ // 可以从 npm 或官方网站获取最新版本
343
+ try {
344
+ const { exec } = await import('node:child_process');
345
+ const { promisify } = await import('node:util');
346
+ const execAsync = promisify(exec);
347
+
348
+ const { stdout } = await execAsync('npm view @anthropic-ai/claude-code version');
349
+ return stdout.trim();
350
+ } catch {
351
+ return null;
352
+ }
353
+ }
354
+
355
+ private async checkCorruption(state: ProjectState): Promise<boolean> {
356
+ // 检查是否存在矛盾的状态
357
+ if (state.initialized && !state.target) {
358
+ return true; // 初始化咗但冇 target
359
+ }
360
+
361
+ // 检查必需组件 - only check agents for claude-code
362
+ if (state.initialized && state.target === 'claude-code' && !state.components.agents.installed) {
363
+ return true; // claude-code 初始化咗但冇 agents
364
+ }
365
+
366
+ return false;
367
+ }
368
+
369
+ private isVersionOutdated(current: string, latest: string): boolean {
370
+ try {
371
+ return this.compareVersions(current, latest) < 0;
372
+ } catch {
373
+ return false;
374
+ }
375
+ }
376
+
377
+ private compareVersions(v1: string, v2: string): number {
378
+ const parts1 = v1.split('.').map(Number);
379
+ const parts2 = v2.split('.').map(Number);
380
+
381
+ for (let i = 0; i < Math.min(parts1.length, parts2.length); i++) {
382
+ if (parts1[i] !== parts2[i]) {
383
+ return parts1[i] - parts2[i];
384
+ }
385
+ }
386
+
387
+ return parts1.length - parts2.length;
388
+ }
389
+ }
@@ -0,0 +1,115 @@
1
+ /**
2
+ * Storage Factory - 簡化存儲工廠
3
+ * Feature-first, composable approach
4
+ */
5
+
6
+ import type { StorageConfig } from '../interfaces/unified-storage.js';
7
+ import { createStorage } from './unified-storage.js';
8
+ import { logger } from '../utils/logger.js';
9
+
10
+ /**
11
+ * Storage configuration builders
12
+ */
13
+ export const StorageConfig = {
14
+ memory: (options?: Partial<StorageConfig>): StorageConfig => ({
15
+ type: 'memory',
16
+ ...options,
17
+ }),
18
+
19
+ cache: (options?: {
20
+ defaultTTL?: number;
21
+ maxCacheSize?: number;
22
+ storageDir?: string;
23
+ }): StorageConfig => ({
24
+ type: 'cache',
25
+ defaultTTL: options?.defaultTTL || 3600,
26
+ maxCacheSize: options?.maxCacheSize,
27
+ storageDir: options?.storageDir,
28
+ }),
29
+
30
+ vector: (options?: {
31
+ vectorDimensions?: number;
32
+ connectionString?: string;
33
+ storageDir?: string;
34
+ }): StorageConfig => ({
35
+ type: 'vector',
36
+ vectorDimensions: options?.vectorDimensions || 1536,
37
+ connectionString: options?.connectionString,
38
+ storageDir: options?.storageDir,
39
+ }),
40
+ } as const;
41
+
42
+ /**
43
+ * Create storage from configuration
44
+ */
45
+ export async function createStorageFromConfig<T = unknown>(config: StorageConfig) {
46
+ try {
47
+ const storage = createStorage<T>(config);
48
+ await storage.initialize();
49
+ logger.info('Storage created successfully', { type: config.type });
50
+ return storage;
51
+ } catch (error) {
52
+ logger.error('Failed to create storage', { type: config.type, error });
53
+ throw error;
54
+ }
55
+ }
56
+
57
+ /**
58
+ * Convenience functions for creating storage
59
+ */
60
+ export async function createMemoryStorage<T = unknown>(options?: Partial<StorageConfig>) {
61
+ return createStorageFromConfig<T>(StorageConfig.memory(options));
62
+ }
63
+
64
+ export async function createCacheStorage<T = unknown>(options?: {
65
+ defaultTTL?: number;
66
+ maxCacheSize?: number;
67
+ storageDir?: string;
68
+ }) {
69
+ return createStorageFromConfig<T>(StorageConfig.cache(options));
70
+ }
71
+
72
+ export async function createVectorStorage<T = unknown>(options?: {
73
+ vectorDimensions?: number;
74
+ connectionString?: string;
75
+ storageDir?: string;
76
+ }) {
77
+ return createStorageFromConfig<T>(StorageConfig.vector(options));
78
+ }
79
+
80
+ /**
81
+ * Create storage from environment configuration
82
+ */
83
+ export async function createStorageFromEnv<T = unknown>(type?: string): Promise<any> {
84
+ const storageType = type || process.env.STORAGE_TYPE || 'memory';
85
+
86
+ const config: StorageConfig = { type: storageType as any };
87
+
88
+ // Load configuration from environment variables
89
+ const connectionString = process.env.STORAGE_CONNECTION_STRING;
90
+ if (connectionString) {
91
+ config.connectionString = connectionString;
92
+ }
93
+
94
+ const storageDir = process.env.STORAGE_DIR;
95
+ if (storageDir) {
96
+ config.storageDir = storageDir;
97
+ }
98
+
99
+ const defaultTTL = process.env.CACHE_DEFAULT_TTL;
100
+ if (defaultTTL) {
101
+ config.defaultTTL = Number.parseInt(defaultTTL, 10);
102
+ }
103
+
104
+ const maxCacheSize = process.env.CACHE_MAX_SIZE;
105
+ if (maxCacheSize) {
106
+ config.maxCacheSize = Number.parseInt(maxCacheSize, 10);
107
+ }
108
+
109
+ const vectorDimensions = process.env.VECTOR_DIMENSIONS;
110
+ if (vectorDimensions) {
111
+ config.vectorDimensions = Number.parseInt(vectorDimensions, 10);
112
+ }
113
+
114
+ return createStorageFromConfig<T>(config);
115
+ }