feat: RAG, memory, plugins, lessons, file editor, split panes, Markdown rendering, PWA + UI overhaul
All checks were successful
Stable Release / stable (push) Successful in 1m34s
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>
This commit is contained in:
267
internal/skills/improver.go
Normal file
267
internal/skills/improver.go
Normal file
@@ -0,0 +1,267 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user