flowmind 1.2.3 → 1.4.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 (36) hide show
  1. package/CHANGELOG.md +46 -0
  2. package/README.md +1 -1
  3. package/bin/flowmind.js +69 -16
  4. package/core/ai/base-model.js +47 -28
  5. package/core/ai/model-manager.js +100 -90
  6. package/core/ai/providers/anthropic.js +21 -14
  7. package/core/ai/providers/deepseek.js +12 -2
  8. package/core/ai/providers/ernie.js +2 -2
  9. package/core/ai/providers/glm.js +12 -2
  10. package/core/ai/providers/mimo.js +12 -2
  11. package/core/ai/providers/ollama.js +5 -4
  12. package/core/ai/providers/openai.js +8 -12
  13. package/core/ai/providers/qwen.js +12 -2
  14. package/core/config-manager.js +1 -0
  15. package/core/index.js +183 -6
  16. package/core/learning-engine.js +122 -30
  17. package/mcp/server.js +2 -1
  18. package/package.json +1 -1
  19. package/skills/api-sync/index.js +130 -0
  20. package/skills/archive-change/index.js +104 -0
  21. package/skills/auto-flow/index.js +124 -0
  22. package/skills/code-review/index.js +79 -0
  23. package/skills/code-review-audit/index.js +77 -0
  24. package/skills/data-logic-validation/index.js +108 -0
  25. package/skills/data-validation/index.js +72 -0
  26. package/skills/git-review/index.js +73 -0
  27. package/skills/learning-engine/index.js +50 -0
  28. package/skills/learning-feedback/index.js +83 -0
  29. package/skills/log-audit/index.js +88 -0
  30. package/skills/project-review/index.js +105 -0
  31. package/skills/requirement-analyst/index.js +88 -0
  32. package/skills/resource-bind/index.js +60 -0
  33. package/skills/sls-log-audit/index.js +120 -0
  34. package/skills/yapi-sync-interface/index.js +101 -0
  35. package/skills/yuque-sync-design/index.js +133 -0
  36. package/tui/app.jsx +1 -1
