@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,351 @@
1
+ /**
2
+ * Unified Result Type - 統一結果類型
3
+ * Single source of truth for Result<T, E> across the entire project
4
+ *
5
+ * This replaces all conflicting Result type definitions:
6
+ * - src/core/type-utils.ts
7
+ * - src/core/functional/result.ts
8
+ * - src/utils/functional.ts
9
+ */
10
+
11
+ // ============================================================================
12
+ // CORE RESULT TYPE
13
+ // ============================================================================
14
+
15
+ /**
16
+ * Result type for functional error handling
17
+ * Represents success or failure without exceptions
18
+ */
19
+ export type Result<T, E = Error> =
20
+ | { success: true; data: T }
21
+ | { success: false; error: E };
22
+
23
+ // ============================================================================
24
+ // CONSTRUCTORS
25
+ // ============================================================================
26
+
27
+ /**
28
+ * Create a successful result
29
+ */
30
+ export const ok = <T>(data: T): Result<T> => ({ success: true, data });
31
+
32
+ /**
33
+ * Create an error result
34
+ */
35
+ export const err = <E>(error: E): Result<never, E> => ({ success: false, error });
36
+
37
+ // ============================================================================
38
+ // TYPE GUARDS
39
+ // ============================================================================
40
+
41
+ /**
42
+ * Check if result is successful
43
+ */
44
+ export const isOk = <T, E>(result: Result<T, E>): result is { success: true; data: T } =>
45
+ result.success;
46
+
47
+ /**
48
+ * Check if result is an error
49
+ */
50
+ export const isErr = <T, E>(result: Result<T, E>): result is { success: false; error: E } =>
51
+ !result.success;
52
+
53
+ // ============================================================================
54
+ // TRANSFORMATIONS
55
+ // ============================================================================
56
+
57
+ /**
58
+ * Transform the success value
59
+ * Error propagates unchanged
60
+ */
61
+ export const map =
62
+ <T, U, E>(fn: (data: T) => U) =>
63
+ (result: Result<T, E>): Result<U, E> => {
64
+ if (isOk(result)) {
65
+ return ok(fn(result.data));
66
+ }
67
+ return result;
68
+ };
69
+
70
+ /**
71
+ * Transform the success value with a function that returns a Result
72
+ * Enables chaining operations that can fail
73
+ * Error propagates unchanged
74
+ */
75
+ export const flatMap =
76
+ <T, U, E>(fn: (data: T) => Result<U, E>) =>
77
+ (result: Result<T, E>): Result<U, E> => {
78
+ if (isOk(result)) {
79
+ return fn(result.data);
80
+ }
81
+ return result;
82
+ };
83
+
84
+ /**
85
+ * Transform the error
86
+ * Success propagates unchanged
87
+ */
88
+ export const mapError =
89
+ <T, E, F>(fn: (error: E) => F) =>
90
+ (result: Result<T, E>): Result<T, F> => {
91
+ if (isErr(result)) {
92
+ return err(fn(result.error));
93
+ }
94
+ return result;
95
+ };
96
+
97
+ /**
98
+ * Extract value or provide default
99
+ */
100
+ export const getOrElse =
101
+ <T>(defaultValue: T) =>
102
+ <E>(result: Result<T, E>): T => {
103
+ if (isOk(result)) {
104
+ return result.data;
105
+ }
106
+ return defaultValue;
107
+ };
108
+
109
+ /**
110
+ * Extract value or compute default
111
+ */
112
+ export const getOrElseLazy =
113
+ <T>(fn: () => T) =>
114
+ <E>(result: Result<T, E>): T => {
115
+ if (isOk(result)) {
116
+ return result.data;
117
+ }
118
+ return fn();
119
+ };
120
+
121
+ /**
122
+ * Pattern matching
123
+ */
124
+ export const match =
125
+ <T, E, U>(onSuccess: (data: T) => U, onError: (error: E) => U) =>
126
+ (result: Result<T, E>): U => {
127
+ if (isOk(result)) {
128
+ return onSuccess(result.data);
129
+ }
130
+ return onError(result.error);
131
+ };
132
+
133
+ /**
134
+ * Extract value or throw error
135
+ * Use only when you're certain the result is successful
136
+ */
137
+ export const unwrap = <T, E>(result: Result<T, E>): T => {
138
+ if (isOk(result)) {
139
+ return result.data;
140
+ }
141
+ throw result.error;
142
+ };
143
+
144
+ /**
145
+ * Extract error or throw
146
+ * Use only when you're certain the result is an error
147
+ */
148
+ export const unwrapError = <T, E>(result: Result<T, E>): E => {
149
+ if (isErr(result)) {
150
+ return result.error;
151
+ }
152
+ throw new Error('Expected error but got success');
153
+ };
154
+
155
+ // ============================================================================
156
+ // ASYNC SUPPORT
157
+ // ============================================================================
158
+
159
+ /**
160
+ * Async result type
161
+ */
162
+ export type AsyncResult<T, E = Error> = Promise<Result<T, E>>;
163
+
164
+ /**
165
+ * Convert thrown exception to Result
166
+ */
167
+ export const tryCatch = <T, E = Error>(
168
+ fn: () => T,
169
+ onError: (error: unknown) => E = (error: unknown) => error as E
170
+ ): Result<T, E> => {
171
+ try {
172
+ return ok(fn());
173
+ } catch (error) {
174
+ return err(onError(error));
175
+ }
176
+ };
177
+
178
+ /**
179
+ * Convert Promise to Result
180
+ */
181
+ export const tryCatchAsync = async <T, E = Error>(
182
+ fn: () => Promise<T>,
183
+ onError: (error: unknown) => E = (error: unknown) => error as E
184
+ ): Promise<Result<T, E>> => {
185
+ try {
186
+ const data = await fn();
187
+ return ok(data);
188
+ } catch (error) {
189
+ return err(onError(error));
190
+ }
191
+ };
192
+
193
+ /**
194
+ * Safe async function wrapper
195
+ */
196
+ export const safeAsync = async <T, E = Error>(
197
+ fn: () => Promise<T>,
198
+ errorFn?: (error: unknown) => E
199
+ ): Promise<Result<T, E>> => {
200
+ return tryCatchAsync(fn, errorFn);
201
+ };
202
+
203
+ /**
204
+ * Safe sync function wrapper
205
+ */
206
+ export const safeSync = <T, E = Error>(
207
+ fn: () => T,
208
+ errorFn?: (error: unknown) => E
209
+ ): Result<T, E> => {
210
+ return tryCatch(fn, errorFn);
211
+ };
212
+
213
+ // ============================================================================
214
+ // COMBINATORS
215
+ // ============================================================================
216
+
217
+ /**
218
+ * Combine multiple Results into a single Result containing an array
219
+ * Fails if any Result is an error (short-circuits on first error)
220
+ */
221
+ export const all = <T, E>(results: Result<T, E>[]): Result<T[], E> => {
222
+ const values: T[] = [];
223
+
224
+ for (const result of results) {
225
+ if (isErr(result)) {
226
+ return result;
227
+ }
228
+ values.push(result.data);
229
+ }
230
+
231
+ return ok(values);
232
+ };
233
+
234
+ /**
235
+ * Combine multiple Results into a single Result containing an array
236
+ * Collects all errors instead of short-circuiting
237
+ */
238
+ export const allWithErrors = <T, E>(results: Result<T, E>[]): Result<T[], E[]> => {
239
+ const values: T[] = [];
240
+ const errors: E[] = [];
241
+
242
+ for (const result of results) {
243
+ if (isOk(result)) {
244
+ values.push(result.data);
245
+ } else {
246
+ errors.push(result.error);
247
+ }
248
+ }
249
+
250
+ return errors.length > 0 ? err(errors) : ok(values);
251
+ };
252
+
253
+ /**
254
+ * Combine multiple AsyncResults
255
+ */
256
+ export const allAsync = async <T, E>(results: AsyncResult<T, E>[]): Promise<Result<T[], E>> => {
257
+ const settled = await Promise.all(results);
258
+ return all(settled);
259
+ };
260
+
261
+ /**
262
+ * Race multiple AsyncResults - returns first successful result
263
+ */
264
+ export const raceAsync = async <T, E>(results: AsyncResult<T, E>[]): Promise<Result<T, E>> => {
265
+ try {
266
+ return await Promise.race(results);
267
+ } catch (error) {
268
+ return err(error as E);
269
+ }
270
+ };
271
+
272
+ // ============================================================================
273
+ // SIDE EFFECTS
274
+ // ============================================================================
275
+
276
+ /**
277
+ * Run side effect for success case
278
+ */
279
+ export const tap =
280
+ <T, E>(fn: (data: T) => void) =>
281
+ (result: Result<T, E>): Result<T, E> => {
282
+ if (isOk(result)) {
283
+ fn(result.data);
284
+ }
285
+ return result;
286
+ };
287
+
288
+ /**
289
+ * Run side effect for error case
290
+ */
291
+ export const tapError =
292
+ <T, E>(fn: (error: E) => void) =>
293
+ (result: Result<T, E>): Result<T, E> => {
294
+ if (isErr(result)) {
295
+ fn(result.error);
296
+ }
297
+ return result;
298
+ };
299
+
300
+ /**
301
+ * Run side effect for both cases
302
+ */
303
+ export const tapBoth =
304
+ <T, E>(onSuccess: (data: T) => void, onError: (error: E) => void) =>
305
+ (result: Result<T, E>): Result<T, E> => {
306
+ if (isOk(result)) {
307
+ onSuccess(result.data);
308
+ } else {
309
+ onError(result.error);
310
+ }
311
+ return result;
312
+ };
313
+
314
+ // ============================================================================
315
+ // LEGACY COMPATIBILITY
316
+ // ============================================================================
317
+
318
+ /**
319
+ * Legacy compatibility aliases
320
+ * These help migrate from old Result implementations
321
+ */
322
+
323
+ // For src/core/functional/result.ts users
324
+ export const success = ok;
325
+ export const failure = err;
326
+ export const isSuccess = isOk;
327
+ export const isFailure = isErr;
328
+
329
+ // For src/utils/functional.ts users
330
+ export const unwrapResult = unwrap;
331
+ export const mapResult = map;
332
+
333
+ // ============================================================================
334
+ // TYPE INFERENCE HELPERS
335
+ // ============================================================================
336
+
337
+ /**
338
+ * Helper to infer the success type from a Result
339
+ */
340
+ export type SuccessType<T> = T extends Result<infer U, any> ? U : never;
341
+
342
+ /**
343
+ * Helper to infer the error type from a Result
344
+ */
345
+ export type ErrorType<T> = T extends Result<any, infer E> ? E : never;
346
+
347
+ /**
348
+ * Create a type-safe Result from a function that might throw
349
+ */
350
+ export type SafeResult<T extends (...args: any[]) => any> =
351
+ Result<ReturnType<T>, Error>;
@@ -0,0 +1,147 @@
1
+ /**
2
+ * Rule Loader
3
+ * Loads rule definitions from markdown files with front matter
4
+ */
5
+
6
+ import { readFile, readdir, access } from 'node:fs/promises';
7
+ import { join, parse, relative, dirname } from 'node:path';
8
+ import { homedir } from 'node:os';
9
+ import { fileURLToPath } from 'node:url';
10
+ import matter from 'gray-matter';
11
+ import type { Rule, RuleMetadata } from '../types/rule.types.js';
12
+
13
+ /**
14
+ * Load a single rule from a markdown file
15
+ */
16
+ export async function loadRuleFromFile(
17
+ filePath: string,
18
+ isBuiltin: boolean = false,
19
+ ruleId?: string
20
+ ): Promise<Rule | null> {
21
+ try {
22
+ const fileContent = await readFile(filePath, 'utf-8');
23
+ const { data, content } = matter(fileContent);
24
+
25
+ // Validate front matter
26
+ if (!data.name || typeof data.name !== 'string') {
27
+ console.error(`Rule file ${filePath} missing required 'name' field`);
28
+ return null;
29
+ }
30
+
31
+ const metadata: RuleMetadata = {
32
+ name: data.name,
33
+ description: data.description || '',
34
+ enabled: data.enabled !== undefined ? Boolean(data.enabled) : true,
35
+ };
36
+
37
+ // Get rule ID from parameter or filename
38
+ const id = ruleId || parse(filePath).name;
39
+
40
+ return {
41
+ id,
42
+ metadata,
43
+ content: content.trim(),
44
+ isBuiltin,
45
+ filePath,
46
+ };
47
+ } catch (error) {
48
+ console.error(`Failed to load rule from ${filePath}:`, error);
49
+ return null;
50
+ }
51
+ }
52
+
53
+ /**
54
+ * Load all rules from a directory (recursively)
55
+ */
56
+ export async function loadRulesFromDirectory(dirPath: string, isBuiltin: boolean = false): Promise<Rule[]> {
57
+ try {
58
+ // Read directory recursively to support subdirectories
59
+ const files = await readdir(dirPath, { recursive: true, withFileTypes: true });
60
+
61
+ // Filter for .md files and calculate rule IDs from relative paths
62
+ const ruleFiles = files
63
+ .filter((f) => f.isFile() && f.name.endsWith('.md'))
64
+ .map((f) => {
65
+ const fullPath = join(f.parentPath || f.path, f.name);
66
+ // Calculate relative path from dirPath and remove .md extension
67
+ const relativePath = relative(dirPath, fullPath).replace(/\.md$/, '');
68
+ return { fullPath, ruleId: relativePath };
69
+ });
70
+
71
+ const rules = await Promise.all(
72
+ ruleFiles.map(({ fullPath, ruleId }) => loadRuleFromFile(fullPath, isBuiltin, ruleId))
73
+ );
74
+
75
+ return rules.filter((rule): rule is Rule => rule !== null);
76
+ } catch (error) {
77
+ // Directory doesn't exist or can't be read
78
+ return [];
79
+ }
80
+ }
81
+
82
+ /**
83
+ * Get system rules path (bundled with the app)
84
+ */
85
+ export async function getSystemRulesPath(): Promise<string> {
86
+ // Get the directory of the current module (cross-platform)
87
+ const currentFile = fileURLToPath(import.meta.url);
88
+ const currentDir = dirname(currentFile);
89
+
90
+ // In production (dist), assets are at dist/assets/rules
91
+ // In development (src), go up to project root: src/core -> project root
92
+ const distPath = join(currentDir, '..', 'assets', 'rules');
93
+ const devPath = join(currentDir, '..', '..', 'assets', 'rules');
94
+
95
+ // Check which one exists (try dist first, then dev)
96
+ try {
97
+ await access(distPath);
98
+ return distPath;
99
+ } catch {
100
+ return devPath;
101
+ }
102
+ }
103
+
104
+ /**
105
+ * Get all rule search paths
106
+ */
107
+ export function getRuleSearchPaths(cwd: string): string[] {
108
+ const globalPath = join(homedir(), '.sylphx-flow', 'rules');
109
+ const projectPath = join(cwd, '.sylphx-flow', 'rules');
110
+
111
+ return [globalPath, projectPath];
112
+ }
113
+
114
+ /**
115
+ * Load all available rules from all sources
116
+ */
117
+ export async function loadAllRules(cwd: string): Promise<Rule[]> {
118
+ const systemPath = await getSystemRulesPath();
119
+ const [globalPath, projectPath] = getRuleSearchPaths(cwd);
120
+
121
+ const [systemRules, globalRules, projectRules] = await Promise.all([
122
+ loadRulesFromDirectory(systemPath, true), // System rules are marked as builtin
123
+ loadRulesFromDirectory(globalPath, false),
124
+ loadRulesFromDirectory(projectPath, false),
125
+ ]);
126
+
127
+ // Priority: system < global < project
128
+ // Use Map to deduplicate by ID (later entries override earlier ones)
129
+ const ruleMap = new Map<string, Rule>();
130
+
131
+ // Add system rules first (lowest priority)
132
+ for (const rule of systemRules) {
133
+ ruleMap.set(rule.id, rule);
134
+ }
135
+
136
+ // Add global rules (override system)
137
+ for (const rule of globalRules) {
138
+ ruleMap.set(rule.id, rule);
139
+ }
140
+
141
+ // Add project rules (override globals and system)
142
+ for (const rule of projectRules) {
143
+ ruleMap.set(rule.id, rule);
144
+ }
145
+
146
+ return Array.from(ruleMap.values());
147
+ }