refactor(skills): add skill-adapter and refactor SkillMarket

- Add skill-adapter.ts to bridge configStore and UI skill formats
- Refactor SkillMarket to use new skill-adapter instead of skill-discovery
- Add health check state to connectionStore
- Update multiple components with improved typing
- Clean up test artifacts and add new test results
- Update README and add skill-market-mvp plan

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
iven
2026-03-21 00:28:03 +08:00
parent 54ccc0a7b0
commit 48a430fc97
50 changed files with 1523 additions and 360 deletions

View File

@@ -0,0 +1,193 @@
/**
* Skill Adapter - Converts between configStore and UI skill formats
*
* Bridges the gap between:
* - configStore.SkillInfo (backend/Gateway format)
* - SkillMarket UI format (based on skill-discovery types)
*
* Part of Phase 1: Skill Market Store Unification
*/
import type { SkillInfo as ConfigSkillInfo } from '../store/configStore';
// === UI Skill Types (aligned with SkillMarket expectations) ===
export interface UISkillInfo {
id: string;
name: string;
description: string;
triggers: string[];
capabilities: string[];
toolDeps: string[];
installed: boolean;
category?: string;
path?: string;
source?: 'builtin' | 'extra';
}
// Category mapping based on skill keywords
const CATEGORY_KEYWORDS: Record<string, string[]> = {
development: ['code', 'git', 'frontend', 'backend', 'react', 'vue', 'api', 'typescript', 'javascript'],
security: ['security', 'audit', 'vulnerability', 'pentest', 'auth'],
analytics: ['data', 'analysis', 'analytics', 'visualization', 'report'],
content: ['writing', 'content', 'article', 'copy', 'chinese'],
ops: ['devops', 'docker', 'k8s', 'deploy', 'ci', 'cd', 'automation'],
management: ['pm', 'project', 'requirement', 'planning', 'prd'],
testing: ['test', 'api test', 'e2e', 'unit'],
business: ['finance', 'budget', 'expense', 'accounting'],
marketing: ['social', 'media', 'marketing', 'campaign', 'operation'],
};
/**
* Infer category from skill name and description
*/
function inferCategory(skill: ConfigSkillInfo): string | undefined {
const text = `${skill.name} ${skill.description || ''}`.toLowerCase();
for (const [category, keywords] of Object.entries(CATEGORY_KEYWORDS)) {
if (keywords.some(keyword => text.includes(keyword))) {
return category;
}
}
return undefined;
}
/**
* Extract trigger patterns from config format
*/
function extractTriggers(triggers?: ConfigSkillInfo['triggers']): string[] {
if (!triggers) return [];
return triggers
.map(t => t.pattern || t.type)
.filter((p): p is string => Boolean(p));
}
/**
* Extract capabilities from actions
*/
function extractCapabilities(actions?: ConfigSkillInfo['actions']): string[] {
if (!actions) return [];
return actions
.map(a => a.type)
.filter((t): t is string => Boolean(t));
}
/**
* Extract tool dependencies from actions params
*/
function extractToolDeps(actions?: ConfigSkillInfo['actions']): string[] {
if (!actions) return [];
const deps = new Set<string>();
for (const action of actions) {
if (action.params?.tools && Array.isArray(action.params.tools)) {
for (const tool of action.params.tools) {
if (typeof tool === 'string') {
deps.add(tool);
}
}
}
if (action.params?.toolDeps && Array.isArray(action.params.toolDeps)) {
for (const dep of action.params.toolDeps) {
if (typeof dep === 'string') {
deps.add(dep);
}
}
}
}
return Array.from(deps);
}
/**
* Adapt a single skill from configStore format to UI format
*/
export function adaptSkillInfo(skill: ConfigSkillInfo): UISkillInfo {
return {
id: skill.id,
name: skill.name,
description: skill.description || '',
triggers: extractTriggers(skill.triggers),
capabilities: extractCapabilities(skill.actions),
toolDeps: extractToolDeps(skill.actions),
installed: skill.enabled ?? false,
category: inferCategory(skill),
path: skill.path,
source: skill.source,
};
}
/**
* Adapt an array of skills from configStore format to UI format
*/
export function adaptSkills(skills: ConfigSkillInfo[]): UISkillInfo[] {
return skills.map(adaptSkillInfo);
}
/**
* Search skills by query string
*/
export function searchSkills(skills: UISkillInfo[], query: string): UISkillInfo[] {
const q = query.toLowerCase().trim();
if (!q) return skills;
const tokens = q.split(/[\s,;.!?.,;!?]+/).filter(t => t.length > 0);
const scored = skills.map(skill => {
let score = 0;
// Name match (highest weight)
if (skill.name.toLowerCase().includes(q)) score += 10;
// Description match
if (skill.description.toLowerCase().includes(q)) score += 5;
// Trigger match
for (const trigger of skill.triggers) {
const tLower = trigger.toLowerCase();
if (tLower === q) { score += 15; break; }
if (tLower.includes(q) || q.includes(tLower)) score += 8;
}
// Capability match
for (const cap of skill.capabilities) {
if (cap.toLowerCase().includes(q)) score += 4;
}
// Token-level matching
for (const token of tokens) {
if (skill.name.toLowerCase().includes(token)) score += 2;
if (skill.description.toLowerCase().includes(token)) score += 1;
for (const trigger of skill.triggers) {
if (trigger.toLowerCase().includes(token)) score += 3;
}
}
// Category match
if (skill.category && skill.category.toLowerCase().includes(q)) score += 3;
return { skill, score };
});
return scored
.filter(s => s.score > 0)
.sort((a, b) => b.score - a.score)
.map(s => s.skill);
}
/**
* Get unique categories from skills
*/
export function getCategories(skills: UISkillInfo[]): string[] {
const categories = new Set<string>();
for (const skill of skills) {
if (skill.category) {
categories.add(skill.category);
}
}
return Array.from(categories);
}