flowmind 1.0.0

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 (47) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +855 -0
  3. package/README_CN.md +854 -0
  4. package/bin/flowmind.js +464 -0
  5. package/core/adapters/api-doc-adapter.js +71 -0
  6. package/core/adapters/base-adapter.js +80 -0
  7. package/core/adapters/database-manager-adapter.js +60 -0
  8. package/core/adapters/database-query-adapter.js +51 -0
  9. package/core/adapters/knowledge-base-adapter.js +75 -0
  10. package/core/adapters/log-service-adapter.js +41 -0
  11. package/core/adapters/mcp-adapter.js +65 -0
  12. package/core/adapters/report-adapter.js +60 -0
  13. package/core/adapters/workflow-adapter.js +62 -0
  14. package/core/component-registry.js +281 -0
  15. package/core/component-types.js +63 -0
  16. package/core/config-manager.js +360 -0
  17. package/core/index.js +223 -0
  18. package/core/learning-engine.js +588 -0
  19. package/core/mcp-compatibility.js +150 -0
  20. package/core/providers/aliyun/dms-adapter.js +98 -0
  21. package/core/providers/aliyun/redis-adapter.js +88 -0
  22. package/core/providers/aliyun/sls-adapter.js +86 -0
  23. package/core/providers/friday/flow-adapter.js +85 -0
  24. package/core/providers/friday/report-adapter.js +83 -0
  25. package/core/providers/yapi/yapi-adapter.js +79 -0
  26. package/core/providers/yuque/yuque-adapter.js +90 -0
  27. package/core/scene-matcher.js +326 -0
  28. package/core/skill-loader.js +291 -0
  29. package/package.json +67 -0
  30. package/scripts/migrate-config.js +153 -0
  31. package/skills/api-sync/SKILL.md +203 -0
  32. package/skills/archive-change/SKILL.md +172 -0
  33. package/skills/auto-flow/SKILL.md +277 -0
  34. package/skills/code-review/SKILL.md +206 -0
  35. package/skills/code-review-audit/SKILL.md +150 -0
  36. package/skills/data-logic-validation/SKILL.md +162 -0
  37. package/skills/data-validation/SKILL.md +210 -0
  38. package/skills/git-review/SKILL.md +190 -0
  39. package/skills/learning-engine/SKILL.md +352 -0
  40. package/skills/learning-feedback/SKILL.md +174 -0
  41. package/skills/log-audit/SKILL.md +226 -0
  42. package/skills/project-review/SKILL.md +196 -0
  43. package/skills/requirement-analyst/SKILL.md +275 -0
  44. package/skills/resource-bind/SKILL.md +222 -0
  45. package/skills/sls-log-audit/SKILL.md +223 -0
  46. package/skills/yapi-sync-interface/SKILL.md +145 -0
  47. package/skills/yuque-sync-design/SKILL.md +157 -0
