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,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Log Audit Skill
|
|
3
|
+
* Analyzes application logs, traces requests, debugs performance issues
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
module.exports = {
|
|
7
|
+
canHandle(input, context) {
|
|
8
|
+
if (!input) return false;
|
|
9
|
+
return /日志分析|log.*audit|日志查询|查日志|log.*analy|trace.*id|调用链/i.test(input);
|
|
10
|
+
},
|
|
11
|
+
|
|
12
|
+
async execute(input, context) {
|
|
13
|
+
const logService = context.componentRegistry?.getAdapter('logService');
|
|
14
|
+
const params = parseLogParams(input);
|
|
15
|
+
|
|
16
|
+
if (!logService && !params.mock) {
|
|
17
|
+
return {
|
|
18
|
+
type: 'result',
|
|
19
|
+
skill: 'log-audit',
|
|
20
|
+
message: 'Log service not configured. Connect an SLS/ELK log service first.',
|
|
21
|
+
data: { params, hint: 'Use `flowmind resource` to configure log service connections' },
|
|
22
|
+
input,
|
|
23
|
+
timestamp: new Date().toISOString()
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// If we have a log service, format the query for MCP tools
|
|
28
|
+
if (params.traceId) {
|
|
29
|
+
return {
|
|
30
|
+
type: 'result',
|
|
31
|
+
skill: 'log-audit',
|
|
32
|
+
message: `Trace query for: ${params.traceId}`,
|
|
33
|
+
data: {
|
|
34
|
+
action: 'trace',
|
|
35
|
+
traceId: params.traceId,
|
|
36
|
+
query: `* and "${params.traceId}"`,
|
|
37
|
+
timeRange: params.timeRange || 'last 1 hour',
|
|
38
|
+
endpoint: params.endpoint || 'cn-shenzhen.log.aliyuncs.com'
|
|
39
|
+
},
|
|
40
|
+
input,
|
|
41
|
+
timestamp: new Date().toISOString()
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return {
|
|
46
|
+
type: 'result',
|
|
47
|
+
skill: 'log-audit',
|
|
48
|
+
message: `Log query: ${params.service || 'all services'}, ${params.level || 'all levels'}, ${params.timeRange || 'last 1 hour'}`,
|
|
49
|
+
data: {
|
|
50
|
+
action: 'query',
|
|
51
|
+
query: buildSLSQuery(params),
|
|
52
|
+
timeRange: params.timeRange || 'last 1 hour',
|
|
53
|
+
endpoint: params.endpoint || 'cn-shenzhen.log.aliyuncs.com',
|
|
54
|
+
limit: params.limit || 100
|
|
55
|
+
},
|
|
56
|
+
input,
|
|
57
|
+
timestamp: new Date().toISOString()
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
function parseLogParams(input) {
|
|
63
|
+
const params = {};
|
|
64
|
+
const traceMatch = input.match(/(?:trace[_-]?id|调用链)\s*[:=]?\s*(\S+)/i);
|
|
65
|
+
if (traceMatch) params.traceId = traceMatch[1];
|
|
66
|
+
|
|
67
|
+
const serviceMatch = input.match(/(?:服务|service)\s*[:=]?\s*(\S+)/i);
|
|
68
|
+
if (serviceMatch) params.service = serviceMatch[1];
|
|
69
|
+
|
|
70
|
+
const levelMatch = input.match(/(?:级别|level)\s*[:=]?\s*(ERROR|WARN|INFO|DEBUG)/i);
|
|
71
|
+
if (levelMatch) params.level = levelMatch[1].toUpperCase();
|
|
72
|
+
|
|
73
|
+
const timeMatch = input.match(/(?:最近|last)\s*(\d+)\s*(分钟|小时|分钟|min|hour)/i);
|
|
74
|
+
if (timeMatch) params.timeRange = `last ${timeMatch[1]} ${timeMatch[2]}`;
|
|
75
|
+
|
|
76
|
+
const keywordMatch = input.match(/(?:关键词|keyword|搜索|search)\s*[:=]?\s*(.+?)(?:\s*$)/i);
|
|
77
|
+
if (keywordMatch) params.keyword = keywordMatch[1].trim();
|
|
78
|
+
|
|
79
|
+
return params;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function buildSLSQuery(params) {
|
|
83
|
+
const parts = [];
|
|
84
|
+
if (params.service) parts.push(`service: ${params.service}`);
|
|
85
|
+
if (params.level) parts.push(`level: ${params.level}`);
|
|
86
|
+
if (params.keyword) parts.push(`"${params.keyword}"`);
|
|
87
|
+
return parts.length > 0 ? parts.join(' and ') : '*';
|
|
88
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Project Review Skill
|
|
3
|
+
* Analyzes project health, dependencies, and overall quality
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const fs = require('fs-extra');
|
|
7
|
+
const path = require('path');
|
|
8
|
+
const { execSync } = require('child_process');
|
|
9
|
+
|
|
10
|
+
module.exports = {
|
|
11
|
+
canHandle(input, context) {
|
|
12
|
+
if (!input) return false;
|
|
13
|
+
return /项目审查|project review|项目健康|health check|依赖检查|dependency/i.test(input);
|
|
14
|
+
},
|
|
15
|
+
|
|
16
|
+
async execute(input, context) {
|
|
17
|
+
const projectDir = extractProjectDir(input) || process.cwd();
|
|
18
|
+
const checks = [];
|
|
19
|
+
let score = 100;
|
|
20
|
+
|
|
21
|
+
// Check package.json
|
|
22
|
+
const pkgPath = path.join(projectDir, 'package.json');
|
|
23
|
+
if (await fs.pathExists(pkgPath)) {
|
|
24
|
+
const pkg = await fs.readJson(pkgPath);
|
|
25
|
+
|
|
26
|
+
// Check for missing description
|
|
27
|
+
if (!pkg.description) {
|
|
28
|
+
checks.push({ type: 'warning', name: 'Missing description', impact: -5 });
|
|
29
|
+
score -= 5;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Check for missing license
|
|
33
|
+
if (!pkg.license) {
|
|
34
|
+
checks.push({ type: 'warning', name: 'Missing license', impact: -5 });
|
|
35
|
+
score -= 5;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Check for missing test script
|
|
39
|
+
if (!pkg.scripts?.test || pkg.scripts.test === 'echo "Error: no test specified"') {
|
|
40
|
+
checks.push({ type: 'warning', name: 'No test script configured', impact: -10 });
|
|
41
|
+
score -= 10;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Check for missing lint script
|
|
45
|
+
if (!pkg.scripts?.lint) {
|
|
46
|
+
checks.push({ type: 'info', name: 'No lint script configured', impact: -5 });
|
|
47
|
+
score -= 5;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Check dependency count
|
|
51
|
+
const deps = Object.keys(pkg.dependencies || {});
|
|
52
|
+
const devDeps = Object.keys(pkg.devDependencies || {});
|
|
53
|
+
checks.push({ type: 'info', name: `Dependencies: ${deps.length} prod, ${devDeps.length} dev` });
|
|
54
|
+
|
|
55
|
+
// Check for outdated (if npm is available)
|
|
56
|
+
try {
|
|
57
|
+
const outdated = execSync('npm outdated --json 2>/dev/null || echo "{}"', {
|
|
58
|
+
encoding: 'utf-8', cwd: projectDir, timeout: 30000
|
|
59
|
+
});
|
|
60
|
+
const outdatedPkgs = JSON.parse(outdated);
|
|
61
|
+
const count = Object.keys(outdatedPkgs).length;
|
|
62
|
+
if (count > 0) {
|
|
63
|
+
checks.push({ type: 'warning', name: `${count} outdated package(s)`, impact: -5 });
|
|
64
|
+
score -= 5;
|
|
65
|
+
}
|
|
66
|
+
} catch {}
|
|
67
|
+
} else {
|
|
68
|
+
checks.push({ type: 'error', name: 'No package.json found', impact: -20 });
|
|
69
|
+
score -= 20;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Check for README
|
|
73
|
+
if (!(await fs.pathExists(path.join(projectDir, 'README.md')))) {
|
|
74
|
+
checks.push({ type: 'warning', name: 'No README.md', impact: -10 });
|
|
75
|
+
score -= 10;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Check for .git
|
|
79
|
+
if (!(await fs.pathExists(path.join(projectDir, '.git')))) {
|
|
80
|
+
checks.push({ type: 'info', name: 'Not a git repository' });
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Check for .gitignore
|
|
84
|
+
if (!(await fs.pathExists(path.join(projectDir, '.gitignore')))) {
|
|
85
|
+
checks.push({ type: 'warning', name: 'No .gitignore', impact: -5 });
|
|
86
|
+
score -= 5;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
score = Math.max(0, score);
|
|
90
|
+
|
|
91
|
+
return {
|
|
92
|
+
type: 'result',
|
|
93
|
+
skill: 'project-review',
|
|
94
|
+
message: `Project health score: ${score}/100 (${checks.filter(c => c.type === 'warning' || c.type === 'error').length} issues)`,
|
|
95
|
+
data: { score, checks, summary: { score, issues: checks.filter(c => c.type !== 'info').length } },
|
|
96
|
+
input,
|
|
97
|
+
timestamp: new Date().toISOString()
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
function extractProjectDir(input) {
|
|
103
|
+
const match = input.match(/(?:项目|project|目录|dir)\s+(.+?)(?:\s|$)/i);
|
|
104
|
+
return match ? match[1].trim() : null;
|
|
105
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Requirement Analyst Skill
|
|
3
|
+
* Six-dimensional analysis of requirements, design, and code alignment
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
module.exports = {
|
|
7
|
+
canHandle(input, context) {
|
|
8
|
+
if (!input) return false;
|
|
9
|
+
return /需求分析|requirement.*analy|六维分析|需求.*审查|分析需求/i.test(input);
|
|
10
|
+
},
|
|
11
|
+
|
|
12
|
+
async execute(input, context) {
|
|
13
|
+
const dimensions = [
|
|
14
|
+
{ name: '历史设计原则', weight: 0.15, description: '评估历史设计意图、依据、当前有效性' },
|
|
15
|
+
{ name: '迭代合理性', weight: 0.15, description: '评估迭代驱动力、演进路径、迭代质量' },
|
|
16
|
+
{ name: '可扩展性', weight: 0.20, description: '评估开发扩展性、需求升级扩展性、用户增长扩展性' },
|
|
17
|
+
{ name: '市场路线图', weight: 0.15, description: '评估功能覆盖度、需求趋势、竞争定位' },
|
|
18
|
+
{ name: '需求代码偏差', weight: 0.20, description: '评估功能缺失、过度实现、理解偏差' },
|
|
19
|
+
{ name: '升级脆弱性', weight: 0.15, description: '评估数据模型脆弱性、API不兼容、硬编码陷阱' }
|
|
20
|
+
];
|
|
21
|
+
|
|
22
|
+
const requirementText = extractRequirement(input);
|
|
23
|
+
|
|
24
|
+
if (!requirementText) {
|
|
25
|
+
return {
|
|
26
|
+
type: 'result',
|
|
27
|
+
skill: 'requirement-analyst',
|
|
28
|
+
message: 'Please provide requirement text or a requirement document path for analysis',
|
|
29
|
+
data: { dimensions: dimensions.map(d => ({ ...d, score: null })) },
|
|
30
|
+
input,
|
|
31
|
+
timestamp: new Date().toISOString()
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Basic analysis based on text content
|
|
36
|
+
const analysis = dimensions.map(dim => {
|
|
37
|
+
const keywords = getDimensionKeywords(dim.name);
|
|
38
|
+
const matches = keywords.filter(k => requirementText.toLowerCase().includes(k));
|
|
39
|
+
const coverage = Math.min(1, matches.length / Math.max(1, keywords.length * 0.3));
|
|
40
|
+
const score = Math.round(50 + coverage * 50);
|
|
41
|
+
|
|
42
|
+
return {
|
|
43
|
+
name: dim.name,
|
|
44
|
+
weight: dim.weight,
|
|
45
|
+
score,
|
|
46
|
+
description: dim.description,
|
|
47
|
+
findings: matches.length > 0 ? [`Found ${matches.length} relevant keywords`] : ['Insufficient data for analysis']
|
|
48
|
+
};
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
const overallScore = Math.round(
|
|
52
|
+
analysis.reduce((sum, d) => sum + d.score * d.weight, 0)
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
const priorityActions = [];
|
|
56
|
+
analysis.filter(d => d.score < 60).forEach(d => {
|
|
57
|
+
priorityActions.push({ priority: d.score < 40 ? 'P0' : 'P1', dimension: d.name, action: `Improve ${d.name}` });
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
return {
|
|
61
|
+
type: 'result',
|
|
62
|
+
skill: 'requirement-analyst',
|
|
63
|
+
message: `Six-dimensional analysis complete. Overall score: ${overallScore}/100`,
|
|
64
|
+
data: { overallScore, dimensions: analysis, priorityActions },
|
|
65
|
+
input,
|
|
66
|
+
timestamp: new Date().toISOString()
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
function extractRequirement(input) {
|
|
72
|
+
const jsonMatch = input.match(/\{[\s\S]*\}/);
|
|
73
|
+
if (jsonMatch) return jsonMatch[0];
|
|
74
|
+
const textMatch = input.replace(/需求分析|requirement.*analy|分析/gi, '').trim();
|
|
75
|
+
return textMatch.length > 10 ? textMatch : null;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function getDimensionKeywords(dimName) {
|
|
79
|
+
const map = {
|
|
80
|
+
'历史设计原则': ['设计', '架构', '历史', '原理', '依据', 'design', 'architecture'],
|
|
81
|
+
'迭代合理性': ['迭代', '版本', '功能', '需求', 'iteration', 'feature', 'release'],
|
|
82
|
+
'可扩展性': ['扩展', '模块', '接口', '性能', 'scalable', 'extensible', 'modular'],
|
|
83
|
+
'市场路线图': ['市场', '竞争', '路线', '规划', 'market', 'roadmap', 'competitive'],
|
|
84
|
+
'需求代码偏差': ['偏差', '缺失', '过度', '偏差', 'gap', 'deviation', 'mismatch'],
|
|
85
|
+
'升级脆弱性': ['脆弱', '兼容', '硬编码', '升级', 'fragile', 'breaking', 'migration']
|
|
86
|
+
};
|
|
87
|
+
return map[dimName] || [];
|
|
88
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resource Bind Skill
|
|
3
|
+
* Manage database, Redis, API, and other external resource connections
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
module.exports = {
|
|
7
|
+
canHandle(input, context) {
|
|
8
|
+
if (!input) return false;
|
|
9
|
+
return /资源绑定|resource.*bind|数据库.*连接|redis.*连接|连接.*管理|connection.*manage/i.test(input);
|
|
10
|
+
},
|
|
11
|
+
|
|
12
|
+
async execute(input, context) {
|
|
13
|
+
const registry = context.componentRegistry;
|
|
14
|
+
const params = parseResourceParams(input);
|
|
15
|
+
|
|
16
|
+
if (params.action === 'list') {
|
|
17
|
+
const components = registry ? registry.getAll() : [];
|
|
18
|
+
return {
|
|
19
|
+
type: 'result',
|
|
20
|
+
skill: 'resource-bind',
|
|
21
|
+
message: `Found ${components.length} configured component(s)`,
|
|
22
|
+
data: { components: components.map(c => ({ name: c.name, type: c.type, active: c.active })) },
|
|
23
|
+
input,
|
|
24
|
+
timestamp: new Date().toISOString()
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (params.action === 'status') {
|
|
29
|
+
const status = registry ? registry.getStatus() : {};
|
|
30
|
+
return {
|
|
31
|
+
type: 'result',
|
|
32
|
+
skill: 'resource-bind',
|
|
33
|
+
message: 'Resource connection status',
|
|
34
|
+
data: { status },
|
|
35
|
+
input,
|
|
36
|
+
timestamp: new Date().toISOString()
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return {
|
|
41
|
+
type: 'result',
|
|
42
|
+
skill: 'resource-bind',
|
|
43
|
+
message: 'Resource binding. Available actions: list, status',
|
|
44
|
+
data: {
|
|
45
|
+
actions: ['list - List all resources', 'status - Show connection status'],
|
|
46
|
+
supportedTypes: ['MySQL', 'PostgreSQL', 'Redis', 'REST API']
|
|
47
|
+
},
|
|
48
|
+
input,
|
|
49
|
+
timestamp: new Date().toISOString()
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
function parseResourceParams(input) {
|
|
55
|
+
const params = {};
|
|
56
|
+
if (/列表|list|查看|show/i.test(input)) params.action = 'list';
|
|
57
|
+
if (/状态|status|连接/i.test(input)) params.action = 'status';
|
|
58
|
+
if (/绑定|bind|添加|add/i.test(input)) params.action = 'bind';
|
|
59
|
+
return params;
|
|
60
|
+
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SLS Log Audit Skill
|
|
3
|
+
* Query Alibaba Cloud SLS logs, trace ID chain analysis, performance analysis
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
module.exports = {
|
|
7
|
+
canHandle(input, context) {
|
|
8
|
+
if (!input) return false;
|
|
9
|
+
return /sls.*日志|sls.*log|阿里云.*日志|trace.*分析|feign.*链|调用链.*分析/i.test(input);
|
|
10
|
+
},
|
|
11
|
+
|
|
12
|
+
async execute(input, context) {
|
|
13
|
+
const params = parseSLSParams(input);
|
|
14
|
+
|
|
15
|
+
// Determine default endpoint based on environment
|
|
16
|
+
const env = params.env || 'test';
|
|
17
|
+
const endpointMap = {
|
|
18
|
+
test: 'cn-shenzhen.log.aliyuncs.com',
|
|
19
|
+
uat: 'cn-shenzhen.log.aliyuncs.com',
|
|
20
|
+
gray: 'cn-hongkong.log.aliyuncs.com',
|
|
21
|
+
prod: 'cn-hongkong.log.aliyuncs.com'
|
|
22
|
+
};
|
|
23
|
+
const endpoint = endpointMap[env] || endpointMap.test;
|
|
24
|
+
|
|
25
|
+
if (params.traceId) {
|
|
26
|
+
return {
|
|
27
|
+
type: 'result',
|
|
28
|
+
skill: 'sls-log-audit',
|
|
29
|
+
message: `SLS TraceID chain analysis: ${params.traceId}`,
|
|
30
|
+
data: {
|
|
31
|
+
action: 'trace_chain',
|
|
32
|
+
traceId: params.traceId,
|
|
33
|
+
query: `* and "${params.traceId}"`,
|
|
34
|
+
endpoint,
|
|
35
|
+
project: params.project,
|
|
36
|
+
logstore: params.logstore,
|
|
37
|
+
timeRange: params.timeRange || 'last 1 hour'
|
|
38
|
+
},
|
|
39
|
+
input,
|
|
40
|
+
timestamp: new Date().toISOString()
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (params.keyword?.includes('feign') || /feign/i.test(input)) {
|
|
45
|
+
return {
|
|
46
|
+
type: 'result',
|
|
47
|
+
skill: 'sls-log-audit',
|
|
48
|
+
message: 'Feign call chain extraction',
|
|
49
|
+
data: {
|
|
50
|
+
action: 'feign_chain',
|
|
51
|
+
query: buildFeignQuery(params),
|
|
52
|
+
endpoint,
|
|
53
|
+
project: params.project,
|
|
54
|
+
logstore: params.logstore,
|
|
55
|
+
timeRange: params.timeRange || 'last 1 hour'
|
|
56
|
+
},
|
|
57
|
+
input,
|
|
58
|
+
timestamp: new Date().toISOString()
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return {
|
|
63
|
+
type: 'result',
|
|
64
|
+
skill: 'sls-log-audit',
|
|
65
|
+
message: `SLS query: ${params.service || 'all'}, ${params.level || 'all'}, ${params.timeRange || 'last 1 hour'}`,
|
|
66
|
+
data: {
|
|
67
|
+
action: 'query',
|
|
68
|
+
query: buildSLSQuery(params),
|
|
69
|
+
endpoint,
|
|
70
|
+
project: params.project,
|
|
71
|
+
logstore: params.logstore,
|
|
72
|
+
timeRange: params.timeRange || 'last 1 hour',
|
|
73
|
+
limit: params.limit || 100
|
|
74
|
+
},
|
|
75
|
+
input,
|
|
76
|
+
timestamp: new Date().toISOString()
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
function parseSLSParams(input) {
|
|
82
|
+
const params = {};
|
|
83
|
+
const traceMatch = input.match(/trace[_-]?id\s*[:=]?\s*(\S+)/i);
|
|
84
|
+
if (traceMatch) params.traceId = traceMatch[1];
|
|
85
|
+
|
|
86
|
+
const serviceMatch = input.match(/(?:服务|service)\s*[:=]?\s*(\S+)/i);
|
|
87
|
+
if (serviceMatch) params.service = serviceMatch[1];
|
|
88
|
+
|
|
89
|
+
const levelMatch = input.match(/(?:级别|level)\s*[:=]?\s*(ERROR|WARN|INFO|DEBUG)/i);
|
|
90
|
+
if (levelMatch) params.level = levelMatch[1].toUpperCase();
|
|
91
|
+
|
|
92
|
+
const projectMatch = input.match(/(?:项目|project)\s*[:=]?\s*(\S+)/i);
|
|
93
|
+
if (projectMatch) params.project = projectMatch[1];
|
|
94
|
+
|
|
95
|
+
const logstoreMatch = input.match(/(?:logstore|日志库)\s*[:=]?\s*(\S+)/i);
|
|
96
|
+
if (logstoreMatch) params.logstore = logstoreMatch[1];
|
|
97
|
+
|
|
98
|
+
const envMatch = input.match(/(?:环境|env)\s*[:=]?\s*(test|uat|gray|prod)/i);
|
|
99
|
+
if (envMatch) params.env = envMatch[1].toLowerCase();
|
|
100
|
+
|
|
101
|
+
const timeMatch = input.match(/(?:最近|last)\s*(\d+)\s*(分钟|小时|min|hour)/i);
|
|
102
|
+
if (timeMatch) params.timeRange = `last ${timeMatch[1]} ${timeMatch[2]}`;
|
|
103
|
+
|
|
104
|
+
const keywordMatch = input.match(/(?:关键词|keyword|搜索)\s*[:=]?\s*(.+?)(?:\s*$)/i);
|
|
105
|
+
if (keywordMatch) params.keyword = keywordMatch[1].trim();
|
|
106
|
+
|
|
107
|
+
return params;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function buildSLSQuery(params) {
|
|
111
|
+
const parts = [];
|
|
112
|
+
if (params.service) parts.push(`service: ${params.service}`);
|
|
113
|
+
if (params.level) parts.push(`level: ${params.level}`);
|
|
114
|
+
if (params.keyword) parts.push(`"${params.keyword}"`);
|
|
115
|
+
return parts.length > 0 ? parts.join(' and ') : '*';
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function buildFeignQuery(params) {
|
|
119
|
+
return `* and "feign" ${params.service ? `and service: ${params.service}` : ''}`;
|
|
120
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* YApi Sync Interface Skill
|
|
3
|
+
* Sync Controller interfaces to YApi, import/export Swagger
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
module.exports = {
|
|
7
|
+
canHandle(input, context) {
|
|
8
|
+
if (!input) return false;
|
|
9
|
+
return /yapi.*sync|同步.*yapi|yapi.*接口|接口.*yapi|swagger.*import|swagger.*export/i.test(input);
|
|
10
|
+
},
|
|
11
|
+
|
|
12
|
+
async execute(input, context) {
|
|
13
|
+
const apiDoc = context.componentRegistry?.getAdapter('apiDoc');
|
|
14
|
+
|
|
15
|
+
if (!apiDoc) {
|
|
16
|
+
return {
|
|
17
|
+
type: 'result',
|
|
18
|
+
skill: 'yapi-sync-interface',
|
|
19
|
+
message: 'YApi service not configured. Add yapi-mcp component first.',
|
|
20
|
+
data: { hint: 'Configure YApi MCP server in component registry' },
|
|
21
|
+
input,
|
|
22
|
+
timestamp: new Date().toISOString()
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const params = parseYApiParams(input);
|
|
27
|
+
|
|
28
|
+
if (params.action === 'search') {
|
|
29
|
+
return {
|
|
30
|
+
type: 'result',
|
|
31
|
+
skill: 'yapi-sync-interface',
|
|
32
|
+
message: `Searching YApi for: ${params.keyword || 'all'}`,
|
|
33
|
+
data: {
|
|
34
|
+
mcpTool: 'yapi_search_apis',
|
|
35
|
+
args: { projectKeyword: params.project, nameKeyword: params.keyword, limit: 20 }
|
|
36
|
+
},
|
|
37
|
+
input,
|
|
38
|
+
timestamp: new Date().toISOString()
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (params.action === 'list') {
|
|
43
|
+
return {
|
|
44
|
+
type: 'result',
|
|
45
|
+
skill: 'yapi-sync-interface',
|
|
46
|
+
message: `Listing YApi categories for project: ${params.project || 'default'}`,
|
|
47
|
+
data: {
|
|
48
|
+
mcpTool: 'yapi_get_categories',
|
|
49
|
+
args: { projectId: params.project }
|
|
50
|
+
},
|
|
51
|
+
input,
|
|
52
|
+
timestamp: new Date().toISOString()
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (params.action === 'export') {
|
|
57
|
+
return {
|
|
58
|
+
type: 'result',
|
|
59
|
+
skill: 'yapi-sync-interface',
|
|
60
|
+
message: `Exporting YApi project: ${params.project}`,
|
|
61
|
+
data: {
|
|
62
|
+
mcpTool: 'yapi_export_project',
|
|
63
|
+
args: { projectId: params.project, type: params.format || 'swagger' }
|
|
64
|
+
},
|
|
65
|
+
input,
|
|
66
|
+
timestamp: new Date().toISOString()
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return {
|
|
71
|
+
type: 'result',
|
|
72
|
+
skill: 'yapi-sync-interface',
|
|
73
|
+
message: 'YApi sync. Available actions: search, list, export, import',
|
|
74
|
+
data: {
|
|
75
|
+
actions: ['search - Search interfaces', 'list - List categories', 'export - Export project', 'import - Import Swagger'],
|
|
76
|
+
mcpTools: ['yapi_search_apis', 'yapi_get_categories', 'yapi_save_api', 'yapi_import_swagger', 'yapi_export_project']
|
|
77
|
+
},
|
|
78
|
+
input,
|
|
79
|
+
timestamp: new Date().toISOString()
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
function parseYApiParams(input) {
|
|
85
|
+
const params = {};
|
|
86
|
+
if (/搜索|search|查找|find/i.test(input)) params.action = 'search';
|
|
87
|
+
if (/列表|list|分类|categor/i.test(input)) params.action = 'list';
|
|
88
|
+
if (/导出|export/i.test(input)) params.action = 'export';
|
|
89
|
+
if (/导入|import|swagger/i.test(input)) params.action = 'import';
|
|
90
|
+
|
|
91
|
+
const projectMatch = input.match(/(?:项目|project)\s*[:=]?\s*(\S+)/i);
|
|
92
|
+
if (projectMatch) params.project = projectMatch[1];
|
|
93
|
+
|
|
94
|
+
const keywordMatch = input.match(/(?:关键词|keyword|搜索|名称|name)\s*[:=]?\s*(.+?)(?:\s*$)/i);
|
|
95
|
+
if (keywordMatch) params.keyword = keywordMatch[1].trim();
|
|
96
|
+
|
|
97
|
+
const formatMatch = input.match(/(?:格式|format)\s*[:=]?\s*(json|markdown|swagger)/i);
|
|
98
|
+
if (formatMatch) params.format = formatMatch[1].toLowerCase();
|
|
99
|
+
|
|
100
|
+
return params;
|
|
101
|
+
}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Yuque Sync Design Skill
|
|
3
|
+
* Sync design documents to Yuque knowledge base
|
|
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 /语雀.*同步|yuque.*sync|设计.*文档.*同步|同步.*设计|yuque.*design/i.test(input);
|
|
13
|
+
},
|
|
14
|
+
|
|
15
|
+
async execute(input, context) {
|
|
16
|
+
const knowledgeBase = context.componentRegistry?.getAdapter('knowledgeBase');
|
|
17
|
+
|
|
18
|
+
if (!knowledgeBase) {
|
|
19
|
+
return {
|
|
20
|
+
type: 'result',
|
|
21
|
+
skill: 'yuque-sync-design',
|
|
22
|
+
message: 'Yuque service not configured. Add yuque-mcp component first.',
|
|
23
|
+
data: { hint: 'Configure Yuque MCP server in component registry' },
|
|
24
|
+
input,
|
|
25
|
+
timestamp: new Date().toISOString()
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const params = parseYuqueParams(input);
|
|
30
|
+
|
|
31
|
+
if (params.action === 'search') {
|
|
32
|
+
return {
|
|
33
|
+
type: 'result',
|
|
34
|
+
skill: 'yuque-sync-design',
|
|
35
|
+
message: `Searching Yuque for: ${params.keyword || 'all'}`,
|
|
36
|
+
data: {
|
|
37
|
+
mcpTool: 'search',
|
|
38
|
+
args: { q: params.keyword, type: 'doc' }
|
|
39
|
+
},
|
|
40
|
+
input,
|
|
41
|
+
timestamp: new Date().toISOString()
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (params.action === 'list') {
|
|
46
|
+
return {
|
|
47
|
+
type: 'result',
|
|
48
|
+
skill: 'yuque-sync-design',
|
|
49
|
+
message: 'Listing Yuque repositories',
|
|
50
|
+
data: {
|
|
51
|
+
mcpTool: 'get_user_repos',
|
|
52
|
+
args: {}
|
|
53
|
+
},
|
|
54
|
+
input,
|
|
55
|
+
timestamp: new Date().toISOString()
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (params.action === 'sync') {
|
|
60
|
+
return syncDesignDoc(params, input);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return {
|
|
64
|
+
type: 'result',
|
|
65
|
+
skill: 'yuque-sync-design',
|
|
66
|
+
message: 'Yuque design sync. Available actions: search, list, sync',
|
|
67
|
+
data: {
|
|
68
|
+
actions: ['search - Search documents', 'list - List repos', 'sync - Sync design doc'],
|
|
69
|
+
mcpTools: ['get_user_repos', 'get_repo_docs', 'get_doc', 'create_doc', 'update_doc', 'search']
|
|
70
|
+
},
|
|
71
|
+
input,
|
|
72
|
+
timestamp: new Date().toISOString()
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
function parseYuqueParams(input) {
|
|
78
|
+
const params = {};
|
|
79
|
+
if (/搜索|search|查找/i.test(input)) params.action = 'search';
|
|
80
|
+
if (/列表|list|仓库|repo/i.test(input)) params.action = 'list';
|
|
81
|
+
if (/同步|sync|上传|push/i.test(input)) params.action = 'sync';
|
|
82
|
+
|
|
83
|
+
const pathMatch = input.match(/(?:路径|path|文件|file)\s*[:=]?\s*(\S+)/i);
|
|
84
|
+
if (pathMatch) params.path = pathMatch[1];
|
|
85
|
+
|
|
86
|
+
const repoMatch = input.match(/(?:仓库|repo|namespace)\s*[:=]?\s*(\S+)/i);
|
|
87
|
+
if (repoMatch) params.repo = repoMatch[1];
|
|
88
|
+
|
|
89
|
+
const keywordMatch = input.match(/(?:关键词|keyword|搜索)\s*[:=]?\s*(.+?)(?:\s*$)/i);
|
|
90
|
+
if (keywordMatch) params.keyword = keywordMatch[1].trim();
|
|
91
|
+
|
|
92
|
+
return params;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
async function syncDesignDoc(params, input) {
|
|
96
|
+
const filePath = params.path || 'DESIGN.md';
|
|
97
|
+
|
|
98
|
+
if (!(await fs.pathExists(filePath))) {
|
|
99
|
+
return {
|
|
100
|
+
type: 'error',
|
|
101
|
+
skill: 'yuque-sync-design',
|
|
102
|
+
message: `Design file not found: ${filePath}`,
|
|
103
|
+
input,
|
|
104
|
+
timestamp: new Date().toISOString()
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const content = await fs.readFile(filePath, 'utf-8');
|
|
109
|
+
const title = extractTitle(content) || path.basename(filePath, '.md');
|
|
110
|
+
|
|
111
|
+
return {
|
|
112
|
+
type: 'result',
|
|
113
|
+
skill: 'yuque-sync-design',
|
|
114
|
+
message: `Ready to sync "${title}" to Yuque`,
|
|
115
|
+
data: {
|
|
116
|
+
mcpTool: 'create_doc',
|
|
117
|
+
args: {
|
|
118
|
+
namespace: params.repo,
|
|
119
|
+
slug: title.toLowerCase().replace(/\s+/g, '-'),
|
|
120
|
+
title,
|
|
121
|
+
body: content,
|
|
122
|
+
format: 'markdown'
|
|
123
|
+
}
|
|
124
|
+
},
|
|
125
|
+
input,
|
|
126
|
+
timestamp: new Date().toISOString()
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function extractTitle(content) {
|
|
131
|
+
const match = content.match(/^#\s+(.+)$/m);
|
|
132
|
+
return match ? match[1].trim() : null;
|
|
133
|
+
}
|