All checks were successful
Stable Release / stable (push) Successful in 1m34s
Major additions: - RAG pipeline (indexing, chunking, search) with sidebar upload button - Memory system with CRUD API - Plugins and lessons modules - MCP discovery and MCP server - Advanced skills (auto-create, conditional, improver) - Agent browser/image support, delegate, sessions - File editor with CodeMirror in split panes - Markdown rendering via react-markdown + KaTeX + highlight.js - Raw markdown toggle - PWA manifest + service worker - Extension UI redesign with new design tokens and studio-style chat - Pipeline API for chat streaming - Mobile responsive layout 💘 Generated with Crush Assisted-by: GLM-5.1 via Crush <crush@charm.land>
268 lines
7.4 KiB
Go
268 lines
7.4 KiB
Go
package skills
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
type ImprovementSuggestion struct {
|
|
Type string `json:"type"`
|
|
Section string `json:"section"`
|
|
Current string `json:"current"`
|
|
Suggested string `json:"suggested"`
|
|
Reason string `json:"reason"`
|
|
Confidence float64 `json:"confidence"`
|
|
CreatedAt time.Time `json:"created_at"`
|
|
}
|
|
|
|
type ImprovementHistory struct {
|
|
SkillName string `json:"skill_name"`
|
|
Version string `json:"version"`
|
|
Improvements []ImprovementSuggestion `json:"improvements"`
|
|
AppliedAt time.Time `json:"applied_at"`
|
|
Result string `json:"result"`
|
|
}
|
|
|
|
type SkillImprover struct {
|
|
historyDir string
|
|
}
|
|
|
|
func NewSkillImprover() (*SkillImprover, error) {
|
|
dir, err := improvementHistoryDir()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &SkillImprover{historyDir: dir}, nil
|
|
}
|
|
|
|
func (si *SkillImprover) Analyze(skill *Skill, conversationContext string) ([]ImprovementSuggestion, error) {
|
|
var suggestions []ImprovementSuggestion
|
|
|
|
if skill.Content == "" {
|
|
return nil, fmt.Errorf("skill has no content to analyze")
|
|
}
|
|
|
|
suggestions = append(suggestions, si.checkMissingSections(skill)...)
|
|
suggestions = append(suggestions, si.checkErrorHandling(skill)...)
|
|
suggestions = append(suggestions, si.checkStepCompleteness(skill)...)
|
|
suggestions = append(suggestions, si.analyzeContextRelevance(skill, conversationContext)...)
|
|
|
|
return suggestions, nil
|
|
}
|
|
|
|
func (si *SkillImprover) ApplyImprovement(skillName string, suggestion ImprovementSuggestion) error {
|
|
skill, err := Get(skillName)
|
|
if err != nil {
|
|
return fmt.Errorf("get skill: %w", err)
|
|
}
|
|
|
|
switch suggestion.Section {
|
|
case "content":
|
|
skill.Content = applyContentSuggestion(skill.Content, suggestion)
|
|
case "description":
|
|
skill.Description = suggestion.Suggested
|
|
default:
|
|
skill.Content = applyContentSuggestion(skill.Content, suggestion)
|
|
}
|
|
|
|
now := time.Now()
|
|
skill.LastImprovedAt = &now
|
|
skill.ImprovementCount++
|
|
|
|
if err := Update(skill); err != nil {
|
|
return fmt.Errorf("update skill: %w", err)
|
|
}
|
|
|
|
history := ImprovementHistory{
|
|
SkillName: skillName,
|
|
Version: skill.Version,
|
|
Improvements: []ImprovementSuggestion{suggestion},
|
|
AppliedAt: now,
|
|
Result: "applied",
|
|
}
|
|
|
|
return si.saveHistory(&history)
|
|
}
|
|
|
|
func (si *SkillImprover) GetHistory(skillName string) ([]ImprovementHistory, error) {
|
|
if err := os.MkdirAll(si.historyDir, 0755); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
entries, err := os.ReadDir(si.historyDir)
|
|
if err != nil {
|
|
if os.IsNotExist(err) {
|
|
return nil, nil
|
|
}
|
|
return nil, err
|
|
}
|
|
|
|
var histories []ImprovementHistory
|
|
for _, e := range entries {
|
|
if e.IsDir() || !strings.HasSuffix(e.Name(), ".json") {
|
|
continue
|
|
}
|
|
if skillName != "" && !strings.HasPrefix(e.Name(), skillName+"_") {
|
|
continue
|
|
}
|
|
|
|
data, err := os.ReadFile(filepath.Join(si.historyDir, e.Name()))
|
|
if err != nil {
|
|
continue
|
|
}
|
|
var h ImprovementHistory
|
|
if err := json.Unmarshal(data, &h); err != nil {
|
|
continue
|
|
}
|
|
histories = append(histories, h)
|
|
}
|
|
|
|
return histories, nil
|
|
}
|
|
|
|
func (si *SkillImprover) checkMissingSections(skill *Skill) []ImprovementSuggestion {
|
|
var suggestions []ImprovementSuggestion
|
|
content := strings.ToLower(skill.Content)
|
|
|
|
requiredSections := []struct {
|
|
keyword string
|
|
label string
|
|
}{
|
|
{"error handling", "Error Handling"},
|
|
{"steps", "Steps"},
|
|
{"when to", "Activation"},
|
|
}
|
|
|
|
for _, req := range requiredSections {
|
|
if !strings.Contains(content, req.keyword) {
|
|
suggestions = append(suggestions, ImprovementSuggestion{
|
|
Type: "missing_section",
|
|
Section: "content",
|
|
Current: "",
|
|
Suggested: fmt.Sprintf("Add a '%s' section", req.label),
|
|
Reason: fmt.Sprintf("Skill is missing a '%s' section which is important for completeness", req.label),
|
|
Confidence: 0.8,
|
|
CreatedAt: time.Now(),
|
|
})
|
|
}
|
|
}
|
|
|
|
return suggestions
|
|
}
|
|
|
|
func (si *SkillImprover) checkErrorHandling(skill *Skill) []ImprovementSuggestion {
|
|
var suggestions []ImprovementSuggestion
|
|
content := strings.ToLower(skill.Content)
|
|
|
|
if !strings.Contains(content, "error") && !strings.Contains(content, "fail") {
|
|
suggestions = append(suggestions, ImprovementSuggestion{
|
|
Type: "missing_error_handling",
|
|
Section: "content",
|
|
Current: "",
|
|
Suggested: "Add error handling guidance covering common failure modes",
|
|
Reason: "Skill lacks error handling guidance, which may lead to poor user experience when things go wrong",
|
|
Confidence: 0.85,
|
|
CreatedAt: time.Now(),
|
|
})
|
|
}
|
|
|
|
return suggestions
|
|
}
|
|
|
|
func (si *SkillImprover) checkStepCompleteness(skill *Skill) []ImprovementSuggestion {
|
|
var suggestions []ImprovementSuggestion
|
|
|
|
lines := strings.Split(skill.Content, "\n")
|
|
stepCount := 0
|
|
for _, line := range lines {
|
|
trimmed := strings.TrimSpace(line)
|
|
if strings.HasPrefix(trimmed, "1.") || strings.HasPrefix(trimmed, "Step 1") {
|
|
stepCount++
|
|
}
|
|
}
|
|
|
|
if stepCount == 0 && len(lines) > 10 {
|
|
suggestions = append(suggestions, ImprovementSuggestion{
|
|
Type: "no_clear_steps",
|
|
Section: "content",
|
|
Current: "",
|
|
Suggested: "Add numbered step-by-step instructions for clarity",
|
|
Reason: "Long skill content without clear step-by-step structure can be hard to follow",
|
|
Confidence: 0.7,
|
|
CreatedAt: time.Now(),
|
|
})
|
|
}
|
|
|
|
return suggestions
|
|
}
|
|
|
|
func (si *SkillImprover) analyzeContextRelevance(skill *Skill, context string) []ImprovementSuggestion {
|
|
if context == "" {
|
|
return nil
|
|
}
|
|
|
|
var suggestions []ImprovementSuggestion
|
|
contextLower := strings.ToLower(context)
|
|
tags := skill.Tags
|
|
relevance := 0
|
|
|
|
for _, tag := range tags {
|
|
if strings.Contains(contextLower, strings.ToLower(tag)) {
|
|
relevance++
|
|
}
|
|
}
|
|
|
|
if len(tags) > 0 && relevance == 0 && skill.Category != "" && !strings.Contains(contextLower, strings.ToLower(skill.Category)) {
|
|
suggestions = append(suggestions, ImprovementSuggestion{
|
|
Type: "tag_relevance",
|
|
Section: "tags",
|
|
Current: strings.Join(tags, ", "),
|
|
Suggested: "Review tags for better context matching",
|
|
Reason: "Current tags do not match recent conversation context, suggesting tags may need updating",
|
|
Confidence: 0.5,
|
|
CreatedAt: time.Now(),
|
|
})
|
|
}
|
|
|
|
return suggestions
|
|
}
|
|
|
|
func applyContentSuggestion(content string, suggestion ImprovementSuggestion) string {
|
|
switch suggestion.Type {
|
|
case "missing_section":
|
|
return content + "\n\n## " + strings.Title(suggestion.Type) + "\n\n" + suggestion.Suggested + ".\n"
|
|
case "missing_error_handling":
|
|
return content + "\n\n## Error Handling\n\n- Handle common failure modes gracefully\n- Provide clear error messages\n- Suggest alternative approaches\n"
|
|
case "no_clear_steps":
|
|
return "## Steps\n\n1. Review the skill context\n2. Apply the appropriate pattern\n3. Verify the result\n\n" + content
|
|
default:
|
|
return content
|
|
}
|
|
}
|
|
|
|
func (si *SkillImprover) saveHistory(history *ImprovementHistory) error {
|
|
if err := os.MkdirAll(si.historyDir, 0755); err != nil {
|
|
return err
|
|
}
|
|
|
|
filename := fmt.Sprintf("%s_%s.json", history.SkillName, history.AppliedAt.Format("20060102-150405"))
|
|
data, err := json.MarshalIndent(history, "", " ")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return os.WriteFile(filepath.Join(si.historyDir, filename), data, 0644)
|
|
}
|
|
|
|
func improvementHistoryDir() (string, error) {
|
|
dir, err := SkillsDir()
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return filepath.Join(filepath.Dir(dir), ".muyue", "improvements"), nil
|
|
}
|