# Bug Fix Implementation Plan > **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. **Goal:** 修复 TRIGGER-001 (user_agents 自动创建) 和 TOKEN-001 (Flutter 硬编码颜色迁移) **Architecture:** - TRIGGER-001: YAML 配置 → catalog 表 → Trigger 自动创建 - TOKEN-001: 渐进式迁移硬编码颜色到 AppColors **Tech Stack:** Python, PostgreSQL, Alembic, Dart, Flutter --- ## TRIGGER-001: user_agents 自动创建 ### Task 1: 创建 user_agent_catalog.yaml 配置文件 **Files:** - Modify: `backend/src/core/config/static/database/user_agent_catalog.yaml` **Step 1: 填充 YAML 配置** ```yaml agents: - agent_type: INTENT_RECOGNITION llm_model_code: qwen3.5-flash status: active config: temperature: 0.7 - agent_type: TASK_EXECUTION llm_model_code: deepseek-v3.2 status: active config: temperature: 0.7 - agent_type: RESULT_REPORTING llm_model_code: deepseek-v3.2 status: active config: temperature: 0.7 ``` **Step 2: 验证 YAML 语法** Run: `cat backend/src/core/config/static/database/user_agent_catalog.yaml` Expected: YAML 格式正确,包含 3 个 agent 配置 **Step 3: Commit** ```bash git add backend/src/core/config/static/database/user_agent_catalog.yaml git commit -m "feat(config): add user_agent_catalog.yaml with default agent configs" ``` --- ### Task 2: 创建 UserAgentCatalog 模型 **Files:** - Create: `backend/src/models/user_agent_catalog.py` - Modify: `backend/src/models/__init__.py` **Step 1: 创建模型文件** ```python # backend/src/models/user_agent_catalog.py from __future__ import annotations from sqlalchemy import ForeignKey, String from sqlalchemy.dialects.postgresql import JSONB, UUID from sqlalchemy.orm import Mapped, mapped_column from core.db.base import Base, TimestampMixin class UserAgentCatalog(TimestampMixin, Base): __tablename__ = "user_agent_catalog" agent_type: Mapped[str] = mapped_column( String(20), primary_key=True, ) llm_id: Mapped[str] = mapped_column( UUID(as_uuid=True), ForeignKey("llms.id", ondelete="RESTRICT"), nullable=False, ) status: Mapped[str] = mapped_column( String(20), nullable=False, ) config: Mapped[dict] = mapped_column( JSONB, nullable=False, server_default="{}", ) ``` **Step 2: 在 __init__.py 中导出** ```python # backend/src/models/__init__.py (添加) from models.user_agent_catalog import UserAgentCatalog __all__ = [ # ... 现有导出 "UserAgentCatalog", ] ``` **Step 3: 运行类型检查** Run: `cd backend && uv run basedpyright src/models/user_agent_catalog.py` Expected: No errors **Step 4: Commit** ```bash git add backend/src/models/user_agent_catalog.py backend/src/models/__init__.py git commit -m "feat(models): add UserAgentCatalog model" ``` --- ### Task 3: 创建 Alembic 迁移 **Files:** - Create: `backend/alembic/versions/20260302_add_user_agent_catalog.py` **Step 1: 生成迁移文件** Run: `cd backend && uv run alembic revision -m "add_user_agent_catalog"` **Step 2: 编写迁移逻辑** ```python # backend/alembic/versions/20260302_add_user_agent_catalog.py """add user_agent_catalog table Revision ID: 202603020001 Revises: Create Date: 2026-03-02 """ from typing import Sequence, Union from alembic import op import sqlalchemy as sa from sqlalchemy.dialects import postgresql revision: str = "202603020001" down_revision: Union[str, Sequence[str], None] = None branch_labels: Union[str, Sequence[str], None] = None depends_on: Union[str, Sequence[str], None] = None def upgrade() -> None: # 创建 user_agent_catalog 表 op.create_table( "user_agent_catalog", sa.Column("agent_type", sa.String(20), nullable=False), sa.Column("llm_id", sa.UUID(), nullable=False), sa.Column("status", sa.String(20), nullable=False), sa.Column( "config", postgresql.JSONB(astext_type=sa.Text()), server_default="{}", nullable=False, ), sa.Column( "created_at", sa.DateTime(timezone=True), server_default=sa.text("now()"), nullable=False, ), sa.Column( "updated_at", sa.DateTime(timezone=True), server_default=sa.text("now()"), nullable=False, ), sa.PrimaryKeyConstraint("agent_type"), sa.ForeignKeyConstraint( ["llm_id"], ["llms.id"], name="fk_user_agent_catalog_llm_id", ondelete="RESTRICT" ), ) op.execute( "ALTER TABLE user_agent_catalog " "ADD CONSTRAINT chk_user_agent_catalog_status " "CHECK (status IN ('active', 'paused', 'migrating'))" ) _enable_rls("user_agent_catalog") # 替换 trigger 函数 op.execute(""" CREATE OR REPLACE FUNCTION public.create_profile_for_new_user() RETURNS trigger LANGUAGE plpgsql SECURITY DEFINER SET search_path = public AS $$ BEGIN INSERT INTO public.profiles (id, username, avatar_url, bio, settings, created_at, updated_at) VALUES ( NEW.id, COALESCE( NEW.raw_user_meta_data ->> 'username', split_part(NEW.email, '@', 1), 'user_' || substring(NEW.id::text, 1, 8) ), NULL, NULL, '{}'::jsonb, now(), now() ) ON CONFLICT (id) DO NOTHING; INSERT INTO public.user_agents (id, user_id, llm_id, agent_type, config, status, created_by, updated_by) SELECT gen_random_uuid(), NEW.id, uac.llm_id, uac.agent_type, uac.config, uac.status, NEW.id, NEW.id FROM public.user_agent_catalog uac; RETURN NEW; END; $$; """) def downgrade() -> None: # 恢复旧 trigger 函数 op.execute(""" CREATE OR REPLACE FUNCTION public.create_profile_for_new_user() RETURNS trigger LANGUAGE plpgsql SECURITY DEFINER SET search_path = public AS $$ BEGIN INSERT INTO public.profiles (id, username, avatar_url, bio, settings, created_at, updated_at) VALUES ( NEW.id, COALESCE( NEW.raw_user_meta_data ->> 'username', split_part(NEW.email, '@', 1), 'user_' || substring(NEW.id::text, 1, 8) ), NULL, NULL, '{}'::jsonb, now(), now() ) ON CONFLICT (id) DO NOTHING; RETURN NEW; END; $$; """) _drop_rls("user_agent_catalog") op.drop_constraint("chk_user_agent_catalog_status", "user_agent_catalog", type_="check") op.drop_constraint("fk_user_agent_catalog_llm_id", "user_agent_catalog", type_="foreignkey") op.drop_table("user_agent_catalog") def _enable_rls(table_name: str) -> None: for role in ["anon", "authenticated"]: for action in ["select", "insert", "update", "delete"]: op.execute( f"DROP POLICY IF EXISTS {role}_{action}_{table_name} ON {table_name}" ) op.execute(f"ALTER TABLE {table_name} ENABLE ROW LEVEL SECURITY") for role in ["anon", "authenticated"]: op.execute( f"CREATE POLICY {role}_select_{table_name} ON {table_name} FOR SELECT TO {role} USING (false)" ) op.execute( f"CREATE POLICY {role}_insert_{table_name} ON {table_name} FOR INSERT TO {role} WITH CHECK (false)" ) op.execute( f"CREATE POLICY {role}_update_{table_name} ON {table_name} FOR UPDATE TO {role} USING (false) WITH CHECK (false)" ) op.execute( f"CREATE POLICY {role}_delete_{table_name} ON {table_name} FOR DELETE TO {role} USING (false)" ) def _drop_rls(table_name: str) -> None: for role in ["anon", "authenticated"]: op.execute(f"DROP POLICY IF EXISTS {role}_delete_{table_name} ON {table_name}") op.execute(f"DROP POLICY IF EXISTS {role}_update_{table_name} ON {table_name}") op.execute(f"DROP POLICY IF EXISTS {role}_insert_{table_name} ON {table_name}") op.execute(f"DROP POLICY IF EXISTS {role}_select_{table_name} ON {table_name}") op.execute(f"ALTER TABLE {table_name} DISABLE ROW LEVEL SECURITY") ``` **Step 3: 运行迁移检查** Run: `cd backend && uv run alembic check` Expected: No issues **Step 4: Commit** ```bash git add backend/alembic/versions/20260302_add_user_agent_catalog.py git commit -m "feat(migration): add user_agent_catalog table and update trigger" ``` --- ### Task 4: 扩展初始化函数 **Files:** - Modify: `backend/src/core/config/initial/init_data.py` **Step 1: 添加 Pydantic 模型** ```python # backend/src/core/config/initial/init_data.py (添加到文件顶部) from models.user_agent_catalog import UserAgentCatalog class UserAgentCatalogSeed(BaseModel): agent_type: str llm_model_code: str status: str config: dict[str, Any] class UserAgentCatalogYaml(BaseModel): agents: list[UserAgentCatalogSeed] ``` **Step 2: 添加 load 函数** ```python # backend/src/core/config/initial/init_data.py (添加) def _default_user_agent_catalog_path() -> Path: return ( Path(__file__).resolve().parents[1] / "static" / "database" / "user_agent_catalog.yaml" ) def load_user_agent_catalog(catalog_path: Path | None = None) -> dict[str, Any]: path = catalog_path or _default_user_agent_catalog_path() with path.open("r", encoding="utf-8") as file: loaded = yaml.safe_load(file) or {} if not isinstance(loaded, dict): raise ValueError(f"Invalid user agent catalog format: {path}") raw_agents = loaded.get("agents", []) if not isinstance(raw_agents, list): raise ValueError(f"Invalid user agent catalog agents section: {path}") try: parsed = UserAgentCatalogYaml.model_validate({"agents": list(raw_agents)}) except ValidationError as exc: raise ValueError(f"Invalid user agent catalog data: {path}") from exc return parsed.model_dump() ``` **Step 3: 添加初始化函数** ```python # backend/src/core/config/initial/init_data.py (添加) async def _upsert_user_agent_catalog( session: AsyncSession, *, agent_type: str, llm_id: uuid.UUID, status: str, config: dict[str, Any], ) -> None: result = await session.execute( select(UserAgentCatalog).where(UserAgentCatalog.agent_type == agent_type) ) catalog_entry = result.scalar_one_or_none() if catalog_entry is None: session.add(UserAgentCatalog( agent_type=agent_type, llm_id=llm_id, status=status, config=config, )) else: catalog_entry.llm_id = llm_id catalog_entry.status = status catalog_entry.config = config async def initialize_user_agent_catalog() -> None: """Initialize user agent catalog from YAML.""" catalog = load_user_agent_catalog() async with AsyncSessionLocal() as session: async with session.begin(): for agent in catalog["agents"]: # 查找 llm_id result = await session.execute( select(Llm).where(Llm.model_code == agent["llm_model_code"]) ) llm = result.scalar_one_or_none() if llm is None: raise RuntimeError( f"LLM model '{agent['llm_model_code']}' not found for agent type '{agent['agent_type']}'" ) await _upsert_user_agent_catalog( session, agent_type=agent["agent_type"], llm_id=llm.id, status=agent["status"], config=agent["config"], ) logger.info("Initialized user agent catalog") ``` **Step 4: 更新 initialize_data 函数** ```python # backend/src/core/config/initial/init_data.py (修改) async def initialize_data() -> bool: """Initialize bootstrap data.""" await initialize_llm_catalog() await initialize_user_agent_catalog() # 新增 return True ``` **Step 5: 运行类型检查** Run: `cd backend && uv run basedpyright src/core/config/initial/init_data.py` Expected: No errors **Step 6: Commit** ```bash git add backend/src/core/config/initial/init_data.py git commit -m "feat(init): add user_agent_catalog initialization" ``` --- ### Task 5: 编写单元测试 **Files:** - Modify: `backend/tests/unit/core/test_agent_init_data.py` **Step 1: 添加测试** ```python # backend/tests/unit/core/test_agent_init_data.py (添加到文件末尾) def test_user_agent_catalog_file_exists_and_has_required_fields() -> None: catalog_path = ( Path(__file__).resolve().parents[4] / "src" / "core" / "config" / "static" / "database" / "user_agent_catalog.yaml" ) assert catalog_path.exists(), f"Catalog file not found: {catalog_path}" catalog = init_data.load_user_agent_catalog(catalog_path) assert "agents" in catalog assert isinstance(catalog["agents"], list) assert len(catalog["agents"]) == 3 for agent in catalog["agents"]: assert "agent_type" in agent assert "llm_model_code" in agent assert "status" in agent assert "config" in agent assert isinstance(agent["config"], dict) def test_load_user_agent_catalog_raises_on_invalid_structure(tmp_path: Path) -> None: catalog_path = tmp_path / "user_agent_catalog.yaml" catalog_path.write_text("invalid: structure\n") with pytest.raises(ValueError, match="Invalid user agent catalog"): init_data.load_user_agent_catalog(catalog_path) ``` **Step 2: 运行测试** Run: `cd backend && uv run pytest tests/unit/core/test_agent_init_data.py -v` Expected: All tests pass **Step 3: Commit** ```bash git add backend/tests/unit/core/test_agent_init_data.py git commit -m "test(init): add user_agent_catalog validation tests" ``` --- ### Task 6: 运行迁移并验证 **Files:** - None (database operations) **Step 1: 运行迁移** Run: `cd backend && uv run alembic upgrade head` Expected: Migration succeeds **Step 2: 运行初始化** Run: `cd backend && uv run python -m core.runtime.cli init-data` Expected: "Initialized user agent catalog" in output **Step 3: 验证数据库** Run: `docker exec -it social-supabase-db psql -U postgres -d postgres -c "SELECT * FROM user_agent_catalog;"` Expected: 3 rows (INTENT_RECOGNITION, TASK_EXECUTION, RESULT_REPORTING) **Step 4: 测试 Trigger(手动)** ```sql -- 创建测试用户 INSERT INTO auth.users (id, email, raw_user_meta_data) VALUES (gen_random_uuid(), 'test@example.com', '{"username": "testuser"}'); -- 验证 profiles 和 user_agents 自动创建 SELECT * FROM profiles WHERE username = 'testuser'; SELECT * FROM user_agents WHERE user_id = (SELECT id FROM profiles WHERE username = 'testuser'); -- 清理测试数据 DELETE FROM user_agents WHERE user_id = (SELECT id FROM profiles WHERE username = 'testuser'); DELETE FROM profiles WHERE username = 'testuser'; DELETE FROM auth.users WHERE email = 'test@example.com'; ``` Expected: profiles 有 1 条,user_agents 有 3 条 --- ### Task 7: 为已存在用户补充 user_agents **Files:** - None (database operations) **Step 1: 执行补充脚本** ```sql -- 为已存在但没有 user_agents 的用户补充记录 INSERT INTO user_agents (id, user_id, llm_id, agent_type, config, status, created_by, updated_by) SELECT gen_random_uuid(), p.id, uac.llm_id, uac.agent_type, uac.config, uac.status, p.id, p.id FROM profiles p CROSS JOIN user_agent_catalog uac WHERE NOT EXISTS ( SELECT 1 FROM user_agents ua WHERE ua.user_id = p.id ); ``` **Step 2: 验证结果** Run: `docker exec -it social-supabase-db psql -U postgres -d postgres -c "SELECT user_id, COUNT(*) FROM user_agents GROUP BY user_id;"` Expected: 每个用户有 3 条 user_agents 记录 --- ## TOKEN-001: Flutter 硬编码颜色迁移 ### Task 8: 扫描硬编码颜色 **Files:** - None (analysis only) **Step 1: 扫描所有硬编码颜色** Run: ```bash cd apps && grep -r "Color(0x" lib --include="*.dart" | \ sed 's/.*Color(0x\([0-9A-Fa-f]*\)).*/\1/' | \ sort | uniq -c | sort -rn > /tmp/color_stats.txt && \ cat /tmp/color_stats.txt ``` Expected: 统计结果输出,显示每个颜色的使用频率 **Step 2: 记录统计结果** 将统计结果保存到临时文件供后续使用。 --- ### Task 9: 扩展 AppColors **Files:** - Modify: `apps/lib/core/theme/design_tokens.dart` **Step 1: 添加新颜色常量** ```dart // apps/lib/core/theme/design_tokens.dart (在 AppColors 类中添加) class AppColors { // 现有颜色... // 从硬编码迁移的颜色(根据 Task 8 的统计结果) static const Color linkBlue = Color(0xFF2196F3); static const Color errorRed = Color(0xFFD32F2F); static const Color successGreen = Color(0xFF4CAF50); static const Color warningOrange = Color(0xFFFF9800); // ... 根据统计结果添加其他高频颜色 } ``` **Step 2: 运行 Flutter 分析** Run: `cd apps && flutter analyze lib/core/theme/design_tokens.dart` Expected: No issues **Step 3: Commit** ```bash git add apps/lib/core/theme/design_tokens.dart git commit -m "feat(theme): add migrated colors to AppColors" ``` --- ### Task 10: 迁移 register_screen.dart **Files:** - Modify: `apps/lib/features/auth/ui/screens/register_screen.dart` **Step 1: 替换硬编码颜色** ```dart // Before Container(color: Color(0xFF2196F3)) // After Container(color: AppColors.linkBlue) ``` **Step 2: 检查所有实例** Run: `cd apps && grep "Color(0x" lib/features/auth/ui/screens/register_screen.dart` Expected: No matches (或只剩下合理的动态颜色) **Step 3: 运行 Flutter 分析** Run: `cd apps && flutter analyze lib/features/auth/ui/screens/register_screen.dart` Expected: No issues **Step 4: Commit** ```bash git add apps/lib/features/auth/ui/screens/register_screen.dart git commit -m "refactor(auth): migrate hardcoded colors in register_screen" ``` --- ### Task 11: 迁移其他页面(批量) **Files:** - Modify: 所有包含硬编码颜色的页面文件 **Step 1: 逐页面迁移** 按照频率顺序迁移: 1. `register_verification_screen.dart` 2. `settings_screen.dart` 3. `account_screen.dart` 4. `contacts_screen.dart` 5. `calendar_event_detail_screen.dart` 6. `add_contact_screen.dart` 7. `features_screen.dart` 8. `memory_screen.dart` 9. `home_screen.dart` 10. `todo_detail_screen.dart` 每个文件重复 Task 10 的步骤。 **Step 2: 批量提交** ```bash git add apps/lib/features/ git commit -m "refactor(ui): migrate hardcoded colors to AppColors" ``` --- ### Task 12: 验证和测试 **Files:** - None (validation only) **Step 1: 检查剩余硬编码** Run: `cd apps && grep -r "Color(0x" lib --include="*.dart" | wc -l` Expected: < 10 (只保留合理的动态颜色) **Step 2: 运行 Flutter 测试** Run: `cd apps && flutter test` Expected: All tests pass **Step 3: 视觉检查** 手动运行应用,检查页面显示是否正常。 --- ## Final Verification ### Task 13: 全量测试 **Files:** - None (testing only) **Step 1: 运行后端测试** Run: `cd backend && uv run pytest` Expected: All tests pass **Step 2: 运行 Flutter 测试** Run: `cd apps && flutter test` Expected: All tests pass **Step 3: 运行迁移检查** Run: `cd backend && uv run alembic check` Expected: No issues **Step 4: 类型检查** Run: `cd backend && uv run basedpyright src/` Expected: No errors --- ## Summary **Total Tasks:** 13 **Estimated Time:** 3-4 hours **Key Deliverables:** - ✅ user_agent_catalog.yaml 配置文件 - ✅ UserAgentCatalog 模型 - ✅ 数据库迁移(表 + trigger) - ✅ 初始化函数扩展 - ✅ 单元测试 - ✅ 硬编码颜色迁移(109 → <10) **Success Criteria:** - [ ] 新用户注册后自动创建 3 个 user_agents 记录 - [ ] 已存在用户不受影响 - [ ] 配置可通过 YAML + 重启更新 - [ ] 硬编码颜色数量 < 10 - [ ] 所有测试通过 - [ ] UI 视觉无回归