Unified Agent + reusable Go agent core.
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

fix: format skill status output

Lyric df3c3094 fc8e8179

+117 -63
+12 -2
cmd/mistermorph/consolecmd/local_runtime_test.go
··· 5 5 "encoding/json" 6 6 "errors" 7 7 "fmt" 8 + "os" 8 9 "path/filepath" 9 10 "strings" 10 11 "testing" ··· 737 738 viper.Set("file_state_dir", prevStateDir) 738 739 viper.Set("skills.dir_name", prevSkillsDirName) 739 740 }() 740 - viper.Set("file_state_dir", t.TempDir()) 741 + stateDir := t.TempDir() 742 + viper.Set("file_state_dir", stateDir) 741 743 viper.Set("skills.dir_name", "skills") 744 + skillDir := filepath.Join(stateDir, "skills", "alpha") 745 + if err := os.MkdirAll(skillDir, 0o755); err != nil { 746 + t.Fatalf("mkdir skill dir: %v", err) 747 + } 748 + skillMD := "---\nname: alpha\ndescription: Alpha skill.\n---\n# Alpha\n" 749 + if err := os.WriteFile(filepath.Join(skillDir, "SKILL.md"), []byte(skillMD), 0o644); err != nil { 750 + t.Fatalf("write SKILL.md: %v", err) 751 + } 742 752 743 753 store, err := daemonruntime.NewConsoleFileStore(daemonruntime.ConsoleFileStoreOptions{ 744 754 HeartbeatTopicID: "_heartbeat", ··· 771 781 result, _ := task.Result.(map[string]any) 772 782 final, _ := result["final"].(map[string]any) 773 783 output := strings.TrimSpace(fmt.Sprint(final["output"])) 774 - for _, want := range []string{"Skills: enabled", "Loaded: none", "No skills discovered."} { 784 + for _, want := range []string{"**Loaded Skills (1)**", "* `alpha`:", "Alpha skill."} { 775 785 if !strings.Contains(output, want) { 776 786 t.Fatalf("final.output missing %q: %q", want, output) 777 787 }
+99 -55
internal/skillsutil/status.go
··· 1 1 package skillsutil 2 2 3 3 import ( 4 - "fmt" 5 4 "strings" 5 + "text/template" 6 6 7 7 "github.com/quailyquaily/mistermorph/skills" 8 8 ) ··· 10 10 type SkillStatus struct { 11 11 Enabled bool 12 12 Loaded []SkillStatusItem 13 - NotLoaded []SkillStatusItem 14 - Missing []string 13 + Available []SkillStatusItem 15 14 } 16 15 17 16 type SkillStatusItem struct { 18 - ID string 19 - Name string 17 + ID string 18 + Name string 19 + Description string 20 + } 21 + 22 + type skillStatusTemplateData struct { 23 + Sections []skillStatusTemplateSection 24 + } 25 + 26 + type skillStatusTemplateSection struct { 27 + Title string 28 + Count int 29 + Items []skillStatusTemplateItem 30 + } 31 + 32 + type skillStatusTemplateItem struct { 33 + Name string 34 + Description string 20 35 } 21 36 37 + const skillStatusEnabledTemplateText = `{{range .Sections}}**{{.Title}} ({{.Count}})** 38 + 39 + {{range .Items}}- ` + "`{{.Name}}`\n" + `: {{.Description}} 40 + {{end}} 41 + {{end}}` 42 + 43 + const skillStatusDisabledTemplateText = `> Skill is disabled` 44 + 45 + var ( 46 + skillStatusEnabledTemplate = template.Must(template.New("skill-status-enabled").Parse(skillStatusEnabledTemplateText)) 47 + skillStatusDisabledTemplate = template.Must(template.New("skill-status-disabled").Parse(skillStatusDisabledTemplateText)) 48 + ) 49 + 22 50 func BuildSkillStatus(cfg SkillsConfig, currentLoaded []string) (SkillStatus, error) { 23 51 status := SkillStatus{Enabled: cfg.Enabled} 52 + if !cfg.Enabled { 53 + return status, nil 54 + } 24 55 discovered, err := skills.Discover(skills.DiscoverOptions{Roots: cfg.Roots}) 25 56 if err != nil { 26 57 return status, err 27 58 } 59 + for i, sk := range discovered { 60 + sk, err := skills.LoadFrontmatter(sk, 64*1024) 61 + if err != nil { 62 + return status, err 63 + } 64 + discovered[i] = sk 65 + } 28 66 29 67 loadedIDs := map[string]bool{} 30 - if cfg.Enabled { 31 - requested := append([]string{}, cfg.Requested...) 32 - requested = append(requested, currentLoaded...) 33 - finalReq, loadAll := normalizeSkillStatusRequests(requested) 34 - if len(finalReq) == 0 { 35 - loadAll = true 68 + requested := append([]string{}, cfg.Requested...) 69 + requested = append(requested, currentLoaded...) 70 + finalReq, loadAll := normalizeSkillStatusRequests(requested) 71 + if len(finalReq) == 0 { 72 + loadAll = true 73 + } 74 + if loadAll { 75 + for _, sk := range discovered { 76 + loadedIDs[strings.ToLower(strings.TrimSpace(sk.ID))] = true 36 77 } 37 - if loadAll { 38 - for _, sk := range discovered { 39 - loadedIDs[strings.ToLower(strings.TrimSpace(sk.ID))] = true 40 - } 41 - } else { 42 - for _, query := range finalReq { 43 - sk, err := skills.Resolve(discovered, query) 44 - if err != nil { 45 - status.Missing = append(status.Missing, query) 46 - continue 47 - } 48 - loadedIDs[strings.ToLower(strings.TrimSpace(sk.ID))] = true 78 + } else { 79 + for _, query := range finalReq { 80 + sk, err := skills.Resolve(discovered, query) 81 + if err != nil { 82 + continue 49 83 } 84 + loadedIDs[strings.ToLower(strings.TrimSpace(sk.ID))] = true 50 85 } 51 86 } 52 87 53 88 for _, sk := range discovered { 89 + name := strings.TrimSpace(sk.Name) 90 + if name == "" { 91 + name = strings.TrimSpace(sk.ID) 92 + } 54 93 item := SkillStatusItem{ 55 - ID: strings.TrimSpace(sk.ID), 56 - Name: strings.TrimSpace(sk.Name), 94 + ID: strings.TrimSpace(sk.ID), 95 + Name: name, 96 + Description: strings.TrimSpace(sk.Description), 57 97 } 58 98 key := strings.ToLower(item.ID) 59 99 if loadedIDs[key] { 60 100 status.Loaded = append(status.Loaded, item) 61 101 } else { 62 - status.NotLoaded = append(status.NotLoaded, item) 102 + status.Available = append(status.Available, item) 63 103 } 64 104 } 65 105 return status, nil ··· 70 110 if err != nil { 71 111 return "", err 72 112 } 73 - var b strings.Builder 113 + data := skillStatusTemplateData{ 114 + Sections: skillStatusSections(status), 115 + } 116 + tmpl := skillStatusDisabledTemplate 74 117 if status.Enabled { 75 - b.WriteString("Skills: enabled") 76 - } else { 77 - b.WriteString("Skills: disabled") 118 + tmpl = skillStatusEnabledTemplate 78 119 } 79 - writeSkillStatusItems(&b, "Loaded", status.Loaded) 80 - writeSkillStatusItems(&b, "Not loaded", status.NotLoaded) 81 - if len(status.Missing) > 0 { 82 - b.WriteString("\nMissing requested:") 83 - for _, name := range status.Missing { 84 - b.WriteString("\n- ") 85 - b.WriteString(name) 86 - } 120 + var b strings.Builder 121 + if err := tmpl.Execute(&b, data); err != nil { 122 + return "", err 87 123 } 88 - if len(status.Loaded) == 0 && len(status.NotLoaded) == 0 { 89 - b.WriteString("\nNo skills discovered.") 90 - } 91 - return b.String(), nil 124 + return strings.TrimRight(b.String(), "\n"), nil 92 125 } 93 126 94 127 func normalizeSkillStatusRequests(requested []string) ([]string, bool) { ··· 113 146 return out, loadAll 114 147 } 115 148 116 - func writeSkillStatusItems(b *strings.Builder, title string, items []SkillStatusItem) { 117 - if b == nil { 118 - return 149 + func skillStatusSections(status SkillStatus) []skillStatusTemplateSection { 150 + var sections []skillStatusTemplateSection 151 + if section, ok := skillStatusSection("Loaded Skills", status.Loaded); ok { 152 + sections = append(sections, section) 119 153 } 120 - if len(items) == 0 { 121 - fmt.Fprintf(b, "\n%s: none", title) 122 - return 154 + if section, ok := skillStatusSection("Available Skills", status.Available); ok { 155 + sections = append(sections, section) 123 156 } 124 - fmt.Fprintf(b, "\n%s (%d):", title, len(items)) 157 + return sections 158 + } 159 + 160 + func skillStatusSection(title string, items []SkillStatusItem) (skillStatusTemplateSection, bool) { 161 + out := make([]skillStatusTemplateItem, 0, len(items)) 125 162 for _, item := range items { 126 - label := strings.TrimSpace(item.ID) 127 - if label == "" { 128 - label = strings.TrimSpace(item.Name) 163 + name := strings.TrimSpace(item.Name) 164 + if name == "" { 165 + name = strings.TrimSpace(item.ID) 129 166 } 130 - if label == "" { 167 + if name == "" { 131 168 continue 132 169 } 133 - b.WriteString("\n- ") 134 - b.WriteString(label) 170 + desc := strings.TrimSpace(item.Description) 171 + if desc != "" { 172 + desc = "\n " + strings.ReplaceAll(desc, "\n", "\n ") 173 + } 174 + out = append(out, skillStatusTemplateItem{Name: name, Description: desc}) 175 + } 176 + if len(out) == 0 { 177 + return skillStatusTemplateSection{}, false 135 178 } 179 + return skillStatusTemplateSection{Title: title, Count: len(out), Items: out}, true 136 180 }
+6 -6
internal/skillsutil/status_test.go
··· 5 5 "testing" 6 6 ) 7 7 8 - func TestRenderSkillStatusShowsLoadedAndNotLoaded(t *testing.T) { 8 + func TestRenderSkillStatusShowsLoadedAndAvailable(t *testing.T) { 9 9 root := t.TempDir() 10 10 writeSkill(t, root, "alpha") 11 11 writeSkill(t, root, "beta") ··· 18 18 if err != nil { 19 19 t.Fatalf("RenderSkillStatus() error = %v", err) 20 20 } 21 - for _, want := range []string{"Skills: enabled", "Loaded (1):", "- alpha", "Not loaded (1):", "- beta"} { 21 + for _, want := range []string{"**Loaded Skills (1)**", "* `alpha`:", "**Available Skills (1)**", "* `beta`:"} { 22 22 if !strings.Contains(out, want) { 23 23 t.Fatalf("status missing %q:\n%s", want, out) 24 24 } ··· 37 37 if err != nil { 38 38 t.Fatalf("RenderSkillStatus() error = %v", err) 39 39 } 40 - if !strings.Contains(out, "Loaded (1):") || !strings.Contains(out, "- beta") { 40 + if !strings.Contains(out, "**Loaded Skills (1)**") || !strings.Contains(out, "* `beta`:") { 41 41 t.Fatalf("status missing current loaded skill:\n%s", out) 42 42 } 43 - if !strings.Contains(out, "Not loaded (1):") || !strings.Contains(out, "- alpha") { 44 - t.Fatalf("status missing not loaded skill:\n%s", out) 43 + if !strings.Contains(out, "**Available Skills (1)**") || !strings.Contains(out, "* `alpha`:") { 44 + t.Fatalf("status missing available skill:\n%s", out) 45 45 } 46 46 } 47 47 ··· 56 56 if err != nil { 57 57 t.Fatalf("RenderSkillStatus() error = %v", err) 58 58 } 59 - for _, want := range []string{"Skills: disabled", "Loaded: none", "Not loaded (1):", "- alpha"} { 59 + for _, want := range []string{"> Skill is disabled"} { 60 60 if !strings.Contains(out, want) { 61 61 t.Fatalf("status missing %q:\n%s", want, out) 62 62 }