import { readFileSync, readdirSync, statSync } from 'node:fs' import { join } from 'node:path' import { describe, expect, it } from 'vitest' const skill = readFileSync(join(process.cwd(), 'docs', 'skill.md'), 'utf8') function routes(): string[] { const found: string[] = [] function walk(dir: string, url: string) { for (const name of readdirSync(dir)) { const path = join(dir, name) if (statSync(path).isDirectory()) { walk(path, `${url}/${name}`) continue } if (name === 'route.ts') { found.push(url) } } } walk(join(process.cwd(), 'src', 'app', 'api', 'v1'), '/api/v1') return found } describe('Skill für Claude', () => { it('trägt Frontmatter mit Namen und Beschreibung', () => { const head = skill.split('---')[1] ?? '' expect(head).toMatch(/\nname: logbuch\n/u) expect(head).toMatch(/\ndescription: .{40,}/u) }) it('nennt nur Endpunkte, die es gibt', () => { const known = new Set(routes().map(url => url.replace(/\/\[[^\]]+\]/gu, ''))) const mentioned = [...skill.matchAll(/`?(?:GET|POST|PUT|PATCH|DELETE) (\/api\/v1[a-z0-9./-]*)/giu)] .map(match => match[1]!.replace(/\/(?::[a-z]+|<[a-z-]+>)/giu, '').replace(/\/$/u, '')) expect(mentioned.length).toBeGreaterThan(5) for (const url of new Set(mentioned)) { expect(known.has(url), `${url} steht im Skill, aber nicht im Code`).toBe(true) } }) it('hält sich an die Hausregeln', () => { expect(skill).not.toMatch(/[—–]/u) expect(skill).not.toMatch(/\p{Extended_Pictographic}/u) expect(skill).not.toMatch(/\b(fuer|ueber|koennen|muessen|loeschen|aendern|groesser|gehoert|laesst|veroeffentlich\w*)\b/iu) }) it('sagt, dass ein Zugang nicht veröffentlichen kann', () => { expect(skill).toMatch(/niemals veröffentlichen/u) }) })