Merge branch 'main' into codex/ci-pr-release-compatibility

This commit is contained in:
chichuan
2026-08-06 21:18:39 +08:00
committed by GitHub
211 changed files with 7181 additions and 5827 deletions
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -323,7 +323,7 @@ def get_reference_file(product: str) -> str:
'calendar': 'calendar.md',
'chat': 'chat.md',
'contact': 'contact.md',
'dev': 'skills/multi/dingtalk-dev/SKILL.md',
'dev': 'skills/multi/dingtalk-misc/references/devapp.md',
'ding': 'ding.md',
'doc': 'doc.md',
'drive': 'drive.md',
+1 -1
View File
@@ -488,7 +488,7 @@ func TestChangelogPRFastPathWorkflowContract(t *testing.T) {
"needs.lint.outputs.platform_sensitive == 'true'",
`COVERAGE_TARGET: "100"`,
`COVERAGE_ENFORCE_OVERALL: "false"`,
`COVERAGE_OVERALL_TOLERANCE: "0"`,
`COVERAGE_OVERALL_TOLERANCE: "0.1"`,
`additional_profile=coverage-shortcut.txt`,
"run: make test-plan",
"run: make format-check",
+21 -14
View File
@@ -219,7 +219,7 @@ func TestInstallEventScriptStaticExpectations(t *testing.T) {
"releases/latest",
"EVENT_VERSION",
"DWS_SKILLS_ONLY",
"dingtalk-event",
"dingtalk-misc",
"user_im_message_receive_o2o",
".config/opencode/skills",
"$HOME/.dws/skills/multi/$EVENT_SKILL_NAME",
@@ -232,6 +232,7 @@ func TestInstallEventScriptStaticExpectations(t *testing.T) {
for _, avoid := range []string{
"releases?per_page=30",
"select(.tag_name",
"dingtalk-event",
"dingtalk-dev",
"client-secret",
"--as app",
@@ -269,9 +270,9 @@ func TestInstallEventScriptInstallsBinaryAndEventSkills(t *testing.T) {
"dws": "fake-event-binary\n",
})
writeZip(t, filepath.Join(releaseDir, "dws-skills.zip"), map[string]string{
"multi/dingtalk-event/SKILL.md": "event skill user_im_message_receive_o2o\n",
"mono/SKILL.md": "mono skill user_im_message_receive_o2o\n",
"SKILL.md": "legacy mono root\n",
"multi/dingtalk-misc/SKILL.md": "event skill user_im_message_receive_o2o\n",
"mono/SKILL.md": "mono skill user_im_message_receive_o2o\n",
"SKILL.md": "legacy mono root\n",
})
writeFakeCurl(t, filepath.Join(stubRoot, "curl"))
@@ -304,7 +305,7 @@ func TestInstallEventScriptInstallsBinaryAndEventSkills(t *testing.T) {
got := string(output)
for _, want := range []string{
"Version: v1.0.51",
"Skill dingtalk-event",
"Skill dingtalk-misc",
"Skill dws",
"dws event consume user_im_message_receive_o2o",
} {
@@ -322,12 +323,12 @@ func TestInstallEventScriptInstallsBinaryAndEventSkills(t *testing.T) {
}
for _, rel := range []string{
".agents/skills/dingtalk-event/SKILL.md",
".codex/skills/dingtalk-event/SKILL.md",
".config/opencode/skills/dingtalk-event/SKILL.md",
".agents/skills/dingtalk-misc/SKILL.md",
".codex/skills/dingtalk-misc/SKILL.md",
".config/opencode/skills/dingtalk-misc/SKILL.md",
".agents/skills/dws/SKILL.md",
".codex/skills/dws/SKILL.md",
".dws/skills/multi/dingtalk-event/SKILL.md",
".dws/skills/multi/dingtalk-misc/SKILL.md",
".dws/skills/mono/SKILL.md",
} {
p := filepath.Join(fakeHome, filepath.FromSlash(rel))
@@ -339,6 +340,9 @@ func TestInstallEventScriptInstallsBinaryAndEventSkills(t *testing.T) {
t.Fatalf("%s does not contain event skill marker: %q", p, string(data))
}
}
if _, err := os.Stat(filepath.Join(fakeHome, ".agents", "skills", "dingtalk-event")); !os.IsNotExist(err) {
t.Fatalf("dingtalk-event should not be installed by install-event.sh, stat err=%v", err)
}
if _, err := os.Stat(filepath.Join(fakeHome, ".agents", "skills", "dingtalk-dev")); !os.IsNotExist(err) {
t.Fatalf("dingtalk-dev should not be installed by install-event.sh, stat err=%v", err)
}
@@ -357,8 +361,8 @@ func TestInstallEventScriptSkillsOnlySkipsBinary(t *testing.T) {
t.Fatalf("MkdirAll(%s) error = %v", releaseDir, err)
}
writeZip(t, filepath.Join(releaseDir, "dws-skills.zip"), map[string]string{
"multi/dingtalk-event/SKILL.md": "event skill user_im_message_receive_o2o\n",
"mono/SKILL.md": "mono skill user_im_message_receive_o2o\n",
"multi/dingtalk-misc/SKILL.md": "event skill user_im_message_receive_o2o\n",
"mono/SKILL.md": "mono skill user_im_message_receive_o2o\n",
})
writeFakeCurl(t, filepath.Join(stubRoot, "curl"))
@@ -383,8 +387,8 @@ func TestInstallEventScriptSkillsOnlySkipsBinary(t *testing.T) {
if _, err := os.Stat(filepath.Join(installDir, "dws")); !os.IsNotExist(err) {
t.Fatalf("DWS_SKILLS_ONLY=1 should not install binary, stat err=%v\noutput:\n%s", err, string(output))
}
if _, err := os.Stat(filepath.Join(fakeHome, ".agents", "skills", "dingtalk-event", "SKILL.md")); err != nil {
t.Fatalf("skills-only should install event skill: %v\noutput:\n%s", err, string(output))
if _, err := os.Stat(filepath.Join(fakeHome, ".agents", "skills", "dingtalk-misc", "SKILL.md")); err != nil {
t.Fatalf("skills-only should install misc skill (hosts event): %v\noutput:\n%s", err, string(output))
}
}
@@ -433,8 +437,11 @@ func TestInstallEventScriptNoSkillsOnlyInstallsBinary(t *testing.T) {
if _, err := os.Stat(filepath.Join(installDir, "dws")); err != nil {
t.Fatalf("DWS_NO_SKILLS=1 should install binary: %v\noutput:\n%s", err, string(output))
}
if _, err := os.Stat(filepath.Join(fakeHome, ".agents", "skills", "dingtalk-misc")); !os.IsNotExist(err) {
t.Fatalf("DWS_NO_SKILLS=1 should not install misc skill, stat err=%v\noutput:\n%s", err, string(output))
}
if _, err := os.Stat(filepath.Join(fakeHome, ".agents", "skills", "dingtalk-event")); !os.IsNotExist(err) {
t.Fatalf("DWS_NO_SKILLS=1 should not install event skill, stat err=%v\noutput:\n%s", err, string(output))
t.Fatalf("DWS_NO_SKILLS=1 should not install retired event skill, stat err=%v\noutput:\n%s", err, string(output))
}
}
+7 -3
View File
@@ -845,7 +845,7 @@ print(json.dumps({
}))
PY
;;
*/actions/runs/42/attempts/1/jobs*)
*/actions/runs/42/attempts/*/jobs*)
python3 - <<'PY'
import json
import os
@@ -891,13 +891,13 @@ for name in required:
print(json.dumps({"jobs": jobs}))
PY
;;
*/actions/runs/42/attempts/1)
*/actions/runs/42|*/actions/runs/42/attempts/1)
python3 - <<'PY'
import json
import os
print(json.dumps({
"id": 42,
"run_attempt": 1,
"run_attempt": int(os.environ.get("RUN_ATTEMPT", "1")),
"repository": {"full_name": "owner/repo"},
"path": ".github/workflows/release.yml",
"event": "workflow_dispatch",
@@ -943,6 +943,10 @@ esac
if output, err := run(); err != nil || !strings.Contains(output, "cloud release run 42") {
t.Fatalf("tag-bound cloud delivery was rejected: err=%v\noutput:\n%s", err, output)
}
if output, err := run("RUN_ATTEMPT=2"); err != nil ||
!strings.Contains(output, "cloud release run 42") {
t.Fatalf("successful rerun of tag-bound cloud delivery was rejected: err=%v\noutput:\n%s", err, output)
}
if output, err := runWithArgs(
[]string{"--channel-repair", "oss", tag, commit},
); err != nil || !strings.Contains(output, "cloud release run 42") {
+2 -2
View File
@@ -83,9 +83,9 @@ var (
// Layer A still validates their Cobra registration. Executing them here can
// trigger local setup, authentication, upgrades, or long-running consumers.
mockUnsupportedRoots = map[string]bool{
"api": true, "auth": true, "cache": true, "catalog": true,
"api": true, "auth": true, "catalog": true,
"completion": true, "config": true, "doctor": true, "event": true,
"mcp": true, "plugin": true, "profile": true, "recovery": true,
"mcp": true, "plugin": true, "profile": true,
"schema": true, "skill": true, "upgrade": true, "version": true,
}
)
+1 -1
View File
@@ -45,7 +45,7 @@ Agent 安装 dws skill 后,仅依据 skill 提供的参考文档,将自然
| `chat` | `references/products/chat.md` | 13 | 31 |
| `contact` | `references/products/contact.md` | 7 | 14 |
| `devdoc` | `references/products/simple.md` | 2 | 7 |
| `dev` | `skills/multi/dingtalk-dev/SKILL.md` | 19 | 25 |
| `dev` | `skills/multi/dingtalk-misc/references/devapp.md` | 19 | 25 |
| `ding` | `references/products/ding.md` | 2 | 5 |
| `event` | `references/products/event.md` | 8 | 11 |
| `report` | `references/products/report.md` | 6 | 26 |
+393
View File
@@ -0,0 +1,393 @@
// Copyright 2026 Alibaba Group
// Licensed under the Apache License, Version 2.0 (the "License");
//
// Mono↔multi skill content QA (G1–G4). Spec: docs/skill-mono-multi-qa.md
// Contract: skills/content-qa/mono-multi-coverage.yaml
package unit_test
import (
"bytes"
"fmt"
"os"
"path/filepath"
"strings"
"testing"
"gopkg.in/yaml.v3"
)
type monoMultiQAContract struct {
SchemaVersion int `yaml:"schema_version"`
Coverage []struct {
Mono string `yaml:"mono"`
MultiSkill string `yaml:"multi_skill"`
MultiRefs []string `yaml:"multi_refs"`
} `yaml:"coverage"`
OmitCoverage []struct {
Mono string `yaml:"mono"`
Disposition string `yaml:"disposition"`
Via string `yaml:"via"`
Reason string `yaml:"reason"`
} `yaml:"omit_coverage"`
GlobalProtocols []struct {
ID string `yaml:"id"`
MultiPath string `yaml:"multi_path"`
} `yaml:"global_protocols"`
OmitGlobal []struct {
ID string `yaml:"id"`
Disposition string `yaml:"disposition"`
Reason string `yaml:"reason"`
ExpectedMulti string `yaml:"expected_multi"`
} `yaml:"omit_global"`
PairedFiles []struct {
Mono string `yaml:"mono"`
Multi string `yaml:"multi"`
Mode string `yaml:"mode"`
} `yaml:"paired_files"`
OrphanScriptsAllowlist []struct {
Path string `yaml:"path"`
Disposition string `yaml:"disposition"`
Reason string `yaml:"reason"`
} `yaml:"orphan_scripts_allowlist"`
SkillsWithoutReferencesAllowlist []string `yaml:"skills_without_references_allowlist"`
}
func loadMonoMultiQAContract(t *testing.T) (string, monoMultiQAContract) {
t.Helper()
root := repoRoot(t)
path := filepath.Join(root, "skills", "content-qa", "mono-multi-coverage.yaml")
data, err := os.ReadFile(path)
if err != nil {
t.Fatalf("read contract: %v", err)
}
var c monoMultiQAContract
if err := yaml.Unmarshal(data, &c); err != nil {
t.Fatalf("parse contract: %v", err)
}
if c.SchemaVersion != 1 {
t.Fatalf("unsupported schema_version %d", c.SchemaVersion)
}
return root, c
}
func TestMonoMultiSkillContentG1Shape(t *testing.T) {
root, c := loadMonoMultiQAContract(t)
multiRoot := filepath.Join(root, "skills", "multi")
entries, err := os.ReadDir(multiRoot)
if err != nil {
t.Fatal(err)
}
foundShared := false
noRefs := map[string]bool{}
for _, name := range c.SkillsWithoutReferencesAllowlist {
noRefs[name] = true
}
for _, e := range entries {
if !e.IsDir() {
t.Errorf("G1: unexpected non-directory %s under skills/multi", e.Name())
continue
}
name := e.Name()
if !strings.HasPrefix(name, "dingtalk-") {
t.Errorf("G1: invalid skill directory name %q (want dingtalk-*)", name)
}
if name == "dingtalk-shared" {
foundShared = true
}
skillMD := filepath.Join(multiRoot, name, "SKILL.md")
if _, err := os.Stat(skillMD); err != nil {
t.Errorf("G1: missing SKILL.md for %s: %v", name, err)
}
refs := filepath.Join(multiRoot, name, "references")
if _, err := os.Stat(refs); err != nil && !noRefs[name] {
t.Errorf("G1: missing references/ for %s (add dir or skills_without_references_allowlist)", name)
}
if noRefs[name] {
if _, err := os.Stat(refs); err == nil {
t.Errorf("G1: %s is on skills_without_references_allowlist but references/ exists; remove allowlist entry", name)
}
}
}
if !foundShared {
t.Error("G1: skills/multi must contain dingtalk-shared")
}
}
func TestMonoMultiSkillContentG2Frontmatter(t *testing.T) {
root, _ := loadMonoMultiQAContract(t)
multiRoot := filepath.Join(root, "skills", "multi")
entries, err := os.ReadDir(multiRoot)
if err != nil {
t.Fatal(err)
}
for _, e := range entries {
if !e.IsDir() {
continue
}
name := e.Name()
path := filepath.Join(multiRoot, name, "SKILL.md")
body, err := os.ReadFile(path)
if err != nil {
t.Errorf("%s: %v", name, err)
continue
}
fm, err := parseSkillFrontmatter(body)
if err != nil {
t.Errorf("G2: %s: %v", name, err)
continue
}
if fm.Name != name {
t.Errorf("G2: %s: frontmatter name %q != directory", name, fm.Name)
}
if strings.TrimSpace(fm.Description) == "" {
t.Errorf("G2: %s: empty description", name)
}
category := fm.Metadata.Category
if category == "" {
t.Errorf("G2: %s: missing metadata.category", name)
} else if category != "product" && category != "shared" {
t.Errorf("G2: %s: metadata.category %q want product|shared", name, category)
}
wantCat := "product"
if name == "dingtalk-shared" {
wantCat = "shared"
}
if category != "" && category != wantCat {
t.Errorf("G2: %s: metadata.category %q want %q", name, category, wantCat)
}
bins := fm.Metadata.Requires.Bins
hasDWS := false
for _, b := range bins {
if b == "dws" {
hasDWS = true
break
}
}
if !hasDWS {
t.Errorf("G2: %s: metadata.requires.bins must include dws", name)
}
}
}
func TestMonoMultiSkillContentG3Coverage(t *testing.T) {
root, c := loadMonoMultiQAContract(t)
products := filepath.Join(root, "skills", "mono", "references", "products")
stems, err := monoProductStems(products)
if err != nil {
t.Fatal(err)
}
covered := map[string]bool{}
for _, row := range c.Coverage {
if row.Mono == "" || row.MultiSkill == "" {
t.Fatalf("G3: coverage row missing mono/multi_skill: %+v", row)
}
if covered[row.Mono] {
t.Errorf("G3: duplicate coverage for mono %q", row.Mono)
}
covered[row.Mono] = true
skillDir := filepath.Join(root, "skills", "multi", row.MultiSkill)
if _, err := os.Stat(filepath.Join(skillDir, "SKILL.md")); err != nil {
t.Errorf("G3: coverage %s → %s missing SKILL.md: %v", row.Mono, row.MultiSkill, err)
}
for _, rel := range row.MultiRefs {
p := filepath.Join(skillDir, filepath.FromSlash(rel))
if _, err := os.Stat(p); err != nil {
t.Errorf("G3: coverage %s → %s missing %s: %v", row.Mono, row.MultiSkill, rel, err)
}
}
}
omitted := map[string]bool{}
for _, row := range c.OmitCoverage {
if row.Mono == "" || strings.TrimSpace(row.Reason) == "" || strings.TrimSpace(row.Disposition) == "" {
t.Fatalf("G3: omit_coverage requires mono, disposition, reason: %+v", row)
}
omitted[row.Mono] = true
}
for stem := range stems {
if covered[stem] || omitted[stem] {
continue
}
t.Errorf("G3: mono product stem %q not in coverage or omit_coverage", stem)
}
for stem := range covered {
if !stems[stem] {
t.Errorf("G3: coverage mono %q has no matching skills/mono/references/products entry", stem)
}
}
for stem := range omitted {
if !stems[stem] {
t.Errorf("G3: omit_coverage mono %q has no matching products entry", stem)
}
}
}
func TestMonoMultiSkillContentG4Drift(t *testing.T) {
root, c := loadMonoMultiQAContract(t)
multiRoot := filepath.Join(root, "skills", "multi")
for _, g := range c.GlobalProtocols {
p := filepath.Join(multiRoot, filepath.FromSlash(g.MultiPath))
if _, err := os.Stat(p); err != nil {
t.Errorf("G4: global protocol %s missing at %s: %v", g.ID, g.MultiPath, err)
}
}
for _, o := range c.OmitGlobal {
if strings.TrimSpace(o.ID) == "" || strings.TrimSpace(o.Disposition) == "" || strings.TrimSpace(o.Reason) == "" {
t.Fatalf("G4: omit_global requires id, disposition, reason: %+v", o)
}
}
for _, pair := range c.PairedFiles {
monoPath := filepath.Join(root, "skills", "mono", filepath.FromSlash(pair.Mono))
multiPath := filepath.Join(multiRoot, filepath.FromSlash(pair.Multi))
monoData, err := os.ReadFile(monoPath)
if err != nil {
t.Errorf("G4: paired mono %s: %v", pair.Mono, err)
continue
}
multiData, err := os.ReadFile(multiPath)
if err != nil {
t.Errorf("G4: paired multi %s: %v", pair.Multi, err)
continue
}
mode := pair.Mode
if mode == "" {
mode = "identical"
}
switch mode {
case "identical":
if !bytes.Equal(monoData, multiData) {
t.Errorf("G4: paired files differ: mono %s vs multi %s", pair.Mono, pair.Multi)
}
default:
t.Errorf("G4: unknown paired mode %q", mode)
}
}
allow := map[string]bool{}
for _, row := range c.OrphanScriptsAllowlist {
if row.Path == "" || row.Disposition == "" || row.Reason == "" {
t.Fatalf("G4: orphan allowlist requires path, disposition, reason: %+v", row)
}
allow[filepath.ToSlash(row.Path)] = true
}
var orphans []string
entries, err := os.ReadDir(multiRoot)
if err != nil {
t.Fatal(err)
}
for _, e := range entries {
if !e.IsDir() {
continue
}
skill := e.Name()
scriptsDir := filepath.Join(multiRoot, skill, "scripts")
if st, err := os.Stat(scriptsDir); err != nil || !st.IsDir() {
continue
}
blob := skillMarkdownBlob(t, filepath.Join(multiRoot, skill))
_ = filepath.WalkDir(scriptsDir, func(path string, d os.DirEntry, walkErr error) error {
if walkErr != nil || d.IsDir() {
return walkErr
}
rel, err := filepath.Rel(multiRoot, path)
if err != nil {
return err
}
relSlash := filepath.ToSlash(rel)
base := filepath.Base(path)
if strings.Contains(blob, base) || strings.Contains(blob, relSlash) {
return nil
}
if allow[relSlash] {
return nil
}
orphans = append(orphans, relSlash)
return nil
})
}
for _, o := range orphans {
t.Errorf("G4: orphan script %s (reference from skill markdown or add orphan_scripts_allowlist)", o)
}
for path := range allow {
full := filepath.Join(multiRoot, filepath.FromSlash(path))
if _, err := os.Stat(full); err != nil {
t.Errorf("G4: orphan allowlist path missing on disk: %s", path)
}
}
}
type skillFrontmatter struct {
Name string `yaml:"name"`
Description string `yaml:"description"`
Metadata struct {
Category string `yaml:"category"`
Requires struct {
Bins []string `yaml:"bins"`
} `yaml:"requires"`
} `yaml:"metadata"`
}
func parseSkillFrontmatter(body []byte) (skillFrontmatter, error) {
var out skillFrontmatter
s := string(body)
if !strings.HasPrefix(s, "---\n") && !strings.HasPrefix(s, "---\r\n") {
return out, fmt.Errorf("missing YAML frontmatter opener")
}
rest := s[3:]
if strings.HasPrefix(rest, "\r\n") {
rest = rest[2:]
} else if strings.HasPrefix(rest, "\n") {
rest = rest[1:]
}
end := strings.Index(rest, "\n---")
if end < 0 {
return out, fmt.Errorf("missing YAML frontmatter closer")
}
block := rest[:end]
if err := yaml.Unmarshal([]byte(block), &out); err != nil {
return out, err
}
return out, nil
}
func monoProductStems(productsDir string) (map[string]bool, error) {
entries, err := os.ReadDir(productsDir)
if err != nil {
return nil, err
}
stems := map[string]bool{}
for _, e := range entries {
name := e.Name()
if e.IsDir() {
stems[name] = true
continue
}
if strings.HasSuffix(name, ".md") {
stems[strings.TrimSuffix(name, ".md")] = true
}
}
return stems, nil
}
func skillMarkdownBlob(t *testing.T, skillDir string) string {
t.Helper()
var b strings.Builder
_ = filepath.WalkDir(skillDir, func(path string, d os.DirEntry, err error) error {
if err != nil || d.IsDir() {
return err
}
if filepath.Ext(path) != ".md" {
return nil
}
data, err := os.ReadFile(path)
if err != nil {
return err
}
b.Write(data)
b.WriteByte('\n')
return nil
})
return b.String()
}
+7 -7
View File
@@ -83,8 +83,8 @@ func TestEventSkillUsesFlatOutputContract(t *testing.T) {
}
root := filepath.Clean(filepath.Join(filepath.Dir(filename), "..", ".."))
paths := []string{
filepath.Join(root, "skills", "multi", "dingtalk-event", "SKILL.md"),
filepath.Join(root, "skills", "multi", "dingtalk-event", "references", "event-im.md"),
filepath.Join(root, "skills", "multi", "dingtalk-misc", "references", "event.md"),
filepath.Join(root, "skills", "multi", "dingtalk-misc", "references", "event-im.md"),
filepath.Join(root, "skills", "mono", "references", "products", "event.md"),
}
for _, path := range paths {
@@ -137,8 +137,8 @@ func TestCrossPlatformCoverageEventSkillPinsSubscriptionRetryOrchestrationContra
}
root := filepath.Clean(filepath.Join(filepath.Dir(filename), "..", ".."))
paths := []string{
filepath.Join(root, "skills", "multi", "dingtalk-event", "SKILL.md"),
filepath.Join(root, "skills", "multi", "dingtalk-event", "references", "event-im.md"),
filepath.Join(root, "skills", "multi", "dingtalk-misc", "references", "event.md"),
filepath.Join(root, "skills", "multi", "dingtalk-misc", "references", "event-im.md"),
filepath.Join(root, "skills", "mono", "references", "products", "event.md"),
filepath.Join(root, "docs", "event-subprocess-contract.md"),
}
@@ -203,8 +203,8 @@ func TestCrossPlatformCoverageEventSkillDocumentsSubscriptionGuardOperations(t *
}
root := filepath.Clean(filepath.Join(filepath.Dir(filename), "..", ".."))
paths := []string{
filepath.Join(root, "skills", "multi", "dingtalk-event", "SKILL.md"),
filepath.Join(root, "skills", "multi", "dingtalk-event", "references", "event-im.md"),
filepath.Join(root, "skills", "multi", "dingtalk-misc", "references", "event.md"),
filepath.Join(root, "skills", "multi", "dingtalk-misc", "references", "event-im.md"),
filepath.Join(root, "skills", "mono", "references", "products", "event.md"),
filepath.Join(root, "docs", "event-subprocess-contract.md"),
}
@@ -263,7 +263,7 @@ func TestEventSkillFrontmatterAdvertisesGroupMemberLifecycle(t *testing.T) {
root := filepath.Clean(filepath.Join(filepath.Dir(filename), "..", ".."))
paths := []string{
filepath.Join(root, "skills", "mono", "SKILL.md"),
filepath.Join(root, "skills", "multi", "dingtalk-event", "SKILL.md"),
filepath.Join(root, "skills", "multi", "dingtalk-misc", "SKILL.md"),
}
for _, path := range paths {
content, err := os.ReadFile(path)