Compare commits

...
14 Commits
Author SHA1 Message Date
Jeffery (jiantw83)andClaude Sonnet 5.5 3523f3f92f refactor: 清空 repo 所有內容
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-15 02:52:44 +00:00
jiantw83 48de0f4fa6 Merge pull request 'feat: 角色包評估的固定情境與重跑腳本' (#17) from issue-10 into main
Reviewed-on: https://gitea.jsc.idv.tw/jiantw83/jsc-plugins/pulls/17
2026-10-15 02:31:56 +00:00
adminandClaude Sonnet 5.5 98d3f54f45 feat: 角色包評估的固定情境與重跑腳本 (#10)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-15 02:23:56 +00:00
jiantw83 db2735d9c8 Merge pull request 'feat: 亞絲娜與直葉的角色包' (#16) from feat/w5-w6-packs into main
Reviewed-on: https://gitea.jsc.idv.tw/jiantw83/jsc-plugins/pulls/16
2026-10-15 01:28:50 +00:00
Jeffery (jiantw83) cfb97d2125 Merge remote-tracking branch 'origin/main' into feat/w5-w6-packs 2026-10-15 01:11:14 +00:00
Jeffery (jiantw83)andClaude Sonnet 5.5 697ed6a0b9 feat: 亞絲娜與直葉的角色包 (#6, #7)
build_pack.py 產生對白索引(放在使用者資料目錄);render_skill.py 把人格卡渲染成
角色 skill 並檢查精選對白逐字來自索引;插件改成執行期從資料目錄讀索引。

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-14 20:52:40 +00:00
Jeffery (jiantw83) 352ce7dcad merge: 併入 W3、W4 的建置工具 2026-10-14 17:59:28 +00:00
Jeffery (jiantw83)andClaude Sonnet 5.5 ea6b9ca7ef feat: 全量標註的檢查、重標與合併工具 (#5)
verify_batches.py 逐批比對 id;make_relabel.py 挑出需重標的 none;
merge_labels.py 支援多個目錄覆蓋與 confidence 縮寫;worry 加入情緒白名單。

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-14 17:52:50 +00:00
jiantw83 770928aef6 Merge pull request 'feat: 功能標籤分類、對白檢索與注入' (#15) from feat/w7-retrieval into main
Reviewed-on: https://gitea.jsc.idv.tw/jiantw83/jsc-plugins/pulls/15
2026-10-14 17:44:56 +00:00
Jeffery (jiantw83)andClaude Sonnet 5.5 d9e50d196b feat: W7 功能標籤分類、對白檢索與注入 (#8)
用 $.model.complete(Haiku)判斷回應的功能標籤,從角色索引取同標籤對白,
以只增不改的訊息附在使用者訊息之前;失敗一律退回不注入。

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-14 17:44:09 +00:00
jiantw83 f02232a373 Merge pull request 'feat: 標註工具與試跑' (#14) from feat/w4-labeling into main
Reviewed-on: https://gitea.jsc.idv.tw/jiantw83/jsc-plugins/pulls/14
2026-10-14 17:41:03 +00:00
Jeffery (jiantw83)andClaude Sonnet 5.5 c85e00a56e feat: 標註工具與試跑 (#5)
標籤白名單、標註指示、批次切分、合併與檢查、抽查清單,以及 100 句試跑結果。

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-14 17:35:09 +00:00
jiantw83 a4b5c1689a Merge pull request 'feat: wiki 讀取與對白抽取腳本' (#13) from feat/w3-corpus-extract into main
Reviewed-on: https://gitea.jsc.idv.tw/jiantw83/jsc-plugins/pulls/13
2026-10-14 17:31:20 +00:00
Jeffery (jiantw83)andClaude Sonnet 5.5 9cf8196a3e feat: wiki 讀取與對白抽取腳本 (#4)
fetch_wiki.py 用 tea 讀取 wiki;extract_dialogues.py 抽出「」對白與前後文,
並用規則判定明確的說話者。原文與輸出放在 repo 之外。

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-14 17:12:20 +00:00
16 changed files with 0 additions and 403 deletions
-14
View File
@@ -1,14 +0,0 @@
{
"name": "jsc-plugins",
"description": "JSC 動漫助理 Claude Code mod 的 marketplace。",
"owner": {
"name": "JSC"
},
"plugins": [
{
"name": "anime-companion",
"source": "./plugins/anime-companion",
"description": "用輕小說角色的個性與語氣取代 Claude 與你對話,並照常協助你做事;以 anime-companion:{作品代號}-{角色代號} 啟動角色"
}
]
}
-5
View File
@@ -1,5 +0,0 @@
node_modules/
.env
# 輕小說原文與建置中間檔放在 repo 之外,不入版本控制
corpus/
build/
-18
View File
@@ -1,18 +0,0 @@
MIT License
Copyright (c) 2026 Jeffery (jiantw83)
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
associated documentation files (the "Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the
following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial
portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO
EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
USE OR OTHER DEALINGS IN THE SOFTWARE.
-46
View File
@@ -1,46 +0,0 @@
<p align="center"><img src="assets/icon.png" alt="jsc-plugins" width="128"></p>
# jsc-plugins
JSC 動漫助理 Claude Code mod 的 marketplace。
## 描述
用輕小說角色的個性與語氣取代 Claude 與你對話,並照常協助你做事。
- 插件 `anime-companion`:每個角色是一個 skill,以 `anime-companion:{作品代號}-{角色代號}` 啟動,例如 `anime-companion:sao-asuna`、`anime-companion:sao-suguha`。
- 人格卡加對白檢索:依這一輪的溝通功能(安慰、吐槽、報告失敗等)檢索角色的對白當範例,讓語氣遷移到工程情境。
- 強度 0~3 可調;錯誤、風險與需確認的事一律先講清楚,再加角色語氣。
- 角色語氣只套在對你說的話,程式碼、commit 與檔案內容保持中性。
- 輕小說原文與建置中間檔不入此 repo。
狀態:規劃中,規格與工作項目見 [議題](https://gitea.jsc.idv.tw/jiantw83/jsc-plugins/issues)。
## 安裝
先加入 marketplace,再安裝插件:
```bash
claude plugin marketplace add https://gitea.jsc.idv.tw/jiantw83/jsc-plugins.git
claude plugin install anime-companion@jsc-plugins
```
## 更新
```bash
claude plugin marketplace update jsc-plugins
claude plugin update anime-companion@jsc-plugins
```
更新後需重新啟動 Claude Code 才會生效。
## 移除
```bash
claude plugin uninstall anime-companion@jsc-plugins
claude plugin marketplace remove jsc-plugins
```
## 授權
[MIT License](LICENSE)
BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.9 KiB

@@ -1,33 +0,0 @@
{
"name": "anime-companion",
"version": "0.0.1",
"description": "用輕小說角色的個性與語氣取代 Claude 與你對話,並照常協助你做事。以 anime-companion:{作品代號}-{角色代號} 啟動角色",
"license": "MIT",
"skills": "./skills",
"author": {
"name": "JSC"
},
"keywords": ["anime", "light-novel", "persona", "companion"],
"userConfig": {
"defaultCharacter": {
"type": "string",
"title": "預設角色",
"description": "新 session 自動套用的角色;none 表示不自動套用,需用角色 skill 啟動。",
"default": "none",
"options": ["none", "sao-asuna", "sao-suguha"]
},
"intensity": {
"type": "string",
"title": "強度",
"description": "0 不套角色;1 只換稱呼與句尾;2 前後各一個對話氣泡;3 再加動作旁白。",
"default": "2",
"options": ["0", "1", "2", "3"]
},
"address": {
"type": "string",
"title": "稱呼",
"description": "角色怎麼稱呼你。留空則依角色與使用者在原作中的關係(亞絲娜叫「桐人」、直葉叫「哥哥」)。",
"default": ""
}
}
}
-40
View File
@@ -1,40 +0,0 @@
# anime-companion
用輕小說角色的個性與語氣取代 Claude 與你對話,並照常協助你做事。
狀態:骨架(0.0.1)。人格卡與對白檢索尚未建置,進度見 [議題](https://gitea.jsc.idv.tw/jiantw83/jsc-plugins/issues)。
## 安裝
```bash
claude plugin marketplace add https://gitea.jsc.idv.tw/jiantw83/jsc-plugins.git
claude plugin install anime-companion@jsc-plugins
```
## 角色
以 `anime-companion:{作品代號}-{角色代號}` 啟動:
| 指令 | 角色 |
| --- | --- |
| `/anime-companion:sao-asuna` | 亞絲娜(《刀劍神域》) |
| `/anime-companion:sao-suguha` | 直葉(莉法)(《刀劍神域》) |
## 設定
在 `/config` 的 anime-companion 設定,或寫在 settings.json 的 `pluginConfigs`。
| 欄位 | 預設 | 說明 |
| --- | --- | --- |
| `defaultCharacter` | `none` | 新 session 自動套用的角色:`none`、`sao-asuna`、`sao-suguha` |
| `intensity` | `2` | 0 不套角色;1 只換稱呼與句尾;2 前後各一個對話氣泡;3 再加動作旁白 |
| `address` | 空 | 角色怎麼稱呼你;留空則依原作關係(亞絲娜「桐人」、直葉「哥哥」) |
缺失或無效的設定值一律退回預設。
## 開發
```bash
claude plugin validate plugins/anime-companion
claude plugin test plugins/anime-companion
```
@@ -1,11 +0,0 @@
// 角色清單:代號為 {作品代號}-{角色代號},與 skills/ 下的資料夾同名。
export const CHARACTERS = {
'sao-asuna': { name: '亞絲娜', address: '桐人' },
'sao-suguha': { name: '直葉', address: '哥哥' },
} as const
export type CharacterCode = keyof typeof CHARACTERS
export function isCharacterCode(value: unknown): value is CharacterCode {
return typeof value === 'string' && Object.hasOwn(CHARACTERS, value)
}
@@ -1,43 +0,0 @@
import { describe, expect, test } from 'claude-code/testing'
import { addressFor, resolveConfig } from './config'
describe('resolveConfig', () => {
test('空設定用預設值', () => {
expect(resolveConfig({})).toEqual({ character: null, intensity: 2, address: null })
})
test('none 與未知角色都視為未設定', () => {
expect(resolveConfig({ defaultCharacter: 'none' }).character).toBeNull()
expect(resolveConfig({ defaultCharacter: 'sao-nobody' }).character).toBeNull()
})
test('讀取角色、強度與稱呼', () => {
expect(resolveConfig({ defaultCharacter: 'sao-suguha', intensity: '3', address: ' 阿桐 ' })).toEqual({
character: 'sao-suguha',
intensity: 3,
address: '阿桐',
})
})
test('無效強度退回預設', () => {
expect(resolveConfig({ intensity: '9' }).intensity).toBe(2)
expect(resolveConfig({ intensity: 'x' }).intensity).toBe(2)
})
test('強度 0 有效', () => {
expect(resolveConfig({ intensity: '0' }).intensity).toBe(0)
})
})
describe('addressFor', () => {
test('沒有覆寫時依角色與使用者的關係', () => {
const config = resolveConfig({})
expect(addressFor(config, 'sao-asuna')).toBe('桐人')
expect(addressFor(config, 'sao-suguha')).toBe('哥哥')
})
test('覆寫優先', () => {
expect(addressFor(resolveConfig({ address: '主人' }), 'sao-asuna')).toBe('主人')
})
})
-31
View File
@@ -1,31 +0,0 @@
import { CHARACTERS, isCharacterCode, type CharacterCode } from './characters'
export type Intensity = 0 | 1 | 2 | 3
export type Config = {
character: CharacterCode | null
intensity: Intensity
address: string | null
}
export const DEFAULT_INTENSITY: Intensity = 2
function toIntensity(value: unknown): Intensity {
const n = typeof value === 'number' ? value : Number(value)
return n === 0 || n === 1 || n === 2 || n === 3 ? n : DEFAULT_INTENSITY
}
// 缺失或無效的值一律退回預設,不讓設定錯誤擋住載入。
export function resolveConfig(options: Readonly<Record<string, unknown>>): Config {
const address = typeof options.address === 'string' ? options.address.trim() : ''
return {
character: isCharacterCode(options.defaultCharacter) ? options.defaultCharacter : null,
intensity: toIntensity(options.intensity),
address: address === '' ? null : address,
}
}
// 使用者的覆寫優先,否則用角色在原作中對使用者的稱呼。
export function addressFor(config: Config, character: CharacterCode): string {
return config.address ?? CHARACTERS[character].address
}
-1
View File
@@ -1 +0,0 @@
{ "modules": ["./register.tsx"] }
@@ -1,15 +0,0 @@
import type { Register } from 'claude-code'
import { resolveConfig } from './config'
import { buildRules, SECTION_ID } from './rules'
// 依強度與稱呼把規則段落加到系統提示最後;人格卡與檢索見 W5~W7。
export const register: Register = (on, options) => {
const config = resolveConfig(options)
on('prompt.compose', async ($, e, next) => {
const result = await next(e)
const text = buildRules(config)
if (text === null) return result
return { sections: [...result.sections, { id: SECTION_ID, text, scope: 'session' }] }
})
}
@@ -1,71 +0,0 @@
import { describe, expect, test } from 'claude-code/testing'
import { resolveConfig } from './config'
import { buildRules, SECTION_ID } from './rules'
const cfg = (o: Record<string, unknown> = {}) => resolveConfig({ defaultCharacter: 'sao-asuna', ...o })
describe('buildRules', () => {
test('強度 0 與未選角色不注入', () => {
expect(buildRules(cfg({ intensity: '0' }))).toBeNull()
expect(buildRules(resolveConfig({}))).toBeNull()
})
test('強度 1 只換稱呼與句尾,沒有氣泡與旁白', () => {
const t = buildRules(cfg({ intensity: '1' }))!
expect(t).toContain('只換稱呼與句尾')
expect(t).not.toContain('前後各一個氣泡')
expect(t).not.toContain('最前面再加一行動作旁白')
})
test('強度 2 前後各一個氣泡,無旁白', () => {
const t = buildRules(cfg())!
expect(t).toContain('前後各一個氣泡')
expect(t).toContain('最多一兩句')
expect(t).toContain('不加動作旁白')
})
test('強度 3 加動作旁白', () => {
const t = buildRules(cfg({ intensity: '3' }))!
expect(t).toContain('前後各一個氣泡')
expect(t).toContain('最前面再加一行動作旁白')
expect(t).not.toContain('不加動作旁白')
})
test('稱呼依角色,設定可覆寫', () => {
expect(buildRules(cfg())!).toContain('「桐人」')
expect(buildRules(cfg(), 'sao-suguha')!).toContain('「哥哥」')
expect(buildRules(cfg({ address: '阿桐' }))!).toContain('「阿桐」')
})
test('各強度都保留安全規則:先用一句清楚的話,需確認時省略收尾氣泡', () => {
for (const intensity of ['1', '2', '3']) {
const t = buildRules(cfg({ intensity }))!
expect(t).toContain('第一句用中性、清楚的話')
expect(t).toContain('不加收尾的對話氣泡')
}
})
test('角色語氣不進程式碼、commit 與檔案內容', () => {
expect(buildRules(cfg())!).toContain('程式碼、commit 訊息與檔案內容保持中性')
})
})
describe('prompt.compose hook', () => {
const input = { model: 'm', promptModel: 'm', surfaces: [], tools: [], outputStyle: null, traits: [] }
const base = { sections: [{ id: 'intro', text: 'x', scope: 'shared' as const }] }
test('在原有段落後加入規則段落', { options: { defaultCharacter: 'sao-asuna', intensity: '2' } }, async ($, on) => {
on('prompt.compose', ($, e, next) => base)
const r = await $.prompt.compose(input)
expect(r.sections.at(-1)?.id).toBe(SECTION_ID)
expect(r.sections.at(-1)?.scope).toBe('session')
expect(r.sections[0]?.id).toBe('intro')
})
test('強度 0 不加段落', { options: { defaultCharacter: 'sao-asuna', intensity: '0' } }, async ($, on) => {
on('prompt.compose', ($, e, next) => base)
const r = await $.prompt.compose(input)
expect(r.sections.some(s => s.id === SECTION_ID)).toBe(false)
})
})
-49
View File
@@ -1,49 +0,0 @@
import { CHARACTERS, type CharacterCode } from './characters'
import { addressFor, type Config, type Intensity } from './config'
export const SECTION_ID = 'anime-companion:rules'
// 任何強度(含 3)都適用:先把該說的事用一句清楚的話說完,再加角色語氣。
const SAFETY = [
'錯誤、風險與需要使用者確認的事,第一句用中性、清楚的話直接說完(不帶角色語氣、不被旁白蓋過),之後才可加角色語氣。',
'危險操作需要確認時,不加收尾的對話氣泡與角色語氣的收尾,只留清楚的問句。',
]
const SCOPE =
'角色語氣只套在對使用者說的話;程式碼、commit 訊息與檔案內容保持中性,不得出現角色口吻。'
function levelRules(name: string, intensity: 1 | 2 | 3): string[] {
switch (intensity) {
case 1:
return ['只換稱呼與句尾語氣;不加口癖、吐槽、對話氣泡或動作旁白。']
case 2:
return [
`加入口癖與簡短吐槽,每則回覆最多一兩句角色化的話。`,
`角色化的話放進對話氣泡(引用區塊,標示角色名,如「> **${name}**:…」),內容前後各一個氣泡,正文放在兩個氣泡之間。`,
'不加動作旁白,不做常駐的狀態列。',
]
case 3:
return [
'完整演出角色:口癖、吐槽、情緒都可充分表現。',
`角色化的話放進對話氣泡(引用區塊,標示角色名,如「> **${name}**:…」),內容前後各一個氣泡,正文放在兩個氣泡之間。`,
'最前面再加一行動作旁白(斜體,如「*動作描寫*」)。',
'不做常駐的狀態列。',
]
}
}
// 依強度與稱呼組成注入系統提示的規則段落;強度 0 或未選角色時回傳 null(不注入)。
export function buildRules(config: Config, character: CharacterCode | null = config.character): string | null {
if (character === null || config.intensity === 0) return null
const { name } = CHARACTERS[character]
const intensity = config.intensity as Exclude<Intensity, 0>
return [
`你現在以《刀劍神域》的「${name}」的個性與語氣與使用者對話,同時照常協助使用者做事。`,
`稱呼使用者為「${addressFor(config, character)}」。`,
SCOPE,
...levelRules(name, intensity),
...SAFETY,
]
.map(line => `- ${line}`)
.join('\n')
}
@@ -1,13 +0,0 @@
---
name: sao-asuna
description: 啟動《刀劍神域》的亞絲娜作為動漫助理,用她的個性與語氣和使用者對話並協助做事。使用者要求切換成亞絲娜時使用。
---
# 亞絲娜(sao-asuna)
> 骨架:人格卡尚未建置(W5,議題 #6)。人格卡完成前,只套用下列基本規則。
- 稱呼使用者為「桐人」,除非設定檔的「稱呼」另有指定。
- 角色語氣只套在對使用者說的話;程式碼、commit 與檔案內容保持中性。
- 錯誤、風險與需要使用者確認的事,先用一句清楚的話說完,再加角色語氣。
- 啟動時告知使用者角色已切換,並說明人格卡尚未完成。
@@ -1,13 +0,0 @@
---
name: sao-suguha
description: 啟動《刀劍神域》的直葉(莉法)作為動漫助理,用她的個性與語氣和使用者對話並協助做事。使用者要求切換成直葉或莉法時使用。
---
# 直葉(sao-suguha)
> 骨架:人格卡尚未建置(W6,議題 #7)。人格卡完成前,只套用下列基本規則。
- 稱呼使用者為「哥哥」,除非設定檔的「稱呼」另有指定。
- 角色語氣只套在對使用者說的話;程式碼、commit 與檔案內容保持中性。
- 錯誤、風險與需要使用者確認的事,先用一句清楚的話說完,再加角色語氣。
- 啟動時告知使用者角色已切換,並說明人格卡尚未完成。