From 9d9cec8e2e4edb5f5f2e84192102ae8264411a9e Mon Sep 17 00:00:00 2001 From: ddCat <2695092045@qq.com> Date: Tue, 22 Sep 2026 11:28:32 +0800 Subject: [PATCH] =?UTF-8?q?fix(skills):=20=E6=8A=80=E8=83=BD=E6=96=87?= =?UTF-8?q?=E4=BB=B6=E5=8C=85=20-=20=E4=BF=AE=E5=A4=8D=E6=97=A7=E6=95=B0?= =?UTF-8?q?=E6=8D=AE=E8=BF=81=E7=A7=BB=E5=9B=9E=E5=A1=AB=E5=85=83=E6=95=B0?= =?UTF-8?q?=E6=8D=AE=E8=B6=85=E9=99=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/internal/skills/skill_packages.go | 4 +- .../internal/skills/skill_packages_test.go | 68 +++++++++++++++++++ docs/content/docs/progress/pending-test.mdx | 6 ++ 3 files changed, 77 insertions(+), 1 deletion(-) diff --git a/backend/internal/skills/skill_packages.go b/backend/internal/skills/skill_packages.go index 699444118..cc6487898 100644 --- a/backend/internal/skills/skill_packages.go +++ b/backend/internal/skills/skill_packages.go @@ -118,7 +118,9 @@ func (s *Service) EnsureSkillPackages() error { } for index := range skills { skill := &skills[index] - archive, err := archiveFromMarkdown([]byte(skill.Instruction), skill.Name, skill.Description) + // Legacy fields are only fallbacks for package metadata. Bound them at + // this migration boundary without rewriting source fields or relaxing uploads. + archive, err := archiveFromMarkdown([]byte(skill.Instruction), truncateSkillMetadata(skill.Name, 80), truncateSkillMetadata(skill.Description, 500)) if err != nil { return fmt.Errorf("迁移技能 %s 文件包失败: %w", skill.ID, err) } diff --git a/backend/internal/skills/skill_packages_test.go b/backend/internal/skills/skill_packages_test.go index 159934c39..240fe3e13 100644 --- a/backend/internal/skills/skill_packages_test.go +++ b/backend/internal/skills/skill_packages_test.go @@ -3,6 +3,7 @@ package skills import ( "archive/zip" "bytes" + "encoding/json" "strings" "testing" @@ -27,6 +28,73 @@ func TestArchiveFromMarkdownInfersMetadata(t *testing.T) { } } +func TestReportedBuiltinSkillPackage(t *testing.T) { + var definitions []builtinSkillDefinition + if err := json.Unmarshal(builtinSkillsJSON, &definitions); err != nil { + t.Fatal(err) + } + for _, skill := range definitions { + if skill.SkillID != "16000000000049" { + continue + } + archive, err := archiveFromMarkdown([]byte(skill.Instruction), skill.SkillName, skill.Description) + if err != nil { + t.Fatal(err) + } + if len([]rune(archive.Metadata.Description)) != 500 || string(archive.Files["SKILL.md"]) != skill.Instruction { + t.Fatal("reported skill must have bounded metadata and unchanged instruction") + } + return + } + t.Fatal("reported builtin skill is missing") +} + +func TestEnsureSkillPackagesBoundsLegacyFallbackMetadata(t *testing.T) { + for _, source := range []int{3, skillSourceUser} { + db, err := gorm.Open(sqlite.Open("file:"+kernel.NewID()+"?mode=memory&cache=shared"), &gorm.Config{}) + if err != nil { + t.Fatal(err) + } + sqlDB, err := db.DB() + if err != nil { + t.Fatal(err) + } + t.Cleanup(func() { _ = sqlDB.Close() }) + if err := db.AutoMigrate(&model.Skill{}, &model.SkillVersion{}, &model.SkillFile{}); err != nil { + t.Fatal(err) + } + svc := New(repository.New(db), t.TempDir(), nil) + skill := model.Skill{ID: kernel.NewID(), Name: strings.Repeat("名", 81), Description: strings.Repeat("文", 503), Instruction: "## 旧技能\n", Status: skillStatusEnabled, Source: source} + if err := db.Create(&skill).Error; err != nil { + t.Fatal(err) + } + for range 2 { + if err := svc.EnsureSkillPackages(); err != nil { + t.Fatal(err) + } + } + assertSkillVersionCount(t, db, skill.ID, 1) + var saved model.Skill + if err := db.First(&saved, "id = ?", skill.ID).Error; err != nil { + t.Fatal(err) + } + if saved.Name != skill.Name || saved.Description != skill.Description || saved.Instruction != skill.Instruction { + t.Fatal("migration changed original skill fields") + } + version, err := svc.repo.SkillVersion(saved.CurrentVersionID) + if err != nil { + t.Fatal(err) + } + body, err := svc.readSkillArchiveEntry(version, "SKILL.md") + if err != nil || string(body) != skill.Instruction { + t.Fatalf("original instruction not preserved: %v", err) + } + } + if _, err := archiveFromMarkdown([]byte("## 旧技能\n"), strings.Repeat("名", 81), strings.Repeat("文", 503)); err == nil { + t.Fatal("non-migration archive input must still reject oversized fallback metadata") + } +} + func TestArchiveFromMarkdownTruncatesInferredDescriptionWithinLimit(t *testing.T) { longDescription := bytes.Repeat([]byte("描述内容。"), 140) data := append([]byte("# 风格库四级匹配序\n\n"), longDescription...) diff --git a/docs/content/docs/progress/pending-test.mdx b/docs/content/docs/progress/pending-test.mdx index ec563cc2a..2e7084257 100644 --- a/docs/content/docs/progress/pending-test.mdx +++ b/docs/content/docs/progress/pending-test.mdx @@ -5,6 +5,12 @@ description: 当前项目已实现功能的手工验证清单与未确认边界 # 待测试 +## 技能文件包迁移元数据长度 + +- [x] 启动迁移对旧技能名称、简介的回填元数据按字符数限制长度,省略号计入上限;不改写原始名称、简介和正文,不放宽非迁移入口的校验。 +- [x] 新增回归先复现旧回填路径的长度错误,修复后技能域全量测试通过;覆盖内置与用户旧技能、重复初始化不重复建版本、原始正文和字段保留,以及 `16000000000049` 内置技能包。 +- [ ] 在报错用户的实际部署核对后端镜像与构建提交,并用包含修复的新产物验证启动迁移。本地通过不代表用户环境已修复;本轮未发布镜像、未操作生产数据库。 + ## 模型失败原因在画布卡片展示 - [x] 后端 Provider、错误分类和请求日志专项回归通过;本地模拟上游验证 HTTP 400 与 HTTP 200 业务失败均保留安全原因,覆盖图片响应和通用 JSON 响应。前端 4 项专项测试通过,覆盖具体原因、HTTP 状态提示、审核元数据及 URL 隐藏。