diff --git a/internal/skillscheck/layout.go b/internal/skillscheck/layout.go index 038cbf9c6..dfe001523 100644 --- a/internal/skillscheck/layout.go +++ b/internal/skillscheck/layout.go @@ -11,6 +11,8 @@ import ( "path/filepath" "sort" "strings" + + "github.com/larksuite/cli/internal/vfs" ) const ( @@ -97,7 +99,7 @@ func installedSkillNamesFromInfos(infos []GlobalSkillInfo) []string { } func listSuiteSubskills(suitePath string) []string { - entries, err := os.ReadDir(filepath.Join(suitePath, "references", "subskills")) + entries, err := vfs.ReadDir(filepath.Join(suitePath, "references", "subskills")) if err != nil { return nil } @@ -213,10 +215,10 @@ func assembleSuiteLayout(layout string, collected []string, keepSharedTopLevel b } subskillsDir := filepath.Join(suiteInfo.Path, "references", "subskills") - if err := os.RemoveAll(subskillsDir); err != nil { + if err := vfs.RemoveAll(subskillsDir); err != nil { return err } - if err := os.MkdirAll(subskillsDir, 0o755); err != nil { + if err := vfs.MkdirAll(subskillsDir, 0o755); err != nil { return err } @@ -241,7 +243,7 @@ func assembleSuiteLayout(layout string, collected []string, keepSharedTopLevel b func renderSuiteRoutes(suitePath string, collected []string) error { skillPath := filepath.Join(suitePath, "SKILL.md") - data, err := os.ReadFile(skillPath) + data, err := vfs.ReadFile(skillPath) if err != nil { return err } @@ -258,7 +260,7 @@ func renderSuiteRoutes(suitePath string, collected []string) error { return fmt.Errorf("%s route placeholder not found", suiteSkillName) } text = strings.Replace(text, suiteRoutesPlaceholder, strings.Join(routes, "\n"), 1) - return os.WriteFile(skillPath, []byte(text), 0o644) + return vfs.WriteFile(skillPath, []byte(text), 0o644) } func normalizeSuiteTemplateText(text string) string { @@ -269,7 +271,7 @@ func normalizeSuiteTemplateText(text string) string { } func skillDescription(path string) string { - data, err := os.ReadFile(path) + data, err := vfs.ReadFile(path) if err != nil { return "" } @@ -313,30 +315,30 @@ func isIndentedYAMLLine(line string) bool { } func moveDir(src, dst string) error { - if err := os.RemoveAll(dst); err != nil { + if err := vfs.RemoveAll(dst); err != nil { return err } - if err := os.MkdirAll(filepath.Dir(dst), 0o755); err != nil { + if err := vfs.MkdirAll(filepath.Dir(dst), 0o755); err != nil { return err } - if err := os.Rename(src, dst); err == nil { + if err := vfs.Rename(src, dst); err == nil { return nil } if err := copyDir(src, dst); err != nil { return err } - return os.RemoveAll(src) + return vfs.RemoveAll(src) } func copyDir(src, dst string) error { - info, err := os.Stat(src) + info, err := vfs.Stat(src) if err != nil { return err } if !info.IsDir() { return fmt.Errorf("%s is not a directory", src) } - if err := os.RemoveAll(dst); err != nil { + if err := vfs.RemoveAll(dst); err != nil { return err } return filepath.WalkDir(src, func(path string, entry os.DirEntry, walkErr error) error { @@ -349,26 +351,28 @@ func copyDir(src, dst string) error { } target := filepath.Join(dst, rel) if entry.IsDir() { - return os.MkdirAll(target, 0o755) + return vfs.MkdirAll(target, 0o755) } return copyFile(path, target) }) } func copyFile(src, dst string) error { - in, err := os.Open(src) + in, err := vfs.Open(src) if err != nil { return err } defer in.Close() - if err := os.MkdirAll(filepath.Dir(dst), 0o755); err != nil { + if err := vfs.MkdirAll(filepath.Dir(dst), 0o755); err != nil { return err } - out, err := os.OpenFile(dst, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0o644) + out, err := vfs.OpenFile(dst, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0o644) if err != nil { return err } - defer out.Close() - _, err = io.Copy(out, in) - return err + if _, err := io.Copy(out, in); err != nil { + _ = out.Close() + return err + } + return out.Close() } diff --git a/internal/skillscheck/sync.go b/internal/skillscheck/sync.go index 3255cdf65..6a3a3a4b0 100644 --- a/internal/skillscheck/sync.go +++ b/internal/skillscheck/sync.go @@ -220,19 +220,7 @@ func PlanSync(input SyncInput) SyncPlan { officialSet := toSet(official) installedOfficial := intersection(input.LocalSkills, officialSet) - - previousOfficial := []string{} - if input.StateReadable && input.PreviousState != nil { - previousOfficial = input.PreviousState.OfficialSkills - } - previousSet := toSet(previousOfficial) - - newAddedOfficial := []string{} - for _, skill := range official { - if !previousSet[skill] { - newAddedOfficial = append(newAddedOfficial, skill) - } - } + newAddedOfficial := newlyOfficialSkills(official, input.PreviousState, input.StateReadable) updateSet := toSet(installedOfficial) for _, skill := range newAddedOfficial { @@ -245,7 +233,7 @@ func PlanSync(input SyncInput) SyncPlan { Version: input.Version, OfficialSkills: official, ToUpdate: toUpdate, - Added: uniqueSorted(newAddedOfficial), + Added: newAddedOfficial, SkippedDeleted: skippedDeleted, } } diff --git a/isolated-skills/lark-suite/SKILL.md b/isolated-skills/lark-suite/SKILL.md index 3b5a9c6fc..99a103fbe 100644 --- a/isolated-skills/lark-suite/SKILL.md +++ b/isolated-skills/lark-suite/SKILL.md @@ -17,9 +17,9 @@ metadata: ## 使用流程 1. 根据用户意图从下方路由表选择一个或多个子能力;即使用户尚未提供链接、ID 或具体工作表,也先选择能力,再由子能力询问缺失信息。 -2. 读取对应子能力说明,优先使用 `references/subskills//SKILL.md`。 -3. 如果目标能力没有出现在本文件中,不代表当前环境不可用;检查是否存在相关的独立 `lark-*` skill。 -4. 如果已选中的子能力说明列出必读、前置或继续阅读的文件,只读取该子能力当前任务所需的前置文件;不要读取无关子能力。 +2. 仅使用本文件列出的路由与对应子能力入口,不要遍历或探测其他技能目录。 +3. 如果目标能力未列出,返回无法路由的明确提示。 +4. 仅读取当前已选子能力明确要求的前置文件。 5. 按目标子能力的说明执行;认证、租户、身份、权限和通用排障优先遵循 `lark-shared`。 `lark-shared` 是共享基础能力,不作为 `--flat-skills` 的可选项。为了保证 suite 内子能力可用,它始终会进入 `lark-suite/references/subskills/lark-shared/SKILL.md`;只有 hybrid 布局存在平铺 skill 时,顶层才会额外保留一份 `lark-shared`。