flowmind 1.2.2 → 1.3.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.
- package/bin/flowmind.js +85 -18
- package/core/config-manager.js +1 -0
- package/core/learning-engine.js +122 -30
- package/mcp/server.js +2 -1
- package/package.json +1 -1
- package/skills/api-sync/index.js +130 -0
- package/skills/archive-change/index.js +104 -0
- package/skills/auto-flow/index.js +124 -0
- package/skills/code-review/index.js +79 -0
- package/skills/code-review-audit/index.js +77 -0
- package/skills/data-logic-validation/index.js +108 -0
- package/skills/data-validation/index.js +72 -0
- package/skills/git-review/index.js +73 -0
- package/skills/learning-engine/index.js +50 -0
- package/skills/learning-feedback/index.js +83 -0
- package/skills/log-audit/index.js +88 -0
- package/skills/project-review/index.js +105 -0
- package/skills/requirement-analyst/index.js +88 -0
- package/skills/resource-bind/index.js +60 -0
- package/skills/sls-log-audit/index.js +120 -0
- package/skills/yapi-sync-interface/index.js +101 -0
- package/skills/yuque-sync-design/index.js +133 -0
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Git Review Skill
|
|
3
|
+
* Reviews commit history, analyzes changes, ensures code quality
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const { execSync } = require('child_process');
|
|
7
|
+
|
|
8
|
+
module.exports = {
|
|
9
|
+
canHandle(input, context) {
|
|
10
|
+
if (!input) return false;
|
|
11
|
+
return /git\s*review|commit.*review|代码提交|提交审查|review.*commit|检查提交/i.test(input);
|
|
12
|
+
},
|
|
13
|
+
|
|
14
|
+
async execute(input, context) {
|
|
15
|
+
const count = extractCount(input) || 5;
|
|
16
|
+
|
|
17
|
+
try {
|
|
18
|
+
const logRaw = execSync(
|
|
19
|
+
`git log --oneline -${count} --format="%H|%s|%an|%ai"`,
|
|
20
|
+
{ encoding: 'utf-8', timeout: 10000 }
|
|
21
|
+
).trim();
|
|
22
|
+
|
|
23
|
+
if (!logRaw) {
|
|
24
|
+
return { type: 'result', skill: 'git-review', message: 'No commits found', input, timestamp: new Date().toISOString() };
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const commits = logRaw.split('\n').map(line => {
|
|
28
|
+
const [hash, subject, author, date] = line.split('|');
|
|
29
|
+
return { hash: hash?.slice(0, 8), subject, author, date };
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
const issues = [];
|
|
33
|
+
const good = [];
|
|
34
|
+
|
|
35
|
+
commits.forEach(c => {
|
|
36
|
+
// Check conventional commit format
|
|
37
|
+
const conventional = /^(feat|fix|docs|style|refactor|test|chore|perf|ci|build)(\(.+\))?: .+/;
|
|
38
|
+
if (!conventional.test(c.subject)) {
|
|
39
|
+
issues.push({ hash: c.hash, type: 'format', message: `Non-conventional commit message: "${c.subject}"` });
|
|
40
|
+
} else {
|
|
41
|
+
good.push({ hash: c.hash, message: c.subject });
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Check length
|
|
45
|
+
if (c.subject && c.subject.length > 72) {
|
|
46
|
+
issues.push({ hash: c.hash, type: 'length', message: `Subject too long (${c.subject.length} chars)` });
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
return {
|
|
51
|
+
type: 'result',
|
|
52
|
+
skill: 'git-review',
|
|
53
|
+
message: `Reviewed ${commits.length} commits: ${good.length} good, ${issues.length} issues`,
|
|
54
|
+
data: { commits, issues, good, summary: { total: commits.length, good: good.length, issues: issues.length } },
|
|
55
|
+
input,
|
|
56
|
+
timestamp: new Date().toISOString()
|
|
57
|
+
};
|
|
58
|
+
} catch (e) {
|
|
59
|
+
return {
|
|
60
|
+
type: 'error',
|
|
61
|
+
skill: 'git-review',
|
|
62
|
+
message: `Git review failed: ${e.message}`,
|
|
63
|
+
input,
|
|
64
|
+
timestamp: new Date().toISOString()
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
function extractCount(input) {
|
|
71
|
+
const match = input.match(/(\d+)\s*(?:个|条|commits?|次)/i) || input.match(/-(\d+)/);
|
|
72
|
+
return match ? parseInt(match[1]) : null;
|
|
73
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Learning Engine Skill
|
|
3
|
+
* Delegates to core LearningEngine for detecting and recording learning patterns
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
module.exports = {
|
|
7
|
+
canHandle(input, context) {
|
|
8
|
+
if (!input) return false;
|
|
9
|
+
const lower = input.toLowerCase();
|
|
10
|
+
const patterns = [
|
|
11
|
+
/不[对是]/, /错[了误]/, /应该[是用]/, /改[成为]/, /优化/, /改进/,
|
|
12
|
+
/记住/, /学习/, /纠正/, /prefer/, /learn/, /correct/, /remember/
|
|
13
|
+
];
|
|
14
|
+
return patterns.some(p => p.test(lower));
|
|
15
|
+
},
|
|
16
|
+
|
|
17
|
+
async execute(input, context) {
|
|
18
|
+
const learning = context.flowmind?.learning;
|
|
19
|
+
if (!learning) {
|
|
20
|
+
return {
|
|
21
|
+
type: 'error',
|
|
22
|
+
skill: 'learning-engine',
|
|
23
|
+
message: 'Learning engine not available',
|
|
24
|
+
input
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const result = await learning.detectLearning(input, context);
|
|
29
|
+
|
|
30
|
+
if (result) {
|
|
31
|
+
return {
|
|
32
|
+
type: 'learning',
|
|
33
|
+
skill: 'learning-engine',
|
|
34
|
+
learningType: result.type,
|
|
35
|
+
message: result.message || `Detected ${result.type} learning pattern`,
|
|
36
|
+
data: result,
|
|
37
|
+
input,
|
|
38
|
+
timestamp: new Date().toISOString()
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return {
|
|
43
|
+
type: 'result',
|
|
44
|
+
skill: 'learning-engine',
|
|
45
|
+
message: 'No learning pattern detected in input',
|
|
46
|
+
input,
|
|
47
|
+
timestamp: new Date().toISOString()
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
};
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Learning Feedback Skill
|
|
3
|
+
* Captures user corrections, feedback, and refinement suggestions.
|
|
4
|
+
* Overlaps with learning-engine but focuses on feedback capture and binding.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
module.exports = {
|
|
8
|
+
canHandle(input, context) {
|
|
9
|
+
if (!input) return false;
|
|
10
|
+
const lower = input.toLowerCase();
|
|
11
|
+
const feedbackPatterns = [
|
|
12
|
+
/不对/, /错了/, /应该是/, /不要/, /别这样/,
|
|
13
|
+
/下次/, /以后/, /记得/, /别再/, /改成/,
|
|
14
|
+
/wrong/, /should be/, /don't/, /next time/, /change to/
|
|
15
|
+
];
|
|
16
|
+
return feedbackPatterns.some(p => p.test(lower));
|
|
17
|
+
},
|
|
18
|
+
|
|
19
|
+
async execute(input, context) {
|
|
20
|
+
const learning = context.flowmind?.learning;
|
|
21
|
+
if (!learning) {
|
|
22
|
+
return {
|
|
23
|
+
type: 'error',
|
|
24
|
+
skill: 'learning-feedback',
|
|
25
|
+
message: 'Learning engine not available',
|
|
26
|
+
input
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Detect correction patterns
|
|
31
|
+
const correction = learning.detectCorrection(input);
|
|
32
|
+
if (correction) {
|
|
33
|
+
const record = await learning.recordCorrection(correction, context);
|
|
34
|
+
return {
|
|
35
|
+
type: 'learning',
|
|
36
|
+
skill: 'learning-feedback',
|
|
37
|
+
learningType: 'correction',
|
|
38
|
+
message: `Recorded correction: ${correction.summary || 'user feedback captured'}`,
|
|
39
|
+
data: record,
|
|
40
|
+
input,
|
|
41
|
+
timestamp: new Date().toISOString()
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Detect preference patterns
|
|
46
|
+
const preference = learning.detectPreference(input);
|
|
47
|
+
if (preference) {
|
|
48
|
+
const record = await learning.recordPreference(preference, context);
|
|
49
|
+
return {
|
|
50
|
+
type: 'learning',
|
|
51
|
+
skill: 'learning-feedback',
|
|
52
|
+
learningType: 'preference',
|
|
53
|
+
message: `Recorded preference: ${preference.type} = ${preference.value}`,
|
|
54
|
+
data: record,
|
|
55
|
+
input,
|
|
56
|
+
timestamp: new Date().toISOString()
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Detect scene mapping
|
|
61
|
+
const scene = learning.detectSceneMapping(input);
|
|
62
|
+
if (scene) {
|
|
63
|
+
const record = await learning.recordSceneMapping(scene, context);
|
|
64
|
+
return {
|
|
65
|
+
type: 'learning',
|
|
66
|
+
skill: 'learning-feedback',
|
|
67
|
+
learningType: 'scene_mapping',
|
|
68
|
+
message: `Recorded scene mapping for skill: ${scene.skill}`,
|
|
69
|
+
data: record,
|
|
70
|
+
input,
|
|
71
|
+
timestamp: new Date().toISOString()
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return {
|
|
76
|
+
type: 'result',
|
|
77
|
+
skill: 'learning-feedback',
|
|
78
|
+
message: 'No feedback pattern detected',
|
|
79
|
+
input,
|
|
80
|
+
timestamp: new Date().toISOString()
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
};
|