@@ -0,0 +1,588 @@
1
+ /**
2
+ * FlowMind Learning Engine
3
+ * Handles learning from user corrections and preferences
4
+ */
5
+
6
+ const fs = require('fs-extra');
7
+ const path = require('path');
8
+ const { v4: uuidv4 } = require('uuid');
9
+
10
+ class LearningEngine {
11
+ constructor(config) {
12
+ this.config = config;
13
+ this.learningPath = config.get('learning.storagePath', '~/.flowmind/learning');
14
+ this.records = {};
15
+ this.skillBindings = {};
16
+ this.stats = {};
17
+ this.initialized = false;
18
+ }
19
+
20
+ /**
21
+ * Initialize learning engine
22
+ */
23
+ async init() {
24
+ // Ensure directories exist
25
+ await fs.ensureDir(this.expandPath(this.learningPath));
26
+ await fs.ensureDir(path.join(this.expandPath(this.learningPath), 'records'));
27
+
28
+ // Load existing data
29
+ await this.loadSkillBindings();
30
+ await this.loadStats();
31
+
32
+ this.initialized = true;
33
+ }
34
+
35
+ /**
36
+ * Detect if input is a learning opportunity
37
+ */
38
+ async detectLearning(input, context) {
39
+ // Check for correction patterns
40
+ const correction = this.detectCorrection(input);
41
+ if (correction) {
42
+ return await this.recordCorrection(correction, context);
43
+ }
44
+
45
+ // Check for scene mapping patterns
46
+ const sceneMapping = this.detectSceneMapping(input);
47
+ if (sceneMapping) {
48
+ return await this.recordSceneMapping(sceneMapping, context);
49
+ }
50
+
51
+ // Check for preference patterns
52
+ const preference = this.detectPreference(input);
53
+ if (preference) {
54
+ return await this.recordPreference(preference, context);
55
+ }
56
+
57
+ return null;
58
+ }
59
+
60
+ /**
61
+ * Detect correction patterns
62
+ */
63
+ detectCorrection(input) {
64
+ const correctionPatterns = [
65
+ // Chinese patterns
66
+ { regex: /不对|错了|错误|有误/i, type: 'correction', severity: 'major' },
67
+ { regex: /应该是|正确的是|改成|改为/i, type: 'correction', severity: 'major' },
68
+ { regex: /不是这样|重新处理|重来/i, type: 'correction', severity: 'major' },
69
+ { regex: /更准确|更好|优化|改进|调整/i, type: 'refinement', severity: 'minor' },
70
+ // English patterns
71
+ { regex: /incorrect|wrong|error|mistake/i, type: 'correction', severity: 'major' },
72
+ { regex: /should be|change to|fix this/i, type: 'correction', severity: 'major' },
73
+ { regex: /refine|improve|optimize|adjust/i, type: 'refinement', severity: 'minor' }
74
+ ];
75
+
76
+ for (const pattern of correctionPatterns) {
77
+ if (pattern.regex.test(input)) {
78
+ return {
79
+ type: pattern.type,
80
+ severity: pattern.severity,
81
+ trigger: input.match(pattern.regex)[0],
82
+ input: input
83
+ };
84
+ }
85
+ }
86
+
87
+ return null;
88
+ }
89
+
90
+ /**
91
+ * Detect scene mapping patterns
92
+ */
93
+ detectSceneMapping(input) {
94
+ const scenePatterns = [
95
+ { regex: /查询.*用.*方式/i, pattern: 'query_method' },
96
+ { regex: /xxx.*问题找.*技能/i, pattern: 'skill_mapping' },
97
+ { regex: /这类.*需求走.*流程/i, pattern: 'workflow' },
98
+ { regex: /以后.*这种.*都用/i, pattern: 'preference' },
99
+ { regex: /直接用.*处理/i, pattern: 'direct_method' },
100
+ { regex: /记住.*方式|下次.*这样/i, pattern: 'remember' }
101
+ ];
102
+
103
+ for (const pattern of scenePatterns) {
104
+ if (pattern.regex.test(input)) {
105
+ return {
106
+ type: 'scene_mapping',
107
+ pattern: pattern.pattern,
108
+ input: input
109
+ };
110
+ }
111
+ }
112
+
113
+ return null;
114
+ }
115
+
116
+ /**
117
+ * Detect preference patterns
118
+ */
119
+ detectPreference(input) {
120
+ const preferencePatterns = [
121
+ { regex: /用.*格式|格式用/i, type: 'format' },
122
+ { regex: /语言用|用.*语言/i, type: 'language' },
123
+ { regex: /排序.*方式|按.*排序/i, type: 'sorting' },
124
+ { regex: /显示.*详情|简洁.*显示/i, type: 'detail_level' }
125
+ ];
126
+
127
+ for (const pattern of preferencePatterns) {
128
+ if (pattern.regex.test(input)) {
129
+ return {
130
+ type: 'preference',
131
+ preferenceType: pattern.type,
132
+ input: input
133
+ };
134
+ }
135
+ }
136
+
137
+ return null;
138
+ }
139
+
140
+ /**
141
+ * Record a correction
142
+ */
143
+ async recordCorrection(correction, context) {
144
+ const record = {
145
+ id: `learn-${Date.now()}-${uuidv4().substr(0, 8)}`,
146
+ timestamp: new Date().toISOString(),
147
+ type: correction.type,
148
+ severity: correction.severity,
149
+ trigger: correction.trigger,
150
+ input: correction.input,
151
+ context: context,
152
+ skill: context.currentSkill || 'unknown',
153
+ application: {
154
+ condition: this.extractCondition(correction.input),
155
+ action: this.extractAction(correction.input),
156
+ priority: correction.severity === 'major' ? 'high' : 'medium'
157
+ },
158
+ stats: {
159
+ appliedCount: 0,
160
+ successCount: 0
161
+ }
162
+ };
163
+
164
+ // Save record
165
+ await this.saveLearningRecord(record);
166
+
167
+ // Update skill bindings
168
+ await this.updateSkillBindings(record);
169
+
170
+ // Update stats
171
+ await this.updateStats('correction', record.skill);
172
+
173
+ return {
174
+ type: 'correction',
175
+ record: record,
176
+ confirmation: this.formatLearningConfirmation(record)
177
+ };
178
+ }
179
+
180
+ /**
181
+ * Record a scene mapping
182
+ */
183
+ async recordSceneMapping(sceneMapping, context) {
184
+ const record = {
185
+ id: `scene-${Date.now()}-${uuidv4().substr(0, 8)}`,
186
+ timestamp: new Date().toISOString(),
187
+ type: 'scene_mapping',
188
+ input: sceneMapping.input,
189
+ pattern: sceneMapping.pattern,
190
+ context: context,
191
+ keywords: this.extractKeywords(sceneMapping.input),
192
+ workflow: this.extractWorkflow(sceneMapping.input),
193
+ preferences: this.extractPreferences(sceneMapping.input),
194
+ stats: {
195
+ useCount: 0,
196
+ lastUsed: null,
197
+ successRate: 1.0
198
+ }
199
+ };
200
+
201
+ // Save scene mapping
202
+ await this.saveSceneMapping(record);
203
+
204
+ // Update stats
205
+ await this.updateStats('scene_mapping', 'global');
206
+
207
+ return {
208
+ type: 'scene_mapping',
209
+ record: record,
210
+ confirmation: this.formatSceneMappingConfirmation(record)
211
+ };
212
+ }
213
+
214
+ /**
215
+ * Record a preference
216
+ */
217
+ async recordPreference(preference, context) {
218
+ const record = {
219
+ id: `pref-${Date.now()}-${uuidv4().substr(0, 8)}`,
220
+ timestamp: new Date().toISOString(),
221
+ type: 'preference',
222
+ preferenceType: preference.preferenceType,
223
+ input: preference.input,
224
+ context: context,
225
+ value: this.extractPreferenceValue(preference.input),
226
+ skill: context.currentSkill || 'global'
227
+ };
228
+
229
+ // Save preference
230
+ await this.savePreference(record);
231
+
232
+ // Update stats
233
+ await this.updateStats('preference', record.skill);
234
+
235
+ return {
236
+ type: 'preference',
237
+ record: record,
238
+ confirmation: this.formatPreferenceConfirmation(record)
239
+ };
240
+ }
241
+
242
+ /**
243
+ * Get learning rules for a skill
244
+ */
245
+ async getSkillLearnings(skillName) {
246
+ const bindings = this.skillBindings[skillName] || { records: [], rules: [] };
247
+ return bindings.rules || [];
248
+ }
249
+
250
+ /**
251
+ * Get preferences for a skill
252
+ */
253
+ async getPreferences(skillName) {
254
+ const prefsPath = path.join(this.expandPath(this.learningPath), 'records', skillName, 'preferences.json');
255
+
256
+ if (await fs.pathExists(prefsPath)) {
257
+ return await fs.readJson(prefsPath);
258
+ }
259
+
260
+ return {};
261
+ }
262
+
263
+ /**
264
+ * Save learning record
265
+ */
266
+ async saveLearningRecord(record) {
267
+ const recordPath = path.join(
268
+ this.expandPath(this.learningPath),
269
+ 'records',
270
+ record.skill,
271
+ `${record.id}.json`
272
+ );
273
+
274
+ await fs.ensureDir(path.dirname(recordPath));
275
+ await fs.writeJson(recordPath, record, { spaces: 2 });
276
+
277
+ // Cache in memory
278
+ if (!this.records[record.skill]) {
279
+ this.records[record.skill] = [];
280
+ }
281
+ this.records[record.skill].push(record);
282
+ }
283
+
284
+ /**
285
+ * Save scene mapping
286
+ */
287
+ async saveSceneMapping(record) {
288
+ const scenesPath = path.join(this.expandPath(this.learningPath), 'scenes.json');
289
+
290
+ let scenes = { version: '1.0', mappings: [] };
291
+ if (await fs.pathExists(scenesPath)) {
292
+ scenes = await fs.readJson(scenesPath);
293
+ }
294
+
295
+ scenes.mappings.push(record);
296
+ scenes.lastUpdated = new Date().toISOString();
297
+
298
+ await fs.writeJson(scenesPath, scenes, { spaces: 2 });
299
+ }
300
+
301
+ /**
302
+ * Save preference
303
+ */
304
+ async savePreference(record) {
305
+ const prefsPath = path.join(
306
+ this.expandPath(this.learningPath),
307
+ 'records',
308
+ record.skill,
309
+ 'preferences.json'
310
+ );
311
+
312
+ let prefs = {};
313
+ if (await fs.pathExists(prefsPath)) {
314
+ prefs = await fs.readJson(prefsPath);
315
+ }
316
+
317
+ prefs[record.preferenceType] = record.value;
318
+ prefs.lastUpdated = new Date().toISOString();
319
+
320
+ await fs.ensureDir(path.dirname(prefsPath));
321
+ await fs.writeJson(prefsPath, prefs, { spaces: 2 });
322
+ }
323
+
324
+ /**
325
+ * Update skill bindings
326
+ */
327
+ async updateSkillBindings(record) {
328
+ if (!this.skillBindings[record.skill]) {
329
+ this.skillBindings[record.skill] = {
330
+ learningCount: 0,
331
+ records: [],
332
+ rules: []
333
+ };
334
+ }
335
+
336
+ const binding = this.skillBindings[record.skill];
337
+ binding.learningCount++;
338
+ binding.lastLearning = record.timestamp;
339
+ binding.records.push({
340
+ recordId: record.id,
341
+ type: record.type,
342
+ summary: this.generateSummary(record),
343
+ priority: record.application.priority
344
+ });
345
+ binding.rules.push(record.application);
346
+
347
+ await this.saveSkillBindings();
348
+ }
349
+
350
+ /**
351
+ * Load skill bindings
352
+ */
353
+ async loadSkillBindings() {
354
+ const bindingsPath = path.join(this.expandPath(this.learningPath), 'skill-bindings.json');
355
+
356
+ if (await fs.pathExists(bindingsPath)) {
357
+ this.skillBindings = await fs.readJson(bindingsPath);
358
+ } else {
359
+ this.skillBindings = { version: '1.0', bindings: {} };
360
+ }
361
+ }
362
+
363
+ /**
364
+ * Save skill bindings
365
+ */
366
+ async saveSkillBindings() {
367
+ const bindingsPath = path.join(this.expandPath(this.learningPath), 'skill-bindings.json');
368
+ this.skillBindings.lastUpdated = new Date().toISOString();
369
+ await fs.writeJson(bindingsPath, this.skillBindings, { spaces: 2 });
370
+ }
371
+
372
+ /**
373
+ * Load stats
374
+ */
375
+ async loadStats() {
376
+ const statsPath = path.join(this.expandPath(this.learningPath), 'stats.json');
377
+
378
+ if (await fs.pathExists(statsPath)) {
379
+ this.stats = await fs.readJson(statsPath);
380
+ } else {
381
+ this.stats = {
382
+ totalRecords: 0,
383
+ byType: {},
384
+ bySkill: {},
385
+ lastLearning: null
386
+ };
387
+ }
388
+ }
389
+
390
+ /**
391
+ * Update stats
392
+ */
393
+ async updateStats(type, skill) {
394
+ this.stats.totalRecords++;
395
+ this.stats.byType[type] = (this.stats.byType[type] || 0) + 1;
396
+ this.stats.bySkill[skill] = (this.stats.bySkill[skill] || 0) + 1;
397
+ this.stats.lastLearning = new Date().toISOString();
398
+
399
+ const statsPath = path.join(this.expandPath(this.learningPath), 'stats.json');
400
+ await fs.writeJson(statsPath, this.stats, { spaces: 2 });
401
+ }
402
+
403
+ /**
404
+ * Get statistics
405
+ */
406
+ async getStats() {
407
+ return this.stats;
408
+ }
409
+
410
+ /**
411
+ * Export learnings
412
+ */
413
+ async export(options = {}) {
414
+ const exportData = {
415
+ version: '1.0',
416
+ exportedAt: new Date().toISOString(),
417
+ stats: this.stats,
418
+ skillBindings: this.skillBindings,
419
+ records: this.records
420
+ };
421
+
422
+ if (options.output) {
423
+ await fs.writeJson(options.output, exportData, { spaces: 2 });
424
+ }
425
+
426
+ return exportData;
427
+ }
428
+
429
+ /**
430
+ * Import learnings
431
+ */
432
+ async import(data) {
433
+ if (typeof data === 'string') {
434
+ data = await fs.readJson(data);
435
+ }
436
+
437
+ // Merge stats
438
+ this.stats.totalRecords += data.stats.totalRecords || 0;
439
+ Object.assign(this.stats.byType, data.stats.byType || {});
440
+ Object.assign(this.stats.bySkill, data.stats.bySkill || {});
441
+
442
+ // Merge skill bindings
443
+ for (const [skill, binding] of Object.entries(data.skillBindings.bindings || {})) {
444
+ if (!this.skillBindings.bindings[skill]) {
445
+ this.skillBindings.bindings[skill] = binding;
446
+ } else {
447
+ this.skillBindings.bindings[skill].records.push(...binding.records);
448
+ this.skillBindings.bindings[skill].rules.push(...binding.rules);
449
+ }
450
+ }
451
+
452
+ // Save
453
+ await this.saveSkillBindings();
454
+ await this.saveStats();
455
+
456
+ return { success: true, imported: data.stats.totalRecords };
457
+ }
458
+
459
+ /**
460
+ * Helper methods
461
+ */
462
+ expandPath(filePath) {
463
+ if (filePath.startsWith('~')) {
464
+ return path.join(process.env.HOME || process.env.USERPROFILE, filePath.slice(1));
465
+ }
466
+ return filePath;
467
+ }
468
+
469
+ extractCondition(input) {
470
+ // Extract when to apply this learning
471
+ const conditionPatterns = [
472
+ /当.*时/i,
473
+ /when.*is/i,
474
+ /如果.*的话/i
475
+ ];
476
+
477
+ for (const pattern of conditionPatterns) {
478
+ const match = input.match(pattern);
479
+ if (match) return match[0];
480
+ }
481
+
482
+ return 'Always apply';
483
+ }
484
+
485
+ extractAction(input) {
486
+ // Extract what to do
487
+ return input.replace(/不对|错了|应该是|正确的是|改成|改为/i, '').trim() || 'Apply correction';
488
+ }
489
+
490
+ extractKeywords(input) {
491
+ // Extract keywords for scene matching
492
+ const words = input.match(/[\u4e00-\u9fa5a-zA-Z0-9]+/g) || [];
493
+ return [...new Set(words)].slice(0, 10);
494
+ }
495
+
496
+ extractWorkflow(input) {
497
+ // Extract workflow steps
498
+ return {
499
+ skills: ['auto-detect'],
500
+ params: {}
501
+ };
502
+ }
503
+
504
+ extractPreferences(input) {
505
+ // Extract preferences from input
506
+ const prefs = {};
507
+
508
+ if (/顺序列表|sequential/i.test(input)) {
509
+ prefs.outputFormat = 'sequential-list';
510
+ }
511
+ if (/表格|table/i.test(input)) {
512
+ prefs.outputFormat = 'table';
513
+ }
514
+ if (/简洁|compact/i.test(input)) {
515
+ prefs.detailLevel = 'compact';
516
+ }
517
+ if (/详细|detailed/i.test(input)) {
518
+ prefs.detailLevel = 'detailed';
519
+ }
520
+
521
+ return prefs;
522
+ }
523
+
524
+ extractPreferenceValue(input) {
525
+ // Extract preference value
526
+ const formatMatch = input.match(/用(.+?)格式|格式用(.+)/);
527
+ if (formatMatch) return formatMatch[1] || formatMatch[2];
528
+
529
+ return input;
530
+ }
531
+
532
+ generateSummary(record) {
533
+ // Generate summary for display
534
+ if (record.type === 'correction') {
535
+ return `Corrected: ${record.trigger}`;
536
+ }
537
+ if (record.type === 'scene_mapping') {
538
+ return `Scene: ${record.keywords.join(', ')}`;
539
+ }
540
+ return record.type;
541
+ }
542
+
543
+ formatLearningConfirmation(record) {
544
+ return `
545
+ ┌─────────────────────────────────────────────────────┐
546
+ │ 🧠 Learning Captured │
547
+ ├─────────────────────────────────────────────────────┤
548
+ │ ID: ${record.id} │
549
+ │ Skill: ${record.skill} │
550
+ │ Type: ${record.type} │
551
+ │ Severity: ${record.severity || 'N/A'} │
552
+ ├─────────────────────────────────────────────────────┤
553
+ │ FlowMind has recorded your preference. │
554
+ │ It will be applied automatically next time. │
555
+ └─────────────────────────────────────────────────────┘
556
+ `.trim();
557
+ }
558
+
559
+ formatSceneMappingConfirmation(record) {
560
+ return `
561
+ ┌─────────────────────────────────────────────────────┐
562
+ │ 🎯 Scene Mapping Saved │
563
+ ├─────────────────────────────────────────────────────┤
564
+ │ ID: ${record.id} │
565
+ │ Keywords: ${record.keywords.join(', ')} │
566
+ │ Preferences: ${JSON.stringify(record.preferences)} │
567
+ ├─────────────────────────────────────────────────────┤
568
+ │ FlowMind will auto-apply this workflow next time. │
569
+ └─────────────────────────────────────────────────────┘
570
+ `.trim();
571
+ }
572
+
573
+ formatPreferenceConfirmation(record) {
574
+ return `
575
+ ┌─────────────────────────────────────────────────────┐
576
+ │ ⚙️ Preference Saved │
577
+ ├─────────────────────────────────────────────────────┤
578
+ │ Type: ${record.preferenceType} │
579
+ │ Value: ${record.value} │
580
+ │ Skill: ${record.skill} │
581
+ ├─────────────────────────────────────────────────────┤
582
+ │ FlowMind will use this preference going forward. │
583
+ └─────────────────────────────────────────────────────┘
584
+ `.trim();
585
+ }
586
+ }
587
+
588
+ module.exports = LearningEngine;