feat(character): appearance/motive 全栈穿线 + motive/traits 口径重切

打通 appearance/motive 从生成→schema→契约→落库→前端整条穿线
(DB 列早在初始迁移、此前一直 NULL,无新迁移),并原子重切人物
内驱口径。

- @llm: CharacterCard/character-gen.md 加 appearance/motive(给默认值守
  解析韧性),重切——动机唯一落 motive、traits 只留核心/表层/阴影三层、
  arc 引用 motive;regen prompt_hashes.json 金标准。
- @backend: CharacterCardView + CharacterWriteRepo/SqlCharacterWriteRepo
  (create + update backfill 分支)+ CharacterWriteFields + 路由双向形变
  与 _existing_characters 带上两字段。
- @frontend: pnpm gen:api 重生成 TS 客户端;CharacterCardItem 展示/编辑
  两字段。
- 测试: schema 默认值/解析、动机不双写重切回归、card_to_view 双向、
  ingest 透传、list 暴露、SqlCharacterWriteRepo create/backfill 真 pg 集成。
This commit is contained in:
Yaojia Wang
2026-07-06 14:36:11 +02:00
parent 6854dac98f
commit acde715515
13 changed files with 297 additions and 15 deletions

View File

@@ -12,7 +12,7 @@ JSONB **dict**`traits`/`arc`/`speech_tics``tags`/`relations` 才是 JSO
- `speech_tics: list[str]` → `{"items": [...]}`JSONB dict
- `arc: str` → `{"text": "..."}`JSONB dict
- `tags: list` / `relations: list[dict]` → 直落 JSONB list
- `name` / `role` / `backstory` → Text 列直落
- `name` / `role` / `appearance` / `motive` / `backstory` → Text 列直落
**提交边界**`create` 只 `flush()` 不 `commit()`——提交交端点事务(入库端点末尾一次
`commit()`,与网关 ledger 一并落库;与项目其它写侧 repo 一致,见 memory/gotchas
@@ -66,6 +66,8 @@ class CharacterWriteRepo(Protocol):
name: str,
role: str,
traits: list[str],
appearance: str,
motive: str,
backstory: str,
arc: str,
speech_tics: list[str],
@@ -91,6 +93,8 @@ class SqlCharacterWriteRepo:
name: str,
role: str,
traits: list[str],
appearance: str,
motive: str,
backstory: str,
arc: str,
speech_tics: list[str],
@@ -105,6 +109,8 @@ class SqlCharacterWriteRepo:
if existing is not None:
existing.role = role
existing.traits = _traits_to_jsonb(traits)
existing.appearance = appearance
existing.motive = motive
existing.backstory = backstory
existing.arc = _arc_to_jsonb(arc)
existing.speech_tics = _traits_to_jsonb(speech_tics)
@@ -118,6 +124,8 @@ class SqlCharacterWriteRepo:
name=name,
role=role,
traits=_traits_to_jsonb(traits),
appearance=appearance,
motive=motive,
backstory=backstory,
arc=_arc_to_jsonb(arc),
speech_tics=_traits_to_jsonb(speech_tics),
@@ -139,6 +147,8 @@ class CharacterWriteFields(BaseModel):
name: str
role: str
traits: list[str] = Field(default_factory=list)
appearance: str = ""
motive: str = ""
backstory: str
arc: str
speech_tics: list[str] = Field(default_factory=list)