@@ -0,0 +1,130 @@
1
+ /**
2
+ * API Sync Skill
3
+ * Sync API definitions, generate documentation, maintain API consistency
4
+ */
5
+
6
+ const fs = require('fs-extra');
7
+ const path = require('path');
8
+
9
+ module.exports = {
10
+ canHandle(input, context) {
11
+ if (!input) return false;
12
+ return /api.*sync|同步.*api|api.*文档|swagger.*sync|接口.*同步/i.test(input);
13
+ },
14
+
15
+ async execute(input, context) {
16
+ const params = parseApiSyncParams(input);
17
+
18
+ if (params.action === 'generate') {
19
+ return generateDocs(params, input);
20
+ }
21
+
22
+ if (params.action === 'sync') {
23
+ return syncToYApi(params, input, context);
24
+ }
25
+
26
+ return {
27
+ type: 'result',
28
+ skill: 'api-sync',
29
+ message: 'API sync. Available actions: generate (from code), sync (to platform)',
30
+ data: {
31
+ actions: ['generate - Generate API docs from code', 'sync - Sync to YApi/Swagger Hub'],
32
+ params
33
+ },
34
+ input,
35
+ timestamp: new Date().toISOString()
36
+ };
37
+ }
38
+ };
39
+
40
+ function parseApiSyncParams(input) {
41
+ const params = {};
42
+ if (/生成|generate|导出/i.test(input)) params.action = 'generate';
43
+ if (/同步|sync|上传|push/i.test(input)) params.action = 'sync';
44
+
45
+ const pathMatch = input.match(/(?:路径|path|目录|dir)\s*[:=]?\s*(\S+)/i);
46
+ if (pathMatch) params.path = pathMatch[1];
47
+
48
+ const platformMatch = input.match(/(?:平台|platform)\s*[:=]?\s*(yapi|swagger|postman)/i);
49
+ if (platformMatch) params.platform = platformMatch[1].toLowerCase();
50
+
51
+ const projectMatch = input.match(/(?:项目|project)\s*[:=]?\s*(\S+)/i);
52
+ if (projectMatch) params.project = projectMatch[1];
53
+
54
+ return params;
55
+ }
56
+
57
+ async function generateDocs(params, input) {
58
+ const dirPath = params.path || '.';
59
+ if (!(await fs.pathExists(dirPath))) {
60
+ return {
61
+ type: 'error', skill: 'api-sync',
62
+ message: `Path not found: ${dirPath}`,
63
+ input, timestamp: new Date().toISOString()
64
+ };
65
+ }
66
+
67
+ const apis = [];
68
+ const files = await findApiFiles(dirPath);
69
+
70
+ for (const file of files.slice(0, 20)) {
71
+ const content = await fs.readFile(file, 'utf-8');
72
+ const endpoints = extractEndpoints(content);
73
+ apis.push(...endpoints.map(e => ({ ...e, file: path.relative(dirPath, file) })));
74
+ }
75
+
76
+ return {
77
+ type: 'result',
78
+ skill: 'api-sync',
79
+ message: `Found ${apis.length} API endpoint(s) in ${files.length} file(s)`,
80
+ data: { apis, totalEndpoints: apis.length, totalFiles: files.length },
81
+ input,
82
+ timestamp: new Date().toISOString()
83
+ };
84
+ }
85
+
86
+ function syncToYApi(params, input, context) {
87
+ return {
88
+ type: 'result',
89
+ skill: 'api-sync',
90
+ message: 'API sync to platform (use yapi-sync-interface skill for YApi-specific operations)',
91
+ data: { platform: params.platform || 'yapi', hint: 'Use yapi-sync-interface skill for direct YApi integration' },
92
+ input,
93
+ timestamp: new Date().toISOString()
94
+ };
95
+ }
96
+
97
+ async function findApiFiles(dir) {
98
+ const results = [];
99
+ const entries = await fs.readdir(dir, { withFileTypes: true });
100
+ for (const entry of entries) {
101
+ if (entry.name.startsWith('.') || entry.name === 'node_modules') continue;
102
+ const fullPath = path.join(dir, entry.name);
103
+ if (entry.isDirectory()) {
104
+ results.push(...await findApiFiles(fullPath));
105
+ } else if (/\.(js|ts|java|py)$/.test(entry.name)) {
106
+ results.push(fullPath);
107
+ }
108
+ }
109
+ return results;
110
+ }
111
+
112
+ function extractEndpoints(content) {
113
+ const endpoints = [];
114
+ const patterns = [
115
+ /@(?:GET|POST|PUT|DELETE|PATCH)Mapping\s*\(\s*["']([^"']+)["']/gi,
116
+ /(?:app|router)\.(get|post|put|delete|patch)\s*\(\s*["']([^"']+)["']/gi,
117
+ /@(?:Get|Post|Put|Delete|Patch)\s*\(\s*["']([^"']+)["']/gi,
118
+ ];
119
+
120
+ for (const pattern of patterns) {
121
+ let match;
122
+ while ((match = pattern.exec(content)) !== null) {
123
+ const method = match[1]?.toUpperCase() || 'GET';
124
+ const urlPath = match[2] || match[1];
125
+ endpoints.push({ method, path: urlPath });
126
+ }
127
+ }
128
+
129
+ return endpoints;
130
+ }
@@ -0,0 +1,104 @@
1
+ /**
2
+ * Archive Change Skill
3
+ * Archive completed changes and maintain project history
4
+ */
5
+
6
+ const fs = require('fs-extra');
7
+ const path = require('path');
8
+
9
+ module.exports = {
10
+ canHandle(input, context) {
11
+ if (!input) return false;
12
+ return /归档|archive|完成.*存档|change.*archive|整理.*完成/i.test(input);
13
+ },
14
+
15
+ async execute(input, context) {
16
+ const params = parseArchiveParams(input);
17
+
18
+ if (params.action === 'check') {
19
+ const checklist = await checkArchiveReadiness(params.path || '.');
20
+ return {
21
+ type: 'result',
22
+ skill: 'archive-change',
23
+ message: `Archive readiness: ${checklist.passed}/${checklist.total} checks passed`,
24
+ data: { checklist },
25
+ input,
26
+ timestamp: new Date().toISOString()
27
+ };
28
+ }
29
+
30
+ if (params.action === 'archive') {
31
+ const archiveDir = params.archiveDir || '.archive';
32
+ const changeName = params.name || `change-${Date.now()}`;
33
+
34
+ return {
35
+ type: 'result',
36
+ skill: 'archive-change',
37
+ message: `Archiving change: ${changeName}`,
38
+ data: {
39
+ changeName,
40
+ archiveDir: path.join(archiveDir, changeName),
41
+ structure: ['SUMMARY.md', 'PROPOSAL.md', 'SPECS.md', 'DESIGN.md', 'TASKS.md', 'CHANGELOG.md', 'TESTS.md', 'artifacts/'],
42
+ preCheck: await checkArchiveReadiness(params.path || '.')
43
+ },
44
+ input,
45
+ timestamp: new Date().toISOString()
46
+ };
47
+ }
48
+
49
+ return {
50
+ type: 'result',
51
+ skill: 'archive-change',
52
+ message: 'Archive change. Available actions: check, archive',
53
+ data: {
54
+ actions: ['check - Check readiness', 'archive - Archive change'],
55
+ archiveStructure: ['SUMMARY.md', 'PROPOSAL.md', 'SPECS.md', 'DESIGN.md', 'TASKS.md', 'CHANGELOG.md', 'TESTS.md']
56
+ },
57
+ input,
58
+ timestamp: new Date().toISOString()
59
+ };
60
+ }
61
+ };
62
+
63
+ function parseArchiveParams(input) {
64
+ const params = {};
65
+ if (/检查|check|就绪|ready/i.test(input)) params.action = 'check';
66
+ if (/归档|archive|执行/i.test(input)) params.action = 'archive';
67
+
68
+ const nameMatch = input.match(/(?:名称|name)\s*[:=]?\s*(\S+)/i);
69
+ if (nameMatch) params.name = nameMatch[1];
70
+
71
+ const pathMatch = input.match(/(?:路径|path|目录|dir)\s*[:=]?\s*(\S+)/i);
72
+ if (pathMatch) params.path = pathMatch[1];
73
+
74
+ return params;
75
+ }
76
+
77
+ async function checkArchiveReadiness(dir) {
78
+ const checks = [
79
+ { name: 'README exists', check: () => fs.pathExists(path.join(dir, 'README.md')) },
80
+ { name: 'Has git history', check: () => fs.pathExists(path.join(dir, '.git')) },
81
+ { name: 'Has package.json or similar', check: async () =>
82
+ await fs.pathExists(path.join(dir, 'package.json')) ||
83
+ await fs.pathExists(path.join(dir, 'pom.xml')) ||
84
+ await fs.pathExists(path.join(dir, 'Cargo.toml'))
85
+ },
86
+ ];
87
+
88
+ const results = [];
89
+ for (const c of checks) {
90
+ try {
91
+ const passed = await c.check();
92
+ results.push({ name: c.name, passed });
93
+ } catch {
94
+ results.push({ name: c.name, passed: false });
95
+ }
96
+ }
97
+
98
+ return {
99
+ checks: results,
100
+ passed: results.filter(r => r.passed).length,
101
+ total: results.length,
102
+ ready: results.every(r => r.passed)
103
+ };
104
+ }
@@ -0,0 +1,124 @@
1
+ /**
2
+ * Auto Flow Skill
3
+ * Define, execute, and manage complex multi-step workflows
4
+ */
5
+
6
+ const fs = require('fs-extra');
7
+ const path = require('path');
8
+
9
+ const BUILTIN_WORKFLOWS = {
10
+ 'dev-workflow': {
11
+ name: 'Development Workflow',
12
+ steps: [
13
+ { id: 'review', skill: 'code-review', name: 'Code Review' },
14
+ { id: 'test', action: 'run-command', command: 'npm test', depends_on: ['review'] },
15
+ { id: 'docs', action: 'run-command', command: 'npm run docs', depends_on: ['test'] },
16
+ { id: 'archive', skill: 'archive-change', name: 'Archive', depends_on: ['docs'] }
17
+ ]
18
+ },
19
+ 'deploy-workflow': {
20
+ name: 'Deploy Workflow',
21
+ steps: [
22
+ { id: 'validate', action: 'run-command', command: 'npm run validate' },
23
+ { id: 'build', action: 'run-command', command: 'npm run build', depends_on: ['validate'] },
24
+ { id: 'deploy', action: 'deploy', depends_on: ['build'] },
25
+ { id: 'verify', action: 'run-command', command: 'npm run verify', depends_on: ['deploy'] },
26
+ { id: 'notify', action: 'notify', depends_on: ['verify'] }
27
+ ]
28
+ }
29
+ };
30
+
31
+ module.exports = {
32
+ canHandle(input, context) {
33
+ if (!input) return false;
34
+ return /自动流程|auto.*flow|工作流|workflow|执行流程|run.*flow/i.test(input);
35
+ },
36
+
37
+ async execute(input, context) {
38
+ const params = parseFlowParams(input);
39
+
40
+ if (params.action === 'list') {
41
+ return {
42
+ type: 'result',
43
+ skill: 'auto-flow',
44
+ message: `Available workflows: ${Object.keys(BUILTIN_WORKFLOWS).join(', ')}`,
45
+ data: { workflows: Object.entries(BUILTIN_WORKFLOWS).map(([k, v]) => ({ id: k, name: v.name, steps: v.steps.length })) },
46
+ input,
47
+ timestamp: new Date().toISOString()
48
+ };
49
+ }
50
+
51
+ if (params.action === 'run') {
52
+ const workflow = BUILTIN_WORKFLOWS[params.workflow];
53
+ if (!workflow) {
54
+ return {
55
+ type: 'error', skill: 'auto-flow',
56
+ message: `Workflow not found: ${params.workflow}. Available: ${Object.keys(BUILTIN_WORKFLOWS).join(', ')}`,
57
+ input, timestamp: new Date().toISOString()
58
+ };
59
+ }
60
+
61
+ return {
62
+ type: 'result',
63
+ skill: 'auto-flow',
64
+ message: `Starting workflow: ${workflow.name} (${workflow.steps.length} steps)`,
65
+ data: {
66
+ workflow: params.workflow,
67
+ steps: workflow.steps.map(s => ({
68
+ id: s.id,
69
+ name: s.name || s.action || s.skill,
70
+ depends_on: s.depends_on || [],
71
+ status: 'pending'
72
+ })),
73
+ status: 'started'
74
+ },
75
+ input,
76
+ timestamp: new Date().toISOString()
77
+ };
78
+ }
79
+
80
+ if (params.action === 'define') {
81
+ return {
82
+ type: 'result',
83
+ skill: 'auto-flow',
84
+ message: 'Define a workflow in YAML format',
85
+ data: {
86
+ format: 'YAML workflow definition',
87
+ example: {
88
+ name: 'My Workflow',
89
+ steps: [
90
+ { id: 'step1', action: 'run-command', command: 'echo hello' },
91
+ { id: 'step2', skill: 'code-review', depends_on: ['step1'] }
92
+ ]
93
+ }
94
+ },
95
+ input,
96
+ timestamp: new Date().toISOString()
97
+ };
98
+ }
99
+
100
+ return {
101
+ type: 'result',
102
+ skill: 'auto-flow',
103
+ message: 'Auto Flow. Available actions: list, run, define',
104
+ data: {
105
+ actions: ['list - List workflows', 'run <name> - Run workflow', 'define - Define new workflow'],
106
+ builtinWorkflows: Object.keys(BUILTIN_WORKFLOWS)
107
+ },
108
+ input,
109
+ timestamp: new Date().toISOString()
110
+ };
111
+ }
112
+ };
113
+
114
+ function parseFlowParams(input) {
115
+ const params = {};
116
+ if (/列表|list/i.test(input)) params.action = 'list';
117
+ if (/执行|run|start|运行/i.test(input)) params.action = 'run';
118
+ if (/定义|define|创建|create/i.test(input)) params.action = 'define';
119
+
120
+ const workflowMatch = input.match(/(?:workflow|流程|工作流)\s*[:=]?\s*(\S+)/i);
121
+ if (workflowMatch) params.workflow = workflowMatch[1];
122
+
123
+ return params;
124
+ }
@@ -0,0 +1,79 @@
1
+ /**
2
+ * Code Review Skill
3
+ * Analyzes code for security vulnerabilities, style violations, and best practices
4
+ */
5
+
6
+ const fs = require('fs-extra');
7
+ const path = require('path');
8
+
9
+ const SECURITY_PATTERNS = [
10
+ { pattern: /(\bselect\b.*\bfrom\b.*\bwhere\b.*['"]\s*\+\s*)/i, name: 'SQL Injection', severity: 'HIGH' },
11
+ { pattern: /eval\s*\(/, name: 'Code Injection (eval)', severity: 'HIGH' },
12
+ { pattern: /innerHTML\s*=/, name: 'XSS (innerHTML)', severity: 'MEDIUM' },
13
+ { pattern: /password\s*=\s*['"][^'"]+['"]/i, name: 'Hardcoded Password', severity: 'HIGH' },
14
+ { pattern: /api[_-]?key\s*=\s*['"][^'"]+['"]/i, name: 'Hardcoded API Key', severity: 'HIGH' },
15
+ { pattern: /secret\s*=\s*['"][^'"]+['"]/i, name: 'Hardcoded Secret', severity: 'HIGH' },
16
+ { pattern: /md5\s*\(/i, name: 'Weak Crypto (MD5)', severity: 'MEDIUM' },
17
+ { pattern: /exec\s*\(\s*['"`]/, name: 'Command Injection', severity: 'HIGH' },
18
+ ];
19
+
20
+ const QUALITY_PATTERNS = [
21
+ { pattern: /console\.(log|debug|info)\s*\(/, name: 'Console statement', severity: 'LOW' },
22
+ { pattern: /\/\/\s*TODO/i, name: 'TODO comment', severity: 'LOW' },
23
+ { pattern: /\/\/\s*FIXME/i, name: 'FIXME comment', severity: 'MEDIUM' },
24
+ { pattern: /\/\/\s*HACK/i, name: 'HACK comment', severity: 'MEDIUM' },
25
+ { pattern: /catch\s*\(\s*\w*\s*\)\s*\{\s*\}/, name: 'Empty catch block', severity: 'MEDIUM' },
26
+ ];
27
+
28
+ module.exports = {
29
+ canHandle(input, context) {
30
+ if (!input) return false;
31
+ const lower = input.toLowerCase();
32
+ return /代码审查|code review|review code|检查代码|分析代码/.test(lower);
33
+ },
34
+
35
+ async execute(input, context) {
36
+ const issues = [];
37
+ const filePath = extractFilePath(input);
38
+
39
+ if (filePath && await fs.pathExists(filePath)) {
40
+ const content = await fs.readFile(filePath, 'utf-8');
41
+ const lines = content.split('\n');
42
+ const relPath = path.basename(filePath);
43
+
44
+ lines.forEach((line, idx) => {
45
+ SECURITY_PATTERNS.forEach(({ pattern, name, severity }) => {
46
+ if (pattern.test(line)) {
47
+ issues.push({ file: relPath, line: idx + 1, type: 'security', name, severity, code: line.trim() });
48
+ }
49
+ });
50
+ QUALITY_PATTERNS.forEach(({ pattern, name, severity }) => {
51
+ if (pattern.test(line)) {
52
+ issues.push({ file: relPath, line: idx + 1, type: 'quality', name, severity, code: line.trim() });
53
+ }
54
+ });
55
+ });
56
+ }
57
+
58
+ const high = issues.filter(i => i.severity === 'HIGH').length;
59
+ const medium = issues.filter(i => i.severity === 'MEDIUM').length;
60
+ const low = issues.filter(i => i.severity === 'LOW').length;
61
+
62
+ return {
63
+ type: 'result',
64
+ skill: 'code-review',
65
+ message: issues.length > 0
66
+ ? `Found ${issues.length} issue(s): ${high} HIGH, ${medium} MEDIUM, ${low} LOW`
67
+ : 'No issues found',
68
+ data: { issues, summary: { total: issues.length, high, medium, low } },
69
+ input,
70
+ timestamp: new Date().toISOString()
71
+ };
72
+ }
73
+ };
74
+
75
+ function extractFilePath(input) {
76
+ const match = input.match(/(?:review|审查|检查|分析)\s+(.+?\.[a-zA-Z]+)$/i)
77
+ || input.match(/([^\s]+\.(js|ts|jsx|tsx|py|java|go|rs|c|cpp|rb))$/i);
78
+ return match ? match[1].trim() : null;
79
+ }
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Code Review Audit Skill
3
+ * Three-dimensional review: security audit, design compliance, mandatory constraints
4
+ */
5
+
6
+ const fs = require('fs-extra');
7
+
8
+ const SECURITY_CHECKS = [
9
+ { pattern: /(\bselect\b.*\bfrom\b.*\bwhere\b.*['"]\s*\+\s*)/i, name: 'SQL Injection', severity: 'HIGH' },
10
+ { pattern: /eval\s*\(/, name: 'Code Injection', severity: 'HIGH' },
11
+ { pattern: /innerHTML\s*=/, name: 'XSS Risk', severity: 'MEDIUM' },
12
+ { pattern: /password|secret|apikey|token/i, name: 'Potential Sensitive Data', severity: 'MEDIUM' },
13
+ ];
14
+
15
+ const CONSTRAINT_CHECKS = [
16
+ { pattern: /class\s+[a-z]/, name: 'Class naming (should be PascalCase)', severity: 'LOW' },
17
+ { pattern: /function\s+[A-Z]/, name: 'Function naming (should be camelCase)', severity: 'LOW' },
18
+ { pattern: /catch\s*\(\s*\w*\s*\)\s*\{\s*\}/, name: 'Empty catch block', severity: 'MEDIUM' },
19
+ { pattern: /\/\/\s*TODO/i, name: 'TODO comment', severity: 'LOW' },
20
+ ];
21
+
22
+ module.exports = {
23
+ canHandle(input, context) {
24
+ if (!input) return false;
25
+ return /安全审计|security audit|design compliance|设计合规|code review audit|审查审计/i.test(input);
26
+ },
27
+
28
+ async execute(input, context) {
29
+ const findings = [];
30
+ const filePath = extractFilePath(input);
31
+
32
+ if (filePath && await fs.pathExists(filePath)) {
33
+ const content = await fs.readFile(filePath, 'utf-8');
34
+ const lines = content.split('\n');
35
+
36
+ lines.forEach((line, idx) => {
37
+ SECURITY_CHECKS.forEach(({ pattern, name, severity }) => {
38
+ if (pattern.test(line)) {
39
+ findings.push({ dimension: 'security', line: idx + 1, name, severity, code: line.trim() });
40
+ }
41
+ });
42
+ CONSTRAINT_CHECKS.forEach(({ pattern, name, severity }) => {
43
+ if (pattern.test(line)) {
44
+ findings.push({ dimension: 'constraint', line: idx + 1, name, severity, code: line.trim() });
45
+ }
46
+ });
47
+ });
48
+ }
49
+
50
+ const high = findings.filter(f => f.severity === 'HIGH').length;
51
+ const medium = findings.filter(f => f.severity === 'MEDIUM').length;
52
+ const verdict = high > 0 ? 'FAIL' : medium > 0 ? 'CONDITIONAL' : 'PASS';
53
+
54
+ return {
55
+ type: 'result',
56
+ skill: 'code-review-audit',
57
+ message: `Audit verdict: ${verdict} (${findings.length} findings)`,
58
+ data: {
59
+ verdict,
60
+ findings,
61
+ summary: { total: findings.length, high, medium, low: findings.filter(f => f.severity === 'LOW').length },
62
+ dimensions: {
63
+ security: findings.filter(f => f.dimension === 'security').length,
64
+ constraint: findings.filter(f => f.dimension === 'constraint').length
65
+ }
66
+ },
67
+ input,
68
+ timestamp: new Date().toISOString()
69
+ };
70
+ }
71
+ };
72
+
73
+ function extractFilePath(input) {
74
+ const match = input.match(/(?:audit|审计|审查)\s+(.+?\.[a-zA-Z]+)$/i)
75
+ || input.match(/([^\s]+\.(js|ts|jsx|tsx|py|java|go))$/i);
76
+ return match ? match[1].trim() : null;
77
+ }
@@ -0,0 +1,108 @@
1
+ /**
2
+ * Data Logic Validation Skill
3
+ * Verify SQL queries, Redis operations, and data processing logic against real data via MCP
4
+ */
5
+
6
+ module.exports = {
7
+ canHandle(input, context) {
8
+ if (!input) return false;
9
+ return /数据逻辑|data.*logic.*valid|sql.*验证|redis.*验证|数据.*校验.*逻辑/i.test(input);
10
+ },
11
+
12
+ async execute(input, context) {
13
+ const registry = context.componentRegistry;
14
+ const params = parseValidationParams(input);
15
+
16
+ // Check MCP component availability
17
+ const dbManager = registry?.getAdapter('databaseManager');
18
+ const dbQuery = registry?.getAdapter('databaseQuery');
19
+ const redisMonitor = registry?.getAdapter('redisMonitor');
20
+
21
+ const availableComponents = {
22
+ databaseManager: !!dbManager,
23
+ databaseQuery: !!dbQuery,
24
+ redisMonitor: !!redisMonitor
25
+ };
26
+
27
+ if (params.action === 'sql') {
28
+ if (!dbQuery) {
29
+ return {
30
+ type: 'result',
31
+ skill: 'data-logic-validation',
32
+ message: 'Database query service not configured (need friday-rds-redis-query MCP)',
33
+ data: { availableComponents, hint: 'Configure database query MCP server first' },
34
+ input,
35
+ timestamp: new Date().toISOString()
36
+ };
37
+ }
38
+
39
+ return {
40
+ type: 'result',
41
+ skill: 'data-logic-validation',
42
+ message: `SQL validation ready for: ${params.query || 'provided query'}`,
43
+ data: {
44
+ action: 'validate_sql',
45
+ query: params.query,
46
+ mcpTool: 'mcpQueryExec',
47
+ availableComponents
48
+ },
49
+ input,
50
+ timestamp: new Date().toISOString()
51
+ };
52
+ }
53
+
54
+ if (params.action === 'redis') {
55
+ if (!redisMonitor) {
56
+ return {
57
+ type: 'result',
58
+ skill: 'data-logic-validation',
59
+ message: 'Redis monitor not configured (need friday-aliyun-sz-rds-redis MCP)',
60
+ data: { availableComponents, hint: 'Configure Redis monitor MCP server first' },
61
+ input,
62
+ timestamp: new Date().toISOString()
63
+ };
64
+ }
65
+
66
+ return {
67
+ type: 'result',
68
+ skill: 'data-logic-validation',
69
+ message: `Redis validation ready for key: ${params.key || 'provided key'}`,
70
+ data: {
71
+ action: 'validate_redis',
72
+ key: params.key,
73
+ mcpTools: ['mcpRedisKeyGet', 'mcpRedisKeyInfo', 'mcpRedisKeys'],
74
+ availableComponents
75
+ },
76
+ input,
77
+ timestamp: new Date().toISOString()
78
+ };
79
+ }
80
+
81
+ return {
82
+ type: 'result',
83
+ skill: 'data-logic-validation',
84
+ message: 'Data logic validation. Available: sql, redis',
85
+ data: {
86
+ actions: ['sql - Validate SQL queries', 'redis - Validate Redis operations'],
87
+ availableComponents,
88
+ requiredMCP: ['databaseManager', 'databaseQuery', 'redisMonitor']
89
+ },
90
+ input,
91
+ timestamp: new Date().toISOString()
92
+ };
93
+ }
94
+ };
95
+
96
+ function parseValidationParams(input) {
97
+ const params = {};
98
+ if (/sql|查询|query|select|insert|update|delete/i.test(input)) params.action = 'sql';
99
+ if (/redis|缓存|cache|key/i.test(input)) params.action = 'redis';
100
+
101
+ const sqlMatch = input.match(/(?:sql|查询)\s*[:=]?\s*(select\s+.+?)(?:\s*$)/i);
102
+ if (sqlMatch) params.query = sqlMatch[1].trim();
103
+
104
+ const keyMatch = input.match(/(?:key|键)\s*[:=]?\s*(\S+)/i);
105
+ if (keyMatch) params.key = keyMatch[1];
106
+
107
+ return params;
108
+ }
@@ -0,0 +1,72 @@
1
+ /**
2
+ * Data Validation Skill
3
+ * Validates data against schema rules and business logic
4
+ */
5
+
6
+ module.exports = {
7
+ canHandle(input, context) {
8
+ if (!input) return false;
9
+ return /数据校验|数据验证|data valid|validate data|校验数据|检查数据/i.test(input);
10
+ },
11
+
12
+ async execute(input, context) {
13
+ const data = extractData(input);
14
+
15
+ if (!data) {
16
+ return {
17
+ type: 'result',
18
+ skill: 'data-validation',
19
+ message: 'No data found to validate. Provide JSON data or a file path.',
20
+ input,
21
+ timestamp: new Date().toISOString()
22
+ };
23
+ }
24
+
25
+ const issues = [];
26
+
27
+ // Schema validation
28
+ if (data.rules && data.values) {
29
+ for (const [field, rule] of Object.entries(data.rules)) {
30
+ const value = data.values[field];
31
+
32
+ if (rule.required && (value === undefined || value === null || value === '')) {
33
+ issues.push({ field, rule: 'required', message: `${field} is required but missing`, severity: 'ERROR' });
34
+ }
35
+ if (value !== undefined && rule.type && typeof value !== rule.type) {
36
+ issues.push({ field, rule: 'type', message: `${field} should be ${rule.type}, got ${typeof value}`, severity: 'ERROR' });
37
+ }
38
+ if (value !== undefined && rule.min !== undefined && value < rule.min) {
39
+ issues.push({ field, rule: 'min', message: `${field} (${value}) below minimum (${rule.min})`, severity: 'WARNING' });
40
+ }
41
+ if (value !== undefined && rule.max !== undefined && value > rule.max) {
42
+ issues.push({ field, rule: 'max', message: `${field} (${value}) above maximum (${rule.max})`, severity: 'WARNING' });
43
+ }
44
+ if (value !== undefined && rule.pattern && !new RegExp(rule.pattern).test(String(value))) {
45
+ issues.push({ field, rule: 'pattern', message: `${field} doesn't match pattern`, severity: 'WARNING' });
46
+ }
47
+ }
48
+ }
49
+
50
+ const errors = issues.filter(i => i.severity === 'ERROR').length;
51
+ const warnings = issues.filter(i => i.severity === 'WARNING').length;
52
+
53
+ return {
54
+ type: 'result',
55
+ skill: 'data-validation',
56
+ message: issues.length > 0
57
+ ? `Validation: ${errors} errors, ${warnings} warnings`
58
+ : 'All validations passed',
59
+ data: { issues, summary: { errors, warnings, passed: issues.length === 0 } },
60
+ input,
61
+ timestamp: new Date().toISOString()
62
+ };
63
+ }
64
+ };
65
+
66
+ function extractData(input) {
67
+ const jsonMatch = input.match(/\{[\s\S]*\}/);
68
+ if (jsonMatch) {
69
+ try { return JSON.parse(jsonMatch[0]); } catch {}
70
+ }
71
+ return null;
72
+ }