Ship a Logbuch skill and record token use
- docs/skill.md teaches a Claude what Logbuch is for, how an entry sounds and in which order the calls go, served at GET /api/v1/skill and as a download next to the spec and the guide - a test keeps the skill from drifting: frontmatter, only endpoints that exist, house rules - resolveClient stamps last_used_at, the access list said never used even after eleven entries
This commit is contained in:
@@ -0,0 +1,59 @@
|
||||
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)
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user