diff --git a/packages/opencode/src/altimate/tools/training-import.ts b/packages/opencode/src/altimate/tools/training-import.ts index 194a81d14..720d729c4 100644 --- a/packages/opencode/src/altimate/tools/training-import.ts +++ b/packages/opencode/src/altimate/tools/training-import.ts @@ -223,10 +223,11 @@ function parseMarkdownSections(markdown: string): MarkdownSection[] { } function slugify(text: string): string { - return text + const slug = text .toLowerCase() .replace(/[^a-z0-9\s-]/g, "") .replace(/\s+/g, "-") .replace(/^-+|-+$/g, "") .slice(0, 64) + return slug || `entry-${Date.now()}` } diff --git a/packages/opencode/test/altimate/training-import.test.ts b/packages/opencode/test/altimate/training-import.test.ts index fee9dee18..226803065 100644 --- a/packages/opencode/test/altimate/training-import.test.ts +++ b/packages/opencode/test/altimate/training-import.test.ts @@ -274,6 +274,24 @@ describe("training_import: slugify edge cases", () => { // Slugified name should strip special chars and use hyphens expect(result.output).toContain("cte-best-practices-v20-updated") }) + + test("handles pure non-ASCII headings with fallback", async () => { + setupMocks({ + fileContent: [ + "## \u65E5\u672C\u8A9E\u30B9\u30BF\u30A4\u30EB\u30AC\u30A4\u30C9", + "Use consistent naming.", + ].join("\n"), + }) + + const tool = await TrainingImportTool.init() + const result = await tool.execute( + { file_path: "japanese.md", kind: "standard", scope: "project", dry_run: true, max_entries: 20 }, + ctx, + ) + expect(result.metadata.count).toBe(1) + // Non-ASCII heading should produce a fallback slug, not an empty string + expect(result.output).toContain("entry-") + }) }) describe("training_import: error handling", () => {