196 lines
5.4 KiB
JavaScript
196 lines
5.4 KiB
JavaScript
|
|
#!/usr/bin/env node
|
|||
|
|
/**
|
|||
|
|
* 技能链推荐引擎 (v5.1)
|
|||
|
|
*
|
|||
|
|
* 基于 composable 关系图 + 工作流模式,
|
|||
|
|
* 推荐技能调用链 (最多 4 步)。
|
|||
|
|
*
|
|||
|
|
* 核心函数:
|
|||
|
|
* buildComposableGraph(index) → 邻接表
|
|||
|
|
* recommendChain(primarySkill, graph, workflowPatterns, maxLen) → 推荐链
|
|||
|
|
*/
|
|||
|
|
|
|||
|
|
const fs = require('fs');
|
|||
|
|
const path = require('path');
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 从 skills-index.json 构建 composable 关系邻接表
|
|||
|
|
* @param {Object} index - skills-index.json
|
|||
|
|
* @returns {Object} { skillName → { enhances, requires, enhancedBy } }
|
|||
|
|
*/
|
|||
|
|
function buildComposableGraph(index) {
|
|||
|
|
const graph = {};
|
|||
|
|
|
|||
|
|
// 初始化所有技能
|
|||
|
|
for (const skill of (index.skills || [])) {
|
|||
|
|
graph[skill.name] = { enhances: [], requires: [], enhancedBy: [] };
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 填充关系
|
|||
|
|
for (const skill of (index.skills || [])) {
|
|||
|
|
const comp = skill.composable || {};
|
|||
|
|
|
|||
|
|
if (comp.enhances) {
|
|||
|
|
for (const target of comp.enhances) {
|
|||
|
|
if (graph[skill.name]) {
|
|||
|
|
graph[skill.name].enhances.push(target);
|
|||
|
|
}
|
|||
|
|
// 反向关系
|
|||
|
|
if (graph[target]) {
|
|||
|
|
graph[target].enhancedBy.push(skill.name);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (comp.requires) {
|
|||
|
|
for (const target of comp.requires) {
|
|||
|
|
if (graph[skill.name]) {
|
|||
|
|
graph[skill.name].requires.push(target);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return graph;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 推荐技能调用链
|
|||
|
|
* @param {string} primarySkill - 主技能
|
|||
|
|
* @param {Object} graph - buildComposableGraph 的结果
|
|||
|
|
* @param {Object} workflowPatterns - minePatterns 的结果 (可选)
|
|||
|
|
* @param {number} maxLen - 最大链长 (默认 4)
|
|||
|
|
* @returns {Array<{ chain: string[], score: number, reason: string }>} 推荐链列表
|
|||
|
|
*/
|
|||
|
|
function recommendChain(primarySkill, graph, workflowPatterns, maxLen = 4) {
|
|||
|
|
const chains = [];
|
|||
|
|
const node = graph[primarySkill];
|
|||
|
|
if (!node) return chains;
|
|||
|
|
|
|||
|
|
// 策略 1: enhances 链 (A enhances B enhances C)
|
|||
|
|
const enhanceChain = [primarySkill];
|
|||
|
|
const visited = new Set([primarySkill]);
|
|||
|
|
let current = primarySkill;
|
|||
|
|
|
|||
|
|
for (let i = 1; i < maxLen; i++) {
|
|||
|
|
const currentNode = graph[current];
|
|||
|
|
if (!currentNode || currentNode.enhances.length === 0) break;
|
|||
|
|
|
|||
|
|
// 选择未访问过的 enhances 目标
|
|||
|
|
const next = currentNode.enhances.find(s => !visited.has(s) && graph[s]);
|
|||
|
|
if (!next) break;
|
|||
|
|
|
|||
|
|
enhanceChain.push(next);
|
|||
|
|
visited.add(next);
|
|||
|
|
current = next;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (enhanceChain.length >= 2) {
|
|||
|
|
chains.push({
|
|||
|
|
chain: enhanceChain,
|
|||
|
|
score: enhanceChain.length * 0.3,
|
|||
|
|
reason: 'composable-enhances',
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 策略 2: requires 链 (先执行依赖, 再执行主技能)
|
|||
|
|
if (node.requires.length > 0) {
|
|||
|
|
const reqChain = [...node.requires.slice(0, maxLen - 1), primarySkill];
|
|||
|
|
chains.push({
|
|||
|
|
chain: reqChain,
|
|||
|
|
score: reqChain.length * 0.25,
|
|||
|
|
reason: 'composable-requires',
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 策略 3: 工作流模式驱动的链
|
|||
|
|
if (workflowPatterns && workflowPatterns.bigrams) {
|
|||
|
|
const wfChain = [primarySkill];
|
|||
|
|
let wfCurrent = primarySkill;
|
|||
|
|
const wfVisited = new Set([primarySkill]);
|
|||
|
|
|
|||
|
|
for (let i = 1; i < maxLen; i++) {
|
|||
|
|
// 找 bigram 中 wfCurrent 的最高频后继
|
|||
|
|
let bestNext = null;
|
|||
|
|
let bestCount = 0;
|
|||
|
|
|
|||
|
|
for (const [key, count] of Object.entries(workflowPatterns.bigrams)) {
|
|||
|
|
const [from, to] = key.split('→');
|
|||
|
|
if (from === wfCurrent && !wfVisited.has(to) && count > bestCount) {
|
|||
|
|
bestNext = to;
|
|||
|
|
bestCount = count;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (!bestNext) break;
|
|||
|
|
wfChain.push(bestNext);
|
|||
|
|
wfVisited.add(bestNext);
|
|||
|
|
wfCurrent = bestNext;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (wfChain.length >= 2) {
|
|||
|
|
chains.push({
|
|||
|
|
chain: wfChain,
|
|||
|
|
score: wfChain.length * 0.2 + 0.1,
|
|||
|
|
reason: 'workflow-pattern',
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 策略 4: enhancedBy → primarySkill → enhances (上下文丰富链)
|
|||
|
|
if (node.enhancedBy.length > 0 && node.enhances.length > 0) {
|
|||
|
|
const contextChain = [
|
|||
|
|
node.enhancedBy[0],
|
|||
|
|
primarySkill,
|
|||
|
|
...node.enhances.slice(0, maxLen - 2),
|
|||
|
|
].slice(0, maxLen);
|
|||
|
|
|
|||
|
|
chains.push({
|
|||
|
|
chain: contextChain,
|
|||
|
|
score: contextChain.length * 0.2,
|
|||
|
|
reason: 'context-bridge',
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 按分数排序并去重
|
|||
|
|
chains.sort((a, b) => b.score - a.score);
|
|||
|
|
return chains;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 模块导出
|
|||
|
|
if (typeof module !== 'undefined') {
|
|||
|
|
module.exports = { buildComposableGraph, recommendChain };
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// CLI 入口
|
|||
|
|
if (require.main === module) {
|
|||
|
|
const detectClaudeRoot = () => require('./paths.config.js').PATHS.root;
|
|||
|
|
|
|||
|
|
const ROOT = detectClaudeRoot();
|
|||
|
|
const indexFile = path.join(ROOT, 'skills-index.json');
|
|||
|
|
|
|||
|
|
if (!fs.existsSync(indexFile)) {
|
|||
|
|
console.error('skills-index.json 不存在');
|
|||
|
|
process.exit(1);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const skill = process.argv[2];
|
|||
|
|
if (!skill) {
|
|||
|
|
console.log('Usage: node skill-chain-recommender.js <skillName>');
|
|||
|
|
process.exit(0);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const index = JSON.parse(fs.readFileSync(indexFile, 'utf8'));
|
|||
|
|
const graph = buildComposableGraph(index);
|
|||
|
|
const chains = recommendChain(skill, graph, null, 4);
|
|||
|
|
|
|||
|
|
console.log(`=== 技能链推荐: ${skill} ===`);
|
|||
|
|
if (chains.length === 0) {
|
|||
|
|
console.log('无推荐链');
|
|||
|
|
} else {
|
|||
|
|
for (const c of chains) {
|
|||
|
|
console.log(` [${c.reason}] ${c.chain.join(' → ')} (score: ${c.score.toFixed(2)})`);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|