feat: 亞絲娜與直葉的角色包 (#6, #7)

build_pack.py 產生對白索引(放在使用者資料目錄);render_skill.py 把人格卡渲染成
角色 skill 並檢查精選對白逐字來自索引;插件改成執行期從資料目錄讀索引。

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
Jeffery (jiantw83)
2026-10-14 20:52:40 +00:00
co-authored by Claude Sonnet 5.5
parent 352ce7dcad
commit 697ed6a0b9
15 changed files with 587 additions and 24 deletions
@@ -0,0 +1,44 @@
import { describe, expect, test } from 'claude-code/testing'
import { indexPath, loadIndex, parseIndex } from './index-loader'
const line = (o: Record<string, unknown>) => JSON.stringify({ id: 'a', function: 'comfort', emotion: 'worry', mode: null, text: '沒事的喔', ...o })
describe('indexPath', () => {
test('dataDir 留空用家目錄下的預設位置', () => {
expect(indexPath('', '/home/u', 'sao-asuna')).toBe('/home/u/.claude/anime-companion/sao-asuna/index.jsonl')
})
test('dataDir 優先,去掉結尾斜線', () => {
expect(indexPath('/data/x/', '/home/u', 'sao-suguha')).toBe('/data/x/sao-suguha/index.jsonl')
})
test('沒有家目錄也沒有 dataDir 回傳 null', () => {
expect(indexPath(' ', undefined, 'sao-asuna')).toBeNull()
})
})
describe('parseIndex', () => {
test('讀取合法的行', () => {
expect(parseIndex([line({}), line({ id: 'b', mode: 'leafa' })].join('\n'))).toEqual([
{ id: 'a', function: 'comfort', emotion: 'worry', mode: null, text: '沒事的喔' },
{ id: 'b', function: 'comfort', emotion: 'worry', mode: 'leafa', text: '沒事的喔' },
])
})
test('略過壞行、空行、白名單外的功能、過長或空的句子', () => {
const text = ['not json', '', line({ function: 'flirt' }), line({ text: '' }), line({ text: '字'.repeat(81) }), line({ id: 'ok' })].join('\n')
expect(parseIndex(text).map(e => e.id)).toEqual(['ok'])
})
})
describe('loadIndex', () => {
test('讀取失敗或沒有路徑回傳空陣列', async () => {
expect(await loadIndex(async () => { throw new Error('ENOENT') }, '/x')).toEqual([])
expect(await loadIndex(async () => line({}), null)).toEqual([])
})
test('成功讀取', async () => {
expect(await loadIndex(async () => line({}), '/x')).toHaveLength(1)
})
})