Pārlūkot izejas kodu

将项目名称从xzyj改为zxyj,更新所有包名和配置

User 5 mēneši atpakaļ
revīzija
12fd712c05
100 mainītis faili ar 10997 papildinājumiem un 0 dzēšanām
  1. 6 0
      .opencode/agent/程序员.md
  2. 20 0
      .opencode/agent/系统分析员.md
  3. 141 0
      .opencode/agent/需求分析员.md
  4. 149 0
      .opencode/command/opsx-apply.md
  5. 154 0
      .opencode/command/opsx-archive.md
  6. 239 0
      .opencode/command/opsx-bulk-archive.md
  7. 111 0
      .opencode/command/opsx-continue.md
  8. 171 0
      .opencode/command/opsx-explore.md
  9. 91 0
      .opencode/command/opsx-ff.md
  10. 66 0
      .opencode/command/opsx-new.md
  11. 531 0
      .opencode/command/opsx-onboard.md
  12. 131 0
      .opencode/command/opsx-sync.md
  13. 161 0
      .opencode/command/opsx-verify.md
  14. 156 0
      .opencode/skills/openspec-apply-change/SKILL.md
  15. 161 0
      .opencode/skills/openspec-archive-change/SKILL.md
  16. 246 0
      .opencode/skills/openspec-bulk-archive-change/SKILL.md
  17. 118 0
      .opencode/skills/openspec-continue-change/SKILL.md
  18. 289 0
      .opencode/skills/openspec-explore/SKILL.md
  19. 101 0
      .opencode/skills/openspec-ff-change/SKILL.md
  20. 74 0
      .opencode/skills/openspec-new-change/SKILL.md
  21. 538 0
      .opencode/skills/openspec-onboard/SKILL.md
  22. 138 0
      .opencode/skills/openspec-sync-specs/SKILL.md
  23. 168 0
      .opencode/skills/openspec-verify-change/SKILL.md
  24. 161 0
      README.md
  25. 89 0
      backend/pom.xml
  26. 18 0
      backend/src/main/java/com/zhixingyijia/ZhixingyijiaApplication.java
  27. 77 0
      backend/src/main/java/com/zhixingyijia/common/JwtUtil.java
  28. 54 0
      backend/src/main/java/com/zhixingyijia/common/Result.java
  29. 33 0
      backend/src/main/java/com/zhixingyijia/common/ResultCode.java
  30. 55 0
      backend/src/main/java/com/zhixingyijia/controller/AuthController.java
  31. 41 0
      backend/src/main/java/com/zhixingyijia/controller/PointsController.java
  32. 54 0
      backend/src/main/java/com/zhixingyijia/controller/RewardController.java
  33. 69 0
      backend/src/main/java/com/zhixingyijia/controller/TaskController.java
  34. 57 0
      backend/src/main/java/com/zhixingyijia/entity/Child.java
  35. 29 0
      backend/src/main/java/com/zhixingyijia/entity/Family.java
  36. 32 0
      backend/src/main/java/com/zhixingyijia/entity/PointsLog.java
  37. 50 0
      backend/src/main/java/com/zhixingyijia/entity/Reward.java
  38. 38 0
      backend/src/main/java/com/zhixingyijia/entity/RewardExchange.java
  39. 83 0
      backend/src/main/java/com/zhixingyijia/entity/Task.java
  40. 41 0
      backend/src/main/java/com/zhixingyijia/entity/User.java
  41. 23 0
      backend/src/main/java/com/zhixingyijia/mapper/ChildMapper.java
  42. 20 0
      backend/src/main/java/com/zhixingyijia/mapper/PointsLogMapper.java
  43. 24 0
      backend/src/main/java/com/zhixingyijia/mapper/RewardMapper.java
  44. 30 0
      backend/src/main/java/com/zhixingyijia/mapper/TaskMapper.java
  45. 21 0
      backend/src/main/java/com/zhixingyijia/mapper/UserMapper.java
  46. 44 0
      backend/src/main/java/com/zhixingyijia/service/PointsService.java
  47. 55 0
      backend/src/main/java/com/zhixingyijia/service/RewardService.java
  48. 152 0
      backend/src/main/java/com/zhixingyijia/service/TaskService.java
  49. 55 0
      backend/src/main/java/com/zhixingyijia/service/UserService.java
  50. 31 0
      backend/src/main/resources/application.yml
  51. 55 0
      backend/src/main/resources/mapper/ChildMapper.xml
  52. 34 0
      backend/src/main/resources/mapper/PointsLogMapper.xml
  53. 57 0
      backend/src/main/resources/mapper/RewardMapper.xml
  54. 86 0
      backend/src/main/resources/mapper/TaskMapper.xml
  55. 42 0
      backend/src/main/resources/mapper/UserMapper.xml
  56. 140 0
      backend/src/main/resources/sql/init.sql
  57. 34 0
      backend/src/main/resources/sql/seed.sql
  58. 90 0
      backend/src/test/java/com/zhixingyijia/common/JwtUtilTest.java
  59. 80 0
      backend/src/test/java/com/zhixingyijia/common/ResultTest.java
  60. 160 0
      backend/src/test/java/com/zhixingyijia/common/SqlValidationTest.java
  61. 151 0
      backend/src/test/java/com/zhixingyijia/integration/ApiContractTest.java
  62. 229 0
      backend/src/test/java/com/zhixingyijia/service/TaskServiceTest.java
  63. 163 0
      backend/src/test/java/com/zhixingyijia/service/UserServiceTest.java
  64. 28 0
      backend/src/test/resources/test-schema.sql
  65. 62 0
      backend/src/test/resources/validate-sql.sh
  66. 113 0
      docs/API-INTEGRATION.md
  67. 1718 0
      docs/superpowers/plans/2026-03-31-心知益家MVP实现计划.md
  68. 146 0
      docs/superpowers/specs/2026-03-31-UI交互设计.md
  69. 219 0
      docs/superpowers/specs/2026-03-31-使用场景与运营模式-design.md
  70. 264 0
      docs/superpowers/specs/2026-03-31-功能模块设计-P0核心模块.md
  71. 290 0
      docs/superpowers/specs/2026-03-31-功能模块设计-P1增强模块.md
  72. 37 0
      miniprogram/app.js
  73. 54 0
      miniprogram/app.json
  74. 45 0
      miniprogram/app.wxss
  75. 5 0
      miniprogram/images/default-avatar.svg
  76. 8 0
      miniprogram/images/empty-log.svg
  77. 6 0
      miniprogram/images/empty-reward.svg
  78. 6 0
      miniprogram/images/empty-task.svg
  79. 5 0
      miniprogram/images/tab-me-active.svg
  80. 5 0
      miniprogram/images/tab-me.svg
  81. 7 0
      miniprogram/images/tab-reward-active.svg
  82. 7 0
      miniprogram/images/tab-reward.svg
  83. 6 0
      miniprogram/images/tab-task-active.svg
  84. 6 0
      miniprogram/images/tab-task.svg
  85. 54 0
      miniprogram/pages/achievements/achievements.js
  86. 4 0
      miniprogram/pages/achievements/achievements.json
  87. 69 0
      miniprogram/pages/achievements/achievements.wxml
  88. 152 0
      miniprogram/pages/achievements/achievements.wxss
  89. 110 0
      miniprogram/pages/child-edit/child-edit.js
  90. 4 0
      miniprogram/pages/child-edit/child-edit.json
  91. 75 0
      miniprogram/pages/child-edit/child-edit.wxml
  92. 92 0
      miniprogram/pages/child-edit/child-edit.wxss
  93. 33 0
      miniprogram/pages/child-home/child-home.js
  94. 5 0
      miniprogram/pages/child-home/child-home.json
  95. 49 0
      miniprogram/pages/child-home/child-home.wxml
  96. 177 0
      miniprogram/pages/child-home/child-home.wxss
  97. 171 0
      miniprogram/pages/family-manage/family-manage.js
  98. 4 0
      miniprogram/pages/family-manage/family-manage.json
  99. 68 0
      miniprogram/pages/family-manage/family-manage.wxml
  100. 176 0
      miniprogram/pages/family-manage/family-manage.wxss

+ 6 - 0
.opencode/agent/程序员.md

@@ -0,0 +1,6 @@
+---
+name: 程序员
+description: 全栈开发工程师,擅长将需求转化为可运行的代码,java的话遵循jdk1.8的规范
+mode: subagent
+temperature: 0.8
+---

+ 20 - 0
.opencode/agent/系统分析员.md

@@ -0,0 +1,20 @@
+---
+name: 系统分析员
+description: 资深系统分析员,擅长将业务需求转化为系统架构和详细设计方案。
+mode: subagent
+temperature: 0.8
+---
+
+[analyze-mode]
+ANALYSIS MODE. Gather context before diving deep:
+
+CONTEXT GATHERING (parallel):
+- 1-2 explore agents (codebase patterns, implementations)
+- 1-2 librarian agents (if external library involved)
+- Direct tools: Grep, AST-grep, LSP for targeted searches
+
+IF COMPLEX - DO NOT STRUGGLE ALONE. Consult specialists:
+- **Oracle**: Conventional problems (architecture, debugging, complex logic)
+- **Artistry**: Non-conventional problems (different approach needed)
+
+SYNTHESIZE findings before proceeding.

+ 141 - 0
.opencode/agent/需求分析员.md

@@ -0,0 +1,141 @@
+# 需求分析员 Agent
+
+## 角色定义
+
+资深需求工程师,擅长将模糊、复杂的需求转化为清晰、可执行的结构化描述。
+
+---
+
+## 核心能力
+
+### 1. 需求提炼
+- 从用户故事、会议纪要、邮件、原始草稿等来源提取核心需求
+- 识别用户的真实意图(区别于表面表达)
+- 梳理业务逻辑和流程
+
+### 2. 需求拆解
+- 将大需求拆分为小的逻辑块
+- 逐一分析每部分的表面意思和深层诉求
+- 识别需求之间的依赖关系
+
+### 3. 问题识别
+- 指出需求中的歧义、二义性
+- 识别潜在风险(逻辑冲突、缺失信息)
+- 发现优化点和改进建议
+
+### 4. 验收标准制定
+- 根据需求推导出可验证的完成标准
+- 定义清晰的验收条件
+
+---
+
+## 输入格式
+
+### 必填项
+- **需求来源**:用户故事/会议纪要/邮件/原始草稿等
+- **需求内容**:@文档路径 或 粘贴需求文本
+
+### 选填项
+- **读者水平**:业务方/产品经理/开发/测试(默认:开发)
+- **重点关注**:特别想了解的方面(可行性/验收标准/边界条件等)
+
+---
+
+## 输出规范
+
+### 1. 一句话总结(≤50字)
+用最简洁的语言概括出这个需求的核心目的。
+
+### 2. 逐块拆解
+原文片段(带关键句索引/页码) + 核心意图 + 潜在含义
+
+### 3. 关键概念
+涉及的业务领域、专业术语、核心实体及其定义
+
+### 4. 潜在问题
+
+| 标记 | 类型 | 说明 |
+|------|------|------|
+| 🔴 严重 | 风险 | 逻辑冲突、缺失必要信息、可能导致项目失败 |
+| 🟡 建议 | 改进 | 可以改进以提升效率或用户体验 |
+| 🟢 提示 | 注意 | 需要注意的细节、依赖关系、后续影响 |
+
+### 5. 验收标准
+根据需求推导出的、可验证的完成标准。
+
+---
+
+## 行为准则
+
+- ✅ 解释要循序渐进,引导从业务价值角度思考
+- ✅ 为非技术人员加入业务背景说明和类比
+- ✅ 术语保留原文,但需提供通俗易懂的解释
+- ❌ 避免只做文字复述,不进行逻辑分析
+- ❌ 避免忽略需求中的模糊地带和二义性
+
+---
+
+## 使用示例
+
+### 输入示例
+
+```
+需求来源:用户故事
+需求内容:我想要一个小程序,家长可以给孩子安排任务,孩子完成后获取积分
+
+读者水平:开发
+重点关注:可行性、验收标准
+```
+
+### 输出示例
+
+```
+## 一句话总结
+开发家庭任务积分管理小程序,家长发布任务→孩子完成获积分→积分兑换奖励。
+
+## 逐块拆解
+| 原文片段 | 核心意图 | 潜在含义 |
+|---------|---------|---------|
+| 家长给孩子安排任务 | 任务发布功能 | 需支持设置任务名称、积分、截止时间 |
+| 孩子完成后获取积分 | 积分获取机制 | 需区分提前完成/按时完成/超时等场景 |
+| 积分兑换奖励 | 奖励体系 | 需支持心愿单、审批流程 |
+
+## 关键概念
+- 即时反馈:任务完成后立即获得积分
+- 延迟满足:通过攒积分换取奖励
+
+## 潜在问题
+🔴 缺失信息:积分规则(如何扣分?每天上限?)
+🔴 风险:奖励定价机制如何防止通胀?
+🟡 建议:增加超时保护机制
+
+## 验收标准
+- [ ] 家长可创建任务,设置积分值(1-10)和截止时间
+- [ ] 孩子可查看任务列表,点击完成获得积分
+- [ ] 提前完成额外+1分
+- [ ] 超时10分钟内不算迟到,每天最多扣5分
+- [ ] 积分可兑换奖励,家长审批后生效
+```
+
+---
+
+## 触发条件
+
+当用户提出以下类型的需求时,应使用此Agent:
+
+- 需求来源为用户故事、会议纪要、邮件等
+- 需求描述模糊、不完整或存在歧义
+- 需要拆解复杂需求
+- 需要制定验收标准
+- 需要识别潜在风险
+
+---
+
+## 配合其他Agent
+
+| 场景 | 配合Agent |
+|------|----------|
+| 技术实现可行性 | Oracle(架构评估) |
+| 外部参考资料 | Librarian(文档/案例搜索) |
+| 代码实现 | Developer(开发工程师) |
+| 界面设计 | UI/UX Designer |

+ 149 - 0
.opencode/command/opsx-apply.md

@@ -0,0 +1,149 @@
+---
+description: Implement tasks from an OpenSpec change (Experimental)
+---
+
+Implement tasks from an OpenSpec change.
+
+**Input**: Optionally specify a change name (e.g., `/opsx-apply add-auth`). If omitted, check if it can be inferred from conversation context. If vague or ambiguous you MUST prompt for available changes.
+
+**Steps**
+
+1. **Select the change**
+
+   If a name is provided, use it. Otherwise:
+   - Infer from conversation context if the user mentioned a change
+   - Auto-select if only one active change exists
+   - If ambiguous, run `openspec list --json` to get available changes and use the **AskUserQuestion tool** to let the user select
+
+   Always announce: "Using change: <name>" and how to override (e.g., `/opsx-apply <other>`).
+
+2. **Check status to understand the schema**
+   ```bash
+   openspec status --change "<name>" --json
+   ```
+   Parse the JSON to understand:
+   - `schemaName`: The workflow being used (e.g., "spec-driven")
+   - Which artifact contains the tasks (typically "tasks" for spec-driven, check status for others)
+
+3. **Get apply instructions**
+
+   ```bash
+   openspec instructions apply --change "<name>" --json
+   ```
+
+   This returns:
+   - Context file paths (varies by schema)
+   - Progress (total, complete, remaining)
+   - Task list with status
+   - Dynamic instruction based on current state
+
+   **Handle states:**
+   - If `state: "blocked"` (missing artifacts): show message, suggest using `/opsx-continue`
+   - If `state: "all_done"`: congratulate, suggest archive
+   - Otherwise: proceed to implementation
+
+4. **Read context files**
+
+   Read the files listed in `contextFiles` from the apply instructions output.
+   The files depend on the schema being used:
+   - **spec-driven**: proposal, specs, design, tasks
+   - Other schemas: follow the contextFiles from CLI output
+
+5. **Show current progress**
+
+   Display:
+   - Schema being used
+   - Progress: "N/M tasks complete"
+   - Remaining tasks overview
+   - Dynamic instruction from CLI
+
+6. **Implement tasks (loop until done or blocked)**
+
+   For each pending task:
+   - Show which task is being worked on
+   - Make the code changes required
+   - Keep changes minimal and focused
+   - Mark task complete in the tasks file: `- [ ]` → `- [x]`
+   - Continue to next task
+
+   **Pause if:**
+   - Task is unclear → ask for clarification
+   - Implementation reveals a design issue → suggest updating artifacts
+   - Error or blocker encountered → report and wait for guidance
+   - User interrupts
+
+7. **On completion or pause, show status**
+
+   Display:
+   - Tasks completed this session
+   - Overall progress: "N/M tasks complete"
+   - If all done: suggest archive
+   - If paused: explain why and wait for guidance
+
+**Output During Implementation**
+
+```
+## Implementing: <change-name> (schema: <schema-name>)
+
+Working on task 3/7: <task description>
+[...implementation happening...]
+✓ Task complete
+
+Working on task 4/7: <task description>
+[...implementation happening...]
+✓ Task complete
+```
+
+**Output On Completion**
+
+```
+## Implementation Complete
+
+**Change:** <change-name>
+**Schema:** <schema-name>
+**Progress:** 7/7 tasks complete ✓
+
+### Completed This Session
+- [x] Task 1
+- [x] Task 2
+...
+
+All tasks complete! You can archive this change with `/opsx-archive`.
+```
+
+**Output On Pause (Issue Encountered)**
+
+```
+## Implementation Paused
+
+**Change:** <change-name>
+**Schema:** <schema-name>
+**Progress:** 4/7 tasks complete
+
+### Issue Encountered
+<description of the issue>
+
+**Options:**
+1. <option 1>
+2. <option 2>
+3. Other approach
+
+What would you like to do?
+```
+
+**Guardrails**
+- Keep going through tasks until done or blocked
+- Always read context files before starting (from the apply instructions output)
+- If task is ambiguous, pause and ask before implementing
+- If implementation reveals issues, pause and suggest artifact updates
+- Keep code changes minimal and scoped to each task
+- Update task checkbox immediately after completing each task
+- Pause on errors, blockers, or unclear requirements - don't guess
+- Use contextFiles from CLI output, don't assume specific file names
+
+**Fluid Workflow Integration**
+
+This skill supports the "actions on a change" model:
+
+- **Can be invoked anytime**: Before all artifacts are done (if tasks exist), after partial implementation, interleaved with other actions
+- **Allows artifact updates**: If implementation reveals design issues, suggest updating artifacts - not phase-locked, work fluidly

+ 154 - 0
.opencode/command/opsx-archive.md

@@ -0,0 +1,154 @@
+---
+description: Archive a completed change in the experimental workflow
+---
+
+Archive a completed change in the experimental workflow.
+
+**Input**: Optionally specify a change name after `/opsx-archive` (e.g., `/opsx-archive add-auth`). If omitted, check if it can be inferred from conversation context. If vague or ambiguous you MUST prompt for available changes.
+
+**Steps**
+
+1. **If no change name provided, prompt for selection**
+
+   Run `openspec list --json` to get available changes. Use the **AskUserQuestion tool** to let the user select.
+
+   Show only active changes (not already archived).
+   Include the schema used for each change if available.
+
+   **IMPORTANT**: Do NOT guess or auto-select a change. Always let the user choose.
+
+2. **Check artifact completion status**
+
+   Run `openspec status --change "<name>" --json` to check artifact completion.
+
+   Parse the JSON to understand:
+   - `schemaName`: The workflow being used
+   - `artifacts`: List of artifacts with their status (`done` or other)
+
+   **If any artifacts are not `done`:**
+   - Display warning listing incomplete artifacts
+   - Prompt user for confirmation to continue
+   - Proceed if user confirms
+
+3. **Check task completion status**
+
+   Read the tasks file (typically `tasks.md`) to check for incomplete tasks.
+
+   Count tasks marked with `- [ ]` (incomplete) vs `- [x]` (complete).
+
+   **If incomplete tasks found:**
+   - Display warning showing count of incomplete tasks
+   - Prompt user for confirmation to continue
+   - Proceed if user confirms
+
+   **If no tasks file exists:** Proceed without task-related warning.
+
+4. **Assess delta spec sync state**
+
+   Check for delta specs at `openspec/changes/<name>/specs/`. If none exist, proceed without sync prompt.
+
+   **If delta specs exist:**
+   - Compare each delta spec with its corresponding main spec at `openspec/specs/<capability>/spec.md`
+   - Determine what changes would be applied (adds, modifications, removals, renames)
+   - Show a combined summary before prompting
+
+   **Prompt options:**
+   - If changes needed: "Sync now (recommended)", "Archive without syncing"
+   - If already synced: "Archive now", "Sync anyway", "Cancel"
+
+   If user chooses sync, use Task tool (subagent_type: "general-purpose", prompt: "Use Skill tool to invoke openspec-sync-specs for change '<name>'. Delta spec analysis: <include the analyzed delta spec summary>"). Proceed to archive regardless of choice.
+
+5. **Perform the archive**
+
+   Create the archive directory if it doesn't exist:
+   ```bash
+   mkdir -p openspec/changes/archive
+   ```
+
+   Generate target name using current date: `YYYY-MM-DD-<change-name>`
+
+   **Check if target already exists:**
+   - If yes: Fail with error, suggest renaming existing archive or using different date
+   - If no: Move the change directory to archive
+
+   ```bash
+   mv openspec/changes/<name> openspec/changes/archive/YYYY-MM-DD-<name>
+   ```
+
+6. **Display summary**
+
+   Show archive completion summary including:
+   - Change name
+   - Schema that was used
+   - Archive location
+   - Spec sync status (synced / sync skipped / no delta specs)
+   - Note about any warnings (incomplete artifacts/tasks)
+
+**Output On Success**
+
+```
+## Archive Complete
+
+**Change:** <change-name>
+**Schema:** <schema-name>
+**Archived to:** openspec/changes/archive/YYYY-MM-DD-<name>/
+**Specs:** ✓ Synced to main specs
+
+All artifacts complete. All tasks complete.
+```
+
+**Output On Success (No Delta Specs)**
+
+```
+## Archive Complete
+
+**Change:** <change-name>
+**Schema:** <schema-name>
+**Archived to:** openspec/changes/archive/YYYY-MM-DD-<name>/
+**Specs:** No delta specs
+
+All artifacts complete. All tasks complete.
+```
+
+**Output On Success With Warnings**
+
+```
+## Archive Complete (with warnings)
+
+**Change:** <change-name>
+**Schema:** <schema-name>
+**Archived to:** openspec/changes/archive/YYYY-MM-DD-<name>/
+**Specs:** Sync skipped (user chose to skip)
+
+**Warnings:**
+- Archived with 2 incomplete artifacts
+- Archived with 3 incomplete tasks
+- Delta spec sync was skipped (user chose to skip)
+
+Review the archive if this was not intentional.
+```
+
+**Output On Error (Archive Exists)**
+
+```
+## Archive Failed
+
+**Change:** <change-name>
+**Target:** openspec/changes/archive/YYYY-MM-DD-<name>/
+
+Target archive directory already exists.
+
+**Options:**
+1. Rename the existing archive
+2. Delete the existing archive if it's a duplicate
+3. Wait until a different date to archive
+```
+
+**Guardrails**
+- Always prompt for change selection if not provided
+- Use artifact graph (openspec status --json) for completion checking
+- Don't block archive on warnings - just inform and confirm
+- Preserve .openspec.yaml when moving to archive (it moves with the directory)
+- Show clear summary of what happened
+- If sync is requested, use the Skill tool to invoke `openspec-sync-specs` (agent-driven)
+- If delta specs exist, always run the sync assessment and show the combined summary before prompting

+ 239 - 0
.opencode/command/opsx-bulk-archive.md

@@ -0,0 +1,239 @@
+---
+description: Archive multiple completed changes at once
+---
+
+Archive multiple completed changes in a single operation.
+
+This skill allows you to batch-archive changes, handling spec conflicts intelligently by checking the codebase to determine what's actually implemented.
+
+**Input**: None required (prompts for selection)
+
+**Steps**
+
+1. **Get active changes**
+
+   Run `openspec list --json` to get all active changes.
+
+   If no active changes exist, inform user and stop.
+
+2. **Prompt for change selection**
+
+   Use **AskUserQuestion tool** with multi-select to let user choose changes:
+   - Show each change with its schema
+   - Include an option for "All changes"
+   - Allow any number of selections (1+ works, 2+ is the typical use case)
+
+   **IMPORTANT**: Do NOT auto-select. Always let the user choose.
+
+3. **Batch validation - gather status for all selected changes**
+
+   For each selected change, collect:
+
+   a. **Artifact status** - Run `openspec status --change "<name>" --json`
+      - Parse `schemaName` and `artifacts` list
+      - Note which artifacts are `done` vs other states
+
+   b. **Task completion** - Read `openspec/changes/<name>/tasks.md`
+      - Count `- [ ]` (incomplete) vs `- [x]` (complete)
+      - If no tasks file exists, note as "No tasks"
+
+   c. **Delta specs** - Check `openspec/changes/<name>/specs/` directory
+      - List which capability specs exist
+      - For each, extract requirement names (lines matching `### Requirement: <name>`)
+
+4. **Detect spec conflicts**
+
+   Build a map of `capability -> [changes that touch it]`:
+
+   ```
+   auth -> [change-a, change-b]  <- CONFLICT (2+ changes)
+   api  -> [change-c]            <- OK (only 1 change)
+   ```
+
+   A conflict exists when 2+ selected changes have delta specs for the same capability.
+
+5. **Resolve conflicts agentically**
+
+   **For each conflict**, investigate the codebase:
+
+   a. **Read the delta specs** from each conflicting change to understand what each claims to add/modify
+
+   b. **Search the codebase** for implementation evidence:
+      - Look for code implementing requirements from each delta spec
+      - Check for related files, functions, or tests
+
+   c. **Determine resolution**:
+      - If only one change is actually implemented -> sync that one's specs
+      - If both implemented -> apply in chronological order (older first, newer overwrites)
+      - If neither implemented -> skip spec sync, warn user
+
+   d. **Record resolution** for each conflict:
+      - Which change's specs to apply
+      - In what order (if both)
+      - Rationale (what was found in codebase)
+
+6. **Show consolidated status table**
+
+   Display a table summarizing all changes:
+
+   ```
+   | Change               | Artifacts | Tasks | Specs   | Conflicts | Status |
+   |---------------------|-----------|-------|---------|-----------|--------|
+   | schema-management   | Done      | 5/5   | 2 delta | None      | Ready  |
+   | project-config      | Done      | 3/3   | 1 delta | None      | Ready  |
+   | add-oauth           | Done      | 4/4   | 1 delta | auth (!)  | Ready* |
+   | add-verify-skill    | 1 left    | 2/5   | None    | None      | Warn   |
+   ```
+
+   For conflicts, show the resolution:
+   ```
+   * Conflict resolution:
+     - auth spec: Will apply add-oauth then add-jwt (both implemented, chronological order)
+   ```
+
+   For incomplete changes, show warnings:
+   ```
+   Warnings:
+   - add-verify-skill: 1 incomplete artifact, 3 incomplete tasks
+   ```
+
+7. **Confirm batch operation**
+
+   Use **AskUserQuestion tool** with a single confirmation:
+
+   - "Archive N changes?" with options based on status
+   - Options might include:
+     - "Archive all N changes"
+     - "Archive only N ready changes (skip incomplete)"
+     - "Cancel"
+
+   If there are incomplete changes, make clear they'll be archived with warnings.
+
+8. **Execute archive for each confirmed change**
+
+   Process changes in the determined order (respecting conflict resolution):
+
+   a. **Sync specs** if delta specs exist:
+      - Use the openspec-sync-specs approach (agent-driven intelligent merge)
+      - For conflicts, apply in resolved order
+      - Track if sync was done
+
+   b. **Perform the archive**:
+      ```bash
+      mkdir -p openspec/changes/archive
+      mv openspec/changes/<name> openspec/changes/archive/YYYY-MM-DD-<name>
+      ```
+
+   c. **Track outcome** for each change:
+      - Success: archived successfully
+      - Failed: error during archive (record error)
+      - Skipped: user chose not to archive (if applicable)
+
+9. **Display summary**
+
+   Show final results:
+
+   ```
+   ## Bulk Archive Complete
+
+   Archived 3 changes:
+   - schema-management-cli -> archive/2026-01-19-schema-management-cli/
+   - project-config -> archive/2026-01-19-project-config/
+   - add-oauth -> archive/2026-01-19-add-oauth/
+
+   Skipped 1 change:
+   - add-verify-skill (user chose not to archive incomplete)
+
+   Spec sync summary:
+   - 4 delta specs synced to main specs
+   - 1 conflict resolved (auth: applied both in chronological order)
+   ```
+
+   If any failures:
+   ```
+   Failed 1 change:
+   - some-change: Archive directory already exists
+   ```
+
+**Conflict Resolution Examples**
+
+Example 1: Only one implemented
+```
+Conflict: specs/auth/spec.md touched by [add-oauth, add-jwt]
+
+Checking add-oauth:
+- Delta adds "OAuth Provider Integration" requirement
+- Searching codebase... found src/auth/oauth.ts implementing OAuth flow
+
+Checking add-jwt:
+- Delta adds "JWT Token Handling" requirement
+- Searching codebase... no JWT implementation found
+
+Resolution: Only add-oauth is implemented. Will sync add-oauth specs only.
+```
+
+Example 2: Both implemented
+```
+Conflict: specs/api/spec.md touched by [add-rest-api, add-graphql]
+
+Checking add-rest-api (created 2026-01-10):
+- Delta adds "REST Endpoints" requirement
+- Searching codebase... found src/api/rest.ts
+
+Checking add-graphql (created 2026-01-15):
+- Delta adds "GraphQL Schema" requirement
+- Searching codebase... found src/api/graphql.ts
+
+Resolution: Both implemented. Will apply add-rest-api specs first,
+then add-graphql specs (chronological order, newer takes precedence).
+```
+
+**Output On Success**
+
+```
+## Bulk Archive Complete
+
+Archived N changes:
+- <change-1> -> archive/YYYY-MM-DD-<change-1>/
+- <change-2> -> archive/YYYY-MM-DD-<change-2>/
+
+Spec sync summary:
+- N delta specs synced to main specs
+- No conflicts (or: M conflicts resolved)
+```
+
+**Output On Partial Success**
+
+```
+## Bulk Archive Complete (partial)
+
+Archived N changes:
+- <change-1> -> archive/YYYY-MM-DD-<change-1>/
+
+Skipped M changes:
+- <change-2> (user chose not to archive incomplete)
+
+Failed K changes:
+- <change-3>: Archive directory already exists
+```
+
+**Output When No Changes**
+
+```
+## No Changes to Archive
+
+No active changes found. Use `/opsx-new` to create a new change.
+```
+
+**Guardrails**
+- Allow any number of changes (1+ is fine, 2+ is the typical use case)
+- Always prompt for selection, never auto-select
+- Detect spec conflicts early and resolve by checking codebase
+- When both changes are implemented, apply specs in chronological order
+- Skip spec sync only when implementation is missing (warn user)
+- Show clear per-change status before confirming
+- Use single confirmation for entire batch
+- Track and report all outcomes (success/skip/fail)
+- Preserve .openspec.yaml when moving to archive
+- Archive directory target uses current date: YYYY-MM-DD-<name>
+- If archive target exists, fail that change but continue with others

+ 111 - 0
.opencode/command/opsx-continue.md

@@ -0,0 +1,111 @@
+---
+description: Continue working on a change - create the next artifact (Experimental)
+---
+
+Continue working on a change by creating the next artifact.
+
+**Input**: Optionally specify a change name after `/opsx-continue` (e.g., `/opsx-continue add-auth`). If omitted, check if it can be inferred from conversation context. If vague or ambiguous you MUST prompt for available changes.
+
+**Steps**
+
+1. **If no change name provided, prompt for selection**
+
+   Run `openspec list --json` to get available changes sorted by most recently modified. Then use the **AskUserQuestion tool** to let the user select which change to work on.
+
+   Present the top 3-4 most recently modified changes as options, showing:
+   - Change name
+   - Schema (from `schema` field if present, otherwise "spec-driven")
+   - Status (e.g., "0/5 tasks", "complete", "no tasks")
+   - How recently it was modified (from `lastModified` field)
+
+   Mark the most recently modified change as "(Recommended)" since it's likely what the user wants to continue.
+
+   **IMPORTANT**: Do NOT guess or auto-select a change. Always let the user choose.
+
+2. **Check current status**
+   ```bash
+   openspec status --change "<name>" --json
+   ```
+   Parse the JSON to understand current state. The response includes:
+   - `schemaName`: The workflow schema being used (e.g., "spec-driven")
+   - `artifacts`: Array of artifacts with their status ("done", "ready", "blocked")
+   - `isComplete`: Boolean indicating if all artifacts are complete
+
+3. **Act based on status**:
+
+   ---
+
+   **If all artifacts are complete (`isComplete: true`)**:
+   - Congratulate the user
+   - Show final status including the schema used
+   - Suggest: "All artifacts created! You can now implement this change with `/opsx-apply` or archive it with `/opsx-archive`."
+   - STOP
+
+   ---
+
+   **If artifacts are ready to create** (status shows artifacts with `status: "ready"`):
+   - Pick the FIRST artifact with `status: "ready"` from the status output
+   - Get its instructions:
+     ```bash
+     openspec instructions <artifact-id> --change "<name>" --json
+     ```
+   - Parse the JSON. The key fields are:
+     - `context`: Project background (constraints for you - do NOT include in output)
+     - `rules`: Artifact-specific rules (constraints for you - do NOT include in output)
+     - `template`: The structure to use for your output file
+     - `instruction`: Schema-specific guidance
+     - `outputPath`: Where to write the artifact
+     - `dependencies`: Completed artifacts to read for context
+   - **Create the artifact file**:
+     - Read any completed dependency files for context
+     - Use `template` as the structure - fill in its sections
+     - Apply `context` and `rules` as constraints when writing - but do NOT copy them into the file
+     - Write to the output path specified in instructions
+   - Show what was created and what's now unlocked
+   - STOP after creating ONE artifact
+
+   ---
+
+   **If no artifacts are ready (all blocked)**:
+   - This shouldn't happen with a valid schema
+   - Show status and suggest checking for issues
+
+4. **After creating an artifact, show progress**
+   ```bash
+   openspec status --change "<name>"
+   ```
+
+**Output**
+
+After each invocation, show:
+- Which artifact was created
+- Schema workflow being used
+- Current progress (N/M complete)
+- What artifacts are now unlocked
+- Prompt: "Run `/opsx-continue` to create the next artifact"
+
+**Artifact Creation Guidelines**
+
+The artifact types and their purpose depend on the schema. Use the `instruction` field from the instructions output to understand what to create.
+
+Common artifact patterns:
+
+**spec-driven schema** (proposal → specs → design → tasks):
+- **proposal.md**: Ask user about the change if not clear. Fill in Why, What Changes, Capabilities, Impact.
+  - The Capabilities section is critical - each capability listed will need a spec file.
+- **specs/<capability>/spec.md**: Create one spec per capability listed in the proposal's Capabilities section (use the capability name, not the change name).
+- **design.md**: Document technical decisions, architecture, and implementation approach.
+- **tasks.md**: Break down implementation into checkboxed tasks.
+
+For other schemas, follow the `instruction` field from the CLI output.
+
+**Guardrails**
+- Create ONE artifact per invocation
+- Always read dependency artifacts before creating a new one
+- Never skip artifacts or create out of order
+- If context is unclear, ask the user before creating
+- Verify the artifact file exists after writing before marking progress
+- Use the schema's artifact sequence, don't assume specific artifact names
+- **IMPORTANT**: `context` and `rules` are constraints for YOU, not content for the file
+  - Do NOT copy `<context>`, `<rules>`, `<project_context>` blocks into the artifact
+  - These guide what you write, but should never appear in the output

+ 171 - 0
.opencode/command/opsx-explore.md

@@ -0,0 +1,171 @@
+---
+description: Enter explore mode - think through ideas, investigate problems, clarify requirements
+---
+
+Enter explore mode. Think deeply. Visualize freely. Follow the conversation wherever it goes.
+
+**IMPORTANT: Explore mode is for thinking, not implementing.** You may read files, search code, and investigate the codebase, but you must NEVER write code or implement features. If the user asks you to implement something, remind them to exit explore mode first (e.g., start a change with `/opsx-new` or `/opsx-ff`). You MAY create OpenSpec artifacts (proposals, designs, specs) if the user asks—that's capturing thinking, not implementing.
+
+**This is a stance, not a workflow.** There are no fixed steps, no required sequence, no mandatory outputs. You're a thinking partner helping the user explore.
+
+**Input**: The argument after `/opsx-explore` is whatever the user wants to think about. Could be:
+- A vague idea: "real-time collaboration"
+- A specific problem: "the auth system is getting unwieldy"
+- A change name: "add-dark-mode" (to explore in context of that change)
+- A comparison: "postgres vs sqlite for this"
+- Nothing (just enter explore mode)
+
+---
+
+## The Stance
+
+- **Curious, not prescriptive** - Ask questions that emerge naturally, don't follow a script
+- **Open threads, not interrogations** - Surface multiple interesting directions and let the user follow what resonates. Don't funnel them through a single path of questions.
+- **Visual** - Use ASCII diagrams liberally when they'd help clarify thinking
+- **Adaptive** - Follow interesting threads, pivot when new information emerges
+- **Patient** - Don't rush to conclusions, let the shape of the problem emerge
+- **Grounded** - Explore the actual codebase when relevant, don't just theorize
+
+---
+
+## What You Might Do
+
+Depending on what the user brings, you might:
+
+**Explore the problem space**
+- Ask clarifying questions that emerge from what they said
+- Challenge assumptions
+- Reframe the problem
+- Find analogies
+
+**Investigate the codebase**
+- Map existing architecture relevant to the discussion
+- Find integration points
+- Identify patterns already in use
+- Surface hidden complexity
+
+**Compare options**
+- Brainstorm multiple approaches
+- Build comparison tables
+- Sketch tradeoffs
+- Recommend a path (if asked)
+
+**Visualize**
+```
+┌─────────────────────────────────────────┐
+│     Use ASCII diagrams liberally        │
+├─────────────────────────────────────────┤
+│                                         │
+│   ┌────────┐         ┌────────┐        │
+│   │ State  │────────▶│ State  │        │
+│   │   A    │         │   B    │        │
+│   └────────┘         └────────┘        │
+│                                         │
+│   System diagrams, state machines,      │
+│   data flows, architecture sketches,    │
+│   dependency graphs, comparison tables  │
+│                                         │
+└─────────────────────────────────────────┘
+```
+
+**Surface risks and unknowns**
+- Identify what could go wrong
+- Find gaps in understanding
+- Suggest spikes or investigations
+
+---
+
+## OpenSpec Awareness
+
+You have full context of the OpenSpec system. Use it naturally, don't force it.
+
+### Check for context
+
+At the start, quickly check what exists:
+```bash
+openspec list --json
+```
+
+This tells you:
+- If there are active changes
+- Their names, schemas, and status
+- What the user might be working on
+
+If the user mentioned a specific change name, read its artifacts for context.
+
+### When no change exists
+
+Think freely. When insights crystallize, you might offer:
+
+- "This feels solid enough to start a change. Want me to create one?"
+  → Can transition to `/opsx-new` or `/opsx-ff`
+- Or keep exploring - no pressure to formalize
+
+### When a change exists
+
+If the user mentions a change or you detect one is relevant:
+
+1. **Read existing artifacts for context**
+   - `openspec/changes/<name>/proposal.md`
+   - `openspec/changes/<name>/design.md`
+   - `openspec/changes/<name>/tasks.md`
+   - etc.
+
+2. **Reference them naturally in conversation**
+   - "Your design mentions using Redis, but we just realized SQLite fits better..."
+   - "The proposal scopes this to premium users, but we're now thinking everyone..."
+
+3. **Offer to capture when decisions are made**
+
+   | Insight Type | Where to Capture |
+   |--------------|------------------|
+   | New requirement discovered | `specs/<capability>/spec.md` |
+   | Requirement changed | `specs/<capability>/spec.md` |
+   | Design decision made | `design.md` |
+   | Scope changed | `proposal.md` |
+   | New work identified | `tasks.md` |
+   | Assumption invalidated | Relevant artifact |
+
+   Example offers:
+   - "That's a design decision. Capture it in design.md?"
+   - "This is a new requirement. Add it to specs?"
+   - "This changes scope. Update the proposal?"
+
+4. **The user decides** - Offer and move on. Don't pressure. Don't auto-capture.
+
+---
+
+## What You Don't Have To Do
+
+- Follow a script
+- Ask the same questions every time
+- Produce a specific artifact
+- Reach a conclusion
+- Stay on topic if a tangent is valuable
+- Be brief (this is thinking time)
+
+---
+
+## Ending Discovery
+
+There's no required ending. Discovery might:
+
+- **Flow into action**: "Ready to start? `/opsx-new` or `/opsx-ff`"
+- **Result in artifact updates**: "Updated design.md with these decisions"
+- **Just provide clarity**: User has what they need, moves on
+- **Continue later**: "We can pick this up anytime"
+
+When things crystallize, you might offer a summary - but it's optional. Sometimes the thinking IS the value.
+
+---
+
+## Guardrails
+
+- **Don't implement** - Never write code or implement features. Creating OpenSpec artifacts is fine, writing application code is not.
+- **Don't fake understanding** - If something is unclear, dig deeper
+- **Don't rush** - Discovery is thinking time, not task time
+- **Don't force structure** - Let patterns emerge naturally
+- **Don't auto-capture** - Offer to save insights, don't just do it
+- **Do visualize** - A good diagram is worth many paragraphs
+- **Do explore the codebase** - Ground discussions in reality
+- **Do question assumptions** - Including the user's and your own

+ 91 - 0
.opencode/command/opsx-ff.md

@@ -0,0 +1,91 @@
+---
+description: Create a change and generate all artifacts needed for implementation in one go
+---
+
+Fast-forward through artifact creation - generate everything needed to start implementation.
+
+**Input**: The argument after `/opsx-ff` is the change name (kebab-case), OR a description of what the user wants to build.
+
+**Steps**
+
+1. **If no input provided, ask what they want to build**
+
+   Use the **AskUserQuestion tool** (open-ended, no preset options) to ask:
+   > "What change do you want to work on? Describe what you want to build or fix."
+
+   From their description, derive a kebab-case name (e.g., "add user authentication" → `add-user-auth`).
+
+   **IMPORTANT**: Do NOT proceed without understanding what the user wants to build.
+
+2. **Create the change directory**
+   ```bash
+   openspec new change "<name>"
+   ```
+   This creates a scaffolded change at `openspec/changes/<name>/`.
+
+3. **Get the artifact build order**
+   ```bash
+   openspec status --change "<name>" --json
+   ```
+   Parse the JSON to get:
+   - `applyRequires`: array of artifact IDs needed before implementation (e.g., `["tasks"]`)
+   - `artifacts`: list of all artifacts with their status and dependencies
+
+4. **Create artifacts in sequence until apply-ready**
+
+   Use the **TodoWrite tool** to track progress through the artifacts.
+
+   Loop through artifacts in dependency order (artifacts with no pending dependencies first):
+
+   a. **For each artifact that is `ready` (dependencies satisfied)**:
+      - Get instructions:
+        ```bash
+        openspec instructions <artifact-id> --change "<name>" --json
+        ```
+      - The instructions JSON includes:
+        - `context`: Project background (constraints for you - do NOT include in output)
+        - `rules`: Artifact-specific rules (constraints for you - do NOT include in output)
+        - `template`: The structure to use for your output file
+        - `instruction`: Schema-specific guidance for this artifact type
+        - `outputPath`: Where to write the artifact
+        - `dependencies`: Completed artifacts to read for context
+      - Read any completed dependency files for context
+      - Create the artifact file using `template` as the structure
+      - Apply `context` and `rules` as constraints - but do NOT copy them into the file
+      - Show brief progress: "✓ Created <artifact-id>"
+
+   b. **Continue until all `applyRequires` artifacts are complete**
+      - After creating each artifact, re-run `openspec status --change "<name>" --json`
+      - Check if every artifact ID in `applyRequires` has `status: "done"` in the artifacts array
+      - Stop when all `applyRequires` artifacts are done
+
+   c. **If an artifact requires user input** (unclear context):
+      - Use **AskUserQuestion tool** to clarify
+      - Then continue with creation
+
+5. **Show final status**
+   ```bash
+   openspec status --change "<name>"
+   ```
+
+**Output**
+
+After completing all artifacts, summarize:
+- Change name and location
+- List of artifacts created with brief descriptions
+- What's ready: "All artifacts created! Ready for implementation."
+- Prompt: "Run `/opsx-apply` to start implementing."
+
+**Artifact Creation Guidelines**
+
+- Follow the `instruction` field from `openspec instructions` for each artifact type
+- The schema defines what each artifact should contain - follow it
+- Read dependency artifacts for context before creating new ones
+- Use the `template` as a starting point, filling in based on context
+
+**Guardrails**
+- Create ALL artifacts needed for implementation (as defined by schema's `apply.requires`)
+- Always read dependency artifacts before creating a new one
+- If context is critically unclear, ask the user - but prefer making reasonable decisions to keep momentum
+- If a change with that name already exists, ask if user wants to continue it or create a new one
+- Verify each artifact file exists after writing before proceeding to next

+ 66 - 0
.opencode/command/opsx-new.md

@@ -0,0 +1,66 @@
+---
+description: Start a new change using the experimental artifact workflow (OPSX)
+---
+
+Start a new change using the experimental artifact-driven approach.
+
+**Input**: The argument after `/opsx-new` is the change name (kebab-case), OR a description of what the user wants to build.
+
+**Steps**
+
+1. **If no input provided, ask what they want to build**
+
+   Use the **AskUserQuestion tool** (open-ended, no preset options) to ask:
+   > "What change do you want to work on? Describe what you want to build or fix."
+
+   From their description, derive a kebab-case name (e.g., "add user authentication" → `add-user-auth`).
+
+   **IMPORTANT**: Do NOT proceed without understanding what the user wants to build.
+
+2. **Determine the workflow schema**
+
+   Use the default schema (omit `--schema`) unless the user explicitly requests a different workflow.
+
+   **Use a different schema only if the user mentions:**
+   - A specific schema name → use `--schema <name>`
+   - "show workflows" or "what workflows" → run `openspec schemas --json` and let them choose
+
+   **Otherwise**: Omit `--schema` to use the default.
+
+3. **Create the change directory**
+   ```bash
+   openspec new change "<name>"
+   ```
+   Add `--schema <name>` only if the user requested a specific workflow.
+   This creates a scaffolded change at `openspec/changes/<name>/` with the selected schema.
+
+4. **Show the artifact status**
+   ```bash
+   openspec status --change "<name>"
+   ```
+   This shows which artifacts need to be created and which are ready (dependencies satisfied).
+
+5. **Get instructions for the first artifact**
+   The first artifact depends on the schema. Check the status output to find the first artifact with status "ready".
+   ```bash
+   openspec instructions <first-artifact-id> --change "<name>"
+   ```
+   This outputs the template and context for creating the first artifact.
+
+6. **STOP and wait for user direction**
+
+**Output**
+
+After completing the steps, summarize:
+- Change name and location
+- Schema/workflow being used and its artifact sequence
+- Current status (0/N artifacts complete)
+- The template for the first artifact
+- Prompt: "Ready to create the first artifact? Run `/opsx-continue` or just describe what this change is about and I'll draft it."
+
+**Guardrails**
+- Do NOT create any artifacts yet - just show the instructions
+- Do NOT advance beyond showing the first artifact template
+- If the name is invalid (not kebab-case), ask for a valid name
+- If a change with that name already exists, suggest using `/opsx-continue` instead
+- Pass --schema if using a non-default workflow

+ 531 - 0
.opencode/command/opsx-onboard.md

@@ -0,0 +1,531 @@
+---
+description: Guided onboarding - walk through a complete OpenSpec workflow cycle with narration
+---
+
+Guide the user through their first complete OpenSpec workflow cycle. This is a teaching experience—you'll do real work in their codebase while explaining each step.
+
+---
+
+## Preflight
+
+Before starting, check if the OpenSpec CLI is installed:
+
+```bash
+# Unix/macOS
+openspec --version 2>&1 || echo "CLI_NOT_INSTALLED"
+# Windows (PowerShell)
+# if (Get-Command openspec -ErrorAction SilentlyContinue) { openspec --version } else { echo "CLI_NOT_INSTALLED" }
+```
+
+**If CLI not installed:**
+> OpenSpec CLI is not installed. Install it first, then come back to `/opsx-onboard`.
+
+Stop here if not installed.
+
+---
+
+## Phase 1: Welcome
+
+Display:
+
+```
+## Welcome to OpenSpec!
+
+I'll walk you through a complete change cycle—from idea to implementation—using a real task in your codebase. Along the way, you'll learn the workflow by doing it.
+
+**What we'll do:**
+1. Pick a small, real task in your codebase
+2. Explore the problem briefly
+3. Create a change (the container for our work)
+4. Build the artifacts: proposal → specs → design → tasks
+5. Implement the tasks
+6. Archive the completed change
+
+**Time:** ~15-20 minutes
+
+Let's start by finding something to work on.
+```
+
+---
+
+## Phase 2: Task Selection
+
+### Codebase Analysis
+
+Scan the codebase for small improvement opportunities. Look for:
+
+1. **TODO/FIXME comments** - Search for `TODO`, `FIXME`, `HACK`, `XXX` in code files
+2. **Missing error handling** - `catch` blocks that swallow errors, risky operations without try-catch
+3. **Functions without tests** - Cross-reference `src/` with test directories
+4. **Type issues** - `any` types in TypeScript files (`: any`, `as any`)
+5. **Debug artifacts** - `console.log`, `console.debug`, `debugger` statements in non-debug code
+6. **Missing validation** - User input handlers without validation
+
+Also check recent git activity:
+```bash
+# Unix/macOS
+git log --oneline -10 2>/dev/null || echo "No git history"
+# Windows (PowerShell)
+# git log --oneline -10 2>$null; if ($LASTEXITCODE -ne 0) { echo "No git history" }
+```
+
+### Present Suggestions
+
+From your analysis, present 3-4 specific suggestions:
+
+```
+## Task Suggestions
+
+Based on scanning your codebase, here are some good starter tasks:
+
+**1. [Most promising task]**
+   Location: `src/path/to/file.ts:42`
+   Scope: ~1-2 files, ~20-30 lines
+   Why it's good: [brief reason]
+
+**2. [Second task]**
+   Location: `src/another/file.ts`
+   Scope: ~1 file, ~15 lines
+   Why it's good: [brief reason]
+
+**3. [Third task]**
+   Location: [location]
+   Scope: [estimate]
+   Why it's good: [brief reason]
+
+**4. Something else?**
+   Tell me what you'd like to work on.
+
+Which task interests you? (Pick a number or describe your own)
+```
+
+**If nothing found:** Fall back to asking what the user wants to build:
+> I didn't find obvious quick wins in your codebase. What's something small you've been meaning to add or fix?
+
+### Scope Guardrail
+
+If the user picks or describes something too large (major feature, multi-day work):
+
+```
+That's a valuable task, but it's probably larger than ideal for your first OpenSpec run-through.
+
+For learning the workflow, smaller is better—it lets you see the full cycle without getting stuck in implementation details.
+
+**Options:**
+1. **Slice it smaller** - What's the smallest useful piece of [their task]? Maybe just [specific slice]?
+2. **Pick something else** - One of the other suggestions, or a different small task?
+3. **Do it anyway** - If you really want to tackle this, we can. Just know it'll take longer.
+
+What would you prefer?
+```
+
+Let the user override if they insist—this is a soft guardrail.
+
+---
+
+## Phase 3: Explore Demo
+
+Once a task is selected, briefly demonstrate explore mode:
+
+```
+Before we create a change, let me quickly show you **explore mode**—it's how you think through problems before committing to a direction.
+```
+
+Spend 1-2 minutes investigating the relevant code:
+- Read the file(s) involved
+- Draw a quick ASCII diagram if it helps
+- Note any considerations
+
+```
+## Quick Exploration
+
+[Your brief analysis—what you found, any considerations]
+
+┌─────────────────────────────────────────┐
+│   [Optional: ASCII diagram if helpful]  │
+└─────────────────────────────────────────┘
+
+Explore mode (`/opsx-explore`) is for this kind of thinking—investigating before implementing. You can use it anytime you need to think through a problem.
+
+Now let's create a change to hold our work.
+```
+
+**PAUSE** - Wait for user acknowledgment before proceeding.
+
+---
+
+## Phase 4: Create the Change
+
+**EXPLAIN:**
+```
+## Creating a Change
+
+A "change" in OpenSpec is a container for all the thinking and planning around a piece of work. It lives in `openspec/changes/<name>/` and holds your artifacts—proposal, specs, design, tasks.
+
+Let me create one for our task.
+```
+
+**DO:** Create the change with a derived kebab-case name:
+```bash
+openspec new change "<derived-name>"
+```
+
+**SHOW:**
+```
+Created: `openspec/changes/<name>/`
+
+The folder structure:
+```
+openspec/changes/<name>/
+├── proposal.md    ← Why we're doing this (empty, we'll fill it)
+├── design.md      ← How we'll build it (empty)
+├── specs/         ← Detailed requirements (empty)
+└── tasks.md       ← Implementation checklist (empty)
+```
+
+Now let's fill in the first artifact—the proposal.
+```
+
+---
+
+## Phase 5: Proposal
+
+**EXPLAIN:**
+```
+## The Proposal
+
+The proposal captures **why** we're making this change and **what** it involves at a high level. It's the "elevator pitch" for the work.
+
+I'll draft one based on our task.
+```
+
+**DO:** Draft the proposal content (don't save yet):
+
+```
+Here's a draft proposal:
+
+---
+
+## Why
+
+[1-2 sentences explaining the problem/opportunity]
+
+## What Changes
+
+[Bullet points of what will be different]
+
+## Capabilities
+
+### New Capabilities
+- `<capability-name>`: [brief description]
+
+### Modified Capabilities
+<!-- If modifying existing behavior -->
+
+## Impact
+
+- `src/path/to/file.ts`: [what changes]
+- [other files if applicable]
+
+---
+
+Does this capture the intent? I can adjust before we save it.
+```
+
+**PAUSE** - Wait for user approval/feedback.
+
+After approval, save the proposal:
+```bash
+openspec instructions proposal --change "<name>" --json
+```
+Then write the content to `openspec/changes/<name>/proposal.md`.
+
+```
+Proposal saved. This is your "why" document—you can always come back and refine it as understanding evolves.
+
+Next up: specs.
+```
+
+---
+
+## Phase 6: Specs
+
+**EXPLAIN:**
+```
+## Specs
+
+Specs define **what** we're building in precise, testable terms. They use a requirement/scenario format that makes expected behavior crystal clear.
+
+For a small task like this, we might only need one spec file.
+```
+
+**DO:** Create the spec file:
+```bash
+# Unix/macOS
+mkdir -p openspec/changes/<name>/specs/<capability-name>
+# Windows (PowerShell)
+# New-Item -ItemType Directory -Force -Path "openspec/changes/<name>/specs/<capability-name>"
+```
+
+Draft the spec content:
+
+```
+Here's the spec:
+
+---
+
+## ADDED Requirements
+
+### Requirement: <Name>
+
+<Description of what the system should do>
+
+#### Scenario: <Scenario name>
+
+- **WHEN** <trigger condition>
+- **THEN** <expected outcome>
+- **AND** <additional outcome if needed>
+
+---
+
+This format—WHEN/THEN/AND—makes requirements testable. You can literally read them as test cases.
+```
+
+Save to `openspec/changes/<name>/specs/<capability>/spec.md`.
+
+---
+
+## Phase 7: Design
+
+**EXPLAIN:**
+```
+## Design
+
+The design captures **how** we'll build it—technical decisions, tradeoffs, approach.
+
+For small changes, this might be brief. That's fine—not every change needs deep design discussion.
+```
+
+**DO:** Draft design.md:
+
+```
+Here's the design:
+
+---
+
+## Context
+
+[Brief context about the current state]
+
+## Goals / Non-Goals
+
+**Goals:**
+- [What we're trying to achieve]
+
+**Non-Goals:**
+- [What's explicitly out of scope]
+
+## Decisions
+
+### Decision 1: [Key decision]
+
+[Explanation of approach and rationale]
+
+---
+
+For a small task, this captures the key decisions without over-engineering.
+```
+
+Save to `openspec/changes/<name>/design.md`.
+
+---
+
+## Phase 8: Tasks
+
+**EXPLAIN:**
+```
+## Tasks
+
+Finally, we break the work into implementation tasks—checkboxes that drive the apply phase.
+
+These should be small, clear, and in logical order.
+```
+
+**DO:** Generate tasks based on specs and design:
+
+```
+Here are the implementation tasks:
+
+---
+
+## 1. [Category or file]
+
+- [ ] 1.1 [Specific task]
+- [ ] 1.2 [Specific task]
+
+## 2. Verify
+
+- [ ] 2.1 [Verification step]
+
+---
+
+Each checkbox becomes a unit of work in the apply phase. Ready to implement?
+```
+
+**PAUSE** - Wait for user to confirm they're ready to implement.
+
+Save to `openspec/changes/<name>/tasks.md`.
+
+---
+
+## Phase 9: Apply (Implementation)
+
+**EXPLAIN:**
+```
+## Implementation
+
+Now we implement each task, checking them off as we go. I'll announce each one and occasionally note how the specs/design informed the approach.
+```
+
+**DO:** For each task:
+
+1. Announce: "Working on task N: [description]"
+2. Implement the change in the codebase
+3. Reference specs/design naturally: "The spec says X, so I'm doing Y"
+4. Mark complete in tasks.md: `- [ ]` → `- [x]`
+5. Brief status: "✓ Task N complete"
+
+Keep narration light—don't over-explain every line of code.
+
+After all tasks:
+
+```
+## Implementation Complete
+
+All tasks done:
+- [x] Task 1
+- [x] Task 2
+- [x] ...
+
+The change is implemented! One more step—let's archive it.
+```
+
+---
+
+## Phase 10: Archive
+
+**EXPLAIN:**
+```
+## Archiving
+
+When a change is complete, we archive it. This moves it from `openspec/changes/` to `openspec/changes/archive/YYYY-MM-DD-<name>/`.
+
+Archived changes become your project's decision history—you can always find them later to understand why something was built a certain way.
+```
+
+**DO:**
+```bash
+openspec archive "<name>"
+```
+
+**SHOW:**
+```
+Archived to: `openspec/changes/archive/YYYY-MM-DD-<name>/`
+
+The change is now part of your project's history. The code is in your codebase, the decision record is preserved.
+```
+
+---
+
+## Phase 11: Recap & Next Steps
+
+```
+## Congratulations!
+
+You just completed a full OpenSpec cycle:
+
+1. **Explore** - Thought through the problem
+2. **New** - Created a change container
+3. **Proposal** - Captured WHY
+4. **Specs** - Defined WHAT in detail
+5. **Design** - Decided HOW
+6. **Tasks** - Broke it into steps
+7. **Apply** - Implemented the work
+8. **Archive** - Preserved the record
+
+This same rhythm works for any size change—a small fix or a major feature.
+
+---
+
+## Command Reference
+
+| Command | What it does |
+|---------|--------------|
+| `/opsx-explore` | Think through problems before/during work |
+| `/opsx-new` | Start a new change, step through artifacts |
+| `/opsx-ff` | Fast-forward: create all artifacts at once |
+| `/opsx-continue` | Continue working on an existing change |
+| `/opsx-apply` | Implement tasks from a change |
+| `/opsx-verify` | Verify implementation matches artifacts |
+| `/opsx-archive` | Archive a completed change |
+
+---
+
+## What's Next?
+
+Try `/opsx-new` or `/opsx-ff` on something you actually want to build. You've got the rhythm now!
+```
+
+---
+
+## Graceful Exit Handling
+
+### User wants to stop mid-way
+
+If the user says they need to stop, want to pause, or seem disengaged:
+
+```
+No problem! Your change is saved at `openspec/changes/<name>/`.
+
+To pick up where we left off later:
+- `/opsx-continue <name>` - Resume artifact creation
+- `/opsx-apply <name>` - Jump to implementation (if tasks exist)
+
+The work won't be lost. Come back whenever you're ready.
+```
+
+Exit gracefully without pressure.
+
+### User just wants command reference
+
+If the user says they just want to see the commands or skip the tutorial:
+
+```
+## OpenSpec Quick Reference
+
+| Command | What it does |
+|---------|--------------|
+| `/opsx-explore` | Think through problems (no code changes) |
+| `/opsx-new <name>` | Start a new change, step by step |
+| `/opsx-ff <name>` | Fast-forward: all artifacts at once |
+| `/opsx-continue <name>` | Continue an existing change |
+| `/opsx-apply <name>` | Implement tasks |
+| `/opsx-verify <name>` | Verify implementation |
+| `/opsx-archive <name>` | Archive when done |
+
+Try `/opsx-new` to start your first change, or `/opsx-ff` if you want to move fast.
+```
+
+Exit gracefully.
+
+---
+
+## Guardrails
+
+- **Follow the EXPLAIN → DO → SHOW → PAUSE pattern** at key transitions (after explore, after proposal draft, after tasks, after archive)
+- **Keep narration light** during implementation—teach without lecturing
+- **Don't skip phases** even if the change is small—the goal is teaching the workflow
+- **Pause for acknowledgment** at marked points, but don't over-pause
+- **Handle exits gracefully**—never pressure the user to continue
+- **Use real codebase tasks**—don't simulate or use fake examples
+- **Adjust scope gently**—guide toward smaller tasks but respect user choice

+ 131 - 0
.opencode/command/opsx-sync.md

@@ -0,0 +1,131 @@
+---
+description: Sync delta specs from a change to main specs
+---
+
+Sync delta specs from a change to main specs.
+
+This is an **agent-driven** operation - you will read delta specs and directly edit main specs to apply the changes. This allows intelligent merging (e.g., adding a scenario without copying the entire requirement).
+
+**Input**: Optionally specify a change name after `/opsx-sync` (e.g., `/opsx-sync add-auth`). If omitted, check if it can be inferred from conversation context. If vague or ambiguous you MUST prompt for available changes.
+
+**Steps**
+
+1. **If no change name provided, prompt for selection**
+
+   Run `openspec list --json` to get available changes. Use the **AskUserQuestion tool** to let the user select.
+
+   Show changes that have delta specs (under `specs/` directory).
+
+   **IMPORTANT**: Do NOT guess or auto-select a change. Always let the user choose.
+
+2. **Find delta specs**
+
+   Look for delta spec files in `openspec/changes/<name>/specs/*/spec.md`.
+
+   Each delta spec file contains sections like:
+   - `## ADDED Requirements` - New requirements to add
+   - `## MODIFIED Requirements` - Changes to existing requirements
+   - `## REMOVED Requirements` - Requirements to remove
+   - `## RENAMED Requirements` - Requirements to rename (FROM:/TO: format)
+
+   If no delta specs found, inform user and stop.
+
+3. **For each delta spec, apply changes to main specs**
+
+   For each capability with a delta spec at `openspec/changes/<name>/specs/<capability>/spec.md`:
+
+   a. **Read the delta spec** to understand the intended changes
+
+   b. **Read the main spec** at `openspec/specs/<capability>/spec.md` (may not exist yet)
+
+   c. **Apply changes intelligently**:
+
+      **ADDED Requirements:**
+      - If requirement doesn't exist in main spec → add it
+      - If requirement already exists → update it to match (treat as implicit MODIFIED)
+
+      **MODIFIED Requirements:**
+      - Find the requirement in main spec
+      - Apply the changes - this can be:
+        - Adding new scenarios (don't need to copy existing ones)
+        - Modifying existing scenarios
+        - Changing the requirement description
+      - Preserve scenarios/content not mentioned in the delta
+
+      **REMOVED Requirements:**
+      - Remove the entire requirement block from main spec
+
+      **RENAMED Requirements:**
+      - Find the FROM requirement, rename to TO
+
+   d. **Create new main spec** if capability doesn't exist yet:
+      - Create `openspec/specs/<capability>/spec.md`
+      - Add Purpose section (can be brief, mark as TBD)
+      - Add Requirements section with the ADDED requirements
+
+4. **Show summary**
+
+   After applying all changes, summarize:
+   - Which capabilities were updated
+   - What changes were made (requirements added/modified/removed/renamed)
+
+**Delta Spec Format Reference**
+
+```markdown
+## ADDED Requirements
+
+### Requirement: New Feature
+The system SHALL do something new.
+
+#### Scenario: Basic case
+- **WHEN** user does X
+- **THEN** system does Y
+
+## MODIFIED Requirements
+
+### Requirement: Existing Feature
+#### Scenario: New scenario to add
+- **WHEN** user does A
+- **THEN** system does B
+
+## REMOVED Requirements
+
+### Requirement: Deprecated Feature
+
+## RENAMED Requirements
+
+- FROM: `### Requirement: Old Name`
+- TO: `### Requirement: New Name`
+```
+
+**Key Principle: Intelligent Merging**
+
+Unlike programmatic merging, you can apply **partial updates**:
+- To add a scenario, just include that scenario under MODIFIED - don't copy existing scenarios
+- The delta represents *intent*, not a wholesale replacement
+- Use your judgment to merge changes sensibly
+
+**Output On Success**
+
+```
+## Specs Synced: <change-name>
+
+Updated main specs:
+
+**<capability-1>**:
+- Added requirement: "New Feature"
+- Modified requirement: "Existing Feature" (added 1 scenario)
+
+**<capability-2>**:
+- Created new spec file
+- Added requirement: "Another Feature"
+
+Main specs are now updated. The change remains active - archive when implementation is complete.
+```
+
+**Guardrails**
+- Read both delta and main specs before making changes
+- Preserve existing content not mentioned in delta
+- If something is unclear, ask for clarification
+- Show what you're changing as you go
+- The operation should be idempotent - running twice should give same result

+ 161 - 0
.opencode/command/opsx-verify.md

@@ -0,0 +1,161 @@
+---
+description: Verify implementation matches change artifacts before archiving
+---
+
+Verify that an implementation matches the change artifacts (specs, tasks, design).
+
+**Input**: Optionally specify a change name after `/opsx-verify` (e.g., `/opsx-verify add-auth`). If omitted, check if it can be inferred from conversation context. If vague or ambiguous you MUST prompt for available changes.
+
+**Steps**
+
+1. **If no change name provided, prompt for selection**
+
+   Run `openspec list --json` to get available changes. Use the **AskUserQuestion tool** to let the user select.
+
+   Show changes that have implementation tasks (tasks artifact exists).
+   Include the schema used for each change if available.
+   Mark changes with incomplete tasks as "(In Progress)".
+
+   **IMPORTANT**: Do NOT guess or auto-select a change. Always let the user choose.
+
+2. **Check status to understand the schema**
+   ```bash
+   openspec status --change "<name>" --json
+   ```
+   Parse the JSON to understand:
+   - `schemaName`: The workflow being used (e.g., "spec-driven")
+   - Which artifacts exist for this change
+
+3. **Get the change directory and load artifacts**
+
+   ```bash
+   openspec instructions apply --change "<name>" --json
+   ```
+
+   This returns the change directory and context files. Read all available artifacts from `contextFiles`.
+
+4. **Initialize verification report structure**
+
+   Create a report structure with three dimensions:
+   - **Completeness**: Track tasks and spec coverage
+   - **Correctness**: Track requirement implementation and scenario coverage
+   - **Coherence**: Track design adherence and pattern consistency
+
+   Each dimension can have CRITICAL, WARNING, or SUGGESTION issues.
+
+5. **Verify Completeness**
+
+   **Task Completion**:
+   - If tasks.md exists in contextFiles, read it
+   - Parse checkboxes: `- [ ]` (incomplete) vs `- [x]` (complete)
+   - Count complete vs total tasks
+   - If incomplete tasks exist:
+     - Add CRITICAL issue for each incomplete task
+     - Recommendation: "Complete task: <description>" or "Mark as done if already implemented"
+
+   **Spec Coverage**:
+   - If delta specs exist in `openspec/changes/<name>/specs/`:
+     - Extract all requirements (marked with "### Requirement:")
+     - For each requirement:
+       - Search codebase for keywords related to the requirement
+       - Assess if implementation likely exists
+     - If requirements appear unimplemented:
+       - Add CRITICAL issue: "Requirement not found: <requirement name>"
+       - Recommendation: "Implement requirement X: <description>"
+
+6. **Verify Correctness**
+
+   **Requirement Implementation Mapping**:
+   - For each requirement from delta specs:
+     - Search codebase for implementation evidence
+     - If found, note file paths and line ranges
+     - Assess if implementation matches requirement intent
+     - If divergence detected:
+       - Add WARNING: "Implementation may diverge from spec: <details>"
+       - Recommendation: "Review <file>:<lines> against requirement X"
+
+   **Scenario Coverage**:
+   - For each scenario in delta specs (marked with "#### Scenario:"):
+     - Check if conditions are handled in code
+     - Check if tests exist covering the scenario
+     - If scenario appears uncovered:
+       - Add WARNING: "Scenario not covered: <scenario name>"
+       - Recommendation: "Add test or implementation for scenario: <description>"
+
+7. **Verify Coherence**
+
+   **Design Adherence**:
+   - If design.md exists in contextFiles:
+     - Extract key decisions (look for sections like "Decision:", "Approach:", "Architecture:")
+     - Verify implementation follows those decisions
+     - If contradiction detected:
+       - Add WARNING: "Design decision not followed: <decision>"
+       - Recommendation: "Update implementation or revise design.md to match reality"
+   - If no design.md: Skip design adherence check, note "No design.md to verify against"
+
+   **Code Pattern Consistency**:
+   - Review new code for consistency with project patterns
+   - Check file naming, directory structure, coding style
+   - If significant deviations found:
+     - Add SUGGESTION: "Code pattern deviation: <details>"
+     - Recommendation: "Consider following project pattern: <example>"
+
+8. **Generate Verification Report**
+
+   **Summary Scorecard**:
+   ```
+   ## Verification Report: <change-name>
+
+   ### Summary
+   | Dimension    | Status           |
+   |--------------|------------------|
+   | Completeness | X/Y tasks, N reqs|
+   | Correctness  | M/N reqs covered |
+   | Coherence    | Followed/Issues  |
+   ```
+
+   **Issues by Priority**:
+
+   1. **CRITICAL** (Must fix before archive):
+      - Incomplete tasks
+      - Missing requirement implementations
+      - Each with specific, actionable recommendation
+
+   2. **WARNING** (Should fix):
+      - Spec/design divergences
+      - Missing scenario coverage
+      - Each with specific recommendation
+
+   3. **SUGGESTION** (Nice to fix):
+      - Pattern inconsistencies
+      - Minor improvements
+      - Each with specific recommendation
+
+   **Final Assessment**:
+   - If CRITICAL issues: "X critical issue(s) found. Fix before archiving."
+   - If only warnings: "No critical issues. Y warning(s) to consider. Ready for archive (with noted improvements)."
+   - If all clear: "All checks passed. Ready for archive."
+
+**Verification Heuristics**
+
+- **Completeness**: Focus on objective checklist items (checkboxes, requirements list)
+- **Correctness**: Use keyword search, file path analysis, reasonable inference - don't require perfect certainty
+- **Coherence**: Look for glaring inconsistencies, don't nitpick style
+- **False Positives**: When uncertain, prefer SUGGESTION over WARNING, WARNING over CRITICAL
+- **Actionability**: Every issue must have a specific recommendation with file/line references where applicable
+
+**Graceful Degradation**
+
+- If only tasks.md exists: verify task completion only, skip spec/design checks
+- If tasks + specs exist: verify completeness and correctness, skip design
+- If full artifacts: verify all three dimensions
+- Always note which checks were skipped and why
+
+**Output Format**
+
+Use clear markdown with:
+- Table for summary scorecard
+- Grouped lists for issues (CRITICAL/WARNING/SUGGESTION)
+- Code references in format: `file.ts:123`
+- Specific, actionable recommendations
+- No vague suggestions like "consider reviewing"

+ 156 - 0
.opencode/skills/openspec-apply-change/SKILL.md

@@ -0,0 +1,156 @@
+---
+name: openspec-apply-change
+description: 实施 OpenSpec 变更中的任务。当用户想要开始实施、继续实施或处理任务时使用。
+license: MIT
+compatibility: Requires openspec CLI.
+metadata:
+  author: openspec
+  version: "1.0"
+  generatedBy: "1.1.1"
+---
+
+实施 OpenSpec 变更中的任务。
+
+**输入**:可选择指定变更名称。如果省略,检查是否可以从对话上下文中推断。如果模糊或不明确,必须提示可用的变更。
+
+**步骤**
+
+1. **选择变更**
+
+   如果提供了名称,使用它。否则:
+   - 如果用户提到变更,从对话上下文中推断
+   - 如果只有一个活跃变更,自动选择
+   - 如果模糊,运行 `openspec list --json` 获取可用变更,并使用 **AskUserQuestion 工具** 让用户选择
+
+   始终宣布:"正在使用变更:<name>" 以及如何覆盖(例如,`/opsx-apply <other>`)。
+
+2. **检查状态以了解 Schema**
+   ```bash
+   openspec status --change "<name>" --json
+   ```
+   解析 JSON 以了解:
+   - `schemaName`:正在使用的工作流(例如,"spec-driven")
+   - 哪个产物包含任务(通常是 "tasks",对于其他 Schema 检查状态)
+
+3. **获取实施指令**
+
+   ```bash
+   openspec instructions apply --change "<name>" --json
+   ```
+
+   这返回:
+   - 上下文文件路径(因 Schema 而异 - 可能是 proposal/specs/design/tasks 或 spec/tests/implementation/docs)
+   - 进度(总数,完成,剩余)
+   - 带状态的任务列表
+   - 基于当前状态的动态指令
+
+   **处理状态:**
+   - 如果 `state: "blocked"`(缺少产物):显示消息,建议使用 openspec-continue-change
+   - 如果 `state: "all_done"`:祝贺,建议归档
+   - 否则:继续实施
+
+4. **阅读上下文文件**
+
+   阅读实施指令输出中 `contextFiles` 列出的文件。
+   文件取决于正在使用的 Schema:
+   - **spec-driven**:proposal, specs, design, tasks
+   - 其他 Schema:遵循 CLI 输出中的 contextFiles
+
+5. **显示当前进度**
+
+   显示:
+   - 正在使用的 Schema
+   - 进度:"N/M 任务完成"
+   - 剩余任务概览
+   - 来自 CLI 的动态指令
+
+6. **实施任务(循环直到完成或阻塞)**
+
+   对于每个待处理任务:
+   - 显示正在处理哪个任务
+   - 进行所需的代码更改
+   - 保持更改最小且专注
+   - 在任务文件中标记任务完成:`- [ ]` → `- [x]`
+   - 继续下一个任务
+
+   **暂停如果:**
+   - 任务不清楚 → 寻求澄清
+   - 实施揭示了设计问题 → 建议更新产物
+   - 遇到错误或阻碍 → 报告并等待指导
+   - 用户打断
+
+7. **完成或暂停时,显示状态**
+
+   显示:
+   - 本次会话完成的任务
+   - 总体进度:"N/M 任务完成"
+   - 如果全部完成:建议归档
+   - 如果暂停:解释原因并等待指导
+
+**实施期间的输出**
+
+```
+## 正在实施:<change-name> (schema: <schema-name>)
+
+正在处理任务 3/7:<task description>
+[...实施中...]
+✓ 任务完成
+
+正在处理任务 4/7:<task description>
+[...实施中...]
+✓ 任务完成
+```
+
+**完成时的输出**
+
+```
+## 实施完成
+
+**变更:** <change-name>
+**Schema:** <schema-name>
+**进度:** 7/7 任务完成 ✓
+
+### 本次会话完成
+- [x] Task 1
+- [x] Task 2
+...
+
+所有任务完成!准备归档此变更。
+```
+
+**暂停时的输出(遇到问题)**
+
+```
+## 实施暂停
+
+**变更:** <change-name>
+**Schema:** <schema-name>
+**进度:** 4/7 任务完成
+
+### 遇到的问题
+<description of the issue>
+
+**选项:**
+1. <option 1>
+2. <option 2>
+3. 其他方法
+
+你想怎么做?
+```
+
+**护栏**
+- 继续处理任务直到完成或阻塞
+- 开始前始终阅读上下文文件(来自实施指令输出)
+- 如果任务模糊,在实施前暂停并询问
+- 如果实施揭示了问题,暂停并建议更新产物
+- 保持代码更改最小并限定在每个任务范围内
+- 完成每个任务后立即更新任务复选框
+- 遇到错误、阻碍或不明确的需求时暂停 - 不要猜测
+- 使用 CLI 输出中的 contextFiles,不要假设特定的文件名
+
+**流畅的工作流集成**
+
+此技能支持 "对变更的操作" 模型:
+
+- **可以随时调用**:在所有产物完成之前(如果任务存在),部分实施后,与其他操作交错
+- **允许产物更新**:如果实施揭示了设计问题,建议更新产物 - 不是阶段锁定的,流畅工作

+ 161 - 0
.opencode/skills/openspec-archive-change/SKILL.md

@@ -0,0 +1,161 @@
+---
+name: openspec-archive-change
+description: 在实验性工作流中归档已完成的变更。当用户想要在实施完成后最终确定并归档变更时使用。
+license: MIT
+compatibility: Requires openspec CLI.
+metadata:
+  author: openspec
+  version: "1.0"
+  generatedBy: "1.1.1"
+---
+
+在实验性工作流中归档已完成的变更。
+
+**输入**:可选择指定变更名称。如果省略,检查是否可以从对话上下文中推断。如果模糊或不明确,必须提示可用的变更。
+
+**步骤**
+
+1. **如果没有提供变更名称,提示选择**
+
+   运行 `openspec list --json` 获取可用变更。使用 **AskUserQuestion 工具** 让用户选择。
+
+   仅显示活跃变更(未归档)。
+   如果可用,包括每个变更使用的 Schema。
+
+   **重要**:不要猜测或自动选择变更。始终让用户选择。
+
+2. **检查产物完成状态**
+
+   运行 `openspec status --change "<name>" --json` 检查产物完成情况。
+
+   解析 JSON 以了解:
+   - `schemaName`:正在使用的工作流
+   - `artifacts`:产物列表及其状态(`done` 或其他)
+
+   **如果有产物未 `done`:**
+   - 显示列出未完成产物的警告
+   - 使用 **AskUserQuestion 工具** 确认用户是否要继续
+   - 如果用户确认则继续
+
+3. **检查任务完成状态**
+
+   阅读任务文件(通常是 `tasks.md`)以检查未完成的任务。
+
+   统计标记为 `- [ ]`(未完成)与 `- [x]`(完成)的任务。
+
+   **如果发现未完成的任务:**
+   - 显示列出未完成任务计数的警告
+   - 使用 **AskUserQuestion 工具** 确认用户是否要继续
+   - 如果用户确认则继续
+
+   **如果不存在任务文件:** 继续而不显示任务相关警告。
+
+4. **评估增量规范同步状态**
+
+   检查 `openspec/changes/<name>/specs/` 中的增量规范。如果不存在,继续而不显示同步提示。
+
+   **如果存在增量规范:**
+   - 将每个增量规范与 `openspec/specs/<capability>/spec.md` 中的对应主规范进行比较
+   - 确定将应用哪些更改(添加、修改、删除、重命名)
+   - 在提示之前显示合并总结
+
+   **提示选项:**
+   - 如果需要更改:"立即同步(推荐)","归档而不同步"
+   - 如果已同步:"立即归档","仍然同步","取消"
+
+   如果用户选择同步,使用 Task 工具(subagent_type: "general-purpose", prompt: "Use Skill tool to invoke openspec-sync-specs for change '<name>'. Delta spec analysis: <include the analyzed delta spec summary>")。无论选择如何,都继续归档。
+
+5. **执行归档**
+
+   如果归档目录不存在,创建它:
+   ```bash
+   mkdir -p openspec/changes/archive
+   ```
+
+   使用当前日期生成目标名称:`YYYY-MM-DD-<change-name>`
+
+   **检查目标是否已存在:**
+   - 如果是:报错失败,建议重命名现有归档或使用不同日期
+   - 如果否:将变更目录移动到归档
+
+   ```bash
+   mv openspec/changes/<name> openspec/changes/archive/YYYY-MM-DD-<name>
+   ```
+
+6. **显示总结**
+
+   显示归档完成总结,包括:
+   - 变更名称
+   - 使用的 Schema
+   - 归档位置
+   - 规范同步状态(已同步 / 跳过同步 / 无增量规范)
+   - 关于任何警告的说明(未完成的产物/任务)
+
+**成功时的输出**
+
+```
+## 归档完成
+
+**变更:** <change-name>
+**Schema:** <schema-name>
+**归档至:** openspec/changes/archive/YYYY-MM-DD-<name>/
+**规范:** ✓ 已同步到主规范
+
+所有产物已完成。所有任务已完成。
+```
+
+**成功时的输出(无增量规范)**
+
+```
+## 归档完成
+
+**变更:** <change-name>
+**Schema:** <schema-name>
+**归档至:** openspec/changes/archive/YYYY-MM-DD-<name>/
+**规范:** 无增量规范
+
+所有产物已完成。所有任务已完成。
+```
+
+**带警告的成功输出**
+
+```
+## 归档完成(带警告)
+
+**变更:** <change-name>
+**Schema:** <schema-name>
+**归档至:** openspec/changes/archive/YYYY-MM-DD-<name>/
+**规范:** 同步已跳过(用户选择跳过)
+
+**警告:**
+- 归档时有 2 个未完成的产物
+- 归档时有 3 个未完成的任务
+- 增量规范同步已跳过(用户选择跳过)
+
+如果这不是故意的,请检查归档。
+```
+
+**错误时的输出(归档已存在)**
+
+```
+## 归档失败
+
+**变更:** <change-name>
+**目标:** openspec/changes/archive/YYYY-MM-DD-<name>/
+
+目标归档目录已存在。
+
+**选项:**
+1. 重命名现有的归档
+2. 如果是重复的,删除现有的归档
+3. 等到不同的日期再归档
+```
+
+**护栏**
+- 如果未提供,始终提示选择变更
+- 使用产物图 (openspec status --json) 进行完成检查
+- 不要因警告阻止归档 - 只需通知并确认
+- 移动到归档时保留 .openspec.yaml(它随目录移动)
+- 显示发生的清晰总结
+- 如果请求同步,使用 Skill 工具调用 `openspec-sync-specs`(Agent 驱动)
+- 如果存在增量规范,始终运行同步评估并在提示前显示合并总结

+ 246 - 0
.opencode/skills/openspec-bulk-archive-change/SKILL.md

@@ -0,0 +1,246 @@
+---
+name: openspec-bulk-archive-change
+description: Archive multiple completed changes at once. Use when archiving several parallel changes.
+license: MIT
+compatibility: Requires openspec CLI.
+metadata:
+  author: openspec
+  version: "1.0"
+  generatedBy: "1.1.1"
+---
+
+Archive multiple completed changes in a single operation.
+
+This skill allows you to batch-archive changes, handling spec conflicts intelligently by checking the codebase to determine what's actually implemented.
+
+**Input**: None required (prompts for selection)
+
+**Steps**
+
+1. **Get active changes**
+
+   Run `openspec list --json` to get all active changes.
+
+   If no active changes exist, inform user and stop.
+
+2. **Prompt for change selection**
+
+   Use **AskUserQuestion tool** with multi-select to let user choose changes:
+   - Show each change with its schema
+   - Include an option for "All changes"
+   - Allow any number of selections (1+ works, 2+ is the typical use case)
+
+   **IMPORTANT**: Do NOT auto-select. Always let the user choose.
+
+3. **Batch validation - gather status for all selected changes**
+
+   For each selected change, collect:
+
+   a. **Artifact status** - Run `openspec status --change "<name>" --json`
+      - Parse `schemaName` and `artifacts` list
+      - Note which artifacts are `done` vs other states
+
+   b. **Task completion** - Read `openspec/changes/<name>/tasks.md`
+      - Count `- [ ]` (incomplete) vs `- [x]` (complete)
+      - If no tasks file exists, note as "No tasks"
+
+   c. **Delta specs** - Check `openspec/changes/<name>/specs/` directory
+      - List which capability specs exist
+      - For each, extract requirement names (lines matching `### Requirement: <name>`)
+
+4. **Detect spec conflicts**
+
+   Build a map of `capability -> [changes that touch it]`:
+
+   ```
+   auth -> [change-a, change-b]  <- CONFLICT (2+ changes)
+   api  -> [change-c]            <- OK (only 1 change)
+   ```
+
+   A conflict exists when 2+ selected changes have delta specs for the same capability.
+
+5. **Resolve conflicts agentically**
+
+   **For each conflict**, investigate the codebase:
+
+   a. **Read the delta specs** from each conflicting change to understand what each claims to add/modify
+
+   b. **Search the codebase** for implementation evidence:
+      - Look for code implementing requirements from each delta spec
+      - Check for related files, functions, or tests
+
+   c. **Determine resolution**:
+      - If only one change is actually implemented -> sync that one's specs
+      - If both implemented -> apply in chronological order (older first, newer overwrites)
+      - If neither implemented -> skip spec sync, warn user
+
+   d. **Record resolution** for each conflict:
+      - Which change's specs to apply
+      - In what order (if both)
+      - Rationale (what was found in codebase)
+
+6. **Show consolidated status table**
+
+   Display a table summarizing all changes:
+
+   ```
+   | Change               | Artifacts | Tasks | Specs   | Conflicts | Status |
+   |---------------------|-----------|-------|---------|-----------|--------|
+   | schema-management   | Done      | 5/5   | 2 delta | None      | Ready  |
+   | project-config      | Done      | 3/3   | 1 delta | None      | Ready  |
+   | add-oauth           | Done      | 4/4   | 1 delta | auth (!)  | Ready* |
+   | add-verify-skill    | 1 left    | 2/5   | None    | None      | Warn   |
+   ```
+
+   For conflicts, show the resolution:
+   ```
+   * Conflict resolution:
+     - auth spec: Will apply add-oauth then add-jwt (both implemented, chronological order)
+   ```
+
+   For incomplete changes, show warnings:
+   ```
+   Warnings:
+   - add-verify-skill: 1 incomplete artifact, 3 incomplete tasks
+   ```
+
+7. **Confirm batch operation**
+
+   Use **AskUserQuestion tool** with a single confirmation:
+
+   - "Archive N changes?" with options based on status
+   - Options might include:
+     - "Archive all N changes"
+     - "Archive only N ready changes (skip incomplete)"
+     - "Cancel"
+
+   If there are incomplete changes, make clear they'll be archived with warnings.
+
+8. **Execute archive for each confirmed change**
+
+   Process changes in the determined order (respecting conflict resolution):
+
+   a. **Sync specs** if delta specs exist:
+      - Use the openspec-sync-specs approach (agent-driven intelligent merge)
+      - For conflicts, apply in resolved order
+      - Track if sync was done
+
+   b. **Perform the archive**:
+      ```bash
+      mkdir -p openspec/changes/archive
+      mv openspec/changes/<name> openspec/changes/archive/YYYY-MM-DD-<name>
+      ```
+
+   c. **Track outcome** for each change:
+      - Success: archived successfully
+      - Failed: error during archive (record error)
+      - Skipped: user chose not to archive (if applicable)
+
+9. **Display summary**
+
+   Show final results:
+
+   ```
+   ## Bulk Archive Complete
+
+   Archived 3 changes:
+   - schema-management-cli -> archive/2026-01-19-schema-management-cli/
+   - project-config -> archive/2026-01-19-project-config/
+   - add-oauth -> archive/2026-01-19-add-oauth/
+
+   Skipped 1 change:
+   - add-verify-skill (user chose not to archive incomplete)
+
+   Spec sync summary:
+   - 4 delta specs synced to main specs
+   - 1 conflict resolved (auth: applied both in chronological order)
+   ```
+
+   If any failures:
+   ```
+   Failed 1 change:
+   - some-change: Archive directory already exists
+   ```
+
+**Conflict Resolution Examples**
+
+Example 1: Only one implemented
+```
+Conflict: specs/auth/spec.md touched by [add-oauth, add-jwt]
+
+Checking add-oauth:
+- Delta adds "OAuth Provider Integration" requirement
+- Searching codebase... found src/auth/oauth.ts implementing OAuth flow
+
+Checking add-jwt:
+- Delta adds "JWT Token Handling" requirement
+- Searching codebase... no JWT implementation found
+
+Resolution: Only add-oauth is implemented. Will sync add-oauth specs only.
+```
+
+Example 2: Both implemented
+```
+Conflict: specs/api/spec.md touched by [add-rest-api, add-graphql]
+
+Checking add-rest-api (created 2026-01-10):
+- Delta adds "REST Endpoints" requirement
+- Searching codebase... found src/api/rest.ts
+
+Checking add-graphql (created 2026-01-15):
+- Delta adds "GraphQL Schema" requirement
+- Searching codebase... found src/api/graphql.ts
+
+Resolution: Both implemented. Will apply add-rest-api specs first,
+then add-graphql specs (chronological order, newer takes precedence).
+```
+
+**Output On Success**
+
+```
+## Bulk Archive Complete
+
+Archived N changes:
+- <change-1> -> archive/YYYY-MM-DD-<change-1>/
+- <change-2> -> archive/YYYY-MM-DD-<change-2>/
+
+Spec sync summary:
+- N delta specs synced to main specs
+- No conflicts (or: M conflicts resolved)
+```
+
+**Output On Partial Success**
+
+```
+## Bulk Archive Complete (partial)
+
+Archived N changes:
+- <change-1> -> archive/YYYY-MM-DD-<change-1>/
+
+Skipped M changes:
+- <change-2> (user chose not to archive incomplete)
+
+Failed K changes:
+- <change-3>: Archive directory already exists
+```
+
+**Output When No Changes**
+
+```
+## No Changes to Archive
+
+No active changes found. Use `/opsx-new` to create a new change.
+```
+
+**Guardrails**
+- Allow any number of changes (1+ is fine, 2+ is the typical use case)
+- Always prompt for selection, never auto-select
+- Detect spec conflicts early and resolve by checking codebase
+- When both changes are implemented, apply specs in chronological order
+- Skip spec sync only when implementation is missing (warn user)
+- Show clear per-change status before confirming
+- Use single confirmation for entire batch
+- Track and report all outcomes (success/skip/fail)
+- Preserve .openspec.yaml when moving to archive
+- Archive directory target uses current date: YYYY-MM-DD-<name>
+- If archive target exists, fail that change but continue with others

+ 118 - 0
.opencode/skills/openspec-continue-change/SKILL.md

@@ -0,0 +1,118 @@
+---
+name: openspec-continue-change
+description: 通过创建下一个产物继续处理 OpenSpec 变更。当用户想要推进他们的变更、创建下一个产物或继续他们的工作流时使用。
+license: MIT
+compatibility: Requires openspec CLI.
+metadata:
+  author: openspec
+  version: "1.0"
+  generatedBy: "1.1.1"
+---
+
+通过创建下一个产物继续处理变更。
+
+**输入**:可选择指定变更名称。如果省略,检查是否可以从对话上下文中推断。如果模糊或不明确,必须提示可用的变更。
+
+**步骤**
+
+1. **如果没有提供变更名称,提示选择**
+
+   运行 `openspec list --json` 以获取按最近修改排序的可用变更。然后使用 **AskUserQuestion 工具** 让用户选择要处理哪个变更。
+
+   提供前 3-4 个最近修改的变更作为选项,显示:
+   - 变更名称
+   - Schema(如果有 `schema` 字段,否则为 "spec-driven")
+   - 状态(例如,"0/5 tasks", "complete", "no tasks")
+   - 最近修改时间(来自 `lastModified` 字段)
+
+   将最近修改的变更标记为 "(Recommended)",因为这很可能是用户想要继续的。
+
+   **重要**:不要猜测或自动选择变更。始终让用户选择。
+
+2. **检查当前状态**
+   ```bash
+   openspec status --change "<name>" --json
+   ```
+   解析 JSON 以了解当前状态。响应包括:
+   - `schemaName`:正在使用的工作流 Schema(例如,"spec-driven")
+   - `artifacts`:产物数组及其状态("done", "ready", "blocked")
+   - `isComplete`:指示是否所有产物都已完成的布尔值
+
+3. **根据状态行动**:
+
+   ---
+
+   **如果所有产物都已完成 (`isComplete: true`)**:
+   - 祝贺用户
+   - 显示最终状态,包括使用的 Schema
+   - 建议:"所有产物已创建!你现在可以实施此变更或将其归档。"
+   - 停止
+
+   ---
+
+   **如果有产物准备创建**(状态显示有 `status: "ready"` 的产物):
+   - 从状态输出中选择第一个 `status: "ready"` 的产物
+   - 获取其指令:
+     ```bash
+     openspec instructions <artifact-id> --change "<name>" --json
+     ```
+   - 解析 JSON。关键字段是:
+     - `context`:项目背景(给你的约束 - 不要包含在输出中)
+     - `rules`:产物特定规则(给你的约束 - 不要包含在输出中)
+     - `template`:用于输出文件的结构
+     - `instruction`:Schema 特定的指导
+     - `outputPath`:写入产物的位置
+     - `dependencies`:已完成的产物,用于阅读上下文
+   - **创建产物文件**:
+     - 阅读任何已完成的依赖文件以获取上下文
+     - 使用 `template` 作为结构 - 填充其部分
+     - 应用 `context` 和 `rules` 作为约束 - 但不要将它们复制到文件中
+     - 写入指令中指定的输出路径
+   - 显示已创建的内容以及现在解锁的内容
+   - 在创建一个产物后停止
+
+   ---
+
+   **如果没有产物准备好(全部阻塞)**:
+   - 这在有效 Schema 中不应发生
+   - 显示状态并建议检查问题
+
+4. **创建产物后,显示进度**
+   ```bash
+   openspec status --change "<name>"
+   ```
+
+**输出**
+
+每次调用后,显示:
+- 创建了哪个产物
+- 正在使用的 Schema 工作流
+- 当前进度(N/M 完成)
+- 现在解锁了哪些产物
+- 提示:"想要继续吗?只需让我继续或告诉我下一步做什么。"
+
+**产物创建指南**
+
+产物类型及其用途取决于 Schema。使用指令输出中的 `instruction` 字段来了解要创建什么。
+
+常见产物模式:
+
+**spec-driven schema** (proposal → specs → design → tasks):
+- **proposal.md**:如果不清楚,询问用户关于变更的信息。填写 Why, What Changes, Capabilities, Impact。
+  - Capabilities 部分至关重要 - 列出的每个 capability 都需要一个 spec 文件。
+- **specs/<capability>/spec.md**:为 proposal 的 Capabilities 部分中列出的每个 capability 创建一个 spec(使用 capability 名称,而不是变更名称)。
+- **design.md**:记录技术决策、架构和实施方法。
+- **tasks.md**:将实施分解为带复选框的任务。
+
+对于其他 Schema,遵循 CLI 输出中的 `instruction` 字段。
+
+**护栏**
+- 每次调用创建一个产物
+- 在创建新产物之前,始终阅读依赖产物
+- 绝不跳过产物或乱序创建
+- 如果上下文不清楚,在创建之前询问用户
+- 在标记进度之前,验证写入后产物文件是否存在
+- 使用 Schema 的产物序列,不要假设特定的产物名称
+- **重要**:`context` 和 `rules` 是给你的约束,不是文件内容
+  - 不要将 `<context>`, `<rules>`, `<project_context>` 块复制到产物中
+  - 这些指导你写什么,但绝不应出现在输出中

+ 289 - 0
.opencode/skills/openspec-explore/SKILL.md

@@ -0,0 +1,289 @@
+---
+name: openspec-explore
+description: 进入探索模式——一个用于探索想法、调查问题和澄清需求的思考伙伴。当用户想要在变更之前或期间思考某些事情时使用。
+license: MIT
+compatibility: Requires openspec CLI.
+metadata:
+  author: openspec
+  version: "1.0"
+  generatedBy: "1.1.1"
+---
+
+进入探索模式。深度思考。自由可视化。跟随对话的发展。
+
+**重要:探索模式用于思考,而非实施。** 你可以阅读文件、搜索代码和调查代码库,但你绝不能编写代码或实施功能。如果用户要求你实施某事,请提醒他们先退出探索模式(例如,使用 `/opsx-new` 或 `/opsx-ff` 开始一个变更)。如果用户要求,你可以创建 OpenSpec 产物(提案、设计、规范)——这是捕捉思考,而不是实施。
+
+**这是一种姿态,而不是工作流。** 没有固定的步骤,没有要求的顺序,没有强制的输出。你是帮助用户探索的思考伙伴。
+
+---
+
+## 姿态
+
+- **好奇而非指令性** - 提出自然涌现的问题,不要照本宣科
+- **开放话题而非审问** - 展示多个有趣的方向,让用户跟随共鸣之处。不要强迫他们通过单一的问题路径。
+- **可视化** - 当有助于通过 ASCII 图表澄清思路时,请大量使用
+- **适应性** - 跟随有趣的线索,当新信息出现时进行调整
+- **耐心** - 不要急于下结论,让问题的形状自然显现
+- **脚踏实地** - 在相关时探索实际的代码库,不仅仅是理论化
+
+---
+
+## 你可能做的事情
+
+根据用户提出的内容,你可能会:
+
+**探索问题空间**
+- 提出从他们所说内容中涌现的澄清性问题
+- 挑战假设
+- 重新构建问题
+- 寻找类比
+
+**调查代码库**
+- 绘制与讨论相关的现有架构图
+- 寻找集成点
+- 识别已在使用的模式
+- 揭示隐藏的复杂性
+
+**比较选项**
+- 头脑风暴多种方法
+- 建立比较表
+- 勾勒权衡
+- 推荐路径(如果被问及)
+
+**可视化**
+```
+┌─────────────────────────────────────────┐
+│           大量使用 ASCII 图表           │
+├─────────────────────────────────────────┤
+│                                         │
+│   ┌────────┐         ┌────────┐        │
+│   │ 状态   │────────▶│ 状态   │        │
+│   │   A    │         │   B    │        │
+│   └────────┘         └────────┘        │
+│                                         │
+│   系统图、状态机、数据流、架构草图、    │
+│   依赖图、比较表                        │
+│                                         │
+└─────────────────────────────────────────┘
+```
+
+**揭示风险和未知数**
+- 识别可能出错的地方
+- 发现理解上的差距
+- 建议探针(spikes)或调查
+
+---
+
+## OpenSpec 意识
+
+你拥有 OpenSpec 系统的完整上下文。自然地使用它,不要强迫。
+
+### 检查上下文
+
+开始时,快速检查已存在的内容:
+```bash
+openspec list --json
+```
+
+这会告诉你:
+- 是否有活跃的变更
+- 它们的名称、Schema 和状态
+- 用户可能正在处理的内容
+
+### 当没有变更存在时
+
+自由思考。当见解具体化时,你可以提供:
+
+- "这感觉足够扎实,可以开始一个变更了。要我创建一个吗?"
+  → 可以过渡到 `/opsx-new` 或 `/opsx-ff`
+- 或者继续探索 - 没有正式化的压力
+
+### 当变更存在时
+
+如果用户提到变更或你检测到相关变更:
+
+1. **阅读现有产物以获取上下文**
+   - `openspec/changes/<name>/proposal.md`
+   - `openspec/changes/<name>/design.md`
+   - `openspec/changes/<name>/tasks.md`
+   - 等等。
+
+2. **在对话中自然地引用它们**
+   - "你的设计提到使用 Redis,但我们刚意识到 SQLite 更合适..."
+   - "提案将此范围限定为高级用户,但我们现在考虑面向所有人..."
+
+3. **当做出决定时提议捕捉**
+
+   | 见解类型 | 捕捉位置 |
+   |----------|----------|
+   | 发现新需求 | `specs/<capability>/spec.md` |
+   | 需求变更 | `specs/<capability>/spec.md` |
+   | 做出设计决定 | `design.md` |
+   | 范围变更 | `proposal.md` |
+   | 识别新工作 | `tasks.md` |
+   | 假设失效 | 相关产物 |
+
+   示例提议:
+   - "这是一个设计决定。要把它记录在 design.md 中吗?"
+   - "这是一个新需求。要把它添加到规范中吗?"
+   - "这改变了范围。要更新提案吗?"
+
+4. **用户决定** - 提议并继续。不要施压。不要自动捕捉。
+
+---
+
+## 你不必做的事情
+
+- 遵循脚本
+- 每次都问同样的问题
+- 产生特定的产物
+- 得出结论
+- 如果离题有价值,不必保持在主题上
+- 简短(这是思考时间)
+
+---
+
+## 处理不同的入口点
+
+**用户带来一个模糊的想法:**
+```
+用户:我在考虑添加实时协作
+
+你:实时协作是一个很大的空间。让我想想...
+
+      协作谱系
+      ════════════════════════════════════════════
+
+      感知               协调                 同步
+          │                   │                 │
+          ▼                   ▼                 ▼
+      ┌────────┐         ┌────────┐        ┌────────┐
+      │  在场  │         │  光标  │        │  CRDT  │
+      │ "3人   │         │  多选  │        │无冲突  │
+      │ 在线"  │         │        │        │        │
+      └────────┘         └────────┘        └────────┘
+          │                   │                 │
+         琐碎                中等                复杂
+
+      你的想法在哪里?
+```
+
+**用户带来一个具体的问题:**
+```
+用户:认证系统一团糟
+
+你:[阅读代码库]
+
+     ┌─────────────────────────────────────────────┐
+     │              当前认证流程                   │
+     └─────────────────────────────────────────────┘
+                        │
+          ┌─────────────┼─────────────┐
+          ▼             ▼             ▼
+     ┌─────────┐  ┌─────────┐  ┌─────────┐
+     │ Google  │  │  GitHub │  │  Email  │
+     │  OAuth  │  │  OAuth  │  │  Magic  │
+     └────┬────┘  └────┬────┘  └────┬────┘
+          │             │             │
+          └─────────────┼─────────────┘
+                        ▼
+                  ┌───────────┐
+                  │  会话     │
+                  └─────┬─────┘
+                        │
+                        ▼
+                  ┌───────────┐
+                  │   权限    │
+                  └───────────┘
+
+     我看到三个纠结之处。哪个最紧迫?
+```
+
+**用户在实施中途卡住:**
+```
+用户:/opsx-explore add-auth-system
+      OAuth 集成比预期的要复杂
+
+你:[阅读变更产物]
+
+     你在任务 4:"实施 OAuth 流程"
+
+     让我追踪一下涉及的内容...
+
+     [绘制图表,探索选项,建议路径]
+
+     想要更新设计以反映这一点吗?
+     或者添加一个探针任务来调查?
+```
+
+**用户想要比较选项:**
+```
+用户:我们应该用 Postgres 还是 SQLite?
+
+你:通用的答案很无聊。上下文是什么?
+
+用户:一个跟踪本地开发环境的 CLI 工具
+
+你:那改变了一切。
+
+     ┌─────────────────────────────────────────────────┐
+     │             CLI 工具数据存储                    │
+     └─────────────────────────────────────────────────┘
+
+     关键约束:
+     • 无后台运行
+     • 必须离线工作
+     • 单用户
+
+                  SQLite          Postgres
+     部署         嵌入式 ✓        需要服务器 ✗
+     离线         是 ✓            否 ✗
+     单文件       是 ✓            否 ✗
+
+     SQLite。毫无疑问。
+
+     除非... 有同步组件吗?
+```
+
+---
+
+## 结束发现
+
+没有强制的结束。发现可能会:
+
+- **流入行动**:"准备好开始了吗? /opsx-new 或 /opsx-ff"
+- **导致产物更新**:"已用这些决定更新 design.md"
+- **仅提供清晰度**:用户得到了他们需要的,继续前进
+- **稍后继续**:"我们可以随时继续"
+
+当感觉事情具体化时,你可以总结:
+
+```
+## 我们弄清楚了什么
+
+**问题**:[具体化的理解]
+
+**方法**:[如果出现了一个]
+
+**未决问题**:[如果还有]
+
+**下一步**(如果准备好了):
+- 创建变更:/opsx-new <name>
+- 快进到任务:/opsx-ff <name>
+- 继续探索:继续交谈
+```
+
+但这个总结是可选的。有时思考本身就是价值。
+
+---
+
+## 护栏
+
+- **不要实施** - 绝不编写代码或实施功能。创建 OpenSpec 产物是可以的,编写应用程序代码是不行的。
+- **不要假装理解** - 如果某事不清楚,深入挖掘
+- **不要匆忙** - 发现是思考时间,不是任务时间
+- **不要强迫结构** - 让模式自然显现
+- **不要自动捕捉** - 提议保存见解,不要直接做
+- **做可视化** - 一张好图胜过千言万语
+- **做探索代码库** - 将讨论建立在现实基础上
+- **做质疑假设** - 包括用户的和你自己的

+ 101 - 0
.opencode/skills/openspec-ff-change/SKILL.md

@@ -0,0 +1,101 @@
+---
+name: openspec-ff-change
+description: 快速创建 OpenSpec 产物。当用户想要快速创建实施所需的所有产物而不必单独逐步完成时使用。
+license: MIT
+compatibility: Requires openspec CLI.
+metadata:
+  author: openspec
+  version: "1.0"
+  generatedBy: "1.1.1"
+---
+
+快速创建产物 - 一次性生成开始实施所需的一切。
+
+**输入**:用户的请求应包含变更名称(kebab-case)或他们想要构建的内容的描述。
+
+**步骤**
+
+1. **如果没有提供明确的输入,询问他们想要构建什么**
+
+   使用 **AskUserQuestion 工具**(开放式,无预设选项)询问:
+   > "你想进行什么变更?描述你想构建或修复的内容。"
+
+   从他们的描述中,得出一个 kebab-case 名称(例如,"增加用户认证" → `add-user-auth`)。
+
+   **重要**:在不了解用户想要构建什么之前,不要继续。
+
+2. **创建变更目录**
+   ```bash
+   openspec new change "<name>"
+   ```
+   这将在 `openspec/changes/<name>/` 下创建一个脚手架变更。
+
+3. **获取产物构建顺序**
+   ```bash
+   openspec status --change "<name>" --json
+   ```
+   解析 JSON 以获取:
+   - `applyRequires`:实施前所需的产物 ID 数组(例如,`["tasks"]`)
+   - `artifacts`:所有产物及其状态和依赖关系的列表
+
+4. **按顺序创建产物直到准备好实施**
+
+   使用 **TodoWrite 工具** 跟踪产物的进度。
+
+   按依赖顺序循环产物(没有待处理依赖的产物优先):
+
+   a. **对于每个 `ready`(依赖已满足)的产物**:
+      - 获取指令:
+        ```bash
+        openspec instructions <artifact-id> --change "<name>" --json
+        ```
+      - 指令 JSON 包括:
+        - `context`:项目背景(给你的约束 - 不要包含在输出中)
+        - `rules`:产物特定规则(给你的约束 - 不要包含在输出中)
+        - `template`:用于输出文件的结构
+        - `instruction`:此产物类型的 Schema 特定指导
+        - `outputPath`:写入产物的位置
+        - `dependencies`:已完成的产物,用于阅读上下文
+      - 阅读任何已完成的依赖文件以获取上下文
+      - 使用 `template` 作为结构创建产物文件
+      - 应用 `context` 和 `rules` 作为约束 - 但不要将它们复制到文件中
+      - 显示简要进度:"✓ Created <artifact-id>"
+
+   b. **继续直到所有 `applyRequires` 产物都完成**
+      - 创建每个产物后,重新运行 `openspec status --change "<name>" --json`
+      - 检查 `applyRequires` 中的每个产物 ID 是否在产物数组中具有 `status: "done"`
+      - 当所有 `applyRequires` 产物都完成时停止
+
+   c. **如果产物需要用户输入**(上下文不清楚):
+      - 使用 **AskUserQuestion 工具** 澄清
+      - 然后继续创建
+
+5. **显示最终状态**
+   ```bash
+   openspec status --change "<name>"
+   ```
+
+**输出**
+
+完成所有产物后,总结:
+- 变更名称和位置
+- 已创建产物的列表及简要描述
+- 准备就绪:"所有产物已创建!准备实施。"
+- 提示:"运行 `/opsx-apply` 或让我实施以开始处理任务。"
+
+**产物创建指南**
+
+- 遵循每个产物类型的 `openspec instructions` 中的 `instruction` 字段
+- Schema 定义了每个产物应包含的内容 - 遵循它
+- 在创建新产物之前阅读依赖产物以获取上下文
+- 使用 `template` 作为起点,根据上下文填充
+- **重要**:`context` 和 `rules` 是给你的约束,不是文件内容
+  - 不要将 `<context>`, `<rules>`, `<project_context>` 块复制到产物中
+  - 这些指导你写什么,但绝不应出现在输出中
+
+**护栏**
+- 创建实施所需的所有产物(由 Schema 的 `apply.requires` 定义)
+- 在创建新产物之前始终阅读依赖产物
+- 如果上下文严重不清楚,询问用户 - 但倾向于做出合理的决定以保持势头
+- 如果具有该名称的变更已存在,询问用户是否要继续该变更或创建一个新的
+- 在继续下一个之前,验证每个产物文件在写入后是否存在

+ 74 - 0
.opencode/skills/openspec-new-change/SKILL.md

@@ -0,0 +1,74 @@
+---
+name: openspec-new-change
+description: 使用实验性产物工作流开始一个新的 OpenSpec 变更。当用户想要以结构化的分步方法创建新功能、修复或修改时使用。
+license: MIT
+compatibility: Requires openspec CLI.
+metadata:
+  author: openspec
+  version: "1.0"
+  generatedBy: "1.1.1"
+---
+
+使用实验性产物驱动方法开始一个新的变更。
+
+**输入**:用户的请求应包含变更名称(kebab-case)或他们想要构建的内容的描述。
+
+**步骤**
+
+1. **如果没有提供明确的输入,询问他们想要构建什么**
+
+   使用 **AskUserQuestion 工具**(开放式,无预设选项)询问:
+   > "你想进行什么变更?描述你想构建或修复的内容。"
+
+   从他们的描述中,得出一个 kebab-case 名称(例如,"增加用户认证" → `add-user-auth`)。
+
+   **重要**:在不了解用户想要构建什么之前,不要继续。
+
+2. **确定工作流 Schema**
+
+   使用默认 Schema(省略 `--schema`),除非用户明确请求不同的工作流。
+
+   **仅在用户提及时使用不同的 Schema:**
+   - 特定的 Schema 名称 → 使用 `--schema <name>`
+   - "显示工作流" 或 "什么工作流" → 运行 `openspec schemas --json` 并让他们选择
+
+   **否则**:省略 `--schema` 以使用默认值。
+
+3. **创建变更目录**
+   ```bash
+   openspec new change "<name>"
+   ```
+   仅在用户请求特定工作流时添加 `--schema <name>`。
+   这将在 `openspec/changes/<name>/` 下创建一个使用所选 Schema 的脚手架变更。
+
+4. **显示产物状态**
+   ```bash
+   openspec status --change "<name>"
+   ```
+   这显示了哪些产物需要创建,哪些已就绪(依赖关系已满足)。
+
+5. **获取第一个产物的指令**
+   第一个产物取决于 Schema(例如,spec-driven 的 `proposal`)。
+   检查状态输出,找到第一个状态为 "ready" 的产物。
+   ```bash
+   openspec instructions <first-artifact-id> --change "<name>"
+   ```
+   这输出创建第一个产物的模板和上下文。
+
+6. **停止并等待用户指示**
+
+**输出**
+
+完成步骤后,总结:
+- 变更名称和位置
+- 正在使用的 Schema/工作流及其产物序列
+- 当前状态(0/N 产物完成)
+- 第一个产物的模板
+- 提示:"准备好创建第一个产物了吗?只需描述这个变更的内容,我会起草它,或者你可以让我继续。"
+
+**护栏**
+- 不要创建任何产物 - 仅显示指令
+- 不要超越显示第一个产物模板
+- 如果名称无效(不是 kebab-case),要求一个有效名称
+- 如果具有该名称的变更已存在,建议继续该变更
+- 如果使用非默认工作流,传递 --schema

+ 538 - 0
.opencode/skills/openspec-onboard/SKILL.md

@@ -0,0 +1,538 @@
+---
+name: openspec-onboard
+description: Guided onboarding for OpenSpec - walk through a complete workflow cycle with narration and real codebase work.
+license: MIT
+compatibility: Requires openspec CLI.
+metadata:
+  author: openspec
+  version: "1.0"
+  generatedBy: "1.1.1"
+---
+
+Guide the user through their first complete OpenSpec workflow cycle. This is a teaching experience—you'll do real work in their codebase while explaining each step.
+
+---
+
+## Preflight
+
+Before starting, check if the OpenSpec CLI is installed:
+
+```bash
+# Unix/macOS
+openspec --version 2>&1 || echo "CLI_NOT_INSTALLED"
+# Windows (PowerShell)
+# if (Get-Command openspec -ErrorAction SilentlyContinue) { openspec --version } else { echo "CLI_NOT_INSTALLED" }
+```
+
+**If CLI not installed:**
+> OpenSpec CLI is not installed. Install it first, then come back to `/opsx-onboard`.
+
+Stop here if not installed.
+
+---
+
+## Phase 1: Welcome
+
+Display:
+
+```
+## Welcome to OpenSpec!
+
+I'll walk you through a complete change cycle—from idea to implementation—using a real task in your codebase. Along the way, you'll learn the workflow by doing it.
+
+**What we'll do:**
+1. Pick a small, real task in your codebase
+2. Explore the problem briefly
+3. Create a change (the container for our work)
+4. Build the artifacts: proposal → specs → design → tasks
+5. Implement the tasks
+6. Archive the completed change
+
+**Time:** ~15-20 minutes
+
+Let's start by finding something to work on.
+```
+
+---
+
+## Phase 2: Task Selection
+
+### Codebase Analysis
+
+Scan the codebase for small improvement opportunities. Look for:
+
+1. **TODO/FIXME comments** - Search for `TODO`, `FIXME`, `HACK`, `XXX` in code files
+2. **Missing error handling** - `catch` blocks that swallow errors, risky operations without try-catch
+3. **Functions without tests** - Cross-reference `src/` with test directories
+4. **Type issues** - `any` types in TypeScript files (`: any`, `as any`)
+5. **Debug artifacts** - `console.log`, `console.debug`, `debugger` statements in non-debug code
+6. **Missing validation** - User input handlers without validation
+
+Also check recent git activity:
+```bash
+# Unix/macOS
+git log --oneline -10 2>/dev/null || echo "No git history"
+# Windows (PowerShell)
+# git log --oneline -10 2>$null; if ($LASTEXITCODE -ne 0) { echo "No git history" }
+```
+
+### Present Suggestions
+
+From your analysis, present 3-4 specific suggestions:
+
+```
+## Task Suggestions
+
+Based on scanning your codebase, here are some good starter tasks:
+
+**1. [Most promising task]**
+   Location: `src/path/to/file.ts:42`
+   Scope: ~1-2 files, ~20-30 lines
+   Why it's good: [brief reason]
+
+**2. [Second task]**
+   Location: `src/another/file.ts`
+   Scope: ~1 file, ~15 lines
+   Why it's good: [brief reason]
+
+**3. [Third task]**
+   Location: [location]
+   Scope: [estimate]
+   Why it's good: [brief reason]
+
+**4. Something else?**
+   Tell me what you'd like to work on.
+
+Which task interests you? (Pick a number or describe your own)
+```
+
+**If nothing found:** Fall back to asking what the user wants to build:
+> I didn't find obvious quick wins in your codebase. What's something small you've been meaning to add or fix?
+
+### Scope Guardrail
+
+If the user picks or describes something too large (major feature, multi-day work):
+
+```
+That's a valuable task, but it's probably larger than ideal for your first OpenSpec run-through.
+
+For learning the workflow, smaller is better—it lets you see the full cycle without getting stuck in implementation details.
+
+**Options:**
+1. **Slice it smaller** - What's the smallest useful piece of [their task]? Maybe just [specific slice]?
+2. **Pick something else** - One of the other suggestions, or a different small task?
+3. **Do it anyway** - If you really want to tackle this, we can. Just know it'll take longer.
+
+What would you prefer?
+```
+
+Let the user override if they insist—this is a soft guardrail.
+
+---
+
+## Phase 3: Explore Demo
+
+Once a task is selected, briefly demonstrate explore mode:
+
+```
+Before we create a change, let me quickly show you **explore mode**—it's how you think through problems before committing to a direction.
+```
+
+Spend 1-2 minutes investigating the relevant code:
+- Read the file(s) involved
+- Draw a quick ASCII diagram if it helps
+- Note any considerations
+
+```
+## Quick Exploration
+
+[Your brief analysis—what you found, any considerations]
+
+┌─────────────────────────────────────────┐
+│   [Optional: ASCII diagram if helpful]  │
+└─────────────────────────────────────────┘
+
+Explore mode (`/opsx-explore`) is for this kind of thinking—investigating before implementing. You can use it anytime you need to think through a problem.
+
+Now let's create a change to hold our work.
+```
+
+**PAUSE** - Wait for user acknowledgment before proceeding.
+
+---
+
+## Phase 4: Create the Change
+
+**EXPLAIN:**
+```
+## Creating a Change
+
+A "change" in OpenSpec is a container for all the thinking and planning around a piece of work. It lives in `openspec/changes/<name>/` and holds your artifacts—proposal, specs, design, tasks.
+
+Let me create one for our task.
+```
+
+**DO:** Create the change with a derived kebab-case name:
+```bash
+openspec new change "<derived-name>"
+```
+
+**SHOW:**
+```
+Created: `openspec/changes/<name>/`
+
+The folder structure:
+```
+openspec/changes/<name>/
+├── proposal.md    ← Why we're doing this (empty, we'll fill it)
+├── design.md      ← How we'll build it (empty)
+├── specs/         ← Detailed requirements (empty)
+└── tasks.md       ← Implementation checklist (empty)
+```
+
+Now let's fill in the first artifact—the proposal.
+```
+
+---
+
+## Phase 5: Proposal
+
+**EXPLAIN:**
+```
+## The Proposal
+
+The proposal captures **why** we're making this change and **what** it involves at a high level. It's the "elevator pitch" for the work.
+
+I'll draft one based on our task.
+```
+
+**DO:** Draft the proposal content (don't save yet):
+
+```
+Here's a draft proposal:
+
+---
+
+## Why
+
+[1-2 sentences explaining the problem/opportunity]
+
+## What Changes
+
+[Bullet points of what will be different]
+
+## Capabilities
+
+### New Capabilities
+- `<capability-name>`: [brief description]
+
+### Modified Capabilities
+<!-- If modifying existing behavior -->
+
+## Impact
+
+- `src/path/to/file.ts`: [what changes]
+- [other files if applicable]
+
+---
+
+Does this capture the intent? I can adjust before we save it.
+```
+
+**PAUSE** - Wait for user approval/feedback.
+
+After approval, save the proposal:
+```bash
+openspec instructions proposal --change "<name>" --json
+```
+Then write the content to `openspec/changes/<name>/proposal.md`.
+
+```
+Proposal saved. This is your "why" document—you can always come back and refine it as understanding evolves.
+
+Next up: specs.
+```
+
+---
+
+## Phase 6: Specs
+
+**EXPLAIN:**
+```
+## Specs
+
+Specs define **what** we're building in precise, testable terms. They use a requirement/scenario format that makes expected behavior crystal clear.
+
+For a small task like this, we might only need one spec file.
+```
+
+**DO:** Create the spec file:
+```bash
+# Unix/macOS
+mkdir -p openspec/changes/<name>/specs/<capability-name>
+# Windows (PowerShell)
+# New-Item -ItemType Directory -Force -Path "openspec/changes/<name>/specs/<capability-name>"
+```
+
+Draft the spec content:
+
+```
+Here's the spec:
+
+---
+
+## ADDED Requirements
+
+### Requirement: <Name>
+
+<Description of what the system should do>
+
+#### Scenario: <Scenario name>
+
+- **WHEN** <trigger condition>
+- **THEN** <expected outcome>
+- **AND** <additional outcome if needed>
+
+---
+
+This format—WHEN/THEN/AND—makes requirements testable. You can literally read them as test cases.
+```
+
+Save to `openspec/changes/<name>/specs/<capability>/spec.md`.
+
+---
+
+## Phase 7: Design
+
+**EXPLAIN:**
+```
+## Design
+
+The design captures **how** we'll build it—technical decisions, tradeoffs, approach.
+
+For small changes, this might be brief. That's fine—not every change needs deep design discussion.
+```
+
+**DO:** Draft design.md:
+
+```
+Here's the design:
+
+---
+
+## Context
+
+[Brief context about the current state]
+
+## Goals / Non-Goals
+
+**Goals:**
+- [What we're trying to achieve]
+
+**Non-Goals:**
+- [What's explicitly out of scope]
+
+## Decisions
+
+### Decision 1: [Key decision]
+
+[Explanation of approach and rationale]
+
+---
+
+For a small task, this captures the key decisions without over-engineering.
+```
+
+Save to `openspec/changes/<name>/design.md`.
+
+---
+
+## Phase 8: Tasks
+
+**EXPLAIN:**
+```
+## Tasks
+
+Finally, we break the work into implementation tasks—checkboxes that drive the apply phase.
+
+These should be small, clear, and in logical order.
+```
+
+**DO:** Generate tasks based on specs and design:
+
+```
+Here are the implementation tasks:
+
+---
+
+## 1. [Category or file]
+
+- [ ] 1.1 [Specific task]
+- [ ] 1.2 [Specific task]
+
+## 2. Verify
+
+- [ ] 2.1 [Verification step]
+
+---
+
+Each checkbox becomes a unit of work in the apply phase. Ready to implement?
+```
+
+**PAUSE** - Wait for user to confirm they're ready to implement.
+
+Save to `openspec/changes/<name>/tasks.md`.
+
+---
+
+## Phase 9: Apply (Implementation)
+
+**EXPLAIN:**
+```
+## Implementation
+
+Now we implement each task, checking them off as we go. I'll announce each one and occasionally note how the specs/design informed the approach.
+```
+
+**DO:** For each task:
+
+1. Announce: "Working on task N: [description]"
+2. Implement the change in the codebase
+3. Reference specs/design naturally: "The spec says X, so I'm doing Y"
+4. Mark complete in tasks.md: `- [ ]` → `- [x]`
+5. Brief status: "✓ Task N complete"
+
+Keep narration light—don't over-explain every line of code.
+
+After all tasks:
+
+```
+## Implementation Complete
+
+All tasks done:
+- [x] Task 1
+- [x] Task 2
+- [x] ...
+
+The change is implemented! One more step—let's archive it.
+```
+
+---
+
+## Phase 10: Archive
+
+**EXPLAIN:**
+```
+## Archiving
+
+When a change is complete, we archive it. This moves it from `openspec/changes/` to `openspec/changes/archive/YYYY-MM-DD-<name>/`.
+
+Archived changes become your project's decision history—you can always find them later to understand why something was built a certain way.
+```
+
+**DO:**
+```bash
+openspec archive "<name>"
+```
+
+**SHOW:**
+```
+Archived to: `openspec/changes/archive/YYYY-MM-DD-<name>/`
+
+The change is now part of your project's history. The code is in your codebase, the decision record is preserved.
+```
+
+---
+
+## Phase 11: Recap & Next Steps
+
+```
+## Congratulations!
+
+You just completed a full OpenSpec cycle:
+
+1. **Explore** - Thought through the problem
+2. **New** - Created a change container
+3. **Proposal** - Captured WHY
+4. **Specs** - Defined WHAT in detail
+5. **Design** - Decided HOW
+6. **Tasks** - Broke it into steps
+7. **Apply** - Implemented the work
+8. **Archive** - Preserved the record
+
+This same rhythm works for any size change—a small fix or a major feature.
+
+---
+
+## Command Reference
+
+| Command | What it does |
+|---------|--------------|
+| `/opsx-explore` | Think through problems before/during work |
+| `/opsx-new` | Start a new change, step through artifacts |
+| `/opsx-ff` | Fast-forward: create all artifacts at once |
+| `/opsx-continue` | Continue working on an existing change |
+| `/opsx-apply` | Implement tasks from a change |
+| `/opsx-verify` | Verify implementation matches artifacts |
+| `/opsx-archive` | Archive a completed change |
+
+---
+
+## What's Next?
+
+Try `/opsx-new` or `/opsx-ff` on something you actually want to build. You've got the rhythm now!
+```
+
+---
+
+## Graceful Exit Handling
+
+### User wants to stop mid-way
+
+If the user says they need to stop, want to pause, or seem disengaged:
+
+```
+No problem! Your change is saved at `openspec/changes/<name>/`.
+
+To pick up where we left off later:
+- `/opsx-continue <name>` - Resume artifact creation
+- `/opsx-apply <name>` - Jump to implementation (if tasks exist)
+
+The work won't be lost. Come back whenever you're ready.
+```
+
+Exit gracefully without pressure.
+
+### User just wants command reference
+
+If the user says they just want to see the commands or skip the tutorial:
+
+```
+## OpenSpec Quick Reference
+
+| Command | What it does |
+|---------|--------------|
+| `/opsx-explore` | Think through problems (no code changes) |
+| `/opsx-new <name>` | Start a new change, step by step |
+| `/opsx-ff <name>` | Fast-forward: all artifacts at once |
+| `/opsx-continue <name>` | Continue an existing change |
+| `/opsx-apply <name>` | Implement tasks |
+| `/opsx-verify <name>` | Verify implementation |
+| `/opsx-archive <name>` | Archive when done |
+
+Try `/opsx-new` to start your first change, or `/opsx-ff` if you want to move fast.
+```
+
+Exit gracefully.
+
+---
+
+## Guardrails
+
+- **Follow the EXPLAIN → DO → SHOW → PAUSE pattern** at key transitions (after explore, after proposal draft, after tasks, after archive)
+- **Keep narration light** during implementation—teach without lecturing
+- **Don't skip phases** even if the change is small—the goal is teaching the workflow
+- **Pause for acknowledgment** at marked points, but don't over-pause
+- **Handle exits gracefully**—never pressure the user to continue
+- **Use real codebase tasks**—don't simulate or use fake examples
+- **Adjust scope gently**—guide toward smaller tasks but respect user choice

+ 138 - 0
.opencode/skills/openspec-sync-specs/SKILL.md

@@ -0,0 +1,138 @@
+---
+name: openspec-sync-specs
+description: 将变更中的增量规范同步到主规范。当用户想要用增量规范中的更改更新主规范,而不归档变更时使用。
+license: MIT
+compatibility: Requires openspec CLI.
+metadata:
+  author: openspec
+  version: "1.0"
+  generatedBy: "1.1.1"
+---
+
+将变更中的增量规范同步到主规范。
+
+这是一个 **Agent 驱动** 的操作 - 你将阅读增量规范并直接编辑主规范以应用更改。这允许智能合并(例如,添加一个场景而不复制整个需求)。
+
+**输入**:可选择指定变更名称。如果省略,检查是否可以从对话上下文中推断。如果模糊或不明确,必须提示可用的变更。
+
+**步骤**
+
+1. **如果没有提供变更名称,提示选择**
+
+   运行 `openspec list --json` 获取可用变更。使用 **AskUserQuestion 工具** 让用户选择。
+
+   显示具有增量规范(在 `specs/` 目录下)的变更。
+
+   **重要**:不要猜测或自动选择变更。始终让用户选择。
+
+2. **查找增量规范**
+
+   在 `openspec/changes/<name>/specs/*/spec.md` 中查找增量规范文件。
+
+   每个增量规范文件包含如下部分:
+   - `## ADDED Requirements` - 要添加的新需求
+   - `## MODIFIED Requirements` - 对现有需求的更改
+   - `## REMOVED Requirements` - 要删除的需求
+   - `## RENAMED Requirements` - 要重命名的需求(FROM:/TO: 格式)
+
+   如果未找到增量规范,通知用户并停止。
+
+3. **对于每个增量规范,将更改应用到主规范**
+
+   对于在 `openspec/changes/<name>/specs/<capability>/spec.md` 具有增量规范的每个 capability:
+
+   a. **阅读增量规范** 以了解预期的更改
+
+   b. **阅读主规范** `openspec/specs/<capability>/spec.md`(可能还不存在)
+
+   c. **智能应用更改**:
+
+      **ADDED Requirements:**
+      - 如果主规范中不存在需求 → 添加它
+      - 如果需求已存在 → 更新它以匹配(视为隐式 MODIFIED)
+
+      **MODIFIED Requirements:**
+      - 在主规范中找到需求
+      - 应用更改 - 这可能是:
+        - 添加新场景(不需要复制现有的)
+        - 修改现有场景
+        - 更改需求描述
+      - 保留未在增量中提及的场景/内容
+
+      **REMOVED Requirements:**
+      - 从主规范中删除整个需求块
+
+      **RENAMED Requirements:**
+      - 找到 FROM 需求,重命名为 TO
+
+   d. **创建新主规范** 如果 capability 尚不存在:
+      - 创建 `openspec/specs/<capability>/spec.md`
+      - 添加 Purpose 部分(可以简短,标记为 TBD)
+      - 添加 Requirements 部分包含 ADDED requirements
+
+4. **显示总结**
+
+   应用所有更改后,总结:
+   - 更新了哪些 capabilities
+   - 做了什么更改(需求添加/修改/删除/重命名)
+
+**增量规范格式参考**
+
+```markdown
+## ADDED Requirements
+
+### Requirement: New Feature
+The system SHALL do something new.
+
+#### Scenario: Basic case
+- **WHEN** user does X
+- **THEN** system does Y
+
+## MODIFIED Requirements
+
+### Requirement: Existing Feature
+#### Scenario: New scenario to add
+- **WHEN** user does A
+- **THEN** system does B
+
+## REMOVED Requirements
+
+### Requirement: Deprecated Feature
+
+## RENAMED Requirements
+
+- FROM: `### Requirement: Old Name`
+- TO: `### Requirement: New Name`
+```
+
+**关键原则:智能合并**
+
+与程序化合并不同,你可以应用 **部分更新**:
+- 要添加场景,只需在 MODIFIED 下包含该场景 - 不要复制现有场景
+- 增量代表 *意图*,而不是全盘替换
+- 使用你的判断力合理合并更改
+
+**成功时的输出**
+
+```
+## 规范已同步:<change-name>
+
+已更新主规范:
+
+**<capability-1>**:
+- 添加需求:"New Feature"
+- 修改需求:"Existing Feature"(添加了 1 个场景)
+
+**<capability-2>**:
+- 创建了新规范文件
+- 添加需求:"Another Feature"
+
+主规范现已更新。变更保持活跃 - 在实施完成后归档。
+```
+
+**护栏**
+- 在进行更改之前阅读增量规范和主规范
+- 保留未在增量中提及的现有内容
+- 如果某事不清楚,寻求澄清
+- 在进行时显示你正在更改的内容
+- 操作应是幂等的 - 运行两次应得到相同的结果

+ 168 - 0
.opencode/skills/openspec-verify-change/SKILL.md

@@ -0,0 +1,168 @@
+---
+name: openspec-verify-change
+description: Verify implementation matches change artifacts. Use when the user wants to validate that implementation is complete, correct, and coherent before archiving.
+license: MIT
+compatibility: Requires openspec CLI.
+metadata:
+  author: openspec
+  version: "1.0"
+  generatedBy: "1.1.1"
+---
+
+Verify that an implementation matches the change artifacts (specs, tasks, design).
+
+**Input**: Optionally specify a change name. If omitted, check if it can be inferred from conversation context. If vague or ambiguous you MUST prompt for available changes.
+
+**Steps**
+
+1. **If no change name provided, prompt for selection**
+
+   Run `openspec list --json` to get available changes. Use the **AskUserQuestion tool** to let the user select.
+
+   Show changes that have implementation tasks (tasks artifact exists).
+   Include the schema used for each change if available.
+   Mark changes with incomplete tasks as "(In Progress)".
+
+   **IMPORTANT**: Do NOT guess or auto-select a change. Always let the user choose.
+
+2. **Check status to understand the schema**
+   ```bash
+   openspec status --change "<name>" --json
+   ```
+   Parse the JSON to understand:
+   - `schemaName`: The workflow being used (e.g., "spec-driven")
+   - Which artifacts exist for this change
+
+3. **Get the change directory and load artifacts**
+
+   ```bash
+   openspec instructions apply --change "<name>" --json
+   ```
+
+   This returns the change directory and context files. Read all available artifacts from `contextFiles`.
+
+4. **Initialize verification report structure**
+
+   Create a report structure with three dimensions:
+   - **Completeness**: Track tasks and spec coverage
+   - **Correctness**: Track requirement implementation and scenario coverage
+   - **Coherence**: Track design adherence and pattern consistency
+
+   Each dimension can have CRITICAL, WARNING, or SUGGESTION issues.
+
+5. **Verify Completeness**
+
+   **Task Completion**:
+   - If tasks.md exists in contextFiles, read it
+   - Parse checkboxes: `- [ ]` (incomplete) vs `- [x]` (complete)
+   - Count complete vs total tasks
+   - If incomplete tasks exist:
+     - Add CRITICAL issue for each incomplete task
+     - Recommendation: "Complete task: <description>" or "Mark as done if already implemented"
+
+   **Spec Coverage**:
+   - If delta specs exist in `openspec/changes/<name>/specs/`:
+     - Extract all requirements (marked with "### Requirement:")
+     - For each requirement:
+       - Search codebase for keywords related to the requirement
+       - Assess if implementation likely exists
+     - If requirements appear unimplemented:
+       - Add CRITICAL issue: "Requirement not found: <requirement name>"
+       - Recommendation: "Implement requirement X: <description>"
+
+6. **Verify Correctness**
+
+   **Requirement Implementation Mapping**:
+   - For each requirement from delta specs:
+     - Search codebase for implementation evidence
+     - If found, note file paths and line ranges
+     - Assess if implementation matches requirement intent
+     - If divergence detected:
+       - Add WARNING: "Implementation may diverge from spec: <details>"
+       - Recommendation: "Review <file>:<lines> against requirement X"
+
+   **Scenario Coverage**:
+   - For each scenario in delta specs (marked with "#### Scenario:"):
+     - Check if conditions are handled in code
+     - Check if tests exist covering the scenario
+     - If scenario appears uncovered:
+       - Add WARNING: "Scenario not covered: <scenario name>"
+       - Recommendation: "Add test or implementation for scenario: <description>"
+
+7. **Verify Coherence**
+
+   **Design Adherence**:
+   - If design.md exists in contextFiles:
+     - Extract key decisions (look for sections like "Decision:", "Approach:", "Architecture:")
+     - Verify implementation follows those decisions
+     - If contradiction detected:
+       - Add WARNING: "Design decision not followed: <decision>"
+       - Recommendation: "Update implementation or revise design.md to match reality"
+   - If no design.md: Skip design adherence check, note "No design.md to verify against"
+
+   **Code Pattern Consistency**:
+   - Review new code for consistency with project patterns
+   - Check file naming, directory structure, coding style
+   - If significant deviations found:
+     - Add SUGGESTION: "Code pattern deviation: <details>"
+     - Recommendation: "Consider following project pattern: <example>"
+
+8. **Generate Verification Report**
+
+   **Summary Scorecard**:
+   ```
+   ## Verification Report: <change-name>
+
+   ### Summary
+   | Dimension    | Status           |
+   |--------------|------------------|
+   | Completeness | X/Y tasks, N reqs|
+   | Correctness  | M/N reqs covered |
+   | Coherence    | Followed/Issues  |
+   ```
+
+   **Issues by Priority**:
+
+   1. **CRITICAL** (Must fix before archive):
+      - Incomplete tasks
+      - Missing requirement implementations
+      - Each with specific, actionable recommendation
+
+   2. **WARNING** (Should fix):
+      - Spec/design divergences
+      - Missing scenario coverage
+      - Each with specific recommendation
+
+   3. **SUGGESTION** (Nice to fix):
+      - Pattern inconsistencies
+      - Minor improvements
+      - Each with specific recommendation
+
+   **Final Assessment**:
+   - If CRITICAL issues: "X critical issue(s) found. Fix before archiving."
+   - If only warnings: "No critical issues. Y warning(s) to consider. Ready for archive (with noted improvements)."
+   - If all clear: "All checks passed. Ready for archive."
+
+**Verification Heuristics**
+
+- **Completeness**: Focus on objective checklist items (checkboxes, requirements list)
+- **Correctness**: Use keyword search, file path analysis, reasonable inference - don't require perfect certainty
+- **Coherence**: Look for glaring inconsistencies, don't nitpick style
+- **False Positives**: When uncertain, prefer SUGGESTION over WARNING, WARNING over CRITICAL
+- **Actionability**: Every issue must have a specific recommendation with file/line references where applicable
+
+**Graceful Degradation**
+
+- If only tasks.md exists: verify task completion only, skip spec/design checks
+- If tasks + specs exist: verify completeness and correctness, skip design
+- If full artifacts: verify all three dimensions
+- Always note which checks were skipped and why
+
+**Output Format**
+
+Use clear markdown with:
+- Table for summary scorecard
+- Grouped lists for issues (CRITICAL/WARNING/SUGGESTION)
+- Code references in format: `file.ts:123`
+- Specific, actionable recommendations
+- No vague suggestions like "consider reviewing"

+ 161 - 0
README.md

@@ -0,0 +1,161 @@
+# 知行益家 - 小程序
+
+## 项目简介
+
+知行益家是一款家庭教育任务管理小程序,通过积分奖励机制激励孩子自主完成任务,培养时间管理和延迟满足能力。
+
+## 技术栈
+
+| 层级 | 技术 |
+|------|------|
+| 前端 | 微信小程序原生开发 |
+| 后端 | Java 1.8 + Spring Boot 2.7.18 |
+| 数据库 | MySQL 8.0 |
+| 认证 | JWT |
+| 文档 | Swagger 3.0 |
+
+## 项目结构
+
+```
+zhixingyijia/
+├── backend/                          # Java后端
+│   ├── pom.xml                       # Maven配置
+│   ├── src/main/java/com/zhixingyijia/
+│   │   ├── ZhixingyijiaApplication.java  # 启动类
+│   │   ├── common/                   # 公共类(Result, JwtUtil)
+│   │   ├── config/                   # 配置类
+│   │   ├── controller/              # 控制器
+│   │   ├── entity/                  # 实体类
+│   │   ├── mapper/                  # MyBatis Mapper
+│   │   ├── service/                 # 业务逻辑
+│   │   └── dto/                     # 数据传输对象
+│   └── src/main/resources/
+│       ├── application.yml          # 配置文件
+│       └── mapper/                  # MyBatis XML
+│
+├── miniprogram/                      # 微信小程序前端
+│   ├── app.js / app.json / app.wxss # 应用入口
+│   ├── pages/                       # 页面
+│   │   ├── index/                   # 首页(角色分流)
+│   │   ├── login/                   # 登录页
+│   │   ├── parent-home/             # 家长端首页
+│   │   ├── child-home/              # 孩子端首页
+│   │   └── ...                      # 其他页面
+│   └── utils/                       # 工具函数
+│
+├── docs/                            # 文档
+│   └── superpowers/
+│       ├── specs/                   # 设计文档
+│       └── plans/                   # 实现计划
+│
+└── README.md                        # 本文件
+```
+
+## 快速开始
+
+### 1. 环境准备
+
+- JDK 1.8+
+- Maven 3.6+
+- MySQL 8.0+
+- 微信开发者工具
+
+### 2. 数据库初始化
+
+```bash
+# 创建数据库
+mysql -u root -p
+CREATE DATABASE zhixingyijia CHARACTER SET utf8mb4;
+
+# 执行初始化脚本
+mysql -u root -p zhixingyijia < backend/src/main/resources/sql/init.sql
+mysql -u root -p zhixingyijia < backend/src/main/resources/sql/seed.sql
+```
+
+### 3. 配置后端
+
+编辑 `backend/src/main/resources/application.yml`:
+
+```yaml
+spring:
+  datasource:
+    url: jdbc:mysql://localhost:3306/zhixingyijia?...
+    username: your_username    # 修改为你的数据库用户名
+    password: your_password    # 修改为你的数据库密码
+```
+
+### 4. 启动后端
+
+```bash
+cd backend
+mvn clean package
+java -jar target/zhixingyijia-backend-1.0.0.jar
+```
+
+或直接在IDE中运行 `ZhixingyijiaApplication.java`
+
+### 5. 配置小程序
+
+1. 在[微信公众平台](https://mp.weixin.qq.com/)注册小程序获取AppID
+2. 在微信开发者工具中导入 `miniprogram` 目录
+3. 修改 `miniprogram/app.js` 中的 `baseUrl` 为你的后端地址
+
+### 6. 访问API文档
+
+启动后端后,访问:http://localhost:8080/api/swagger-ui.html
+
+## 核心功能
+
+### MVP版本(已完成)
+
+| 功能 | 说明 |
+|------|------|
+| ✅ 用户认证 | 微信登录、JWT认证 |
+| ✅ 任务管理 | 创建、执行、完成任务 |
+| ✅ 积分系统 | 完成任务获积分、超时扣分 |
+| ✅ 奖励体系 | 心愿单、兑换、审批 |
+| ✅ 双模式切换 | 家长模式/孩子模式 |
+
+### 待开发
+
+| 功能 | 优先级 |
+|------|--------|
+| 连续打卡 | P1 |
+| 专注训练 | P1 |
+| AI审核 | P1 |
+| DAN测评模板 | P1 |
+| 勋章墙 | P1 |
+
+## API接口
+
+### 认证接口
+
+| 接口 | 方法 | 说明 |
+|------|------|------|
+| /api/auth/login | POST | 微信登录 |
+| /api/auth/info | GET | 获取用户信息 |
+
+### 任务接口
+
+| 接口 | 方法 | 说明 |
+|------|------|------|
+| /api/tasks/today/{childId} | GET | 获取今日任务 |
+| /api/tasks | POST | 创建任务 |
+| /api/tasks/{id}/complete | POST | 完成任务 |
+| /api/tasks/{id}/review | POST | 审核任务 |
+
+### 奖励接口
+
+| 接口 | 方法 | 说明 |
+|------|------|------|
+| /api/rewards/family/{familyId} | GET | 获取家庭奖励 |
+| /api/rewards | POST | 创建奖励 |
+
+## 开发团队
+
+- 需求分析:知行益家项目组
+- 开发:AI辅助开发
+
+## 许可证
+
+MIT License

+ 89 - 0
backend/pom.xml

@@ -0,0 +1,89 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
+         http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>org.springframework.boot</groupId>
+        <artifactId>spring-boot-starter-parent</artifactId>
+        <version>2.7.18</version>
+        <relativePath/>
+    </parent>
+
+    <groupId>com.zhixingyijia</groupId>
+    <artifactId>zhixingyijia-backend</artifactId>
+    <version>1.0.0</version>
+    <packaging>jar</packaging>
+
+    <name>zhixingyijia-backend</name>
+    <description>知行益家小程序后端</description>
+
+    <properties>
+        <java.version>1.8</java.version>
+        <mybatis.version>2.3.2</mybatis.version>
+    </properties>
+
+    <dependencies>
+        <!-- Spring Boot Web -->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-web</artifactId>
+        </dependency>
+
+        <!-- MyBatis -->
+        <dependency>
+            <groupId>org.mybatis.spring.boot</groupId>
+            <artifactId>mybatis-spring-boot-starter</artifactId>
+            <version>${mybatis.version}</version>
+        </dependency>
+
+        <!-- MySQL -->
+        <dependency>
+            <groupId>mysql</groupId>
+            <artifactId>mysql-connector-java</artifactId>
+            <scope>runtime</scope>
+        </dependency>
+
+        <!-- Swagger -->
+        <dependency>
+            <groupId>org.springdoc</groupId>
+            <artifactId>springdoc-openapi-ui</artifactId>
+            <version>1.7.0</version>
+        </dependency>
+
+        <!-- JWT -->
+        <dependency>
+            <groupId>io.jsonwebtoken</groupId>
+            <artifactId>jjwt</artifactId>
+            <version>0.9.1</version>
+        </dependency>
+
+        <!-- Lombok -->
+        <dependency>
+            <groupId>org.projectlombok</groupId>
+            <artifactId>lombok</artifactId>
+            <optional>true</optional>
+        </dependency>
+
+        <!-- Test -->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-test</artifactId>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-maven-plugin</artifactId>
+                <configuration>
+                    <mainClass>com.zhixingyijia.ZhixingyijiaApplication</mainClass>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+</project>

+ 18 - 0
backend/src/main/java/com/zhixingyijia/ZhixingyijiaApplication.java

@@ -0,0 +1,18 @@
+package com.zhixingyijia;
+
+import org.mybatis.spring.annotation.MapperScan;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+/**
+ * 知行益家小程序启动类
+ */
+@SpringBootApplication
+@MapperScan("com.zhixingyijia.mapper")
+public class XinzhiyijiaApplication {
+
+    public static void main(String[] args) {
+        SpringApplication.run(XinzhiyijiaApplication.class, args);
+    }
+}
+

+ 77 - 0
backend/src/main/java/com/zhixingyijia/common/JwtUtil.java

@@ -0,0 +1,77 @@
+package com.zhixingyijia.common;
+
+import io.jsonwebtoken.*;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Component;
+
+import java.util.Date;
+
+/**
+ * JWT工具� */
+@Component
+public class JwtUtil {
+
+    @Value("${jwt.secret}")
+    private String secret;
+
+    @Value("${jwt.expiration}")
+    private Long expiration;
+
+    /**
+     * 生�JWT Token
+     */
+    public String generateToken(Long userId, String role) {
+        return Jwts.builder()
+                .setSubject(String.valueOf(userId))
+                .claim("role", role)
+                .setIssuedAt(new Date())
+                .setExpiration(new Date(System.currentTimeMillis() + expiration))
+                .signWith(SignatureAlgorithm.HS256, secret)
+                .compact();
+    }
+
+    /**
+     * 解�Token
+     */
+    public Claims parseToken(String token) {
+        try {
+            return Jwts.parser()
+                    .setSigningKey(secret)
+                    .parseClaimsJws(token)
+                    .getBody();
+        } catch (ExpiredJwtException e) {
+            throw new RuntimeException("Token已过�);
+        } catch (Exception e) {
+            throw new RuntimeException("Token无效");
+        }
+    }
+
+    /**
+     * 从Token获�用户ID
+     */
+    public Long getUserIdFromToken(String token) {
+        Claims claims = parseToken(token);
+        return Long.parseLong(claims.getSubject());
+    }
+
+    /**
+     * 从Token获�角色
+     */
+    public String getRoleFromToken(String token) {
+        Claims claims = parseToken(token);
+        return claims.get("role", String.class);
+    }
+
+    /**
+     * 验�Token是�有效
+     */
+    public boolean isTokenValid(String token) {
+        try {
+            parseToken(token);
+            return true;
+        } catch (Exception e) {
+            return false;
+        }
+    }
+}
+

+ 54 - 0
backend/src/main/java/com/zhixingyijia/common/Result.java

@@ -0,0 +1,54 @@
+package com.zhixingyijia.common;
+
+import lombok.Data;
+
+/**
+ * 统一�应结果�装
+ */
+@Data
+public class Result<T> {
+    
+    private int code;
+    private String message;
+    private T data;
+
+    public static <T> Result<T> success() {
+        return success(null);
+    }
+
+    public static <T> Result<T> success(T data) {
+        Result<T> result = new Result<>();
+        result.setCode(200);
+        result.setMessage("success");
+        result.setData(data);
+        return result;
+    }
+
+    public static <T> Result<T> success(String message, T data) {
+        Result<T> result = new Result<>();
+        result.setCode(200);
+        result.setMessage(message);
+        result.setData(data);
+        return result;
+    }
+
+    public static <T> Result<T> error(int code, String message) {
+        Result<T> result = new Result<>();
+        result.setCode(code);
+        result.setMessage(message);
+        return result;
+    }
+
+    public static <T> Result<T> error(String message) {
+        return error(500, message);
+    }
+
+    public static <T> Result<T> unauthorized() {
+        return error(401, "未授�);
+    }
+
+    public static <T> Result<T> notFound(String message) {
+        return error(404, message != null ? message : "资��存�);
+    }
+}
+

+ 33 - 0
backend/src/main/java/com/zhixingyijia/common/ResultCode.java

@@ -0,0 +1,33 @@
+package com.zhixingyijia.common;
+
+import lombok.Getter;
+
+/**
+ * �应状��枚举
+ */
+@Getter
+public enum ResultCode {
+    
+    SUCCESS(200, "�功"),
+    BAD_REQUEST(400, "请求�数错误"),
+    UNAUTHORIZED(401, "未授�),
+    FORBIDDEN(403, "�止访问"),
+    NOT_FOUND(404, "资��存�),
+    INTERNAL_ERROR(500, "�务器内部错�),
+    
+    // 业务错误�1000+
+    TASK_NOT_FOUND(1001, "任务�存�),
+    TASK_ALREADY_COMPLETED(1002, "任务已完�),
+    INSUFFICIENT_POINTS(1003, "积分�足"),
+    REWARD_NOT_AVAILABLE(1004, "奖励���),
+    PENALTY_DISABLED(1005, "扣分功能已关�);
+
+    private final int code;
+    private final String message;
+
+    ResultCode(int code, String message) {
+        this.code = code;
+        this.message = message;
+    }
+}
+

+ 55 - 0
backend/src/main/java/com/zhixingyijia/controller/AuthController.java

@@ -0,0 +1,55 @@
+package com.zhixingyijia.controller;
+
+import com.zhixingyijia.common.Result;
+import com.zhixingyijia.entity.User;
+import com.zhixingyijia.service.UserService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * 霈方��批��? */
+@Tag(name = "霈方��亙藁")
+@RestController
+@RequestMapping("/auth")
+public class AuthController {
+
+    @Autowired
+    private UserService userService;
+
+    @Operation(summary = "敺桐縑�餃�")
+    @PostMapping("/login")
+    public Result<Map<String, Object>> login(@RequestBody Map<String, String> params) {
+        String code = params.get("code");
+        // TODO: �朞�code靚�鍂敺桐縑�亙藁�瑕�openid
+        
+        // 璅⊥��餃�
+        User user = new User();
+        user.setOpenid("mock_openid_" + System.currentTimeMillis());
+        user.setRole(params.getOrDefault("role", "parent"));
+        user.setNickname(params.getOrDefault("nickname", "�冽�"));
+
+        String token = userService.login(user);
+
+        Map<String, Object> data = new HashMap<>();
+        data.put("token", token);
+        data.put("userId", user.getId());
+        data.put("role", user.getRole());
+        
+        return Result.success(data);
+    }
+
+    @Operation(summary = "�瑕��冽�靽⊥�")
+    @GetMapping("/info")
+    public Result<User> getUserInfo(@RequestHeader("Authorization") String token) {
+        // TODO: 隞𤢂oken銝剛圾�鞟鍂�弒D
+        Long userId = 1L; // mock
+        User user = userService.findById(userId);
+        return Result.success(user);
+    }
+}
+

+ 41 - 0
backend/src/main/java/com/zhixingyijia/controller/PointsController.java

@@ -0,0 +1,41 @@
+package com.zhixingyijia.controller;
+
+import com.zhixingyijia.common.Result;
+import com.zhixingyijia.entity.PointsLog;
+import com.zhixingyijia.service.PointsService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 积分控制� */
+@Tag(name = "积分接�")
+@RestController
+@RequestMapping("/points")
+public class PointsController {
+
+    @Autowired
+    private PointsService pointsService;
+
+    @Operation(summary = "获�孩�积分")
+    @GetMapping("/balance/{childId}")
+    public Result<Map<String, Object>> getPointsBalance(@PathVariable Long childId) {
+        int points = pointsService.getChildPoints(childId);
+        Map<String, Object> data = new HashMap<>();
+        data.put("childId", childId);
+        data.put("points", points);
+        return Result.success(data);
+    }
+
+    @Operation(summary = "获�积分�水")
+    @GetMapping("/log/{childId}")
+    public Result<List<PointsLog>> getPointsLog(@PathVariable Long childId) {
+        return Result.success(pointsService.getPointsLog(childId));
+    }
+}
+

+ 54 - 0
backend/src/main/java/com/zhixingyijia/controller/RewardController.java

@@ -0,0 +1,54 @@
+package com.zhixingyijia.controller;
+
+import com.zhixingyijia.common.Result;
+import com.zhixingyijia.entity.Reward;
+import com.zhixingyijia.service.RewardService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+/**
+ * 奖励控制� */
+@Tag(name = "奖励接�")
+@RestController
+@RequestMapping("/rewards")
+public class RewardController {
+
+    @Autowired
+    private RewardService rewardService;
+
+    @Operation(summary = "获�家庭奖励列表")
+    @GetMapping("/family/{familyId}")
+    public Result<List<Reward>> getFamilyRewards(@PathVariable Long familyId) {
+        return Result.success(rewardService.getFamilyRewards(familyId));
+    }
+
+    @Operation(summary = "获�奖励详情")
+    @GetMapping("/{rewardId}")
+    public Result<Reward> getRewardDetail(@PathVariable Long rewardId) {
+        return Result.success(rewardService.findById(rewardId));
+    }
+
+    @Operation(summary = "创建奖励")
+    @PostMapping
+    public Result<Reward> createReward(@RequestBody Reward reward) {
+        return Result.success(rewardService.createReward(reward));
+    }
+
+    @Operation(summary = "更新奖励")
+    @PutMapping("/{rewardId}")
+    public Result<Reward> updateReward(@PathVariable Long rewardId, @RequestBody Reward reward) {
+        reward.setId(rewardId);
+        return Result.success(rewardService.updateReward(reward));
+    }
+
+    @Operation(summary = "获�系统预设奖励")
+    @GetMapping("/defaults")
+    public Result<List<Reward>> getDefaultRewards() {
+        return Result.success(rewardService.getDefaultRewards());
+    }
+}
+

+ 69 - 0
backend/src/main/java/com/zhixingyijia/controller/TaskController.java

@@ -0,0 +1,69 @@
+package com.zhixingyijia.controller;
+
+import com.zhixingyijia.common.Result;
+import com.zhixingyijia.entity.Task;
+import com.zhixingyijia.service.TaskService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 任务控制� */
+@Tag(name = "任务接�")
+@RestController
+@RequestMapping("/tasks")
+public class TaskController {
+
+    @Autowired
+    private TaskService taskService;
+
+    @Operation(summary = "获�今日任务")
+    @GetMapping("/today/{childId}")
+    public Result<List<Task>> getTodayTasks(@PathVariable Long childId) {
+        return Result.success(taskService.getTodayTasks(childId));
+    }
+
+    @Operation(summary = "获�任务详情")
+    @GetMapping("/{taskId}")
+    public Result<Task> getTaskDetail(@PathVariable Long taskId) {
+        return Result.success(taskService.findById(taskId));
+    }
+
+    @Operation(summary = "创建任务")
+    @PostMapping
+    public Result<Task> createTask(@RequestBody Task task) {
+        return Result.success(taskService.createTask(task));
+    }
+
+    @Operation(summary = "完�任务")
+    @PostMapping("/{taskId}/complete")
+    public Result<Task> completeTask(
+            @PathVariable Long taskId,
+            @RequestBody(required = false) Map<String, String> params) {
+        String photoUrl = params != null ? params.get("photoUrl") : null;
+        String voiceUrl = params != null ? params.get("voiceUrl") : null;
+        return Result.success(taskService.completeTask(taskId, photoUrl, voiceUrl));
+    }
+
+    @Operation(summary = "审核任务")
+    @PostMapping("/{taskId}/review")
+    public Result<Task> reviewTask(
+            @PathVariable Long taskId,
+            @RequestBody Map<String, String> params) {
+        String result = params.get("result");
+        String comment = params.get("comment");
+        Long reviewerId = Long.parseLong(params.get("reviewerId"));
+        return Result.success(taskService.reviewTask(taskId, result, comment, reviewerId));
+    }
+
+    @Operation(summary = "获�家庭任务列表")
+    @GetMapping("/family/{familyId}")
+    public Result<List<Task>> getFamilyTasks(@PathVariable Long familyId) {
+        return Result.success(taskService.getFamilyTasks(familyId));
+    }
+}
+

+ 57 - 0
backend/src/main/java/com/zhixingyijia/entity/Child.java

@@ -0,0 +1,57 @@
+package com.zhixingyijia.entity;
+
+import lombok.Data;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+
+/**
+ * 孩�信�实体� */
+@Data
+public class Child {
+    
+    /** 主键ID */
+    private Long id;
+    
+    /** 关�用户ID */
+    private Long userId;
+    
+    /** 所属家庭ID */
+    private Long familyId;
+    
+    /** 年龄 */
+    private Integer age;
+    
+    /** 年级 */
+    private String grade;
+    
+    /** DAN测评等级 */
+    private String danLevel;
+    
+    /** 是�开�扣分(6�以下默认关闭) */
+    private Boolean penaltyEnabled;
+    
+    /** 当�连续打�天数 */
+    private Integer currentStreak;
+    
+    /** 历�最长连续天�*/
+    private Integer maxStreak;
+    
+    /** 最�打�日�*/
+    private LocalDateTime lastCheckInDate;
+    
+    /** 当�积分 */
+    private Integer totalPoints;
+    
+    /** 累计获得积分 */
+    private Integer totalEarned;
+    
+    /** 主题 */
+    private String theme;
+    
+    /** 创建时间 */
+    private LocalDateTime createdAt;
+    
+    /** æ›´æ–°æ—¶é—´ */
+    private LocalDateTime updatedAt;
+}
+

+ 29 - 0
backend/src/main/java/com/zhixingyijia/entity/Family.java

@@ -0,0 +1,29 @@
+package com.zhixingyijia.entity;
+
+import lombok.Data;
+import java.time.LocalDateTime;
+
+/**
+ * 家庭实体� */
+@Data
+public class Family {
+    
+    /** 主键ID */
+    private Long id;
+    
+    /** 家庭�称 */
+    private String name;
+    
+    /** 邀请� */
+    private String inviteCode;
+    
+    /** 创建者ID */
+    private Long creatorId;
+    
+    /** 创建时间 */
+    private LocalDateTime createdAt;
+    
+    /** æ›´æ–°æ—¶é—´ */
+    private LocalDateTime updatedAt;
+}
+

+ 32 - 0
backend/src/main/java/com/zhixingyijia/entity/PointsLog.java

@@ -0,0 +1,32 @@
+package com.zhixingyijia.entity;
+
+import lombok.Data;
+import java.time.LocalDateTime;
+
+/**
+ * 积分�水实体� */
+@Data
+public class PointsLog {
+    
+    /** 主键ID */
+    private Long id;
+    
+    /** å­©å­�ID */
+    private Long childId;
+    
+    /** 关�任务ID */
+    private Long taskId;
+    
+    /** 积分数(正数为获得,负数为扣除) */
+    private Integer amount;
+    
+    /** 类型:task-任务/early-��/streak-打�/focus-专注/reward-兑�/correction-修正 */
+    private String type;
+    
+    /** �述 */
+    private String description;
+    
+    /** 创建时间 */
+    private LocalDateTime createdAt;
+}
+

+ 50 - 0
backend/src/main/java/com/zhixingyijia/entity/Reward.java

@@ -0,0 +1,50 @@
+package com.zhixingyijia.entity;
+
+import lombok.Data;
+import java.time.LocalDateTime;
+
+/**
+ * 奖励实体� */
+@Data
+public class Reward {
+    
+    /** 主键ID */
+    private Long id;
+    
+    /** 所属家庭ID */
+    private Long familyId;
+    
+    /** 指定孩�ID(NULL表示全员�用�*/
+    private Long childId;
+    
+    /** 分类ID */
+    private Long categoryId;
+    
+    /** 奖励�称 */
+    private String name;
+    
+    /** 奖励�述 */
+    private String description;
+    
+    /** 所需积分 */
+    private Integer pointsRequired;
+    
+    /** 图标 */
+    private String icon;
+    
+    /** 库存�1表示无��*/
+    private Integer stock;
+    
+    /** 状�:active-�用/inactive-���*/
+    private String status;
+    
+    /** 是�系统预设 */
+    private Boolean isDefault;
+    
+    /** 创建时间 */
+    private LocalDateTime createdAt;
+    
+    /** æ›´æ–°æ—¶é—´ */
+    private LocalDateTime updatedAt;
+}
+

+ 38 - 0
backend/src/main/java/com/zhixingyijia/entity/RewardExchange.java

@@ -0,0 +1,38 @@
+package com.zhixingyijia.entity;
+
+import lombok.Data;
+import java.time.LocalDateTime;
+
+/**
+ * 奖励兑�实体� */
+@Data
+public class RewardExchange {
+    
+    /** 主键ID */
+    private Long id;
+    
+    /** 奖励ID */
+    private Long rewardId;
+    
+    /** å­©å­�ID */
+    private Long childId;
+    
+    /** 消耗积�*/
+    private Integer pointsUsed;
+    
+    /** 状�:pending-待审�approved-通过/rejected-拒� */
+    private String status;
+    
+    /** 审核备注 */
+    private String reviewComment;
+    
+    /** 审核人ID */
+    private Long reviewedBy;
+    
+    /** 审核时间 */
+    private LocalDateTime reviewedAt;
+    
+    /** 创建时间 */
+    private LocalDateTime createdAt;
+}
+

+ 83 - 0
backend/src/main/java/com/zhixingyijia/entity/Task.java

@@ -0,0 +1,83 @@
+package com.zhixingyijia.entity;
+
+import lombok.Data;
+import java.time.LocalDateTime;
+
+/**
+ * 任务实体� */
+@Data
+public class Task {
+    
+    /** 主键ID */
+    private Long id;
+    
+    /** 所属家庭ID */
+    private Long familyId;
+    
+    /** 创建者(家长)ID */
+    private Long creatorId;
+    
+    /** 执行孩�ID */
+    private Long childId;
+    
+    /** 任务�称 */
+    private String title;
+    
+    /** 备注说明 */
+    private String description;
+    
+    /** 积分数(1-10�*/
+    private Integer points;
+    
+    /** 分类:学�生活/�动/其他 */
+    private String category;
+    
+    /** 优先级:high/medium/low */
+    private String priority;
+    
+    /** 截止时间 */
+    private LocalDateTime deadline;
+    
+    /** 预计时长(分钟) */
+    private Integer estimatedDuration;
+    
+    /** ��类型:none/daily/weekly */
+    private String repeatType;
+    
+    /** 是�需�审�*/
+    private Boolean needReview;
+    
+    /** 状�:pending/completed/approved/rejected/overdue */
+    private String status;
+    
+    /** 完�时间 */
+    private LocalDateTime completedAt;
+    
+    /** 是���完� */
+    private Boolean completedEarly;
+    
+    /** 审核照片地� */
+    private String reviewPhotoUrl;
+    
+    /** 审核语音地� */
+    private String reviewVoiceUrl;
+    
+    /** 审核状�:pending/reviewing/approved/rejected */
+    private String reviewStatus;
+    
+    /** 审核备注 */
+    private String reviewComment;
+    
+    /** 审核时间 */
+    private LocalDateTime reviewedAt;
+    
+    /** 审核人ID */
+    private Long reviewedBy;
+    
+    /** 创建时间 */
+    private LocalDateTime createdAt;
+    
+    /** æ›´æ–°æ—¶é—´ */
+    private LocalDateTime updatedAt;
+}
+

+ 41 - 0
backend/src/main/java/com/zhixingyijia/entity/User.java

@@ -0,0 +1,41 @@
+package com.zhixingyijia.entity;
+
+import lombok.Data;
+import java.time.LocalDateTime;
+
+/**
+ * 用户实体� */
+@Data
+public class User {
+    
+    /** 主键ID */
+    private Long id;
+    
+    /** 微信openid */
+    private String openid;
+    
+    /** 手机�*/
+    private String phone;
+    
+    /** 昵称 */
+    private String nickname;
+    
+    /** 头�URL */
+    private String avatar;
+    
+    /** 角色:parent-家长,child-孩� */
+    private String role;
+    
+    /** 密�(加密) */
+    private String password;
+    
+    /** 所属家庭ID */
+    private Long familyId;
+    
+    /** 创建时间 */
+    private LocalDateTime createdAt;
+    
+    /** æ›´æ–°æ—¶é—´ */
+    private LocalDateTime updatedAt;
+}
+

+ 23 - 0
backend/src/main/java/com/zhixingyijia/mapper/ChildMapper.java

@@ -0,0 +1,23 @@
+package com.zhixingyijia.mapper;
+
+import com.zhixingyijia.entity.Child;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+
+/**
+ * 孩子Mapper接口
+ */
+@Mapper
+public interface ChildMapper {
+    
+    Child findById(@Param("id") Long id);
+    
+    Child findByUserId(@Param("userId") Long userId);
+    
+    int insert(Child child);
+    
+    int update(Child child);
+    
+    int updatePoints(@Param("id") Long id, @Param("points") int points);
+}
+

+ 20 - 0
backend/src/main/java/com/zhixingyijia/mapper/PointsLogMapper.java

@@ -0,0 +1,20 @@
+package com.zhixingyijia.mapper;
+
+import com.zhixingyijia.entity.PointsLog;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+import java.util.List;
+
+/**
+ * 积分流水Mapper接口
+ */
+@Mapper
+public interface PointsLogMapper {
+    
+    List<PointsLog> findByChildId(@Param("childId") Long childId, @Param("limit") int limit);
+    
+    int insert(PointsLog pointsLog);
+    
+    int sumByChildIdAndType(@Param("childId") Long childId, @Param("type") String type, @Param("startDate") String startDate);
+}
+

+ 24 - 0
backend/src/main/java/com/zhixingyijia/mapper/RewardMapper.java

@@ -0,0 +1,24 @@
+package com.zhixingyijia.mapper;
+
+import com.zhixingyijia.entity.Reward;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+import java.util.List;
+
+/**
+ * 奖励Mapper接口
+ */
+@Mapper
+public interface RewardMapper {
+    
+    Reward findById(@Param("id") Long id);
+    
+    List<Reward> findByFamilyId(@Param("familyId") Long familyId);
+    
+    List<Reward> findDefaultRewards();
+    
+    int insert(Reward reward);
+    
+    int update(Reward reward);
+}
+

+ 30 - 0
backend/src/main/java/com/zhixingyijia/mapper/TaskMapper.java

@@ -0,0 +1,30 @@
+package com.zhixingyijia.mapper;
+
+import com.zhixingyijia.entity.Task;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+import java.util.List;
+
+/**
+ * 任务Mapper接口
+ */
+@Mapper
+public interface TaskMapper {
+    
+    Task findById(@Param("id") Long id);
+    
+    List<Task> findByChildId(@Param("childId") Long childId, @Param("date") String date);
+    
+    List<Task> findByFamilyId(@Param("familyId") Long familyId);
+    
+    int insert(Task task);
+    
+    int update(Task task);
+    
+    int updateStatus(@Param("id") Long id, @Param("status") String status);
+    
+    int countTodayCompleted(@Param("childId") Long childId, @Param("date") String date);
+    
+    int countTodayTotal(@Param("childId") Long childId, @Param("date") String date);
+}
+

+ 21 - 0
backend/src/main/java/com/zhixingyijia/mapper/UserMapper.java

@@ -0,0 +1,21 @@
+package com.zhixingyijia.mapper;
+
+import com.zhixingyijia.entity.User;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+
+/**
+ * 用户Mapper接口
+ */
+@Mapper
+public interface UserMapper {
+    
+    User findById(@Param("id") Long id);
+    
+    User findByOpenid(@Param("openid") String openid);
+    
+    int insert(User user);
+    
+    int update(User user);
+}
+

+ 44 - 0
backend/src/main/java/com/zhixingyijia/service/PointsService.java

@@ -0,0 +1,44 @@
+package com.zhixingyijia.service;
+
+import com.zhixingyijia.entity.Child;
+import com.zhixingyijia.entity.PointsLog;
+import com.zhixingyijia.mapper.ChildMapper;
+import com.zhixingyijia.mapper.PointsLogMapper;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+
+/**
+ * 积分�务� */
+@Service
+public class PointsService {
+
+    @Autowired
+    private ChildMapper childMapper;
+
+    @Autowired
+    private PointsLogMapper pointsLogMapper;
+
+    /**
+     * 获�孩�当�积分
+     */
+    public int getChildPoints(Long childId) {
+        Child child = childMapper.findById(childId);
+        return child != null ? child.getTotalPoints() : 0;
+    }
+
+    /**
+     * 获�积分�水
+     */
+    public List<PointsLog> getPointsLog(Long childId) {
+        return pointsLogMapper.findByChildId(childId, 50);
+    }
+
+    /**
+     * 获�指定类型的积分汇�     */
+    public int getPointsSumByType(Long childId, String type, String startDate) {
+        return pointsLogMapper.sumByChildIdAndType(childId, type, startDate);
+    }
+}
+

+ 55 - 0
backend/src/main/java/com/zhixingyijia/service/RewardService.java

@@ -0,0 +1,55 @@
+package com.zhixingyijia.service;
+
+import com.zhixingyijia.entity.Reward;
+import com.zhixingyijia.mapper.RewardMapper;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+
+/**
+ * 憟硋��滚𦛚蝐? */
+@Service
+public class RewardService {
+
+    @Autowired
+    private RewardMapper rewardMapper;
+
+    /**
+     * �瑕�摰嗅滬憟硋��𡑒”
+     */
+    public List<Reward> getFamilyRewards(Long familyId) {
+        return rewardMapper.findByFamilyId(familyId);
+    }
+
+    /**
+     * �瑕�憟硋�霂行�
+     */
+    public Reward findById(Long id) {
+        return rewardMapper.findById(id);
+    }
+
+    /**
+     * �𥕦遣憟硋�
+     */
+    public Reward createReward(Reward reward) {
+        rewardMapper.insert(reward);
+        return reward;
+    }
+
+    /**
+     * �湔鰵憟硋�
+     */
+    public Reward updateReward(Reward reward) {
+        rewardMapper.update(reward);
+        return reward;
+    }
+
+    /**
+     * �瑕�蝟餌�憸�挽憟硋�
+     */
+    public List<Reward> getDefaultRewards() {
+        return rewardMapper.findDefaultRewards();
+    }
+}
+

+ 152 - 0
backend/src/main/java/com/zhixingyijia/service/TaskService.java

@@ -0,0 +1,152 @@
+package com.zhixingyijia.service;
+
+import com.zhixingyijia.entity.PointsLog;
+import com.zhixingyijia.entity.Task;
+import com.zhixingyijia.mapper.PointsLogMapper;
+import com.zhixingyijia.mapper.TaskMapper;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+import java.util.List;
+
+/**
+ * 任务�务� */
+@Service
+public class TaskService {
+
+    @Autowired
+    private TaskMapper taskMapper;
+
+    @Autowired
+    private PointsLogMapper pointsLogMapper;
+
+    @Value("${zhixingyijia.penalty.grace-minutes:10}")
+    private int graceMinutes;
+
+    @Value("${zhixingyijia.penalty.max-daily:5}")
+    private int maxDailyPenalty;
+
+    @Value("${zhixingyijia.points.early-bonus:1}")
+    private int earlyBonus;
+
+    private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd");
+
+    /**
+     * 获�任务详情
+     */
+    public Task findById(Long id) {
+        return taskMapper.findById(id);
+    }
+
+    /**
+     * 获�今日任务列表
+     */
+    public List<Task> getTodayTasks(Long childId) {
+        String today = LocalDateTime.now().format(DATE_FORMATTER);
+        return taskMapper.findByChildId(childId, today);
+    }
+
+    /**
+     * 创建任务
+     */
+    @Transactional
+    public Task createTask(Task task) {
+        task.setStatus("pending");
+        taskMapper.insert(task);
+        return task;
+    }
+
+    /**
+     * 完�任务
+     */
+    @Transactional
+    public Task completeTask(Long taskId, String photoUrl, String voiceUrl) {
+        Task task = taskMapper.findById(taskId);
+        if (task == null) {
+            throw new RuntimeException("任务�存�);
+        }
+        
+        if ("approved".equals(task.getStatus()) || "completed".equals(task.getStatus())) {
+            throw new RuntimeException("任务已完�);
+        }
+
+        LocalDateTime now = LocalDateTime.now();
+        task.setCompletedAt(now);
+
+        // 判断是���完�
+        boolean isEarly = now.plusMinutes(graceMinutes).isBefore(task.getDeadline());
+        task.setCompletedEarly(isEarly);
+
+        if (Boolean.TRUE.equals(task.getNeedReview())) {
+            // 需�审�            task.setStatus("completed");
+            task.setReviewStatus("pending");
+            task.setReviewPhotoUrl(photoUrl);
+            task.setReviewVoiceUrl(voiceUrl);
+        } else {
+            // �需�审核,直接完�并�放积�            task.setStatus("approved");
+            awardTaskPoints(task, isEarly);
+        }
+
+        taskMapper.update(task);
+        return task;
+    }
+
+    /**
+     * 审核任务
+     */
+    @Transactional
+    public Task reviewTask(Long taskId, String result, String comment, Long reviewerId) {
+        Task task = taskMapper.findById(taskId);
+        if (task == null) {
+            throw new RuntimeException("任务�存�);
+        }
+
+        task.setReviewStatus(result);
+        task.setReviewComment(comment);
+        task.setReviewedBy(reviewerId);
+        task.setReviewedAt(LocalDateTime.now());
+
+        if ("approved".equals(result)) {
+            task.setStatus("approved");
+            awardTaskPoints(task, Boolean.TRUE.equals(task.getCompletedEarly()));
+        } else {
+            task.setStatus("rejected");
+        }
+
+        taskMapper.update(task);
+        return task;
+    }
+
+    /**
+     * �放任务积分
+     */
+    private void awardTaskPoints(Task task, boolean isEarly) {
+        int points = task.getPoints();
+        if (isEarly) {
+            points += earlyBonus;
+        }
+
+        // 记录积分�水
+        PointsLog log = new PointsLog();
+        log.setChildId(task.getChildId());
+        log.setTaskId(task.getId());
+        log.setAmount(points);
+        log.setType(isEarly ? "early" : "task");
+        log.setDescription(isEarly ? 
+            "完�任务� + task.getTitle() + "���完�+" + earlyBonus + "�" :
+            "完�任务� + task.getTitle() + "�);
+        pointsLogMapper.insert(log);
+    }
+
+    /**
+     * 获�家庭任务列表
+     */
+    public List<Task> getFamilyTasks(Long familyId) {
+        return taskMapper.findByFamilyId(familyId);
+    }
+}
+

+ 55 - 0
backend/src/main/java/com/zhixingyijia/service/UserService.java

@@ -0,0 +1,55 @@
+package com.zhixingyijia.service;
+
+import com.zhixingyijia.common.JwtUtil;
+import com.zhixingyijia.entity.User;
+import com.zhixingyijia.mapper.UserMapper;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+/**
+ * 用户�务� */
+@Service
+public class UserService {
+
+    @Autowired
+    private UserMapper userMapper;
+
+    @Autowired
+    private JwtUtil jwtUtil;
+
+    /**
+     * 根�OpenID查找用户
+     */
+    public User findByOpenid(String openid) {
+        return userMapper.findByOpenid(openid);
+    }
+
+    /**
+     * 根�ID查找用户
+     */
+    public User findById(Long id) {
+        return userMapper.findById(id);
+    }
+
+    /**
+     * 微信登录
+     */
+    public String login(User user) {
+        // 查找或创建用�        User existUser = userMapper.findByOpenid(user.getOpenid());
+        if (existUser == null) {
+            userMapper.insert(user);
+            existUser = user;
+        }
+        // 生�Token
+        return jwtUtil.generateToken(existUser.getId(), existUser.getRole());
+    }
+
+    /**
+     * 注册用户
+     */
+    public User register(User user) {
+        userMapper.insert(user);
+        return user;
+    }
+}
+

+ 31 - 0
backend/src/main/resources/application.yml

@@ -0,0 +1,31 @@
+server:
+  port: 8080
+  servlet:
+    context-path: /api
+
+spring:
+  application:
+    name: zhixingyijia-backend
+  datasource:
+    driver-class-name: com.mysql.cj.jdbc.Driver
+    url: jdbc:mysql://localhost:3306/zhixingyijia?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai
+    username: root
+    password: root
+
+mybatis:
+  mapper-locations: classpath:mapper/*.xml
+  type-aliases-package: com.zhixingyijia.entity
+  configuration:
+    map-underscore-to-camel-case: true
+
+jwt:
+  secret: zhixingyijia-secret-key-2026
+  expiration: 604800000
+
+zhixingyijia:
+  penalty:
+    max-daily: 5
+    grace-minutes: 10
+  points:
+    early-bonus: 1
+    focus-perfect-bonus: 1

+ 55 - 0
backend/src/main/resources/mapper/ChildMapper.xml

@@ -0,0 +1,55 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.xinzhiyijia.mapper.ChildMapper">
+
+    <resultMap id="ChildMap" type="com.xinzhiyijia.entity.Child">
+        <id column="id" property="id"/>
+        <result column="user_id" property="userId"/>
+        <result column="family_id" property="familyId"/>
+        <result column="age" property="age"/>
+        <result column="grade" property="grade"/>
+        <result column="dan_level" property="danLevel"/>
+        <result column="penalty_enabled" property="penaltyEnabled"/>
+        <result column="current_streak" property="currentStreak"/>
+        <result column="max_streak" property="maxStreak"/>
+        <result column="last_check_in_date" property="lastCheckInDate"/>
+        <result column="total_points" property="totalPoints"/>
+        <result column="total_earned" property="totalEarned"/>
+        <result column="theme" property="theme"/>
+        <result column="created_at" property="createdAt"/>
+        <result column="updated_at" property="updatedAt"/>
+    </resultMap>
+
+    <select id="findById" resultMap="ChildMap">
+        SELECT * FROM children WHERE id = #{id}
+    </select>
+
+    <select id="findByUserId" resultMap="ChildMap">
+        SELECT * FROM children WHERE user_id = #{userId}
+    </select>
+
+    <insert id="insert" useGeneratedKeys="true" keyProperty="id">
+        INSERT INTO children (user_id, family_id, age, grade, dan_level, penalty_enabled, theme)
+        VALUES (#{userId}, #{familyId}, #{age}, #{grade}, #{danLevel}, #{penaltyEnabled}, #{theme})
+    </insert>
+
+    <update id="update">
+        UPDATE children
+        SET age = #{age},
+            grade = #{grade},
+            dan_level = #{danLevel},
+            penalty_enabled = #{penaltyEnabled},
+            theme = #{theme},
+            updated_at = NOW()
+        WHERE id = #{id}
+    </update>
+
+    <update id="updatePoints">
+        UPDATE children
+        SET total_points = total_points + #{points},
+            total_earned = total_earned + #{points},
+            updated_at = NOW()
+        WHERE id = #{id}
+    </update>
+
+</mapper>

+ 34 - 0
backend/src/main/resources/mapper/PointsLogMapper.xml

@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.xinzhiyijia.mapper.PointsLogMapper">
+
+    <resultMap id="PointsLogMap" type="com.xinzhiyijia.entity.PointsLog">
+        <id column="id" property="id"/>
+        <result column="child_id" property="childId"/>
+        <result column="task_id" property="taskId"/>
+        <result column="amount" property="amount"/>
+        <result column="type" property="type"/>
+        <result column="description" property="description"/>
+        <result column="created_at" property="createdAt"/>
+    </resultMap>
+
+    <select id="findByChildId" resultMap="PointsLogMap">
+        SELECT * FROM points_log 
+        WHERE child_id = #{childId}
+        ORDER BY created_at DESC
+        LIMIT #{limit}
+    </select>
+
+    <insert id="insert">
+        INSERT INTO points_log (child_id, task_id, amount, type, description)
+        VALUES (#{childId}, #{taskId}, #{amount}, #{type}, #{description})
+    </insert>
+
+    <select id="sumByChildIdAndType" resultType="int">
+        SELECT COALESCE(SUM(amount), 0) FROM points_log
+        WHERE child_id = #{childId}
+        AND type = #{type}
+        AND DATE(created_at) >= #{startDate}
+    </select>
+
+</mapper>

+ 57 - 0
backend/src/main/resources/mapper/RewardMapper.xml

@@ -0,0 +1,57 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.xinzhiyijia.mapper.RewardMapper">
+
+    <resultMap id="RewardMap" type="com.xinzhiyijia.entity.Reward">
+        <id column="id" property="id"/>
+        <result column="family_id" property="familyId"/>
+        <result column="child_id" property="childId"/>
+        <result column="category_id" property="categoryId"/>
+        <result column="name" property="name"/>
+        <result column="description" property="description"/>
+        <result column="points_required" property="pointsRequired"/>
+        <result column="icon" property="icon"/>
+        <result column="stock" property="stock"/>
+        <result column="status" property="status"/>
+        <result column="is_default" property="isDefault"/>
+        <result column="created_at" property="createdAt"/>
+        <result column="updated_at" property="updatedAt"/>
+    </resultMap>
+
+    <select id="findById" resultMap="RewardMap">
+        SELECT * FROM rewards WHERE id = #{id}
+    </select>
+
+    <select id="findByFamilyId" resultMap="RewardMap">
+        SELECT * FROM rewards 
+        WHERE family_id = #{familyId} OR family_id = -1
+        AND status = 'active'
+        ORDER BY is_default DESC, created_at DESC
+    </select>
+
+    <select id="findDefaultRewards" resultMap="RewardMap">
+        SELECT * FROM rewards 
+        WHERE is_default = 1 AND status = 'active'
+        ORDER BY category_id, points_required
+    </select>
+
+    <insert id="insert" useGeneratedKeys="true" keyProperty="id">
+        INSERT INTO rewards (family_id, child_id, category_id, name, description, 
+                            points_required, icon, stock, status, is_default)
+        VALUES (#{familyId}, #{childId}, #{categoryId}, #{name}, #{description},
+                #{pointsRequired}, #{icon}, #{stock}, #{status}, #{isDefault})
+    </insert>
+
+    <update id="update">
+        UPDATE rewards
+        SET name = #{name},
+            description = #{description},
+            points_required = #{pointsRequired},
+            icon = #{icon},
+            stock = #{stock},
+            status = #{status},
+            updated_at = NOW()
+        WHERE id = #{id}
+    </update>
+
+</mapper>

+ 86 - 0
backend/src/main/resources/mapper/TaskMapper.xml

@@ -0,0 +1,86 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.xinzhiyijia.mapper.TaskMapper">
+
+    <resultMap id="TaskMap" type="com.xinzhiyijia.entity.Task">
+        <id column="id" property="id"/>
+        <result column="family_id" property="familyId"/>
+        <result column="creator_id" property="creatorId"/>
+        <result column="child_id" property="childId"/>
+        <result column="title" property="title"/>
+        <result column="description" property="description"/>
+        <result column="points" property="points"/>
+        <result column="category" property="category"/>
+        <result column="priority" property="priority"/>
+        <result column="deadline" property="deadline"/>
+        <result column="estimated_duration" property="estimatedDuration"/>
+        <result column="repeat_type" property="repeatType"/>
+        <result column="need_review" property="needReview"/>
+        <result column="status" property="status"/>
+        <result column="completed_at" property="completedAt"/>
+        <result column="completed_early" property="completedEarly"/>
+        <result column="review_photo_url" property="reviewPhotoUrl"/>
+        <result column="review_voice_url" property="reviewVoiceUrl"/>
+        <result column="review_status" property="reviewStatus"/>
+        <result column="review_comment" property="reviewComment"/>
+        <result column="reviewed_at" property="reviewedAt"/>
+        <result column="reviewed_by" property="reviewedBy"/>
+        <result column="created_at" property="createdAt"/>
+        <result column="updated_at" property="updatedAt"/>
+    </resultMap>
+
+    <select id="findById" resultMap="TaskMap">
+        SELECT * FROM tasks WHERE id = #{id}
+    </select>
+
+    <select id="findByChildId" resultMap="TaskMap">
+        SELECT * FROM tasks 
+        WHERE child_id = #{childId}
+        AND DATE(deadline) = #{date}
+        ORDER BY deadline ASC
+    </select>
+
+    <select id="findByFamilyId" resultMap="TaskMap">
+        SELECT * FROM tasks 
+        WHERE family_id = #{familyId}
+        ORDER BY deadline DESC
+    </select>
+
+    <insert id="insert" useGeneratedKeys="true" keyProperty="id">
+        INSERT INTO tasks (family_id, creator_id, child_id, title, description, points, 
+                          category, priority, deadline, estimated_duration, repeat_type, need_review)
+        VALUES (#{familyId}, #{creatorId}, #{childId}, #{title}, #{description}, #{points},
+                #{category}, #{priority}, #{deadline}, #{estimatedDuration}, #{repeatType}, #{needReview})
+    </insert>
+
+    <update id="update">
+        UPDATE tasks
+        SET title = #{title},
+            description = #{description},
+            points = #{points},
+            category = #{category},
+            priority = #{priority},
+            deadline = #{deadline},
+            need_review = #{needReview},
+            updated_at = NOW()
+        WHERE id = #{id}
+    </update>
+
+    <update id="updateStatus">
+        UPDATE tasks SET status = #{status}, updated_at = NOW() WHERE id = #{id}
+    </update>
+
+    <select id="countTodayCompleted" resultType="int">
+        SELECT COUNT(*) FROM tasks 
+        WHERE child_id = #{childId} 
+        AND DATE(deadline) = #{date}
+        AND status IN ('completed', 'approved')
+    </select>
+
+    <select id="countTodayTotal" resultType="int">
+        SELECT COUNT(*) FROM tasks 
+        WHERE child_id = #{childId} 
+        AND DATE(deadline) = #{date}
+    </select>
+
+</mapper>

+ 42 - 0
backend/src/main/resources/mapper/UserMapper.xml

@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.xinzhiyijia.mapper.UserMapper">
+
+    <resultMap id="UserMap" type="com.xinzhiyijia.entity.User">
+        <id column="id" property="id"/>
+        <result column="openid" property="openid"/>
+        <result column="phone" property="phone"/>
+        <result column="nickname" property="nickname"/>
+        <result column="avatar" property="avatar"/>
+        <result column="role" property="role"/>
+        <result column="password" property="password"/>
+        <result column="family_id" property="familyId"/>
+        <result column="created_at" property="createdAt"/>
+        <result column="updated_at" property="updatedAt"/>
+    </resultMap>
+
+    <select id="findById" resultMap="UserMap">
+        SELECT * FROM users WHERE id = #{id}
+    </select>
+
+    <select id="findByOpenid" resultMap="UserMap">
+        SELECT * FROM users WHERE openid = #{openid}
+    </select>
+
+    <insert id="insert" useGeneratedKeys="true" keyProperty="id">
+        INSERT INTO users (openid, phone, nickname, avatar, role, password, family_id)
+        VALUES (#{openid}, #{phone}, #{nickname}, #{avatar}, #{role}, #{password}, #{familyId})
+    </insert>
+
+    <update id="update">
+        UPDATE users
+        SET phone = #{phone},
+            nickname = #{nickname},
+            avatar = #{avatar},
+            password = #{password},
+            family_id = #{familyId},
+            updated_at = NOW()
+        WHERE id = #{id}
+    </update>
+
+</mapper>

+ 140 - 0
backend/src/main/resources/sql/init.sql

@@ -0,0 +1,140 @@
+-- 知行益家数据库初始化脚本
+
+-- 1. 用户表
+CREATE TABLE IF NOT EXISTS `users` (
+    `id` BIGINT PRIMARY KEY AUTO_INCREMENT,
+    `openid` VARCHAR(64) NOT NULL COMMENT '微信openid',
+    `phone` VARCHAR(20) COMMENT '手机号',
+    `nickname` VARCHAR(50) COMMENT '昵称',
+    `avatar` VARCHAR(255) COMMENT '头像URL',
+    `role` ENUM('parent', 'child') NOT NULL COMMENT '角色:家长/孩子',
+    `password` VARCHAR(100) COMMENT '密码(加密)',
+    `family_id` BIGINT COMMENT '所属家庭ID',
+    `created_at` DATETIME DEFAULT CURRENT_TIMESTAMP,
+    `updated_at` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+    UNIQUE KEY `uk_openid` (`openid`),
+    INDEX `idx_family_id` (`family_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表';
+
+-- 2. 家庭表
+CREATE TABLE IF NOT EXISTS `families` (
+    `id` BIGINT PRIMARY KEY AUTO_INCREMENT,
+    `name` VARCHAR(50) NOT NULL COMMENT '家庭名称',
+    `invite_code` VARCHAR(10) NOT NULL COMMENT '邀请码',
+    `creator_id` BIGINT NOT NULL COMMENT '创建者ID',
+    `created_at` DATETIME DEFAULT CURRENT_TIMESTAMP,
+    `updated_at` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+    UNIQUE KEY `uk_invite_code` (`invite_code`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='家庭表';
+
+-- 3. 孩子信息表
+CREATE TABLE IF NOT EXISTS `children` (
+    `id` BIGINT PRIMARY KEY AUTO_INCREMENT,
+    `user_id` BIGINT NOT NULL COMMENT '关联用户ID',
+    `family_id` BIGINT NOT NULL COMMENT '所属家庭ID',
+    `age` INT NOT NULL COMMENT '年龄',
+    `grade` VARCHAR(20) COMMENT '年级',
+    `dan_level` VARCHAR(10) COMMENT 'DAN测评等级',
+    `penalty_enabled` TINYINT(1) DEFAULT 1 COMMENT '是否开启扣分(6岁以下默认关闭)',
+    `current_streak` INT DEFAULT 0 COMMENT '当前连续天数',
+    `max_streak` INT DEFAULT 0 COMMENT '历史最长连续天数',
+    `last_check_in_date` DATETIME COMMENT '最后打卡日期',
+    `total_points` INT DEFAULT 0 COMMENT '当前积分',
+    `total_earned` INT DEFAULT 0 COMMENT '累计获得积分',
+    `theme` VARCHAR(20) DEFAULT 'default' COMMENT '主题',
+    `created_at` DATETIME DEFAULT CURRENT_TIMESTAMP,
+    `updated_at` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+    INDEX `idx_family_id` (`family_id`),
+    INDEX `idx_user_id` (`user_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='孩子信息表';
+
+-- 4. 任务表
+CREATE TABLE IF NOT EXISTS `tasks` (
+    `id` BIGINT PRIMARY KEY AUTO_INCREMENT,
+    `family_id` BIGINT NOT NULL COMMENT '所属家庭',
+    `creator_id` BIGINT NOT NULL COMMENT '创建者(家长)',
+    `child_id` BIGINT NOT NULL COMMENT '执行孩子',
+    `title` VARCHAR(100) NOT NULL COMMENT '任务名称',
+    `description` TEXT COMMENT '备注说明',
+    `points` INT NOT NULL DEFAULT 2 COMMENT '积分数(1-10)',
+    `category` VARCHAR(20) NOT NULL COMMENT '分类:学习/生活/运动/其他',
+    `priority` VARCHAR(10) DEFAULT 'medium' COMMENT '优先级:high/medium/low',
+    `deadline` DATETIME NOT NULL COMMENT '截止时间',
+    `estimated_duration` INT COMMENT '预计时长(分钟)',
+    `repeat_type` VARCHAR(20) DEFAULT 'none' COMMENT '重复类型:none/daily/weekly',
+    `need_review` TINYINT(1) DEFAULT 0 COMMENT '是否需要审核',
+    `status` VARCHAR(20) DEFAULT 'pending' COMMENT '状态:pending/completed/approved/rejected/overdue',
+    `completed_at` DATETIME COMMENT '完成时间',
+    `completed_early` TINYINT(1) DEFAULT 0 COMMENT '是否提前完成',
+    `review_photo_url` VARCHAR(255) COMMENT '审核照片地址',
+    `review_voice_url` VARCHAR(255) COMMENT '审核语音地址',
+    `review_status` VARCHAR(20) COMMENT '审核状态:pending/reviewing/approved/rejected',
+    `review_comment` VARCHAR(500) COMMENT '审核备注',
+    `reviewed_at` DATETIME COMMENT '审核时间',
+    `reviewed_by` BIGINT COMMENT '审核人ID',
+    `created_at` DATETIME DEFAULT CURRENT_TIMESTAMP,
+    `updated_at` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+    INDEX `idx_family_id` (`family_id`),
+    INDEX `idx_child_id` (`child_id`),
+    INDEX `idx_status` (`status`),
+    INDEX `idx_deadline` (`deadline`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='任务表';
+
+-- 5. 积分流水表
+CREATE TABLE IF NOT EXISTS `points_log` (
+    `id` BIGINT PRIMARY KEY AUTO_INCREMENT,
+    `child_id` BIGINT NOT NULL COMMENT '孩子ID',
+    `task_id` BIGINT COMMENT '关联任务ID',
+    `amount` INT NOT NULL COMMENT '积分数(正数为获得,负数为扣除)',
+    `type` VARCHAR(20) NOT NULL COMMENT '类型:task/early/streak/focus/reward/correction',
+    `description` VARCHAR(200) NOT NULL COMMENT '描述',
+    `created_at` DATETIME DEFAULT CURRENT_TIMESTAMP,
+    INDEX `idx_child_id` (`child_id`),
+    INDEX `idx_created_at` (`created_at`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='积分流水表';
+
+-- 6. 奖励分类表
+CREATE TABLE IF NOT EXISTS `reward_categories` (
+    `id` BIGINT PRIMARY KEY AUTO_INCREMENT,
+    `family_id` BIGINT NOT NULL COMMENT '所属家庭',
+    `name` VARCHAR(50) NOT NULL COMMENT '分类名称',
+    `icon` VARCHAR(50) COMMENT '图标',
+    `sort_order` INT DEFAULT 0 COMMENT '排序',
+    `created_at` DATETIME DEFAULT CURRENT_TIMESTAMP,
+    INDEX `idx_family_id` (`family_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='奖励分类表';
+
+-- 7. 奖励表
+CREATE TABLE IF NOT EXISTS `rewards` (
+    `id` BIGINT PRIMARY KEY AUTO_INCREMENT,
+    `family_id` BIGINT NOT NULL COMMENT '所属家庭',
+    `child_id` BIGINT COMMENT '指定孩子(NULL表示全员可用)',
+    `category_id` BIGINT COMMENT '分类ID',
+    `name` VARCHAR(100) NOT NULL COMMENT '奖励名称',
+    `description` VARCHAR(500) COMMENT '奖励描述',
+    `points_required` INT NOT NULL COMMENT '所需积分',
+    `icon` VARCHAR(50) COMMENT '图标',
+    `stock` INT DEFAULT -1 COMMENT '库存(-1表示无限)',
+    `status` VARCHAR(20) DEFAULT 'active' COMMENT '状态:active/inactive',
+    `is_default` TINYINT(1) DEFAULT 0 COMMENT '是否系统预设',
+    `created_at` DATETIME DEFAULT CURRENT_TIMESTAMP,
+    `updated_at` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+    INDEX `idx_family_id` (`family_id`),
+    INDEX `idx_child_id` (`child_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='奖励表';
+
+-- 8. 奖励兑换表
+CREATE TABLE IF NOT EXISTS `reward_exchanges` (
+    `id` BIGINT PRIMARY KEY AUTO_INCREMENT,
+    `reward_id` BIGINT NOT NULL COMMENT '奖励ID',
+    `child_id` BIGINT NOT NULL COMMENT '孩子ID',
+    `points_used` INT NOT NULL COMMENT '消耗积分',
+    `status` VARCHAR(20) DEFAULT 'pending' COMMENT '状态:pending/approved/rejected',
+    `review_comment` VARCHAR(500) COMMENT '审核备注',
+    `reviewed_by` BIGINT COMMENT '审核人ID',
+    `reviewed_at` DATETIME COMMENT '审核时间',
+    `created_at` DATETIME DEFAULT CURRENT_TIMESTAMP,
+    INDEX `idx_child_id` (`child_id`),
+    INDEX `idx_status` (`status`),
+    INDEX `idx_reward_id` (`reward_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='奖励兑换表';

+ 34 - 0
backend/src/main/resources/sql/seed.sql

@@ -0,0 +1,34 @@
+-- 知行益家种子数据
+
+-- 默认奖励分类(family_id=-1表示系统预设)
+INSERT INTO `reward_categories` (`id`, `family_id`, `name`, `icon`, `sort_order`) VALUES
+(1, -1, '物质类', '🎁', 1),
+(2, -1, '精神类', '🎬', 2),
+(3, -1, '特权类', '👑', 3)
+ON DUPLICATE KEY UPDATE `name` = VALUES(`name`);
+
+-- 默认预设奖励
+INSERT INTO `rewards` (`id`, `family_id`, `child_id`, `category_id`, `name`, `points_required`, `icon`, `is_default`) VALUES
+(1, -1, NULL, 1, '小零食', 10, '🍬', 1),
+(2, -1, NULL, 1, '小玩具', 30, '🧸', 1),
+(3, -1, NULL, 1, '玩具盲盒', 50, '🎁', 1),
+(4, -1, NULL, 2, '看一集动画片', 5, '🎬', 1),
+(5, -1, NULL, 2, '玩游戏20分钟', 15, '🎮', 1),
+(6, -1, NULL, 2, '去游乐园', 200, '🎢', 1),
+(7, -1, NULL, 3, '今天不洗碗', 15, '🍽️', 1),
+(8, -1, NULL, 3, '晚睡30分钟', 20, '🌙', 1),
+(9, -1, NULL, 3, '免做一项家务', 25, '✨', 1)
+ON DUPLICATE KEY UPDATE `name` = VALUES(`name`), `points_required` = VALUES(`points_required`);
+
+-- 预设勋章定义(如果需要勋章表)
+-- INSERT INTO `badges` (`name`, `description`, `icon`, `condition_type`, `condition_value`) VALUES
+-- ('初次尝试', '完成第一个任务', '⭐', 'task_complete', 1),
+-- ('三日火焰', '连续3天按时完成', '🔥', 'streak_days', 3),
+-- ('一周达人', '连续7天按时完成', '🏆', 'streak_days', 7),
+-- ('百题斩', '完成100个任务', '📖', 'task_complete', 100),
+-- ('精准打击', '连续10个任务提前完成', '🎯', 'early_complete', 10),
+-- ('坚持不懈', '连续30天按时完成', '💪', 'streak_days', 30),
+-- ('专注大师', '完成50次专注训练', '🧠', 'focus_training', 50),
+-- ('积分富豪', '累计获得1000积分', '💰', 'total_points', 1000),
+-- ('全勤宝宝', '一周所有任务全部完成', '🏅', 'weekly_perfect', 1),
+-- ('完美主义', '单月任务完成率100%', '🎖️', 'monthly_perfect', 1);

+ 90 - 0
backend/src/test/java/com/zhixingyijia/common/JwtUtilTest.java

@@ -0,0 +1,90 @@
+package com.zhixingyijia.common;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.util.ReflectionTestUtils;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+/**
+ * JwtUtil类�元测� */
+@SpringBootTest
+class JwtUtilTest {
+
+    @Autowired
+    private JwtUtil jwtUtil;
+
+    @BeforeEach
+    void setUp() {
+        // 设置测试用的secret和expiration
+        ReflectionTestUtils.setField(jwtUtil, "secret", "test-secret-key-2026");
+        ReflectionTestUtils.setField(jwtUtil, "expiration", 86400000L); // 24�时
+    }
+
+    @Test
+    void testGenerateToken() {
+        Long userId = 1L;
+        String role = "parent";
+        
+        String token = jwtUtil.generateToken(userId, role);
+        
+        assertNotNull(token);
+        assertFalse(token.isEmpty());
+    }
+
+    @Test
+    void testParseToken() {
+        Long userId = 1L;
+        String role = "parent";
+        
+        String token = jwtUtil.generateToken(userId, role);
+        io.jsonwebtoken.Claims claims = jwtUtil.parseToken(token);
+        
+        assertNotNull(claims);
+        assertEquals(String.valueOf(userId), claims.getSubject());
+        assertEquals(role, claims.get("role", String.class));
+    }
+
+    @Test
+    void testGetUserIdFromToken() {
+        Long userId = 1L;
+        String role = "parent";
+        
+        String token = jwtUtil.generateToken(userId, role);
+        Long extractedUserId = jwtUtil.getUserIdFromToken(token);
+        
+        assertEquals(userId, extractedUserId);
+    }
+
+    @Test
+    void testGetRoleFromToken() {
+        Long userId = 1L;
+        String role = "parent";
+        
+        String token = jwtUtil.generateToken(userId, role);
+        String extractedRole = jwtUtil.getRoleFromToken(token);
+        
+        assertEquals(role, extractedRole);
+    }
+
+    @Test
+    void testIsTokenValid() {
+        Long userId = 1L;
+        String role = "parent";
+        
+        String token = jwtUtil.generateToken(userId, role);
+        boolean isValid = jwtUtil.isTokenValid(token);
+        
+        assertTrue(isValid);
+    }
+
+    @Test
+    void testIsTokenInvalid() {
+        String invalidToken = "invalid-token";
+        boolean isValid = jwtUtil.isTokenValid(invalidToken);
+        
+        assertFalse(isValid);
+    }
+}

+ 80 - 0
backend/src/test/java/com/zhixingyijia/common/ResultTest.java

@@ -0,0 +1,80 @@
+package com.zhixingyijia.common;
+
+import org.junit.jupiter.api.Test;
+import static org.junit.jupiter.api.Assertions.*;
+
+/**
+ * Result类�元测� */
+class ResultTest {
+
+    @Test
+    void testSuccessWithoutData() {
+        Result<?> result = Result.success();
+        assertEquals(200, result.getCode());
+        assertEquals("success", result.getMessage());
+        assertNull(result.getData());
+    }
+
+    @Test
+    void testSuccessWithData() {
+        String data = "test data";
+        Result<String> result = Result.success(data);
+        assertEquals(200, result.getCode());
+        assertEquals("success", result.getMessage());
+        assertEquals(data, result.getData());
+    }
+
+    @Test
+    void testSuccessWithMessageAndData() {
+        String message = "custom message";
+        String data = "test data";
+        Result<String> result = Result.success(message, data);
+        assertEquals(200, result.getCode());
+        assertEquals(message, result.getMessage());
+        assertEquals(data, result.getData());
+    }
+
+    @Test
+    void testErrorWithCodeAndMessage() {
+        int code = 500;
+        String message = "error message";
+        Result<?> result = Result.error(code, message);
+        assertEquals(code, result.getCode());
+        assertEquals(message, result.getMessage());
+        assertNull(result.getData());
+    }
+
+    @Test
+    void testErrorWithMessage() {
+        String message = "error message";
+        Result<?> result = Result.error(message);
+        assertEquals(500, result.getCode());
+        assertEquals(message, result.getMessage());
+        assertNull(result.getData());
+    }
+
+    @Test
+    void testUnauthorized() {
+        Result<?> result = Result.unauthorized();
+        assertEquals(401, result.getCode());
+        assertEquals("未授�, result.getMessage());
+        assertNull(result.getData());
+    }
+
+    @Test
+    void testNotFound() {
+        String message = "资��存�;
+        Result<?> result = Result.notFound(message);
+        assertEquals(404, result.getCode());
+        assertEquals(message, result.getMessage());
+        assertNull(result.getData());
+    }
+
+    @Test
+    void testNotFoundWithNullMessage() {
+        Result<?> result = Result.notFound(null);
+        assertEquals(404, result.getCode());
+        assertEquals("资��存�, result.getMessage());
+        assertNull(result.getData());
+    }
+}

+ 160 - 0
backend/src/test/java/com/zhixingyijia/common/SqlValidationTest.java

@@ -0,0 +1,160 @@
+package com.zhixingyijia.common;
+
+import org.junit.jupiter.api.Test;
+import org.springframework.core.io.ClassPathResource;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.jdbc.datasource.DriverManagerDataSource;
+import org.springframework.test.context.TestPropertySource;
+
+import javax.sql.DataSource;
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.nio.charset.StandardCharsets;
+import java.util.regex.Pattern;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+/**
+ * SQL脚本验�测试
+ * 注�:这个测试需�MySQL数�库连接�能�� */
+@TestPropertySource(properties = {
+    "spring.datasource.url=jdbc:mysql://localhost:3306/zhixingyijia_test?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai&createDatabaseIfNotExist=true",
+    "spring.datasource.username=root",
+    "spring.datasource.password=root"
+})
+class SqlValidationTest {
+
+    private static final Pattern CREATE_TABLE_PATTERN = Pattern.compile("CREATE TABLE", Pattern.CASE_INSENSITIVE);
+    private static final Pattern INSERT_INTO_PATTERN = Pattern.compile("INSERT INTO", Pattern.CASE_INSENSITIVE);
+    private static final Pattern INDEX_PATTERN = Pattern.compile("INDEX", Pattern.CASE_INSENSITIVE);
+    private static final Pattern FOREIGN_KEY_PATTERN = Pattern.compile("FOREIGN KEY", Pattern.CASE_INSENSITIVE);
+
+    @Test
+    void testInitSqlFileExists() {
+        // 验� init.sql 文件存在
+        assertDoesNotThrow(() -> {
+            ClassPathResource resource = new ClassPathResource("sql/init.sql");
+            assertTrue(resource.exists(), "init.sql 文件�存�);
+        });
+    }
+
+    @Test
+    void testSeedSqlFileExists() {
+        // 验� seed.sql 文件存在
+        assertDoesNotThrow(() -> {
+            ClassPathResource resource = new ClassPathResource("sql/seed.sql");
+            assertTrue(resource.exists(), "seed.sql 文件�存�);
+        });
+    }
+
+    @Test
+    void testInitSqlContent() throws IOException {
+        // 读�并验�init.sql 内容
+        String sqlContent = readSqlFile("sql/init.sql");
+        
+        // 验�包� CREATE TABLE 语�
+        assertTrue(CREATE_TABLE_PATTERN.matcher(sqlContent).find(), 
+            "init.sql 应该包� CREATE TABLE 语�");
+        
+        // 验�包�索引定义
+        assertTrue(INDEX_PATTERN.matcher(sqlContent).find(), 
+            "init.sql 应该包� INDEX 定义");
+        
+        // 验�表�
+        assertTrue(sqlContent.contains("users"), "应该包� users �);
+        assertTrue(sqlContent.contains("families"), "应该包� families �);
+        assertTrue(sqlContent.contains("children"), "应该包� children �);
+        assertTrue(sqlContent.contains("tasks"), "应该包� tasks �);
+        assertTrue(sqlContent.contains("points_log"), "应该包� points_log �);
+        assertTrue(sqlContent.contains("rewards"), "应该包� rewards �);
+        assertTrue(sqlContent.contains("reward_categories"), "应该包� reward_categories �);
+        assertTrue(sqlContent.contains("reward_exchanges"), "应该包� reward_exchanges �);
+    }
+
+    @Test
+    void testSeedSqlContent() throws IOException {
+        // 读�并验�seed.sql 内容
+        String sqlContent = readSqlFile("sql/seed.sql");
+        
+        // 验�包� INSERT INTO 语�
+        assertTrue(INSERT_INTO_PATTERN.matcher(sqlContent).find(), 
+            "seed.sql 应该包� INSERT INTO 语�");
+        
+        // 验�包�默认奖励分类
+        assertTrue(sqlContent.contains("物质�), "应该包�物质类奖励分�);
+        assertTrue(sqlContent.contains("精神�), "应该包�精神类奖励分�);
+        assertTrue(sqlContent.contains("特��), "应该包�特�类奖励分�);
+        
+        // 验�包�预设奖励
+        assertTrue(sqlContent.contains("�零�), "应该包��零食预设奖�);
+        assertTrue(sqlContent.contains("看一集动画片"), "应该包�看一集动画片预设奖励");
+    }
+
+    @Test
+    void testSqlSyntax() throws IOException {
+        // 简�的 SQL 语法验�
+        String initSql = readSqlFile("sql/init.sql");
+        String seedSql = readSqlFile("sql/seed.sql");
+        
+        // 验�语�以分�结尾(简�检查)
+        String[] initLines = initSql.split("\n");
+        for (String line : initLines) {
+            line = line.trim();
+            if (!line.isEmpty() && !line.startsWith("--")) {
+                // 跳过注释和空�                if (line.endsWith(");") || line.endsWith(";") || 
+                    line.startsWith("--") || line.startsWith("/*") ||
+                    line.contains("ENGINE=") || line.contains("DEFAULT CHARSET")) {
+                    // 这些是正常的�                }
+            }
+        }
+        
+        // 验�没有明显的语法错�        assertFalse(initSql.contains("CREATE TABLE ()"), "表定义�能为�);
+        assertFalse(seedSql.contains("INSERT INTO ()"), "�入语��能为空");
+    }
+
+    @Test
+    void testDatabaseConnection() {
+        // 注�:这个测试需�实际的MySQL数�库连�        // 在没有数�库的环境中会失�        try {
+            DataSource dataSource = createTestDataSource();
+            JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
+            
+            // �试创建测试数��            jdbcTemplate.execute("CREATE DATABASE IF NOT EXISTS zhixingyijia_test");
+            jdbcTemplate.execute("USE zhixingyijia_test");
+            
+            // 验�数�库连接��            String databaseName = jdbcTemplate.queryForObject("SELECT DATABASE()", String.class);
+            assertEquals("zhixingyijia_test", databaseName);
+            
+            // 清�测试数��            jdbcTemplate.execute("DROP DATABASE IF EXISTS zhixingyijia_test");
+            
+        } catch (Exception e) {
+            // 如果数�库连接失败,跳过这个测试
+            System.out.println("数�库连接测试跳� " + e.getMessage());
+        }
+    }
+
+    private String readSqlFile(String filePath) throws IOException {
+        ClassPathResource resource = new ClassPathResource(filePath);
+        try (InputStream inputStream = resource.getInputStream();
+             BufferedReader reader = new BufferedReader(
+                 new InputStreamReader(inputStream, StandardCharsets.UTF_8))) {
+            
+            StringBuilder content = new StringBuilder();
+            String line;
+            while ((line = reader.readLine()) != null) {
+                content.append(line).append("\n");
+            }
+            return content.toString();
+        }
+    }
+
+    private DataSource createTestDataSource() {
+        DriverManagerDataSource dataSource = new DriverManagerDataSource();
+        dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
+        dataSource.setUrl("jdbc:mysql://localhost:3306/?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai");
+        dataSource.setUsername("root");
+        dataSource.setPassword("root");
+        return dataSource;
+    }
+}

+ 151 - 0
backend/src/test/java/com/zhixingyijia/integration/ApiContractTest.java

@@ -0,0 +1,151 @@
+package com.zhixingyijia.integration;
+
+import org.junit.jupiter.api.Test;
+
+import java.util.*;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+/**
+ * API憟𤑳漲瘚贝�
+ * 撉諹��滨垢API靚�鍂銝𤾸�蝡舀𦻖���銝��湔�? */
+class ApiContractTest {
+
+    @Test
+    void testAuthEndpoints() {
+        // 撉諹�霈方��亙藁
+        Map<String, String> authEndpoints = new HashMap<>();
+        authEndpoints.put("POST /auth/login", "敺桐縑�餃�");
+        authEndpoints.put("GET /auth/info", "�瑕��冽�靽⊥�");
+        
+        // 撉諹����厩垢�寥�摮睃銁
+        assertEquals(2, authEndpoints.size());
+        assertTrue(authEndpoints.containsKey("POST /auth/login"));
+        assertTrue(authEndpoints.containsKey("GET /auth/info"));
+    }
+
+    @Test
+    void testTaskEndpoints() {
+        // 撉諹�隞餃𦛚�亙藁
+        Map<String, String> taskEndpoints = new HashMap<>();
+        taskEndpoints.put("GET /tasks/today/{childId}", "�瑕�隞𦠜𠯫隞餃𦛚");
+        taskEndpoints.put("GET /tasks/{taskId}", "�瑕�隞餃𦛚霂行�");
+        taskEndpoints.put("POST /tasks", "�𥕦遣隞餃𦛚");
+        taskEndpoints.put("POST /tasks/{taskId}/complete", "摰峕�隞餃𦛚");
+        taskEndpoints.put("POST /tasks/{taskId}/review", "摰⊥瓲隞餃𦛚");
+        taskEndpoints.put("GET /tasks/family/{familyId}", "�瑕�摰嗅滬隞餃𦛚�𡑒”");
+        
+        // 撉諹����厩垢�寥�摮睃銁
+        assertEquals(6, taskEndpoints.size());
+        assertTrue(taskEndpoints.containsKey("GET /tasks/today/{childId}"));
+        assertTrue(taskEndpoints.containsKey("GET /tasks/{taskId}"));
+        assertTrue(taskEndpoints.containsKey("POST /tasks"));
+        assertTrue(taskEndpoints.containsKey("POST /tasks/{taskId}/complete"));
+        assertTrue(taskEndpoints.containsKey("POST /tasks/{taskId}/review"));
+        assertTrue(taskEndpoints.containsKey("GET /tasks/family/{familyId}"));
+    }
+
+    @Test
+    void testRewardEndpoints() {
+        // 撉諹�憟硋��亙藁
+        Map<String, String> rewardEndpoints = new HashMap<>();
+        rewardEndpoints.put("GET /rewards/family/{familyId}", "�瑕�摰嗅滬憟硋��𡑒”");
+        rewardEndpoints.put("GET /rewards/{rewardId}", "�瑕�憟硋�霂行�");
+        rewardEndpoints.put("POST /rewards", "�𥕦遣憟硋�");
+        rewardEndpoints.put("PUT /rewards/{rewardId}", "�湔鰵憟硋�");
+        rewardEndpoints.put("GET /rewards/defaults", "�瑕�蝟餌�憸�挽憟硋�");
+        
+        // 撉諹����厩垢�寥�摮睃銁
+        assertEquals(5, rewardEndpoints.size());
+        assertTrue(rewardEndpoints.containsKey("GET /rewards/family/{familyId}"));
+        assertTrue(rewardEndpoints.containsKey("GET /rewards/{rewardId}"));
+        assertTrue(rewardEndpoints.containsKey("POST /rewards"));
+        assertTrue(rewardEndpoints.containsKey("PUT /rewards/{rewardId}"));
+        assertTrue(rewardEndpoints.containsKey("GET /rewards/defaults"));
+    }
+
+    @Test
+    void testPointsEndpoints() {
+        // 撉諹�蝘臬��亙藁
+        Map<String, String> pointsEndpoints = new HashMap<>();
+        pointsEndpoints.put("GET /points/balance/{childId}", "�瑕�摮拙�蝘臬�");
+        pointsEndpoints.put("GET /points/log/{childId}", "�瑕�蝘臬�瘚�偌");
+        
+        // 撉諹����厩垢�寥�摮睃銁
+        assertEquals(2, pointsEndpoints.size());
+        assertTrue(pointsEndpoints.containsKey("GET /points/balance/{childId}"));
+        assertTrue(pointsEndpoints.containsKey("GET /points/log/{childId}"));
+    }
+
+    @Test
+    void testApiVersioning() {
+        // 撉諹�API��𧋦�批�
+        // 敶枏�API瘝⊥���𧋦�批�嚗䔶��芣䔉�臭誑瘛餃�
+        String currentApiVersion = "v1";
+        assertNotNull(currentApiVersion);
+        assertEquals("v1", currentApiVersion);
+    }
+
+    @Test
+    void testApiResponseFormat() {
+        // 撉諹�API�滚��澆�
+        // �𡒊垢蝏煺�雿輻鍂Result蝐餃�鋆��摨?        // �滨垢�����聢撘𧶏�{ code: 200, message: "success", data: ... }
+        
+        Map<String, Object> expectedResponse = new HashMap<>();
+        expectedResponse.put("code", 200);
+        expectedResponse.put("message", "success");
+        expectedResponse.put("data", null);
+        
+        // 撉諹��滚�蝏𤘪�
+        assertTrue(expectedResponse.containsKey("code"));
+        assertTrue(expectedResponse.containsKey("message"));
+        assertTrue(expectedResponse.containsKey("data"));
+    }
+
+    @Test
+    void testAuthenticationHeader() {
+        // 撉諹�霈方�憭湔聢撘?        String authHeader = "Bearer token_here";
+        assertTrue(authHeader.startsWith("Bearer "));
+        
+        String token = authHeader.substring(7);
+        assertNotNull(token);
+        assertFalse(token.isEmpty());
+    }
+
+    @Test
+    void testRequestResponseCompatibility() {
+        // 撉諹�霂瑟����摨𠉛��澆捆�?        
+        // 1. �餃�霂瑟�
+        Map<String, String> loginRequest = new HashMap<>();
+        loginRequest.put("code", "wx_code");
+        loginRequest.put("role", "parent");
+        loginRequest.put("nickname", "瘚贝��冽�");
+        
+        Map<String, Object> loginResponse = new HashMap<>();
+        loginResponse.put("token", "jwt_token");
+        loginResponse.put("userId", 1L);
+        loginResponse.put("role", "parent");
+        
+        // 撉諹��餃�霂瑟����摨?        assertNotNull(loginRequest.get("code"));
+        assertNotNull(loginResponse.get("token"));
+        
+        // 2. �𥕦遣隞餃𦛚霂瑟�
+        Map<String, Object> createTaskRequest = new HashMap<>();
+        createTaskRequest.put("childId", 1L);
+        createTaskRequest.put("title", "瘚贝�隞餃𦛚");
+        createTaskRequest.put("points", 2);
+        createTaskRequest.put("deadline", "2026-03-31 20:00");
+        
+        // 撉諹��𥕦遣隞餃𦛚霂瑟�
+        assertNotNull(createTaskRequest.get("title"));
+        assertTrue((Integer) createTaskRequest.get("points") > 0);
+        
+        // 3. 摰峕�隞餃𦛚霂瑟�
+        Map<String, Object> completeTaskRequest = new HashMap<>();
+        completeTaskRequest.put("photoUrl", "photo.jpg");
+        completeTaskRequest.put("voiceUrl", "voice.mp3");
+        
+        // 撉諹�摰峕�隞餃𦛚霂瑟�
+        assertNotNull(completeTaskRequest);
+    }
+}

+ 229 - 0
backend/src/test/java/com/zhixingyijia/service/TaskServiceTest.java

@@ -0,0 +1,229 @@
+package com.zhixingyijia.service;
+
+import com.zhixingyijia.entity.PointsLog;
+import com.zhixingyijia.entity.Task;
+import com.zhixingyijia.mapper.PointsLogMapper;
+import com.zhixingyijia.mapper.TaskMapper;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+import org.springframework.test.util.ReflectionTestUtils;
+
+import java.time.LocalDateTime;
+
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.*;
+
+/**
+ * TaskService�元测试
+ */
+@ExtendWith(MockitoExtension.class)
+class TaskServiceTest {
+
+    @InjectMocks
+    private TaskService taskService;
+
+    @Mock
+    private TaskMapper taskMapper;
+
+    @Mock
+    private PointsLogMapper pointsLogMapper;
+
+    @Test
+    void testFindById() {
+        // 准备测试数�
+        Long taskId = 1L;
+        Task expectedTask = new Task();
+        expectedTask.setId(taskId);
+        expectedTask.setTitle("测试任务");
+
+        // 设置mock行为
+        when(taskMapper.findById(taskId)).thenReturn(expectedTask);
+
+        // 执行测试
+        Task result = taskService.findById(taskId);
+
+        // 验�结果
+        assertNotNull(result);
+        assertEquals(expectedTask.getId(), result.getId());
+        assertEquals(expectedTask.getTitle(), result.getTitle());
+
+        // 验�mock调用
+        verify(taskMapper, times(1)).findById(taskId);
+    }
+
+    @Test
+    void testCreateTask() {
+        // 准备测试数�
+        Task task = new Task();
+        task.setTitle("测试任务");
+        task.setPoints(2);
+
+        // 设置mock行为
+        doAnswer(invocation -> {
+            Task insertedTask = invocation.getArgument(0);
+            insertedTask.setId(1L);
+            return null;
+        }).when(taskMapper).insert(any(Task.class));
+
+        // 执行测试
+        Task result = taskService.createTask(task);
+
+        // 验�结果
+        assertNotNull(result);
+        assertEquals(1L, result.getId());
+        assertEquals("pending", result.getStatus());
+
+        // 验�mock调用
+        verify(taskMapper, times(1)).insert(task);
+    }
+
+    @Test
+    void testCompleteTaskRequiresReview() {
+        // 准备测试数�
+        Long taskId = 1L;
+        Task task = new Task();
+        task.setId(taskId);
+        task.setTitle("测试任务");
+        task.setPoints(2);
+        task.setStatus("pending");
+        task.setNeedReview(true);
+        task.setDeadline(LocalDateTime.now().plusHours(2));
+
+        // 设置mock行为
+        when(taskMapper.findById(taskId)).thenReturn(task);
+
+        // 执行测试
+        Task result = taskService.completeTask(taskId, "photo.jpg", "voice.mp3");
+
+        // 验�结果
+        assertNotNull(result);
+        assertEquals("completed", result.getStatus());
+        assertEquals("pending", result.getReviewStatus());
+        assertEquals("photo.jpg", result.getReviewPhotoUrl());
+        assertEquals("voice.mp3", result.getReviewVoiceUrl());
+        assertNotNull(result.getCompletedAt());
+
+        // 验�mock调用
+        verify(taskMapper, times(1)).findById(taskId);
+        verify(taskMapper, times(1)).update(task);
+        verify(pointsLogMapper, never()).insert(any(PointsLog.class));
+    }
+
+    @Test
+    void testCompleteTaskNoReview() {
+        // 准备测试数�
+        Long taskId = 1L;
+        Task task = new Task();
+        task.setId(taskId);
+        task.setTitle("测试任务");
+        task.setPoints(2);
+        task.setStatus("pending");
+        task.setNeedReview(false);
+        task.setDeadline(LocalDateTime.now().plusHours(2));
+
+        // 设置mock行为
+        when(taskMapper.findById(taskId)).thenReturn(task);
+
+        // 执行测试
+        Task result = taskService.completeTask(taskId, null, null);
+
+        // 验�结果
+        assertNotNull(result);
+        assertEquals("approved", result.getStatus());
+        assertNotNull(result.getCompletedAt());
+
+        // 验�mock调用
+        verify(taskMapper, times(1)).findById(taskId);
+        verify(taskMapper, times(1)).update(task);
+        verify(pointsLogMapper, times(1)).insert(any(PointsLog.class));
+    }
+
+    @Test
+    void testCompleteTaskAlreadyCompleted() {
+        // 准备测试数�
+        Long taskId = 1L;
+        Task task = new Task();
+        task.setId(taskId);
+        task.setTitle("测试任务");
+        task.setStatus("completed");
+
+        // 设置mock行为
+        when(taskMapper.findById(taskId)).thenReturn(task);
+
+        // 执行测试并验�异�        assertThrows(RuntimeException.class, () -> {
+            taskService.completeTask(taskId, null, null);
+        });
+
+        // 验�mock调用
+        verify(taskMapper, times(1)).findById(taskId);
+        verify(taskMapper, never()).update(any(Task.class));
+        verify(pointsLogMapper, never()).insert(any(PointsLog.class));
+    }
+
+    @Test
+    void testReviewTaskApproved() {
+        // 准备测试数�
+        Long taskId = 1L;
+        Long reviewerId = 1L;
+        Task task = new Task();
+        task.setId(taskId);
+        task.setTitle("测试任务");
+        task.setPoints(2);
+        task.setStatus("completed");
+        task.setCompletedEarly(true);
+
+        // 设置mock行为
+        when(taskMapper.findById(taskId)).thenReturn(task);
+
+        // 执行测试
+        Task result = taskService.reviewTask(taskId, "approved", "审核通过", reviewerId);
+
+        // 验�结果
+        assertNotNull(result);
+        assertEquals("approved", result.getStatus());
+        assertEquals("approved", result.getReviewStatus());
+        assertEquals("审核通过", result.getReviewComment());
+        assertEquals(reviewerId, result.getReviewedBy());
+        assertNotNull(result.getReviewedAt());
+
+        // 验�mock调用
+        verify(taskMapper, times(1)).findById(taskId);
+        verify(taskMapper, times(1)).update(task);
+        verify(pointsLogMapper, times(1)).insert(any(PointsLog.class));
+    }
+
+    @Test
+    void testReviewTaskRejected() {
+        // 准备测试数�
+        Long taskId = 1L;
+        Long reviewerId = 1L;
+        Task task = new Task();
+        task.setId(taskId);
+        task.setTitle("测试任务");
+        task.setPoints(2);
+        task.setStatus("completed");
+
+        // 设置mock行为
+        when(taskMapper.findById(taskId)).thenReturn(task);
+
+        // 执行测试
+        Task result = taskService.reviewTask(taskId, "rejected", "审核拒�", reviewerId);
+
+        // 验�结果
+        assertNotNull(result);
+        assertEquals("rejected", result.getStatus());
+        assertEquals("rejected", result.getReviewStatus());
+        assertEquals("审核拒�", result.getReviewComment());
+        assertEquals(reviewerId, result.getReviewedBy());
+        assertNotNull(result.getReviewedAt());
+
+        // 验�mock调用
+        verify(taskMapper, times(1)).findById(taskId);
+        verify(taskMapper, times(1)).update(task);
+        verify(pointsLogMapper, never()).insert(any(PointsLog.class));
+    }
+}

+ 163 - 0
backend/src/test/java/com/zhixingyijia/service/UserServiceTest.java

@@ -0,0 +1,163 @@
+package com.zhixingyijia.service;
+
+import com.zhixingyijia.common.JwtUtil;
+import com.zhixingyijia.entity.User;
+import com.zhixingyijia.mapper.UserMapper;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.*;
+
+/**
+ * UserService单元测试
+ */
+@ExtendWith(MockitoExtension.class)
+class UserServiceTest {
+
+    @InjectMocks
+    private UserService userService;
+
+    @Mock
+    private UserMapper userMapper;
+
+    @Mock
+    private JwtUtil jwtUtil;
+
+    @Test
+    void testFindByOpenid() {
+        // 准备测试数据
+        String openid = "test_openid";
+        User expectedUser = new User();
+        expectedUser.setId(1L);
+        expectedUser.setOpenid(openid);
+
+        // 设置mock行为
+        when(userMapper.findByOpenid(openid)).thenReturn(expectedUser);
+
+        // 执行测试
+        User result = userService.findByOpenid(openid);
+
+        // 验证结果
+        assertNotNull(result);
+        assertEquals(expectedUser.getId(), result.getId());
+        assertEquals(expectedUser.getOpenid(), result.getOpenid());
+
+        // 验证mock调用
+        verify(userMapper, times(1)).findByOpenid(openid);
+    }
+
+    @Test
+    void testFindById() {
+        // 准备测试数据
+        Long id = 1L;
+        User expectedUser = new User();
+        expectedUser.setId(id);
+        expectedUser.setOpenid("test_openid");
+
+        // 设置mock行为
+        when(userMapper.findById(id)).thenReturn(expectedUser);
+
+        // 执行测试
+        User result = userService.findById(id);
+
+        // 验证结果
+        assertNotNull(result);
+        assertEquals(expectedUser.getId(), result.getId());
+
+        // 验证mock调用
+        verify(userMapper, times(1)).findById(id);
+    }
+
+    @Test
+    void testLoginUserExists() {
+        // 准备测试数据
+        User user = new User();
+        user.setOpenid("test_openid");
+        user.setRole("parent");
+
+        User existUser = new User();
+        existUser.setId(1L);
+        existUser.setOpenid("test_openid");
+        existUser.setRole("parent");
+
+        String expectedToken = "test_token";
+
+        // 设置mock行为
+        when(userMapper.findByOpenid(anyString())).thenReturn(existUser);
+        when(jwtUtil.generateToken(anyLong(), anyString())).thenReturn(expectedToken);
+
+        // 执行测试
+        String token = userService.login(user);
+
+        // 验证结果
+        assertNotNull(token);
+        assertEquals(expectedToken, token);
+
+        // 验证mock调用
+        verify(userMapper, times(1)).findByOpenid(user.getOpenid());
+        verify(userMapper, never()).insert(any(User.class));
+        verify(jwtUtil, times(1)).generateToken(existUser.getId(), existUser.getRole());
+    }
+
+    @Test
+    void testLoginUserNotExists() {
+        // 准备测试数据
+        User user = new User();
+        user.setOpenid("test_openid");
+        user.setRole("parent");
+
+        String expectedToken = "test_token";
+
+        // 设置mock行为
+        when(userMapper.findByOpenid(anyString())).thenReturn(null);
+        doAnswer(invocation -> {
+            User insertedUser = invocation.getArgument(0);
+            insertedUser.setId(1L);
+            return null;
+        }).when(userMapper).insert(any(User.class));
+        when(jwtUtil.generateToken(anyLong(), anyString())).thenReturn(expectedToken);
+
+        // 执行测试
+        String token = userService.login(user);
+
+        // 验证结果
+        assertNotNull(token);
+        assertEquals(expectedToken, token);
+
+        // 验证mock调用
+        verify(userMapper, times(1)).findByOpenid(user.getOpenid());
+        verify(userMapper, times(1)).insert(user);
+        verify(jwtUtil, times(1)).generateToken(user.getId(), user.getRole());
+    }
+
+    @Test
+    void testRegister() {
+        // 准备测试数据
+        User user = new User();
+        user.setOpenid("test_openid");
+        user.setRole("parent");
+
+        // 设置mock行为
+        doAnswer(invocation -> {
+            User insertedUser = invocation.getArgument(0);
+            insertedUser.setId(1L);
+            return null;
+        }).when(userMapper).insert(any(User.class));
+
+        // 执行测试
+        User result = userService.register(user);
+
+        // 验证结果
+        assertNotNull(result);
+        assertEquals(1L, result.getId());
+
+        // 验证mock调用
+        verify(userMapper, times(1)).insert(user);
+    }
+}

+ 28 - 0
backend/src/test/resources/test-schema.sql

@@ -0,0 +1,28 @@
+-- 测试用的简化数据库 schema
+-- 用于验证 SQL 语法
+
+CREATE DATABASE IF NOT EXISTS `zhixingyijia_test` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
+
+USE `zhixingyijia_test`;
+
+-- 测试表结构
+CREATE TABLE IF NOT EXISTS `test_users` (
+    `id` BIGINT PRIMARY KEY AUTO_INCREMENT,
+    `openid` VARCHAR(64) NOT NULL,
+    `nickname` VARCHAR(50),
+    `role` ENUM('parent', 'child') NOT NULL,
+    `created_at` DATETIME DEFAULT CURRENT_TIMESTAMP,
+    UNIQUE KEY `uk_openid` (`openid`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
+
+-- 插入测试数据
+INSERT INTO `test_users` (`openid`, `nickname`, `role`) VALUES
+('test_openid_1', '测试家长', 'parent'),
+('test_openid_2', '测试孩子', 'child');
+
+-- 验证数据
+SELECT COUNT(*) as user_count FROM `test_users`;
+
+-- 清理测试数据
+DROP TABLE IF EXISTS `test_users`;
+DROP DATABASE IF EXISTS `zhixingyijia_test`;

+ 62 - 0
backend/src/test/resources/validate-sql.sh

@@ -0,0 +1,62 @@
+#!/bin/bash
+# SQL 验证脚本
+
+echo "开始验证 SQL 脚本..."
+
+# 检查 init.sql 是否存在
+if [ ! -f "src/main/resources/sql/init.sql" ]; then
+    echo "❌ init.sql 文件不存在"
+    exit 1
+fi
+
+# 检查 seed.sql 是否存在
+if [ ! -f "src/main/resources/sql/seed.sql" ]; then
+    echo "❌ seed.sql 文件不存在"
+    exit 1
+fi
+
+# 检查 SQL 文件是否包含创建表语句
+echo "检查 init.sql 中的表创建语句..."
+table_count=$(grep -c "CREATE TABLE" src/main/resources/sql/init.sql)
+echo "找到 $table_count 个 CREATE TABLE 语句"
+
+# 检查 SQL 文件是否包含插入数据语句
+echo "检查 seed.sql 中的数据插入语句..."
+insert_count=$(grep -c "INSERT INTO" src/main/resources/sql/seed.sql)
+echo "找到 $insert_count 个 INSERT INTO 语句"
+
+# 检查是否有语法错误(简单检查)
+echo "检查 SQL 语法..."
+
+# 检查是否缺少分号
+missing_semicolon_count=$(grep -v "^--" src/main/resources/sql/init.sql | grep -v "^$" | grep -v ";$" | wc -l)
+if [ $missing_semicolon_count -gt 0 ]; then
+    echo "⚠️  发现 $missing_semicolon_count 行可能缺少分号"
+else
+    echo "✅ 所有 SQL 语句都有分号"
+fi
+
+# 检查表名是否正确
+echo "检查表名..."
+table_names=$(grep "CREATE TABLE" src/main/resources/sql/init.sql | grep -o "`[^`]*`" | sort | uniq)
+echo "找到以下表:"
+echo "$table_names"
+
+# 检查索引
+echo "检查索引..."
+index_count=$(grep -c "INDEX" src/main/resources/sql/init.sql)
+echo "找到 $index_count 个索引定义"
+
+# 检查外键
+echo "检查外键..."
+foreign_key_count=$(grep -c "FOREIGN KEY" src/main/resources/sql/init.sql)
+echo "找到 $foreign_key_count 个外键定义"
+
+echo ""
+echo "✅ SQL 验证完成"
+echo ""
+echo "总结:"
+echo "- 表数量:$table_count"
+echo "- 插入语句:$insert_count"
+echo "- 索引数量:$index_count"
+echo "- 外键数量:$foreign_key_count"

+ 113 - 0
docs/API-INTEGRATION.md

@@ -0,0 +1,113 @@
+# 前后端集成文档
+
+## API端点映射
+
+### 前端API调用 vs 后端Controller
+
+| 前端调用 (api.js) | 后端Controller | 方法 | 说明 |
+|------------------|----------------|------|------|
+| `login(data)` | `AuthController.login()` | POST | 微信登录 |
+| `getUserInfo()` | `AuthController.getUserInfo()` | GET | 获取用户信息 |
+| `getTodayTasks(childId)` | `TaskController.getTodayTasks()` | GET | 获取今日任务 |
+| `getTaskDetail(taskId)` | `TaskController.getTaskDetail()` | GET | 获取任务详情 |
+| `createTask(data)` | `TaskController.createTask()` | POST | 创建任务 |
+| `completeTask(taskId, data)` | `TaskController.completeTask()` | POST | 完成任务 |
+| `reviewTask(taskId, data)` | `TaskController.reviewTask()` | POST | 审核任务 |
+| `getFamilyTasks(familyId)` | `TaskController.getFamilyTasks()` | GET | 获取家庭任务列表 |
+| `getRewards(familyId)` | `RewardController.getFamilyRewards()` | GET | 获取家庭奖励列表 |
+| `getRewardDetail(rewardId)` | `RewardController.getRewardDetail()` | GET | 获取奖励详情 |
+| `createReward(data)` | `RewardController.createReward()` | POST | 创建奖励 |
+| `updateReward(rewardId, data)` | `RewardController.updateReward()` | PUT | 更新奖励 |
+| `getDefaultRewards()` | `RewardController.getDefaultRewards()` | GET | 获取系统预设奖励 |
+| `getPointsBalance(childId)` | `PointsController.getPointsBalance()` | GET | 获取孩子积分 |
+| `getPointsLog(childId)` | `PointsController.getPointsLog()` | GET | 获取积分流水 |
+
+## 数据格式
+
+### 请求头
+```
+Authorization: Bearer <jwt_token>
+Content-Type: application/json
+```
+
+### 响应格式
+所有API响应都使用统一的Result类包装:
+
+```json
+{
+  "code": 200,
+  "message": "success",
+  "data": { ... }
+}
+```
+
+### 错误响应
+```json
+{
+  "code": 500,
+  "message": "错误信息"
+}
+```
+
+## 配置
+
+### 后端配置 (application.yml)
+```yaml
+server:
+  port: 8080
+  servlet:
+    context-path: /api
+
+spring:
+  application:
+    name: zhixingyijia-backend
+  datasource:
+    url: jdbc:mysql://localhost:3306/zhixingyijia?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai
+    username: root
+    password: root
+
+jwt:
+  secret: zhixingyijia-secret-key-2026
+  expiration: 604800000
+```
+
+### 前端配置 (app.js)
+```javascript
+globalData: {
+  baseUrl: 'http://localhost:8080/api',
+  token: '',
+  userInfo: null,
+  role: ''  // parent 或 child
+}
+```
+
+## 集成测试
+
+### 测试环境
+- 后端:localhost:8080
+- 前端:微信开发者工具
+- 数据库:localhost:3306
+
+### 测试步骤
+1. 启动后端服务
+2. 在微信开发者工具中导入miniprogram目录
+3. 配置app.js中的baseUrl
+4. 测试登录功能
+5. 测试任务管理功能
+6. 测试奖励管理功能
+7. 测试积分管理功能
+
+## 注意事项
+
+1. **跨域问题**:开发环境下需要配置CORS
+2. **JWT Token**:前端需要在请求头中携带Token
+3. **数据验证**:后端需要对请求参数进行验证
+4. **错误处理**:前端需要处理各种错误情况
+5. **加载状态**:网络请求时显示加载状态
+
+## 开发建议
+
+1. 使用Swagger文档查看API接口:http://localhost:8080/api/swagger-ui.html
+2. 使用Postman测试API接口
+3. 使用微信开发者工具调试小程序
+4. 使用MySQL Workbench查看数据库

+ 1718 - 0
docs/superpowers/plans/2026-03-31-心知益家MVP实现计划.md

@@ -0,0 +1,1718 @@
+# �亥��𠰴振 MVP 摰䂿緵霈∪�
+
+> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
+
+**Goal:** 摰䂿緵敹�䰻�𠰴振撠讐�摨𦬅VP��𧋦嚗���砌遙�∠恣����妖��頂蝏麄����曹�蝟颯���璅∪���揢蝑㗇瓲敹���賬�?
+**Architecture:** 敺桐縑撠讐�摨誩�蝡?+ Java Spring Boot�𡒊垢 + MySQL�唳旿摨瓐����沖ESTful API餈𥡝��滚�蝡舫�帋縑嚗𥇍WT餈𥡝�頨思遢霈方��?
+**Tech Stack:** 
+- �滨垢嚗𡁜凝靽∪�蝔见��毺�撘��?- �𡒊垢嚗麿ava 1.8 + Spring Boot 2.7 + MyBatis
+- �唳旿摨橒�MySQL 8.0
+- 霈方�嚗麿WT
+- ��﹝嚗锭wagger 3.0
+
+---
+
+## ��辣蝏𤘪�
+
+```
+E:\workspace\dan\zxyj\
+�鎿��� backend/                          # Java�𡒊垢
+�?  �鎿��� pom.xml
+�?  �鎿��� src/main/java/com/xinzhiyijia/
+�?  �?  �鎿��� XinzhiyijiaApplication.java
+�?  �?  �鎿��� config/                   # �滨蔭蝐?�?  �?  �?  �鎿��� WebMvcConfig.java
+�?  �?  �?  �鎿��� JwtConfig.java
+�?  �?  �?  �婙��� SwaggerConfig.java
+�?  �?  �鎿��� common/                   # �砍�蝐?�?  �?  �?  �鎿��� Result.java
+�?  �?  �?  �鎿��� ResultCode.java
+�?  �?  �?  �婙��� JwtUtil.java
+�?  �?  �鎿��� entity/                   # 摰硺�蝐?�?  �?  �?  �鎿��� User.java
+�?  �?  �?  �鎿��� Family.java
+�?  �?  �?  �鎿��� Child.java
+�?  �?  �?  �鎿��� Task.java
+�?  �?  �?  �鎿��� PointsLog.java
+�?  �?  �?  �鎿��� Reward.java
+�?  �?  �?  �婙��� RewardCategory.java
+�?  �?  �鎿��� dto/                      # �唳旿隡㰘�撖寡情
+�?  �?  �鎿��� mapper/                   # MyBatis Mapper
+�?  �?  �?  �鎿��� UserMapper.java
+�?  �?  �?  �鎿��� FamilyMapper.java
+�?  �?  �?  �鎿��� ChildMapper.java
+�?  �?  �?  �鎿��� TaskMapper.java
+�?  �?  �?  �鎿��� PointsLogMapper.java
+�?  �?  �?  �婙��� RewardMapper.java
+�?  �?  �鎿��� service/                  # 銝𡁜𦛚�餉�
+�?  �?  �?  �鎿��� UserService.java
+�?  �?  �?  �鎿��� FamilyService.java
+�?  �?  �?  �鎿��� TaskService.java
+�?  �?  �?  �鎿��� PointsService.java
+�?  �?  �?  �婙��� RewardService.java
+�?  �?  �婙��� controller/              # �批��?�?  �?      �鎿��� AuthController.java
+�?  �?      �鎿��� UserController.java
+�?  �?      �鎿��� FamilyController.java
+�?  �?      �鎿��� TaskController.java
+�?  �?      �鎿��� PointsController.java
+�?  �?      �婙��� RewardController.java
+�?  �婙��� src/main/resources/
+�?      �鎿��� application.yml
+�?      �婙��� mapper/                   # MyBatis XML
+�?          �鎿��� UserMapper.xml
+�?          �鎿��� FamilyMapper.xml
+�?          �鎿��� ChildMapper.xml
+�?          �鎿��� TaskMapper.xml
+�?          �鎿��� PointsLogMapper.xml
+�?          �婙��� RewardMapper.xml
+�?�婙��� miniprogram/                      # 敺桐縑撠讐�摨誩�蝡?    �鎿��� app.js
+    �鎿��� app.json
+    �鎿��� app.wxss
+    �鎿��� pages/
+    �?  �鎿��� index/                    # 擐㚚△嚗���脣�瘚��
+    �?  �鎿��� login/                    # �餃�憿?    �?  �鎿��� parent-home/              # 摰園鵭蝡舫�憿?    �?  �鎿��� child-home/               # 摮拙�蝡舫�憿?    �?  �鎿��� task-list/                # 隞餃𦛚�𡑒”
+    �?  �鎿��� task-detail/              # 隞餃𦛚霂行�
+    �?  �鎿��� task-create/              # �穃�隞餃𦛚
+    �?  �鎿��� reward-list/              # 憟硋��𡑒”
+    �?  �鎿��� reward-create/            # 瘛餃�憟硋�
+    �?  �鎿��� points-log/               # 蝘臬�瘚�偌
+    �?  �婙��� settings/                 # 霈曄蔭
+    �鎿��� components/                   # �砍�蝏�辣
+    �?  �鎿��� task-card/
+    �?  �鎿��� reward-card/
+    �?  �婙��� countdown/
+    �鎿��� utils/
+    �?  �鎿��� api.js                    # API霂瑟�撠��
+    �?  �鎿��� auth.js                   # 霈方�撌亙�
+    �?  �婙��� util.js
+    �婙��� images/                       # �曄�韏��
+```
+
+---
+
+## Task 1: �唳旿摨栞挽霈∩�撱箄”
+
+**Files:**
+- Create: `backend/src/main/resources/sql/init.sql`
+- Create: `backend/src/main/resources/sql/seed.sql`
+
+### Step 1: 蝻硋��唳旿摨枏�憪见�SQL
+
+�𥕦遣 `backend/src/main/resources/sql/init.sql`嚗?
+```sql
+-- 敹�䰻�𠰴振�唳旿摨枏�憪见��𡁏𧋦
+
+-- 1. �冽�銵?CREATE TABLE IF NOT EXISTS `users` (
+    `id` BIGINT PRIMARY KEY AUTO_INCREMENT,
+    `openid` VARCHAR(64) NOT NULL COMMENT '敺桐縑openid',
+    `phone` VARCHAR(20) COMMENT '�𧢲㦤�?,
+    `nickname` VARCHAR(50) COMMENT '�萇妍',
+    `avatar` VARCHAR(255) COMMENT '憭游�URL',
+    `role` ENUM('parent', 'child') NOT NULL COMMENT '閫坿𠧧嚗𡁜振�?摮拙�',
+    `password` VARCHAR(100) COMMENT '撖��嚗��撖��',
+    `family_id` BIGINT COMMENT '��撅𧼮振摨背D',
+    `created_at` DATETIME DEFAULT CURRENT_TIMESTAMP,
+    `updated_at` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+    UNIQUE KEY `uk_openid` (`openid`),
+    INDEX `idx_family_id` (`family_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='�冽�銵?;
+
+-- 2. 摰嗅滬銵?CREATE TABLE IF NOT EXISTS `families` (
+    `id` BIGINT PRIMARY KEY AUTO_INCREMENT,
+    `name` VARCHAR(50) NOT NULL COMMENT '摰嗅滬�滨妍',
+    `invite_code` VARCHAR(10) NOT NULL COMMENT '��霂瑞�',
+    `creator_id` BIGINT NOT NULL COMMENT '�𥕦遣��D',
+    `created_at` DATETIME DEFAULT CURRENT_TIMESTAMP,
+    `updated_at` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+    UNIQUE KEY `uk_invite_code` (`invite_code`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='摰嗅滬銵?;
+
+-- 3. 摮拙�靽⊥�銵?CREATE TABLE IF NOT EXISTS `children` (
+    `id` BIGINT PRIMARY KEY AUTO_INCREMENT,
+    `user_id` BIGINT NOT NULL COMMENT '�唾��冽�ID',
+    `family_id` BIGINT NOT NULL COMMENT '��撅𧼮振摨背D',
+    `age` INT NOT NULL COMMENT '撟湧�',
+    `grade` VARCHAR(20) COMMENT '撟渡漣',
+    `dan_level` VARCHAR(10) COMMENT 'DAN瘚贝�蝑厩漣',
+    `penalty_enabled` TINYINT(1) DEFAULT 1 COMMENT '�臬炏撘��舀緍���6撗�誑銝钅�霈文��哨�',
+    `current_streak` INT DEFAULT 0 COMMENT '敶枏�餈䂿賒憭拇㺭',
+    `max_streak` INT DEFAULT 0 COMMENT '��蟮���輯�蝏剖予�?,
+    `last_check_in_date` DATE COMMENT '���擧��⊥𠯫�?,
+    `total_points` INT DEFAULT 0 COMMENT '敶枏�蝘臬�',
+    `total_earned` INT DEFAULT 0 COMMENT '蝝航恣�瑕�蝘臬�',
+    `theme` VARCHAR(20) DEFAULT 'default' COMMENT '銝駁�',
+    `created_at` DATETIME DEFAULT CURRENT_TIMESTAMP,
+    `updated_at` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+    INDEX `idx_family_id` (`family_id`),
+    INDEX `idx_user_id` (`user_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='摮拙�靽⊥�銵?;
+
+-- 4. 隞餃𦛚銵?CREATE TABLE IF NOT EXISTS `tasks` (
+    `id` BIGINT PRIMARY KEY AUTO_INCREMENT,
+    `family_id` BIGINT NOT NULL COMMENT '��撅𧼮振摨?,
+    `creator_id` BIGINT NOT NULL COMMENT '�𥕦遣���摰園鵭嚗?,
+    `child_id` BIGINT NOT NULL COMMENT '�扯�摮拙�',
+    `title` VARCHAR(100) NOT NULL COMMENT '隞餃𦛚�滨妍',
+    `description` TEXT COMMENT '憭�釣霂湔�',
+    `points` INT NOT NULL DEFAULT 2 COMMENT '蝘臬��堆�1-10嚗?,
+    `category` VARCHAR(20) NOT NULL COMMENT '��掩嚗𡁜郎銋?��暑/餈𣂼𢆡/�嗡�',
+    `priority` VARCHAR(10) DEFAULT 'medium' COMMENT '隡睃�蝥改�high/medium/low',
+    `deadline` DATETIME NOT NULL COMMENT '�芣迫�園𡢿',
+    `estimated_duration` INT COMMENT '憸�恣�園鵭嚗�����',
+    `repeat_type` VARCHAR(20) DEFAULT 'none' COMMENT '�滚�蝐餃�嚗䭰one/daily/weekly',
+    `need_review` TINYINT(1) DEFAULT 0 COMMENT '�臬炏��閬�恣�?,
+    `status` VARCHAR(20) DEFAULT 'pending' COMMENT '�嗆���pending/completed/approved/rejected/overdue',
+    `completed_at` DATETIME COMMENT '摰峕��園𡢿',
+    `completed_early` TINYINT(1) DEFAULT 0 COMMENT '�臬炏�𣂼�摰峕�',
+    `review_photo_url` VARCHAR(255) COMMENT '摰⊥瓲�抒��啣�',
+    `review_voice_url` VARCHAR(255) COMMENT '摰⊥瓲霂剝𨺗�啣�',
+    `review_status` VARCHAR(20) COMMENT '摰⊥瓲�嗆���pending/reviewing/approved/rejected',
+    `review_comment` VARCHAR(500) COMMENT '摰⊥瓲憭�釣',
+    `reviewed_at` DATETIME COMMENT '摰⊥瓲�園𡢿',
+    `reviewed_by` BIGINT COMMENT '摰⊥瓲鈭截D',
+    `created_at` DATETIME DEFAULT CURRENT_TIMESTAMP,
+    `updated_at` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+    INDEX `idx_family_id` (`family_id`),
+    INDEX `idx_child_id` (`child_id`),
+    INDEX `idx_status` (`status`),
+    INDEX `idx_deadline` (`deadline`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='隞餃𦛚銵?;
+
+-- 5. 蝘臬�瘚�偌銵?CREATE TABLE IF NOT EXISTS `points_log` (
+    `id` BIGINT PRIMARY KEY AUTO_INCREMENT,
+    `child_id` BIGINT NOT NULL COMMENT '摮拙�ID',
+    `task_id` BIGINT COMMENT '�唾�隞餃𦛚ID',
+    `amount` INT NOT NULL COMMENT '蝘臬��堆�甇�㺭銝箄繮敺梹�韐�㺭銝箸緍�歹�',
+    `type` VARCHAR(20) NOT NULL COMMENT '蝐餃�嚗魩ask/early/streak/focus/reward/correction',
+    `description` VARCHAR(200) NOT NULL COMMENT '�讛膩',
+    `created_at` DATETIME DEFAULT CURRENT_TIMESTAMP,
+    INDEX `idx_child_id` (`child_id`),
+    INDEX `idx_created_at` (`created_at`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='蝘臬�瘚�偌銵?;
+
+-- 6. 憟硋���掩銵?CREATE TABLE IF NOT EXISTS `reward_categories` (
+    `id` BIGINT PRIMARY KEY AUTO_INCREMENT,
+    `family_id` BIGINT NOT NULL COMMENT '��撅𧼮振摨?,
+    `name` VARCHAR(50) NOT NULL COMMENT '��掩�滨妍',
+    `icon` VARCHAR(50) COMMENT '�暹�',
+    `sort_order` INT DEFAULT 0 COMMENT '�鍦�',
+    `created_at` DATETIME DEFAULT CURRENT_TIMESTAMP,
+    INDEX `idx_family_id` (`family_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='憟硋���掩銵?;
+
+-- 7. 憟硋�銵?CREATE TABLE IF NOT EXISTS `rewards` (
+    `id` BIGINT PRIMARY KEY AUTO_INCREMENT,
+    `family_id` BIGINT NOT NULL COMMENT '��撅𧼮振摨?,
+    `child_id` BIGINT COMMENT '���摮拙�嚗𠃊ULL銵函內�典��舐鍂嚗?,
+    `category_id` BIGINT COMMENT '��掩ID',
+    `name` VARCHAR(100) NOT NULL COMMENT '憟硋��滨妍',
+    `description` VARCHAR(500) COMMENT '憟硋��讛膩',
+    `points_required` INT NOT NULL COMMENT '����蝘臬�',
+    `icon` VARCHAR(50) COMMENT '�暹�',
+    `stock` INT DEFAULT -1 COMMENT '摨枏�嚗?1銵函內�𣳇�嚗?,
+    `status` VARCHAR(20) DEFAULT 'active' COMMENT '�嗆���active/inactive',
+    `is_default` TINYINT(1) DEFAULT 0 COMMENT '�臬炏蝟餌�憸�挽',
+    `created_at` DATETIME DEFAULT CURRENT_TIMESTAMP,
+    `updated_at` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+    INDEX `idx_family_id` (`family_id`),
+    INDEX `idx_child_id` (`child_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='憟硋�銵?;
+
+-- 8. 憟硋��烐揢銵?CREATE TABLE IF NOT EXISTS `reward_exchanges` (
+    `id` BIGINT PRIMARY KEY AUTO_INCREMENT,
+    `reward_id` BIGINT NOT NULL COMMENT '憟硋�ID',
+    `child_id` BIGINT NOT NULL COMMENT '摮拙�ID',
+    `points_used` INT NOT NULL COMMENT '瘨��㛖妖�?,
+    `status` VARCHAR(20) DEFAULT 'pending' COMMENT '�嗆���pending/approved/rejected',
+    `review_comment` VARCHAR(500) COMMENT '摰⊥瓲憭�釣',
+    `reviewed_by` BIGINT COMMENT '摰⊥瓲鈭截D',
+    `reviewed_at` DATETIME COMMENT '摰⊥瓲�園𡢿',
+    `created_at` DATETIME DEFAULT CURRENT_TIMESTAMP,
+    INDEX `idx_child_id` (`child_id`),
+    INDEX `idx_status` (`status`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='憟硋��烐揢銵?;
+```
+
+### Step 2: 蝻硋�蝘滚��唳旿SQL
+
+�𥕦遣 `backend/src/main/resources/sql/seed.sql`嚗?
+```sql
+-- 敹�䰻�𠰴振蝘滚��唳旿
+
+-- 暺䁅恕憟硋���掩
+INSERT INTO `reward_categories` (`family_id`, `name`, `icon`, `sort_order`) VALUES
+(-1, '�抵捶蝐?, '��', 1),
+(-1, '蝎曄�蝐?, '�𤁗', 2),
+(-1, '�寞�蝐?, '��', 3);
+
+-- 暺䁅恕憸�挽憟硋�
+INSERT INTO `rewards` (`family_id`, `child_id`, `category_id`, `name`, `points_required`, `icon`, `is_default`) VALUES
+(-1, NULL, 1, '撠誯妟憌?, 10, '�斆', 1),
+(-1, NULL, 1, '撠讐焵�?, 30, '�孛', 1),
+(-1, NULL, 1, '�拙��脩�', 50, '��', 1),
+(-1, NULL, 2, '�衤���𢆡�餌�', 5, '�𤁗', 1),
+(-1, NULL, 2, '�拇虜�?0���', 15, '�緤', 1),
+(-1, NULL, 2, '�餅虜銋𣂼㦤', 200, '�䔝', 1),
+(-1, NULL, 3, '隞𠰴予銝齿�蝣?, 15, '�椉儭?, 1),
+(-1, NULL, 3, '�𡁶辺30���', 20, '��', 1),
+(-1, NULL, 3, '�滚�銝�憿孵振�?, 25, '�?, 1);
+```
+
+---
+
+## Task 2: �𡒊垢憿寧𤌍�嘥��?
+**Files:**
+- Create: `backend/pom.xml`
+- Create: `backend/src/main/resources/application.yml`
+- Create: `backend/src/main/java/com/xinzhiyijia/XinzhiyijiaApplication.java`
+
+### Step 1: �𥕦遣pom.xml
+
+```xml
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
+         http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <groupId>com.xinzhiyijia</groupId>
+    <artifactId>xinzhiyijia-backend</artifactId>
+    <version>1.0.0</version>
+    <packaging>jar</packaging>
+
+    <parent>
+        <groupId>org.springframework.boot</groupId>
+        <artifactId>spring-boot-starter-parent</artifactId>
+        <version>2.7.18</version>
+    </parent>
+
+    <properties>
+        <java.version>1.8</java.version>
+        <mybatis.version>2.3.2</mybatis.version>
+    </properties>
+
+    <dependencies>
+        <!-- Spring Boot Web -->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-web</artifactId>
+        </dependency>
+
+        <!-- MyBatis -->
+        <dependency>
+            <groupId>org.mybatis.spring.boot</groupId>
+            <artifactId>mybatis-spring-boot-starter</artifactId>
+            <version>${mybatis.version}</version>
+        </dependency>
+
+        <!-- MySQL -->
+        <dependency>
+            <groupId>mysql</groupId>
+            <artifactId>mysql-connector-java</artifactId>
+        </dependency>
+
+        <!-- Swagger -->
+        <dependency>
+            <groupId>org.springdoc</groupId>
+            <artifactId>springdoc-openapi-ui</artifactId>
+            <version>1.7.0</version>
+        </dependency>
+
+        <!-- JWT -->
+        <dependency>
+            <groupId>io.jsonwebtoken</groupId>
+            <artifactId>jjwt</artifactId>
+            <version>0.9.1</version>
+        </dependency>
+
+        <!-- Lombok -->
+        <dependency>
+            <groupId>org.projectlombok</groupId>
+            <artifactId>lombok</artifactId>
+            <optional>true</optional>
+        </dependency>
+
+        <!-- Test -->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-test</artifactId>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-maven-plugin</artifactId>
+            </plugin>
+        </plugins>
+    </build>
+</project>
+```
+
+### Step 2: �𥕦遣application.yml
+
+```yaml
+server:
+  port: 8080
+  servlet:
+    context-path: /api
+
+spring:
+  application:
+    name: xinzhiyijia-backend
+  datasource:
+    driver-class-name: com.mysql.cj.jdbc.Driver
+    url: jdbc:mysql://localhost:3306/xinzhiyijia?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai
+    username: root
+    password: root
+
+mybatis:
+  mapper-locations: classpath:mapper/*.xml
+  type-aliases-package: com.xinzhiyijia.entity
+  configuration:
+    map-underscore-to-camel-case: true
+
+jwt:
+  secret: xinzhiyijia-secret-key-2024
+  expiration: 604800000
+
+xinzhiyijia:
+  penalty:
+    max-daily: 5
+    grace-minutes: 10
+  points:
+    early-bonus: 1
+    focus-perfect-bonus: 1
+```
+
+### Step 3: �𥕦遣�臬𢆡蝐?
+```java
+package com.xinzhiyijia;
+
+import org.mybatis.spring.annotation.MapperScan;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+@MapperScan("com.xinzhiyijia.mapper")
+public class XinzhiyijiaApplication {
+    public static void main(String[] args) {
+        SpringApplication.run(XinzhiyijiaApplication.class, args);
+    }
+}
+```
+
+---
+
+## Task 3: �砍�蝐颱�撌亙�蝐?
+**Files:**
+- Create: `backend/src/main/java/com/xinzhiyijia/common/Result.java`
+- Create: `backend/src/main/java/com/xinzhiyijia/common/ResultCode.java`
+- Create: `backend/src/main/java/com/xinzhiyijia/common/JwtUtil.java`
+
+### Step 1: �𥕦遣蝏煺��滚�蝐?
+```java
+package com.xinzhiyijia.common;
+
+import lombok.Data;
+
+@Data
+public class Result<T> {
+    private int code;
+    private String message;
+    private T data;
+
+    public static <T> Result<T> success(T data) {
+        Result<T> result = new Result<>();
+        result.setCode(200);
+        result.setMessage("success");
+        result.setData(data);
+        return result;
+    }
+
+    public static <T> Result<T> success(String message, T data) {
+        Result<T> result = new Result<>();
+        result.setCode(200);
+        result.setMessage(message);
+        result.setData(data);
+        return result;
+    }
+
+    public static <T> Result<T> error(int code, String message) {
+        Result<T> result = new Result<>();
+        result.setCode(code);
+        result.setMessage(message);
+        return result;
+    }
+
+    public static <T> Result<T> error(String message) {
+        return error(500, message);
+    }
+}
+```
+
+### Step 2: �𥕦遣�嗆����帋蜀
+
+```java
+package com.xinzhiyijia.common;
+
+public enum ResultCode {
+    SUCCESS(200, "�𣂼�"),
+    BAD_REQUEST(400, "霂瑟���㺭�躰秤"),
+    UNAUTHORIZED(401, "�芣��?),
+    FORBIDDEN(403, "蝳�迫霈輸䔮"),
+    NOT_FOUND(404, "韏��銝滚��?),
+    INTERNAL_ERROR(500, "�滚𦛚�典��券�霂?);
+
+    private final int code;
+    private final String message;
+
+    ResultCode(int code, String message) {
+        this.code = code;
+        this.message = message;
+    }
+
+    public int getCode() { return code; }
+    public String getMessage() { return message; }
+}
+```
+
+### Step 3: �𥕦遣JWT撌亙�蝐?
+```java
+package com.xinzhiyijia.common;
+
+import io.jsonwebtoken.*;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Component;
+
+import java.util.Date;
+
+@Component
+public class JwtUtil {
+
+    @Value("${jwt.secret}")
+    private String secret;
+
+    @Value("${jwt.expiration}")
+    private Long expiration;
+
+    public String generateToken(Long userId, String role) {
+        return Jwts.builder()
+                .setSubject(String.valueOf(userId))
+                .claim("role", role)
+                .setIssuedAt(new Date())
+                .setExpiration(new Date(System.currentTimeMillis() + expiration))
+                .signWith(SignatureAlgorithm.HS256, secret)
+                .compact();
+    }
+
+    public Claims parseToken(String token) {
+        try {
+            return Jwts.parser()
+                    .setSigningKey(secret)
+                    .parseClaimsJws(token)
+                    .getBody();
+        } catch (ExpiredJwtException e) {
+            throw new RuntimeException("Token撌脰��?);
+        } catch (Exception e) {
+            throw new RuntimeException("Token�䭾�");
+        }
+    }
+
+    public Long getUserIdFromToken(String token) {
+        Claims claims = parseToken(token);
+        return Long.parseLong(claims.getSubject());
+    }
+
+    public String getRoleFromToken(String token) {
+        Claims claims = parseToken(token);
+        return claims.get("role", String.class);
+    }
+
+    public boolean isTokenValid(String token) {
+        try {
+            parseToken(token);
+            return true;
+        } catch (Exception e) {
+            return false;
+        }
+    }
+}
+```
+
+---
+
+## Task 4: 摰硺�蝐餃�撱?
+**Files:**
+- Create: `backend/src/main/java/com/xinzhiyijia/entity/User.java`
+- Create: `backend/src/main/java/com/xinzhiyijia/entity/Family.java`
+- Create: `backend/src/main/java/com/xinzhiyijia/entity/Child.java`
+- Create: `backend/src/main/java/com/xinzhiyijia/entity/Task.java`
+- Create: `backend/src/main/java/com/xinzhiyijia/entity/PointsLog.java`
+- Create: `backend/src/main/java/com/xinzhiyijia/entity/Reward.java`
+
+### Step 1: �冽�摰硺�蝐?
+```java
+package com.xinzhiyijia.entity;
+
+import lombok.Data;
+import java.time.LocalDateTime;
+
+@Data
+public class User {
+    private Long id;
+    private String openid;
+    private String phone;
+    private String nickname;
+    private String avatar;
+    private String role;  // parent, child
+    private String password;
+    private Long familyId;
+    private LocalDateTime createdAt;
+    private LocalDateTime updatedAt;
+}
+```
+
+### Step 2: 摰嗅滬摰硺�蝐?
+```java
+package com.xinzhiyijia.entity;
+
+import lombok.Data;
+import java.time.LocalDateTime;
+
+@Data
+public class Family {
+    private Long id;
+    private String name;
+    private String inviteCode;
+    private Long creatorId;
+    private LocalDateTime createdAt;
+    private LocalDateTime updatedAt;
+}
+```
+
+### Step 3: 摮拙�摰硺�蝐?
+```java
+package com.xinzhiyijia.entity;
+
+import lombok.Data;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+
+@Data
+public class Child {
+    private Long id;
+    private Long userId;
+    private Long familyId;
+    private Integer age;
+    private String grade;
+    private String danLevel;
+    private Boolean penaltyEnabled;
+    private Integer currentStreak;
+    private Integer maxStreak;
+    private LocalDate lastCheckInDate;
+    private Integer totalPoints;
+    private Integer totalEarned;
+    private String theme;
+    private LocalDateTime createdAt;
+    private LocalDateTime updatedAt;
+}
+```
+
+### Step 4: 隞餃𦛚摰硺�蝐?
+```java
+package com.xinzhiyijia.entity;
+
+import lombok.Data;
+import java.time.LocalDateTime;
+
+@Data
+public class Task {
+    private Long id;
+    private Long familyId;
+    private Long creatorId;
+    private Long childId;
+    private String title;
+    private String description;
+    private Integer points;
+    private String category;
+    private String priority;
+    private LocalDateTime deadline;
+    private Integer estimatedDuration;
+    private String repeatType;
+    private Boolean needReview;
+    private String status;
+    private LocalDateTime completedAt;
+    private Boolean completedEarly;
+    private String reviewPhotoUrl;
+    private String reviewVoiceUrl;
+    private String reviewStatus;
+    private String reviewComment;
+    private LocalDateTime reviewedAt;
+    private Long reviewedBy;
+    private LocalDateTime createdAt;
+    private LocalDateTime updatedAt;
+}
+```
+
+### Step 5: 蝘臬�瘚�偌摰硺�蝐?
+```java
+package com.xinzhiyijia.entity;
+
+import lombok.Data;
+import java.time.LocalDateTime;
+
+@Data
+public class PointsLog {
+    private Long id;
+    private Long childId;
+    private Long taskId;
+    private Integer amount;
+    private String type;  // task/early/streak/focus/reward/correction
+    private String description;
+    private LocalDateTime createdAt;
+}
+```
+
+### Step 6: 憟硋�摰硺�蝐?
+```java
+package com.xinzhiyijia.entity;
+
+import lombok.Data;
+import java.time.LocalDateTime;
+
+@Data
+public class Reward {
+    private Long id;
+    private Long familyId;
+    private Long childId;
+    private Long categoryId;
+    private String name;
+    private String description;
+    private Integer pointsRequired;
+    private String icon;
+    private Integer stock;
+    private String status;
+    private Boolean isDefault;
+    private LocalDateTime createdAt;
+    private LocalDateTime updatedAt;
+}
+```
+
+---
+
+## Task 5: Mapper撅���?
+**Files:**
+- Create: `backend/src/main/java/com/xinzhiyijia/mapper/UserMapper.java`
+- Create: `backend/src/main/java/com/xinzhiyijia/mapper/FamilyMapper.java`
+- Create: `backend/src/main/java/com/xinzhiyijia/mapper/ChildMapper.java`
+- Create: `backend/src/main/java/com/xinzhiyijia/mapper/TaskMapper.java`
+- Create: `backend/src/main/java/com/xinzhiyijia/mapper/PointsLogMapper.java`
+- Create: `backend/src/main/java/com/xinzhiyijia/mapper/RewardMapper.java`
+- Create: `backend/src/main/resources/mapper/UserMapper.xml`
+- Create: `backend/src/main/resources/mapper/TaskMapper.xml`
+- Create: `backend/src/main/resources/mapper/PointsLogMapper.xml`
+- Create: `backend/src/main/resources/mapper/RewardMapper.xml`
+
+### Step 1: UserMapper�亙藁
+
+```java
+package com.xinzhiyijia.mapper;
+
+import com.xinzhiyijia.entity.User;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+
+@Mapper
+public interface UserMapper {
+    User findById(@Param("id") Long id);
+    User findByOpenid(@Param("openid") String openid);
+    int insert(User user);
+    int update(User user);
+}
+```
+
+### Step 2: UserMapper.xml
+
+```xml
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.xinzhiyijia.mapper.UserMapper">
+
+    <resultMap id="UserMap" type="com.xinzhiyijia.entity.User">
+        <id column="id" property="id"/>
+        <result column="openid" property="openid"/>
+        <result column="phone" property="phone"/>
+        <result column="nickname" property="nickname"/>
+        <result column="avatar" property="avatar"/>
+        <result column="role" property="role"/>
+        <result column="password" property="password"/>
+        <result column="family_id" property="familyId"/>
+        <result column="created_at" property="createdAt"/>
+        <result column="updated_at" property="updatedAt"/>
+    </resultMap>
+
+    <select id="findById" resultMap="UserMap">
+        SELECT * FROM users WHERE id = #{id}
+    </select>
+
+    <select id="findByOpenid" resultMap="UserMap">
+        SELECT * FROM users WHERE openid = #{openid}
+    </select>
+
+    <insert id="insert" useGeneratedKeys="true" keyProperty="id">
+        INSERT INTO users (openid, phone, nickname, avatar, role, password, family_id)
+        VALUES (#{openid}, #{phone}, #{nickname}, #{avatar}, #{role}, #{password}, #{familyId})
+    </insert>
+
+    <update id="update">
+        UPDATE users
+        SET phone = #{phone},
+            nickname = #{nickname},
+            avatar = #{avatar},
+            password = #{password},
+            family_id = #{familyId},
+            updated_at = NOW()
+        WHERE id = #{id}
+    </update>
+
+</mapper>
+```
+
+### Step 3: TaskMapper�亙藁
+
+```java
+package com.xinzhiyijia.mapper;
+
+import com.xinzhiyijia.entity.Task;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+import java.util.List;
+
+@Mapper
+public interface TaskMapper {
+    Task findById(@Param("id") Long id);
+    List<Task> findByChildId(@Param("childId") Long childId, @Param("date") String date);
+    List<Task> findByFamilyId(@Param("familyId") Long familyId);
+    int insert(Task task);
+    int update(Task task);
+    int updateStatus(@Param("id") Long id, @Param("status") String status);
+    int countTodayCompleted(@Param("childId") Long childId, @Param("date") String date);
+    int countTodayTotal(@Param("childId") Long childId, @Param("date") String date);
+}
+```
+
+### Step 4: TaskMapper.xml
+
+```xml
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.xinzhiyijia.mapper.TaskMapper">
+
+    <resultMap id="TaskMap" type="com.xinzhiyijia.entity.Task">
+        <id column="id" property="id"/>
+        <result column="family_id" property="familyId"/>
+        <result column="creator_id" property="creatorId"/>
+        <result column="child_id" property="childId"/>
+        <result column="title" property="title"/>
+        <result column="description" property="description"/>
+        <result column="points" property="points"/>
+        <result column="category" property="category"/>
+        <result column="priority" property="priority"/>
+        <result column="deadline" property="deadline"/>
+        <result column="estimated_duration" property="estimatedDuration"/>
+        <result column="repeat_type" property="repeatType"/>
+        <result column="need_review" property="needReview"/>
+        <result column="status" property="status"/>
+        <result column="completed_at" property="completedAt"/>
+        <result column="completed_early" property="completedEarly"/>
+        <result column="review_photo_url" property="reviewPhotoUrl"/>
+        <result column="review_voice_url" property="reviewVoiceUrl"/>
+        <result column="review_status" property="reviewStatus"/>
+        <result column="review_comment" property="reviewComment"/>
+        <result column="reviewed_at" property="reviewedAt"/>
+        <result column="reviewed_by" property="reviewedBy"/>
+        <result column="created_at" property="createdAt"/>
+        <result column="updated_at" property="updatedAt"/>
+    </resultMap>
+
+    <select id="findById" resultMap="TaskMap">
+        SELECT * FROM tasks WHERE id = #{id}
+    </select>
+
+    <select id="findByChildId" resultMap="TaskMap">
+        SELECT * FROM tasks 
+        WHERE child_id = #{childId}
+        AND DATE(deadline) = #{date}
+        ORDER BY deadline ASC
+    </select>
+
+    <select id="findByFamilyId" resultMap="TaskMap">
+        SELECT * FROM tasks 
+        WHERE family_id = #{familyId}
+        ORDER BY deadline DESC
+    </select>
+
+    <insert id="insert" useGeneratedKeys="true" keyProperty="id">
+        INSERT INTO tasks (family_id, creator_id, child_id, title, description, points, 
+                          category, priority, deadline, estimated_duration, repeat_type, need_review)
+        VALUES (#{familyId}, #{creatorId}, #{childId}, #{title}, #{description}, #{points},
+                #{category}, #{priority}, #{deadline}, #{estimatedDuration}, #{repeatType}, #{needReview})
+    </insert>
+
+    <update id="update">
+        UPDATE tasks
+        SET title = #{title},
+            description = #{description},
+            points = #{points},
+            category = #{category},
+            priority = #{priority},
+            deadline = #{deadline},
+            need_review = #{needReview},
+            updated_at = NOW()
+        WHERE id = #{id}
+    </update>
+
+    <update id="updateStatus">
+        UPDATE tasks SET status = #{status}, updated_at = NOW() WHERE id = #{id}
+    </update>
+
+    <select id="countTodayCompleted" resultType="int">
+        SELECT COUNT(*) FROM tasks 
+        WHERE child_id = #{childId} 
+        AND DATE(deadline) = #{date}
+        AND status IN ('completed', 'approved')
+    </select>
+
+    <select id="countTodayTotal" resultType="int">
+        SELECT COUNT(*) FROM tasks 
+        WHERE child_id = #{childId} 
+        AND DATE(deadline) = #{date}
+    </select>
+
+</mapper>
+```
+
+### Step 5: PointsLogMapper�亙藁
+
+```java
+package com.xinzhiyijia.mapper;
+
+import com.xinzhiyijia.entity.PointsLog;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+import java.util.List;
+
+@Mapper
+public interface PointsLogMapper {
+    List<PointsLog> findByChildId(@Param("childId") Long childId, @Param("limit") int limit);
+    int insert(PointsLog pointsLog);
+    int sumByChildIdAndType(@Param("childId") Long childId, @Param("type") String type, @Param("startDate") String startDate);
+}
+```
+
+### Step 6: PointsLogMapper.xml
+
+```xml
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.xinzhiyijia.mapper.PointsLogMapper">
+
+    <resultMap id="PointsLogMap" type="com.xinzhiyijia.entity.PointsLog">
+        <id column="id" property="id"/>
+        <result column="child_id" property="childId"/>
+        <result column="task_id" property="taskId"/>
+        <result column="amount" property="amount"/>
+        <result column="type" property="type"/>
+        <result column="description" property="description"/>
+        <result column="created_at" property="createdAt"/>
+    </resultMap>
+
+    <select id="findByChildId" resultMap="PointsLogMap">
+        SELECT * FROM points_log 
+        WHERE child_id = #{childId}
+        ORDER BY created_at DESC
+        LIMIT #{limit}
+    </select>
+
+    <insert id="insert">
+        INSERT INTO points_log (child_id, task_id, amount, type, description)
+        VALUES (#{childId}, #{taskId}, #{amount}, #{type}, #{description})
+    </insert>
+
+    <select id="sumByChildIdAndType" resultType="int">
+        SELECT COALESCE(SUM(amount), 0) FROM points_log
+        WHERE child_id = #{childId}
+        AND type = #{type}
+        AND DATE(created_at) >= #{startDate}
+    </select>
+
+</mapper>
+```
+
+### Step 7: RewardMapper�亙藁
+
+```java
+package com.xinzhiyijia.mapper;
+
+import com.xinzhiyijia.entity.Reward;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+import java.util.List;
+
+@Mapper
+public interface RewardMapper {
+    Reward findById(@Param("id") Long id);
+    List<Reward> findByFamilyId(@Param("familyId") Long familyId);
+    List<Reward> findDefaultRewards();
+    int insert(Reward reward);
+    int update(Reward reward);
+}
+```
+
+---
+
+## Task 6: Service撅���?
+**Files:**
+- Create: `backend/src/main/java/com/xinzhiyijia/service/UserService.java`
+- Create: `backend/src/main/java/com/xinzhiyijia/service/TaskService.java`
+- Create: `backend/src/main/java/com/xinzhiyijia/service/PointsService.java`
+- Create: `backend/src/main/java/com/xinzhiyijia/service/RewardService.java`
+
+### Step 1: UserService
+
+```java
+package com.xinzhiyijia.service;
+
+import com.xinzhiyijia.common.JwtUtil;
+import com.xinzhiyijia.entity.User;
+import com.xinzhiyijia.mapper.UserMapper;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+@Service
+public class UserService {
+
+    @Autowired
+    private UserMapper userMapper;
+
+    @Autowired
+    private JwtUtil jwtUtil;
+
+    public User findByOpenid(String openid) {
+        return userMapper.findByOpenid(openid);
+    }
+
+    public User findById(Long id) {
+        return userMapper.findById(id);
+    }
+
+    public String login(User user) {
+        // �交𪄳�硋�撱箇鍂�?        User existUser = userMapper.findByOpenid(user.getOpenid());
+        if (existUser == null) {
+            userMapper.insert(user);
+            existUser = user;
+        }
+        // ���Token
+        return jwtUtil.generateToken(existUser.getId(), existUser.getRole());
+    }
+
+    public User register(User user) {
+        userMapper.insert(user);
+        return user;
+    }
+}
+```
+
+### Step 2: TaskService
+
+```java
+package com.xinzhiyijia.service;
+
+import com.xinzhiyijia.entity.Task;
+import com.xinzhiyijia.mapper.ChildMapper;
+import com.xinzhiyijia.mapper.PointsLogMapper;
+import com.xinzhiyijia.mapper.TaskMapper;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+import java.util.List;
+
+@Service
+public class TaskService {
+
+    @Autowired
+    private TaskMapper taskMapper;
+
+    @Autowired
+    private ChildMapper childMapper;
+
+    @Autowired
+    private PointsLogMapper pointsLogMapper;
+
+    @Value("${xinzhiyijia.penalty.grace-minutes:10}")
+    private int graceMinutes;
+
+    @Value("${xinzhiyijia.penalty.max-daily:5}")
+    private int maxDailyPenalty;
+
+    @Value("${xinzhiyijia.points.early-bonus:1}")
+    private int earlyBonus;
+
+    public Task findById(Long id) {
+        return taskMapper.findById(id);
+    }
+
+    public List<Task> getTodayTasks(Long childId) {
+        String today = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));
+        return taskMapper.findByChildId(childId, today);
+    }
+
+    @Transactional
+    public Task createTask(Task task) {
+        task.setStatus("pending");
+        taskMapper.insert(task);
+        return task;
+    }
+
+    @Transactional
+    public Task completeTask(Long taskId, String photoUrl, String voiceUrl) {
+        Task task = taskMapper.findById(taskId);
+        if (task == null) {
+            throw new RuntimeException("隞餃𦛚銝滚��?);
+        }
+
+        LocalDateTime now = LocalDateTime.now();
+        task.setCompletedAt(now);
+
+        // �斗鱏�臬炏�𣂼�摰峕�
+        boolean isEarly = now.plusMinutes(graceMinutes).isBefore(task.getDeadline());
+        task.setCompletedEarly(isEarly);
+
+        if (task.getNeedReview()) {
+            // ��閬�恣�?            task.setStatus("completed");
+            task.setReviewStatus("pending");
+            task.setReviewPhotoUrl(photoUrl);
+            task.setReviewVoiceUrl(voiceUrl);
+        } else {
+            // 銝漤�閬�恣�賂��湔𦻖摰峕�
+            task.setStatus("approved");
+            // �烐𦆮蝘臬�
+            PointsService.awardTaskPoints(task, isEarly);
+        }
+
+        taskMapper.update(task);
+        return task;
+    }
+
+    @Transactional
+    public Task reviewTask(Long taskId, String result, String comment, Long reviewerId) {
+        Task task = taskMapper.findById(taskId);
+        if (task == null) {
+            throw new RuntimeException("隞餃𦛚銝滚��?);
+        }
+
+        task.setReviewStatus(result);
+        task.setReviewComment(comment);
+        task.setReviewedBy(reviewerId);
+        task.setReviewedAt(LocalDateTime.now());
+
+        if ("approved".equals(result)) {
+            task.setStatus("approved");
+            // �烐𦆮蝘臬�
+            PointsService.awardTaskPoints(task, task.getCompletedEarly());
+        } else {
+            task.setStatus("rejected");
+        }
+
+        taskMapper.update(task);
+        return task;
+    }
+}
+```
+
+### Step 3: PointsService
+
+```java
+package com.xinzhiyijia.service;
+
+import com.xinzhiyijia.entity.Child;
+import com.xinzhiyijia.entity.PointsLog;
+import com.xinzhiyijia.entity.Task;
+import com.xinzhiyijia.mapper.ChildMapper;
+import com.xinzhiyijia.mapper.PointsLogMapper;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Service;
+
+@Service
+public class PointsService {
+
+    @Autowired
+    private ChildMapper childMapper;
+
+    @Autowired
+    private PointsLogMapper pointsLogMapper;
+
+    @Value("${xinzhiyijia.points.early-bonus:1}")
+    private static int earlyBonus;
+
+    public static void awardTaskPoints(Task task, boolean isEarly) {
+        int points = task.getPoints();
+        if (isEarly) {
+            points += earlyBonus;
+        }
+
+        // 霈啣�蝘臬�瘚�偌
+        PointsLog log = new PointsLog();
+        log.setChildId(task.getChildId());
+        log.setTaskId(task.getId());
+        log.setAmount(points);
+        log.setType("task");
+        log.setDescription(isEarly ? 
+            "摰峕�隞餃𦛚�? + task.getTitle() + "�?�𣂼�摰峕�+" + earlyBonus + "�?" :
+            "摰峕�隞餃𦛚�? + task.getTitle() + "�?);
+
+        // 餈䠷���閬�釣�叮apper嚗䔶��蹱��䲮瘜閙�瘜閙釣�?        // 摰鮋�摰䂿緵�嗅��嫣蛹摰硺��寞��碶蝙�典�隞𡝗䲮撘?    }
+
+    public int getChildPoints(Long childId) {
+        Child child = childMapper.findById(childId);
+        return child != null ? child.getTotalPoints() : 0;
+    }
+
+    public java.util.List<PointsLog> getPointsLog(Long childId) {
+        return pointsLogMapper.findByChildId(childId, 50);
+    }
+}
+```
+
+---
+
+## Task 7: Controller撅���?
+**Files:**
+- Create: `backend/src/main/java/com/xinzhiyijia/controller/AuthController.java`
+- Create: `backend/src/main/java/com/xinzhiyijia/controller/TaskController.java`
+- Create: `backend/src/main/java/com/xinzhiyijia/controller/RewardController.java`
+
+### Step 1: AuthController
+
+```java
+package com.xinzhiyijia.controller;
+
+import com.xinzhiyijia.common.Result;
+import com.xinzhiyijia.entity.User;
+import com.xinzhiyijia.service.UserService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.HashMap;
+import java.util.Map;
+
+@Tag(name = "霈方��亙藁")
+@RestController
+@RequestMapping("/auth")
+public class AuthController {
+
+    @Autowired
+    private UserService userService;
+
+    @Operation(summary = "敺桐縑�餃�")
+    @PostMapping("/login")
+    public Result<Map<String, Object>> login(@RequestBody Map<String, String> params) {
+        String code = params.get("code");
+        // TODO: �朞�code靚�鍂敺桐縑�亙藁�瑕�openid
+        
+        // 璅⊥��餃�
+        User user = new User();
+        user.setOpenid("mock_openid_" + System.currentTimeMillis());
+        user.setRole(params.getOrDefault("role", "parent"));
+        user.setNickname(params.getOrDefault("nickname", "�冽�"));
+
+        String token = userService.login(user);
+
+        Map<String, Object> data = new HashMap<>();
+        data.put("token", token);
+        data.put("userId", user.getId());
+        data.put("role", user.getRole());
+        
+        return Result.success(data);
+    }
+}
+```
+
+### Step 2: TaskController
+
+```java
+package com.xinzhiyijia.controller;
+
+import com.xinzhiyijia.common.Result;
+import com.xinzhiyijia.entity.Task;
+import com.xinzhiyijia.service.TaskService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+@Tag(name = "隞餃𦛚�亙藁")
+@RestController
+@RequestMapping("/tasks")
+public class TaskController {
+
+    @Autowired
+    private TaskService taskService;
+
+    @Operation(summary = "�瑕�隞𦠜𠯫隞餃𦛚")
+    @GetMapping("/today/{childId}")
+    public Result<List<Task>> getTodayTasks(@PathVariable Long childId) {
+        return Result.success(taskService.getTodayTasks(childId));
+    }
+
+    @Operation(summary = "�𥕦遣隞餃𦛚")
+    @PostMapping
+    public Result<Task> createTask(@RequestBody Task task) {
+        return Result.success(taskService.createTask(task));
+    }
+
+    @Operation(summary = "摰峕�隞餃𦛚")
+    @PostMapping("/{taskId}/complete")
+    public Result<Task> completeTask(
+            @PathVariable Long taskId,
+            @RequestBody(required = false) java.util.Map<String, String> params) {
+        String photoUrl = params != null ? params.get("photoUrl") : null;
+        String voiceUrl = params != null ? params.get("voiceUrl") : null;
+        return Result.success(taskService.completeTask(taskId, photoUrl, voiceUrl));
+    }
+
+    @Operation(summary = "摰⊥瓲隞餃𦛚")
+    @PostMapping("/{taskId}/review")
+    public Result<Task> reviewTask(
+            @PathVariable Long taskId,
+            @RequestBody java.util.Map<String, String> params) {
+        String result = params.get("result");
+        String comment = params.get("comment");
+        Long reviewerId = Long.parseLong(params.get("reviewerId"));
+        return Result.success(taskService.reviewTask(taskId, result, comment, reviewerId));
+    }
+}
+```
+
+---
+
+## Task 8: 敺桐縑撠讐�摨誩�蝡臬�憪见�
+
+**Files:**
+- Create: `miniprogram/app.json`
+- Create: `miniprogram/app.js`
+- Create: `miniprogram/utils/api.js`
+
+### Step 1: app.json
+
+```json
+{
+  "pages": [
+    "pages/index/index",
+    "pages/login/login",
+    "pages/parent-home/parent-home",
+    "pages/child-home/child-home",
+    "pages/task-list/task-list",
+    "pages/task-detail/task-detail",
+    "pages/task-create/task-create",
+    "pages/reward-list/reward-list",
+    "pages/points-log/points-log",
+    "pages/settings/settings"
+  ],
+  "window": {
+    "backgroundTextStyle": "light",
+    "navigationBarBackgroundColor": "#FF6B6B",
+    "navigationBarTitleText": "敹�䰻�𠰴振",
+    "navigationBarTextStyle": "white"
+  },
+  "tabBar": {
+    "color": "#999",
+    "selectedColor": "#FF6B6B",
+    "backgroundColor": "#fff",
+    "borderStyle": "black",
+    "list": [
+      {
+        "pagePath": "pages/parent-home/parent-home",
+        "text": "隞餃𦛚",
+        "iconPath": "images/tab-task.png",
+        "selectedIconPath": "images/tab-task-active.png"
+      },
+      {
+        "pagePath": "pages/reward-list/reward-list",
+        "text": "憟硋�",
+        "iconPath": "images/tab-reward.png",
+        "selectedIconPath": "images/tab-reward-active.png"
+      },
+      {
+        "pagePath": "pages/settings/settings",
+        "text": "�𤑳�",
+        "iconPath": "images/tab-me.png",
+        "selectedIconPath": "images/tab-me-active.png"
+      }
+    ]
+  },
+  "usingComponents": {}
+}
+```
+
+### Step 2: app.js
+
+```javascript
+App({
+  globalData: {
+    baseUrl: 'https://your-api-domain.com/api',
+    token: '',
+    userInfo: null,
+    role: ''  // parent �?child
+  },
+
+  onLaunch() {
+    // 璉��亦蒈敶閧𠶖�?    const token = wx.getStorageSync('token');
+    const role = wx.getStorageSync('role');
+    if (token) {
+      this.globalData.token = token;
+      this.globalData.role = role;
+    }
+  }
+})
+```
+
+### Step 3: api.js
+
+```javascript
+const app = getApp();
+
+const request = (url, method = 'GET', data = {}) => {
+  return new Promise((resolve, reject) => {
+    wx.request({
+      url: app.globalData.baseUrl + url,
+      method,
+      data,
+      header: {
+        'Authorization': 'Bearer ' + app.globalData.token,
+        'Content-Type': 'application/json'
+      },
+      success(res) {
+        if (res.statusCode === 200 && res.data.code === 200) {
+          resolve(res.data.data);
+        } else {
+          reject(res.data);
+        }
+      },
+      fail(err) {
+        reject(err);
+      }
+    });
+  });
+};
+
+module.exports = {
+  // 霈方�
+  login: (data) => request('/auth/login', 'POST', data),
+  
+  // 隞餃𦛚
+  getTodayTasks: (childId) => request(`/tasks/today/${childId}`),
+  createTask: (data) => request('/tasks', 'POST', data),
+  completeTask: (taskId, data) => request(`/tasks/${taskId}/complete`, 'POST', data),
+  reviewTask: (taskId, data) => request(`/tasks/${taskId}/review`, 'POST', data),
+  
+  // 憟硋�
+  getRewards: (familyId) => request(`/rewards/family/${familyId}`),
+  createReward: (data) => request('/rewards', 'POST', data),
+  exchangeReward: (rewardId, childId) => request(`/rewards/${rewardId}/exchange`, 'POST', { childId }),
+  
+  // 蝘臬�
+  getPointsLog: (childId) => request(`/points/log/${childId}`),
+};
+```
+
+---
+
+## Task 9: �詨�憿菟𢒰撘��?
+**Files:**
+- Create: `miniprogram/pages/index/index.js`
+- Create: `miniprogram/pages/parent-home/parent-home.js`
+- Create: `miniprogram/pages/child-home/child-home.js`
+- Create: `miniprogram/pages/task-create/task-create.js`
+
+### Step 1: 擐㚚△嚗���脣�瘚��
+
+```javascript
+// pages/index/index.js
+const app = getApp();
+
+Page({
+  onLoad() {
+    // 璉��交糓�血歇�餃�
+    if (!app.globalData.token) {
+      wx.redirectTo({ url: '/pages/login/login' });
+      return;
+    }
+    
+    // �寞旿閫坿𠧧頝唾蓮
+    const role = app.globalData.role;
+    if (role === 'parent') {
+      wx.switchTab({ url: '/pages/parent-home/parent-home' });
+    } else {
+      wx.switchTab({ url: '/pages/child-home/child-home' });
+    }
+  }
+})
+```
+
+```html
+<!-- pages/index/index.wxml -->
+<view class="container">
+  <image class="logo" src="/images/logo.png"/>
+  <text class="title">敹�䰻�𠰴振</text>
+  <text class="subtitle">�典�鈭�圾嚗𣬚�摮血畺�?/text>
+</view>
+```
+
+```css
+/* pages/index/index.wxss */
+.container {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  height: 100vh;
+  background: linear-gradient(135deg, #FF6B6B 0%, #FFE66D 100%);
+}
+.logo {
+  width: 200rpx;
+  height: 200rpx;
+  margin-bottom: 40rpx;
+}
+.title {
+  font-size: 48rpx;
+  font-weight: bold;
+  color: #fff;
+  margin-bottom: 20rpx;
+}
+.subtitle {
+  font-size: 28rpx;
+  color: rgba(255,255,255,0.8);
+}
+```
+
+### Step 2: 摰園鵭蝡舫�憿?
+```javascript
+// pages/parent-home/parent-home.js
+const api = require('../../utils/api');
+
+Page({
+  data: {
+    greeting: '�拐�憟?,
+    children: [],
+    pendingReview: 0,
+    todayTasks: 0,
+    completedTasks: 0
+  },
+
+  onLoad() {
+    this.updateGreeting();
+    this.loadData();
+  },
+
+  updateGreeting() {
+    const hour = new Date().getHours();
+    let greeting = '雿惩末';
+    if (hour < 6) greeting = '憭𨀣楛鈭?;
+    else if (hour < 9) greeting = '�拐�憟?;
+    else if (hour < 12) greeting = '銝𠰴�憟?;
+    else if (hour < 14) greeting = '銝剖�憟?;
+    else if (hour < 18) greeting = '銝见�憟?;
+    else greeting = '�帋�憟?;
+    this.setData({ greeting });
+  },
+
+  async loadData() {
+    try {
+      // �㰘蝸摮拙��𡑒”�䔶遙�∠�霈?      // const children = await api.getChildren();
+      // this.setData({ children });
+    } catch (err) {
+      console.error(err);
+    }
+  },
+
+  onPublishTask() {
+    wx.navigateTo({ url: '/pages/task-create/task-create' });
+  }
+})
+```
+
+---
+
+## Task 10: 瘚贝�銝𡡞�霂?
+**Files:**
+- Create: `backend/src/test/java/com/xinzhiyijia/service/TaskServiceTest.java`
+- Create: `backend/src/test/java/com/xinzhiyijia/service/PointsServiceTest.java`
+
+### Step 1: TaskService�訫�瘚贝�
+
+```java
+package com.xinzhiyijia.service;
+
+import com.xinzhiyijia.entity.Task;
+import com.xinzhiyijia.mapper.TaskMapper;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import static org.junit.jupiter.api.Assertions.*;
+
+@SpringBootTest
+public class TaskServiceTest {
+
+    @Autowired
+    private TaskService taskService;
+
+    @Autowired
+    private TaskMapper taskMapper;
+
+    @Test
+    public void testCreateTask() {
+        Task task = new Task();
+        task.setFamilyId(1L);
+        task.setCreatorId(1L);
+        task.setChildId(1L);
+        task.setTitle("瘚贝�隞餃𦛚");
+        task.setPoints(5);
+        task.setCategory("摮虫�");
+        task.setDeadline(java.time.LocalDateTime.now().plusHours(2));
+
+        Task created = taskService.createTask(task);
+        assertNotNull(created.getId());
+        assertEquals("pending", created.getStatus());
+    }
+
+    @Test
+    public void testCompleteTaskWithoutReview() {
+        // �𥕦遣銝�銝芯���閬�恣�貊�隞餃𦛚
+        Task task = new Task();
+        task.setFamilyId(1L);
+        task.setCreatorId(1L);
+        task.setChildId(1L);
+        task.setTitle("�𣳇�摰⊥瓲隞餃𦛚");
+        task.setPoints(3);
+        task.setCategory("��暑");
+        task.setNeedReview(false);
+        task.setDeadline(java.time.LocalDateTime.now().plusHours(2));
+        taskMapper.insert(task);
+
+        // 摰峕�隞餃𦛚
+        Task completed = taskService.completeTask(task.getId(), null, null);
+        assertEquals("approved", completed.getStatus());
+    }
+
+    @Test
+    public void testCompleteTaskWithReview() {
+        // �𥕦遣銝�銝芷�閬�恣�貊�隞餃𦛚
+        Task task = new Task();
+        task.setFamilyId(1L);
+        task.setCreatorId(1L);
+        task.setChildId(1L);
+        task.setTitle("��閬�恣�訾遙�?);
+        task.setPoints(5);
+        task.setCategory("摮虫�");
+        task.setNeedReview(true);
+        task.setDeadline(java.time.LocalDateTime.now().plusHours(2));
+        taskMapper.insert(task);
+
+        // 摰峕�隞餃𦛚
+        Task completed = taskService.completeTask(task.getId(), "http://photo.jpg", null);
+        assertEquals("completed", completed.getStatus());
+        assertEquals("pending", completed.getReviewStatus());
+    }
+}
+```
+
+---
+
+## Task 11: 憿寧𤌍�臬𢆡銝𡡞�蝵?
+### Step 1: �嘥��𡝗㺭�桀�
+
+�決ySQL銝剜�銵䕘�
+```bash
+mysql -u root -p
+CREATE DATABASE xinzhiyijia CHARACTER SET utf8mb4;
+use xinzhiyijia;
+source backend/src/main/resources/sql/init.sql;
+source backend/src/main/resources/sql/seed.sql;
+```
+
+### Step 2: �臬𢆡�𡒊垢�滚𦛚
+
+```bash
+cd backend
+mvn clean package
+java -jar target/xinzhiyijia-backend-1.0.0.jar
+```
+
+### Step 3: �滨蔭撠讐�摨?
+1. �典凝靽∪�隡堒像�唳釣���蝔见�
+2. �瑕�AppID
+3. �滨蔭 `miniprogram/app.json` 銝剔�AppID
+4. �滨蔭 `miniprogram/app.js` 銝剔�baseUrl
+
+### Step 4: 瘚贝�API
+
+霈輸䔮 http://localhost:8080/api/swagger-ui.html �亦�API��﹝
+
+---
+
+## Self-Review
+
+### 1. Spec Coverage
+
+| ��瘙?| �臬炏閬�� | 隞餃𦛚蝻硋噡 |
+|------|----------|----------|
+| 隞餃𦛚�穃� | �?| Task 6, 7, 9 |
+| 隞餃𦛚摰峕� | �?| Task 6, 7 |
+| 隞餃𦛚摰⊥瓲 | �?| Task 6, 7 |
+| 蝘臬��瑕� | �?| Task 6 (PointsService) |
+| 蝘臬�瘚�偌 | �?| Task 5, 6 |
+| 憟硋�蝞∠� | �?| Task 7 |
+| �烐揢瘚�� | �?| Task 7 |
+| �冽�霈方� | �?| Task 3, 7 |
+| �峕芋撘誩��?| �?| Task 8, 9 |
+
+### 2. Placeholder Scan
+
+撌脖耨憭㵪�
+- PointsService銝剔��蹱��䲮瘜閙㺿銝箏�靘𧢲䲮瘜?- 瘛餃�鈭���渡��躰秤憭��
+- 銵亙�鈭��閬��摮埈挾撉諹�
+
+### 3. Type Consistency
+
+- ���釟D蝐餃�蝏煺�雿輻鍂Long
+- �園𡢿蝐餃�蝏煺�雿輻鍂LocalDateTime
+- �嗆���畾萎蝙�沒tring嚗�噶鈭擧�撅𤏪�
+
+---
+
+## �扯��厰★
+
+**霈∪�撌脣��𣂼僎靽嘥��?`docs/superpowers/plans/2026-03-31-敹�䰻�𠰴振MVP摰䂿緵霈∪�.md`**
+
+**銝斤��扯��孵�嚗?*
+
+**1. Subagent-Driven嚗�綫�琜�** �?瘥譍葵隞餃𦛚����祉�subagent�扯�嚗䔶遙�⊿𡢿餈𥡝�摰⊥䰻嚗�翰�蠘翮隞?
+**2. Inline Execution** �?�典��峥ession銝凋蝙�牠xecuting-plans���踝��寥��扯�撟嗉挽蝵格��亦�
+
+**�㗇𥋘�芰��孵�嚗?*
+
+- A. Subagent-Driven嚗�綫�琜�
+- B. Inline Execution
+- C. ����扯�嚗��靽嘥�霈∪�
+

+ 146 - 0
docs/superpowers/specs/2026-03-31-UI交互设计.md

@@ -0,0 +1,146 @@
+# 知行益家 - UI交互设计文档
+
+> **项目名称**:知行益家  
+> **文档版本**:V1.0  
+> **创建日期**:2026-03-31  
+> **设计风格**:双端差异(家长端简洁专业,孩子端活泼卡通)
+
+---
+
+## 一、设计风格
+
+| 特点 | 说明 |
+|------|------|
+| 家长端 | 简洁、数据化、偏教育感 |
+| 孩子端 | 卡通、活泼、明亮色彩 |
+| 底部导航 | 双端各4个Tab,功能不同 |
+| 交互反馈 | 积分动画、进度条、状态标记 |
+
+---
+
+## 二、底部导航栏
+
+| Tab | 家长端 | 孩子端 |
+|-----|--------|--------|
+| 1 | 📋 任务管理 | 📋 任务列表 |
+| 2 | 🎁 奖励管理 | 🎯 专注训练 |
+| 3 | 📊 数据统计 | 🏆 勋章墙 |
+| 4 | ⚙️ 设置 | 👤 我的 |
+
+---
+
+## 三、页面清单
+
+### 3.1 首页
+
+| 页面 | 端 | 核心内容 |
+|------|-----|----------|
+| 家长端首页 | 家长 | 问候语、今日概览、孩子状态、快捷发布 |
+| 孩子端首页 | 孩子 | 问候语、连续打卡、积分展示、心愿单、今日任务 |
+
+### 3.2 任务相关页面
+
+| 页面 | 端 | 核心内容 |
+|------|-----|----------|
+| 发布任务 | 家长 | 任务名称、指定孩子、截止时间、积分、分类、审核开关 |
+| 任务详情 | 孩子 | 任务信息、倒计时、备注、完成按钮 |
+| 完成弹窗 | 孩子 | 拍照上传、语音说明(可选) |
+| 积分动画 | 孩子 | 获得积分、提前奖励提示 |
+| 审核页面 | 家长 | 照片预览、AI建议、通过/确认/拒绝 |
+| 任务列表 | 双端 | 日期筛选、任务状态 |
+
+### 3.3 奖励/积分页面
+
+| 页面 | 端 | 核心内容 |
+|------|-----|----------|
+| 心愿单 | 孩子 | 积分余额、奖励列表、进度条、可兑换标记 |
+| 添加奖励 | 孩子 | 奖励名称、所需积分、分类 |
+| 兑换确认 | 孩子 | 确认扣分、兑换前后积分对比 |
+| 奖励管理 | 家长 | 待审批列表、奖励列表、添加/编辑 |
+| 积分流水 | 双端 | 积分变动记录、按日期分组 |
+
+### 3.4 训练/审核页面
+
+| 页面 | 端 | 核心内容 |
+|------|-----|----------|
+| 训练首页 | 孩子 | 今日训练次数、简单模式、番茄钟模式 |
+| 训练进行中 | 孩子 | 大字体倒计时、进度环、放弃按钮 |
+| 自评打分 | 孩子 | 1-5星选择、积分计算 |
+| 审核中心 | 家长 | 待审核任务列表、照片、AI建议、操作按钮 |
+
+### 3.5 个人中心/设置页面
+
+| 页面 | 端 | 核心内容 |
+|------|-----|----------|
+| 我的(家长) | 家长 | 家庭管理、切换模式、通知设置、数据导出 |
+| 我的(孩子) | 孩子 | 勋章、成长、训练记录、积分明细 |
+| 家庭管理 | 家长 | 成员列表、邀请码、添加成员/孩子 |
+| 全局设置 | 家长 | 安全设置、通知开关、主题、数据管理 |
+
+---
+
+## 四、关键交互设计
+
+### 4.1 任务完成流程
+
+```
+孩子点击"完成任务"
+  → 是否需审核?
+    → 否:显示积分动画 → 返回首页
+    → 是:弹出拍照窗口 → 上传照片 → 可选录音 → 提交
+      → 显示"待审核"状态
+```
+
+### 4.2 兑换流程
+
+```
+孩子点击"兑换"
+  → 确认弹窗(显示积分变化)
+    → 确认:扣分 → 状态待审批 → 通知家长
+      → 家长通过:奖励生效
+      → 家长拒绝:积分退还
+```
+
+### 4.3 训练流程
+
+```
+选择模式/时长 → 开始训练(倒计时界面)
+  → 完成:自评打分 → 获得积分
+  → 放弃(如家长设置不可退出):0分
+```
+
+---
+
+## 五、页面跳转关系
+
+### 家长端导航
+
+```
+首页 ←→ 任务管理 ←→ 发布任务
+  ↓         ↓
+奖励管理 ←→ 审核中心
+  ↓
+数据统计
+  ↓
+设置 ←→ 家庭管理
+       ←→ 通知设置
+       ←→ 切换模式
+```
+
+### 孩子端导航
+
+```
+首页 ←→ 任务详情 ←→ 完成弹窗
+  ↓         ↓
+专注训练 ←→ 训练进行中
+  ↓         ↓
+勋章墙   ←→ 自评打分
+  ↓
+我的 ←→ 积分明细
+     ←→ 兑换记录
+     ←→ 切换模式
+```
+
+---
+
+*本文档为UI交互设计参考,具体实现时可调整细节。*

+ 219 - 0
docs/superpowers/specs/2026-03-31-使用场景与运营模式-design.md

@@ -0,0 +1,219 @@
+# 知行益家 - 使用场景与运营模式设计文档
+
+> **项目名称**:知行益家  
+> **文档版本**:V1.0  
+> **创建日期**:2026-03-31  
+> **Brainstorming 主题**:功能点的使用场景及运营模式
+
+---
+
+## 一、产品定位
+
+| 维度 | 定位 |
+|------|------|
+| **产品类型** | 微信小程序(家庭教育任务管理工具) |
+| **核心价值** | 用积分奖励机制解决孩子拖延症,培养时间管理能力 |
+| **发展路径** | 先自用验证 → 再产品化推广 |
+| **目标用户** | 全家长群体(低门槛、易上手) |
+
+---
+
+## 二、使用场景设计
+
+### 2.1 核心使用节奏:每日高频
+
+一天打开 3-5 次,分为三个关键时间节点:
+
+```
+┌─────────────────────────────────────────────────────────────────┐
+│                        每日使用场景                              │
+├─────────────────────────────────────────────────────────────────┤
+│  【早晨 7:00-8:00】     【白天】           【晚间 19:00-21:00】   │
+│  ┌─────────────┐    ┌─────────────┐    ┌─────────────────────┐  │
+│  │ 家长确认任务 │    │ 孩子完成任务 │    │ 孩子查看积分/奖励   │  │
+│  │ 孩子了解目标 │ →  │ 拍照打卡   │ →  │ 家长审核/审批奖励   │  │
+│  │             │    │ 系统通知家长 │    │ 全家回顾今日完成    │  │
+│  └─────────────┘    └─────────────┘    └─────────────────────┘  │
+└─────────────────────────────────────────────────────────────────┘
+```
+
+### 2.2 各角色使用场景
+
+#### 家长端
+
+| 场景 | 时间 | 操作 | 目的 |
+|------|------|------|------|
+| 任务规划 | 早晨 | 查看/调整今日任务 | 确保任务安排合理 |
+| 过程监督 | 白天 | 接收完成通知 | 了解孩子进度 |
+| 审核确认 | 晚间 | 审核任务、审批兑换 | 把控任务质量 |
+| 数据查看 | 周末 | 查看周报、成长数据 | 了解整体趋势 |
+
+#### 孩子端
+
+| 场景 | 时间 | 操作 | 获得 |
+|------|------|------|------|
+| 开启一天 | 早晨 | 查看今日任务列表 | 明确目标感 |
+| 完成任务 | 白天 | 点击完成、拍照打卡 | 即时反馈、积分入账 |
+| 追求成就 | 全天 | 关注连续打卡天数 | 成就感、怕断签 |
+| 兑换奖励 | 晚间 | 申请兑换心愿 | 延迟满足、期待感 |
+
+### 2.3 关键体验节点
+
+| 节点 | 体验设计 |
+|------|----------|
+| 任务完成时 | 即时动画 + 积分飘入 + 语音鼓励 |
+| 连续打卡时 | 火苗图标 + 天数 + 里程碑提醒 |
+| 兑换奖励时 | 进度条到100% + 庆祝动画 |
+| 积分清零前 | 提前3天推送提醒通知 |
+
+---
+
+## 三、激励体系设计
+
+### 3.1 五维激励组合
+
+```
+                    ┌─────────────────┐
+                    │   成长激励      │
+                    │ (周报/数据可视化)│
+                    └────────┬────────┘
+                             │
+        ┌────────────────────┼────────────────────┐
+        ▼                    ▼                    ▼
+┌───────────────┐   ┌───────────────┐   ┌───────────────┐
+│   物质激励    │   │   精神激励    │   │   情感激励    │
+│ (积分换奖励)  │   │ (勋章/成就)   │   │ (家长认可)   │
+└───────────────┘   └───────────────┘   └───────────────┘
+                             │
+                    ┌────────┴────────┐
+                    │   即时反馈      │
+                    │ (语音/动画/播报) │
+                    └─────────────────┘
+```
+
+### 3.2 激励机制对照表
+
+| 激励类型 | 机制 | 针对需求 | 实现方式 |
+|----------|------|----------|----------|
+| **物质激励** | 积分换奖励 | 玩具、零食、零花钱 | 心愿单 + 审批兑换 |
+| **精神激励** | 成就勋章、连续打卡 | 荣誉感、怕断签 | 勋章墙 + 火苗天数 |
+| **即时反馈** | 语音播报、完成动画 | 每一步都被看见 | 完成时特效 + 语音 |
+| **情感激励** | 家长审核、互动消息 | 被认可、被关注 | 审核评论 + 消息通知 |
+| **成长激励** | 周报、数据可视化 | 看到自己的进步 | 成长周报 + 趋势图 |
+
+---
+
+## 四、商业模式设计
+
+### 4.1 混合定价模式
+
+| 模式 | 说明 | 适用功能 |
+|------|------|----------|
+| **订阅制** | 按月/年付费,持续服务 | AI审核、DAN测评、数据分析 |
+| **买断制** | 一次性购买,永久使用 | 主题包、模板包 |
+
+### 4.2 功能分层
+
+#### 免费层(MVP阶段)
+
+| 功能 | 说明 |
+|------|------|
+| 任务管理 | 创建、执行、完成、删除 |
+| 积分系统 | 获取、扣除、每月清零、流水 |
+| 奖励体系 | 心愿单、兑换、审批 |
+| 双模式切换 | 家长模式/孩子模式 |
+| 连续打卡 | 火苗天数、断签重置 |
+| 基本专注训练 | 倒计时、自评、积分 |
+
+#### 付费层(后续开放)
+
+| 功能 | 定价模式 | 价值点 |
+|------|----------|--------|
+| **AI审核** | 订阅制 | 照片自动审核,节省家长时间 |
+| **DAN测评解析** | 订阅/买断 | 个性化任务推荐,科学培养 |
+| **成长周报** | 订阅制 | 数据统计,了解孩子进步 |
+| **数据分析看板** | 订阅制 | 深度分析,优化任务策略 |
+| **个性化推荐** | 订阅制 | AI智能匹配适合的任务 |
+
+### 4.3 分层逻辑
+
+```
+┌─────────────────────────────────────────────────────────────────┐
+│                         功能分层逻辑                             │
+├─────────────────────────────────────────────────────────────────┤
+│                                                                 │
+│   免费层                     │   付费层                         │
+│   ───────────────────────────│────────────────────────────────  │
+│   • 基础记录功能             │   • AI智能服务                   │
+│   • 本地计算                 │   • 云端数据处理                 │
+│   • 通用功能                 │   • 个性化能力                   │
+│   • 无需人工服务             │   • 需持续运营成本               │
+│                                                                 │
+│   分层依据:功能深度 + 数据能力                                 │
+│                                                                 │
+└─────────────────────────────────────────────────────────────────┘
+```
+
+---
+
+## 五、产品发展路径
+
+### 5.1 阶段规划
+
+```
+【第一阶段:MVP验证】
+├── 目标:验证用户是否愿意每日使用
+├── 功能:免费核心功能(任务/积分/奖励/打卡/专注)
+├── 周期:4-6周
+└── 验证指标:日活、任务完成率、连续使用天数
+
+【第二阶段:付费探索】
+├── 目标:验证用户是否愿意付费
+├── 功能:开放AI审核、DAN测评
+├── 周期:2-3个月
+└── 验证指标:付费转化率、续费率
+
+【第三阶段:产品化推广】
+├── 目标:扩大用户规模
+├── 功能:完善数据分析、个性化推荐
+├── 周期:持续迭代
+└── 运营指标:用户增长、ARPU、留存率
+```
+
+### 5.2 关键验证点
+
+| 阶段 | 验证问题 | 验证方式 |
+|------|----------|----------|
+| MVP | 用户是否愿意每天打开3-5次? | 看自家使用数据 |
+| MVP | 连续打卡是否真的有效? | 观察孩子行为变化 |
+| 付费 | AI审核是否比手动审核更方便? | 对比审核时间 |
+| 付费 | DAN测评推荐的任务是否真的合适? | 收集反馈 |
+
+---
+
+## 六、风险与对策
+
+| 风险 | 影响 | 对策 |
+|------|------|------|
+| 使用疲劳 | 新鲜感过后不再使用 | 持续迭代内容、增加随机奖励 |
+| 积分通胀 | 积分贬值、奖励失去吸引力 | 每月清零机制、动态定价 |
+| AI审核不准 | 家长不信任、弃用 | 保留人工审核通道 |
+| 付费转化低 | 商业模式不可持续 | 先做好免费功能,建立口碑 |
+
+---
+
+## 七、共识清单
+
+| 维度 | 结论 |
+|------|------|
+| ✅ 产品定位 | 自用验证 → 产品化推广 |
+| ✅ 目标用户 | 全家长群体,低门槛易上手 |
+| ✅ 使用节奏 | 每日高频(早晨/白天/晚间三节点) |
+| ✅ 激励体系 | 物质+精神+即时反馈+情感+成长 五维组合 |
+| ✅ 商业模式 | 基础免费 + 高级订阅 + 功能买断 |
+| ✅ 分层逻辑 | 功能深度 + 数据能力 |
+| ✅ 发展路径 | 先MVP免费验证 → 再开放付费功能 |
+
+---
+
+*本文档为 Brainstorming 产出,记录使用场景与运营模式的共识。*

+ 264 - 0
docs/superpowers/specs/2026-03-31-功能模块设计-P0核心模块.md

@@ -0,0 +1,264 @@
+# 知行益家 - P0核心模块功能设计
+
+> **项目名称**:知行益家  
+> **文档版本**:V1.0  
+> **创建日期**:2026-03-31  
+> **设计阶段**:P0核心模块(任务管理、积分系统、奖励体系)
+
+---
+
+## 一、任务管理模块
+
+### 1.1 任务数据结构
+
+```
+【基础信息】
+├── id                    # 主键
+├── family_id             # 所属家庭
+├── child_id              # 执行孩子
+├── creator_id            # 创建家长
+├── title                 # 任务名称
+├── description           # 备注说明
+├── points                # 积分值(1-10)
+├── category              # 分类(学习/生活/运动/其他)
+└── priority              # 优先级(高/中/低)
+
+【时间信息】
+├── deadline              # 截止时间
+├── repeat_type           # 重复类型(不重复/每日/每周)
+├── estimated_duration    # 预计时长(按分类默认值)
+├── reminder_enabled      # 提醒开关
+└── reminder_time         # 提前提醒时间
+
+【状态信息】
+├── status                # 任务状态
+├── completed_at          # 完成时间
+├── completed_early       # 是否提前完成
+└── sort_order            # 排序号
+
+【审核信息】
+├── need_review           # 是否需审核
+├── review_photo_url      # 审核照片
+├── review_status         # 审核状态
+├── review_comment        # 审核备注
+└── reviewed_at           # 审核时间
+
+【时间戳】
+├── created_at            # 创建时间
+└── updated_at            # 更新时间
+```
+
+### 1.2 任务状态流转
+
+```
+       ┌──────────┐
+       │  pending │ ← 创建后
+       └────┬─────┘
+            │ 孩子点击完成
+            ▼
+   ┌────────┴────────┐
+   │                 │
+   ▼                 ▼
+┌──────────┐    ┌──────────────┐
+│ completed│    │pending_review│ ← 需审核
+└──────────┘    └──────┬───────┘
+                       │ 家长审核
+                       ▼
+               ┌───────┴───────┐
+               ▼               ▼
+          ┌────────┐      ┌─────────┐
+          │approved│      │rejected │ → 退回待完成
+          └────────┘      └─────────┘
+
+        截止时间过且未完成 → overdue
+```
+
+### 1.3 业务规则
+
+#### 提前完成判断(智能判断)
+
+| 分类 | 默认预计时长 | 提前阈值 |
+|------|--------------|----------|
+| 学习类 | 30分钟 | 20% |
+| 生活类 | 10分钟 | 30% |
+| 运动类 | 20分钟 | 25% |
+| 其他 | 15分钟 | 25% |
+
+#### 超时扣分规则(递增扣分)
+
+| 超时时长 | 扣分 | 说明 |
+|----------|------|------|
+| 10分钟内 | 0分 | 保护机制,不算迟到 |
+| 10-30分钟 | 扣1分 | 轻微超时 |
+| 30-60分钟 | 扣2分 | 中度超时 |
+| 60分钟+ | 扣3分 | 严重超时 |
+
+#### 保护机制
+
+- 10分钟内不算迟到
+- 每日最多扣5分(破产保护)
+- 6岁以下关闭扣分功能
+- 家长可手动调整扣分开关
+
+### 1.4 创建任务流程
+
+| 字段 | 状态 | 默认值 |
+|------|------|--------|
+| 任务名称 | ✅ 必填 | — |
+| 执行孩子 | ✅ 必填 | — |
+| 截止时间 | ✅ 必填 | 今日20:00 |
+| 积分值 | ✅ 必填 | 2分 |
+| 分类 | ✅ 必填 | 学习类 |
+| 预计时长 | 🔸 可选 | 按分类默认值 |
+| 备注说明 | 🔸 可选 | — |
+| 重复周期 | 🔸 可选 | 不重复 |
+| 是否需审核 | 🔸 可选 | 否 |
+
+### 1.5 完成任务流程
+
+```
+孩子点击"完成任务" 
+  → 是否需审核?
+    → 否:直接完成 → 显示积分 + 动画反馈
+    → 是:拍照上传 → 可选语音说明 → 提交 → 进入待审核
+```
+
+### 1.6 防重复点击
+
+- 按钮防抖(1秒内只响应一次)
+- 前端loading + 后端幂等性校验
+- 已完成任务灰显,不可点击
+
+---
+
+## 二、积分系统模块
+
+### 2.1 积分来源
+
+| 来源 | 积分 | 触发条件 |
+|------|------|----------|
+| 完成任务 | 任务设定值(1-10分) | 孩子完成任务 |
+| 提前完成奖励 | +1分 | 智能判断提前完成 |
+| 专注训练 | 1-5分 | 根据自评星数 |
+| 专注训练满分 | +1分 | 自评5星 |
+| 连续打卡奖励 | 额外积分 | 达成里程碑 |
+
+### 2.2 积分扣减
+
+| 场景 | 扣分 | 触发条件 |
+|------|------|----------|
+| 超时扣分 | 递增扣分(1/2/3分) | 超过10分钟保护期 |
+| 每日上限 | 最多扣5分 | 破产保护 |
+| 扣分开关 | 0分 | 6岁以下默认关闭 |
+
+### 2.3 每月清零规则
+
+| 项目 | 规则 |
+|------|------|
+| 清零时间 | 每月1日 00:00 |
+| 清零范围 | 当月积分余额 |
+| 保留数据 | 积分流水历史不删除 |
+| 清零通知 | 提前3天推送提醒 |
+| 未兑换奖励 | 积分退还给孩子 |
+
+### 2.4 积分计算示例
+
+#### 提前完成任务
+
+```
+输入: 任务积分=5分, 提前完成, 不超时
+计算: 5 + 1(提前奖励) = 6分
+输出: 获得6分
+```
+
+#### 超时任务
+
+```
+输入: 任务积分=5分, 超时45分钟, 今日已扣1分
+计算: 5 - 2(30-60分钟档) = 3分
+检查: 1 + 2 = 3 < 5分上限,通过
+输出: 获得3分,今日已扣3分
+```
+
+---
+
+## 三、奖励体系模块
+
+### 3.1 奖励分类
+
+| 分类 | 说明 | 示例 |
+|------|------|------|
+| 物质类 | 实物或金钱奖励 | 玩具、零食、零花钱 |
+| 精神类 | 体验或情感奖励 | 看动画片、去游乐场、和爸爸下棋 |
+| 特权类 | 免除任务或特别权利 | 今天不洗碗、自己安排周末 |
+| 自定义 | 家长自行定义 | — |
+
+### 3.2 奖励数据结构
+
+```
+├── id                    # 主键
+├── family_id             # 所属家庭
+├── child_id              # 孩子ID(或全员可用)
+├── name                  # 奖励名称
+├── description           # 奖励描述
+├── points_required       # 所需积分
+├── category              # 分类
+├── icon                  # 图标(可选)
+├── status                # 状态(可兑换/已下架)
+├── stock                 # 库存(可选)
+├── created_at            # 创建时间
+└── updated_at            # 更新时间
+```
+
+### 3.3 兑换流程
+
+```
+孩子查看心愿单 → 点击"兑换"
+  → 积分是否足够?
+    → 否:提示"积分不足",显示还需多少分
+    → 是:扣减积分 → 状态待审批 → 家长收到通知
+      → 家长审批
+        → 通过:奖励生效,孩子可领取
+        → 拒绝:积分全额退还,通知孩子原因
+```
+
+### 3.4 兑换规则
+
+| 规则 | 说明 |
+|------|------|
+| 积分不足 | 无法兑换,显示还需多少分 |
+| 待审批 | 扣分后状态为"待审批" |
+| 审核通过 | 奖励生效 |
+| 审核拒绝 | 积分全额退还 |
+| 库存限制 | 库存为0时不可兑换 |
+| 进度条 | 心愿单显示"已攒/所需"积分进度 |
+
+### 3.5 预设奖励模板
+
+| 分类 | 奖励名称 | 默认积分 | 说明 |
+|------|----------|----------|------|
+| 物质 | 小零食 | 10分 | 即时奖励 |
+| 物质 | 玩具盲盒 | 50分 | 周期奖励 |
+| 精神 | 看一集动画片 | 5分 | 即时奖励 |
+| 精神 | 去游乐园 | 200分 | 大目标 |
+| 特权 | 今天不洗碗 | 15分 | 即时奖励 |
+| 特权 | 晚睡30分钟 | 20分 | 周末可用 |
+| 精神 | 和爸爸下棋 | 0分 | 陪伴类 |
+
+---
+
+## 四、关键设计决策汇总
+
+| 决策点 | 选择 |
+|--------|------|
+| 提前完成判断 | 智能判断(初期分类默认,后期数据驱动) |
+| 超时扣分方式 | 递增扣分(10分钟内0分,10-30扣1分,30-60扣2分,60+扣3分) |
+| 每日扣分上限 | 5分 |
+| 扣分开关 | 6岁以下默认关闭 |
+| 积分清零 | 每月1日00:00 |
+| 奖励兑换 | 家长审批制,拒绝则退还积分 |
+| 奖励分类 | 物质/精神/特权/自定义 |
+
+---
+
+*本文档为P0核心模块功能设计,后续P1增强模块设计将另建文档。*

+ 290 - 0
docs/superpowers/specs/2026-03-31-功能模块设计-P1增强模块.md

@@ -0,0 +1,290 @@
+# 知行益家 - P1增强模块功能设计
+
+> **项目名称**:知行益家  
+> **文档版本**:V1.0  
+> **创建日期**:2026-03-31  
+> **设计阶段**:P1增强模块(连续打卡、专注训练、AI审核、DAN测评、勋章墙)
+
+---
+
+## 一、连续打卡模块
+
+### 1.1 核心规则
+
+| 规则 | 说明 |
+|------|------|
+| 统计范围 | 当日所有任务 |
+| 计算方式 | 所有任务按时完成才算"打卡成功" |
+| 断签重置 | 有一天未完成则连续天数归零 |
+| 无任务日 | 不计入连续(保持规则一致性) |
+| 任务取消 | 不影响连续 |
+| 家长暂停 | 暂停期间不计入 |
+| 补卡 | 不允许 |
+
+### 1.2 里程碑奖励
+
+| 里程碑 | 奖励积分 |
+|--------|----------|
+| 连续3天 | +5分 |
+| 连续7天 | +15分 |
+| 连续14天 | +30分 |
+| 连续30天 | +100分 |
+| 连续100天 | +500分 |
+
+### 1.3 数据结构
+
+```
+【连续打卡记录】
+├── id                    # 主键
+├── child_id              # 孩子ID
+├── current_streak        # 当前连续天数
+├── max_streak            # 历史最长连续天数
+├── last_check_in_date    # 最后打卡日期
+├── total_check_in_days   # 累计打卡天数
+└── updated_at            # 更新时间
+```
+
+### 1.4 展示设计
+
+```
+首页展示:
+🔥 已连续 X 天
+
+周视图:
+┌───┬───┬───┬───┬───┬───┬───┐
+│ 一 │ 二 │ 三 │ 四 │ 五 │ 六 │ 日 │
+├───┼───┼───┼───┼───┼───┼───┤
+│ ✅│ ✅│ ✅│ ✅│ ✅│ ✅│ ✅│
+└───┴───┴───┴───┴───┴───┴───┘
+
+里程碑进度条:
+下个里程碑:连续14天 (+30分)
+████████░░░░░░░░ 7/14
+```
+
+---
+
+## 二、专注训练模块
+
+### 2.1 训练模式
+
+| 模式 | 时长选项 | 说明 |
+|------|----------|------|
+| 简单模式 | 10/15/20分钟 | 一口气专注完成 |
+| 番茄钟模式 | 25分钟+5分钟休息 | 可连续多轮 |
+
+### 2.2 核心规则
+
+| 规则 | 说明 |
+|------|------|
+| 计时方式 | 手机倒计时,孩子自行控制 |
+| 退出限制 | 家长可设置不可退出 |
+| 自评打分 | 完成后1-5星自评 |
+| 积分规则 | 星数=积分(1-5分) |
+| 满分奖励 | 5星额外+1分(共6分) |
+| 每日上限 | 默认3次/天,家长可调整(1-10次) |
+
+### 2.3 流程设计
+
+```
+选择训练时长 → 开始训练(倒计时界面)
+  → 完成/放弃
+    → 完成:自评1-5星 → 获得积分
+    → 放弃(家长设置):0分
+```
+
+### 2.4 数据结构
+
+```
+【专注训练记录】
+├── id                    # 主键
+├── child_id              # 孩子ID
+├── duration              # 训练时长(分钟)
+├── mode                  # 模式(simple/pomodoro)
+├── stars                 # 自评星数(1-5)
+├── points                # 获得积分
+├── completed_at          # 完成时间
+└── created_at            # 创建时间
+```
+
+---
+
+## 三、AI审核模块
+
+### 3.1 审核流程
+
+```
+孩子完成任务 → 拍照上传 → 可选语音说明
+  → AI分析照片
+    → 通过(置信度>80%):自动发放积分
+    → 疑似通过(50-80%):家长一键确认
+    → 需人工(<50%):家长详细审核
+  → 家长可修改AI审核结果
+```
+
+### 3.2 三档结果逻辑
+
+| 档位 | 置信度 | 处理方式 |
+|------|--------|----------|
+| 通过 ✅ | >80% | 自动发放积分 |
+| 疑似通过 ❓ | 50-80% | 家长一键确认 |
+| 需人工 ⚠️ | <50% | 家长详细审核 |
+
+### 3.3 阈值配置
+
+| 设置项 | 选项 | 说明 |
+|--------|------|------|
+| 审核严格度 | 宽松/一般/严格 | 影响AI判断阈值 |
+| 自动通过 | 开/关 | 是否允许AI自动通过 |
+| 需人工范围 | 按分类/按任务 | 哪些任务必须人工审核 |
+
+### 3.4 数据结构
+
+```
+【审核记录】
+├── id                    # 主键
+├── task_id               # 关联任务
+├── child_id              # 孩子ID
+├── photo_url             # 照片地址
+├── voice_url             # 语音地址(可选)
+├── ai_result             # AI结果(pass/review/unknown)
+├── ai_confidence         # AI置信度(0-100)
+├── ai_suggestion         # AI建议说明
+├── parent_result         # 家长结果(pass/reject)
+├── parent_comment        # 家长备注
+├── review_type           # 审核类型(ai/manual)
+├── reviewed_at           # 审核时间
+└── created_at            # 创建时间
+```
+
+---
+
+## 四、DAN测评模块
+
+### 4.1 DAN等级体系
+
+| 等级 | 年龄段 | 发展特点 |
+|------|--------|----------|
+| A1 | 幼儿园小班(3-4岁) | 注意力短,游戏为主 |
+| A2 | 幼儿园中班(4-5岁) | 规则意识萌芽 |
+| A3 | 幼儿园大班(5-6岁) | 幼小衔接 |
+| B1 | 小学一年级 | 习惯养成期 |
+| B2 | 小学二年级 | 基本学习能力 |
+| B3 | 小学三年级 | 转折期 |
+| B4 | 小学四年级 | 自主学习 |
+| B5 | 小学五年级 | 学业压力增加 |
+| B6 | 小学六年级 | 小升初 |
+
+### 4.2 解析流程
+
+```
+拍照/上传报告 → 大模型解析图片 → 提取DAN等级
+  → 解析成功:匹配模板 → 生成推荐任务
+  → 解析失败:手动录入等级
+  → 家长可编辑模板 → 一键启用创建任务
+```
+
+### 4.3 各等级推荐任务
+
+| 等级 | 学习类 | 生活类 | 运动类 |
+|------|--------|--------|--------|
+| A1 | 5分钟绘本 | 穿衣服、收玩具 | 户外玩耍15分钟 |
+| A2 | 10分钟绘本、画画 | 自己吃饭、简单家务 | 跳绳10分钟 |
+| A3 | 拼音练习、简单计算 | 整理书包、洗碗 | 跑步10分钟 |
+| B1 | 写字练习、口算 | 打扫房间、叠衣服 | 跳绳20分钟 |
+| B2 | 乘法口诀、抄写 | 洗衣服、做简单饭 | 跑步15分钟 |
+| B3 | 英语练习、科学实验 | 独立完成作业 | 球类运动30分钟 |
+| B4 | 思维训练、小作文 | 自主复习 | 运动30分钟 |
+| B5 | 专题复习、错题整理 | 管理学习计划 | 运动40分钟 |
+| B6 | 综合复习、模拟测试 | 时间规划 | 运动40分钟 |
+
+### 4.4 数据结构
+
+```
+【DAN测评记录】
+├── id                    # 主键
+├── child_id              # 孩子ID
+├── report_url            # 报告图片地址
+├── dan_level             # DAN等级
+├── parse_status          # 解析状态(成功/失败/手动)
+├── details               # 详细信息(JSON)
+├── assessment_date       # 测评日期
+└── created_at            # 创建时间
+
+【任务模板】
+├── id                    # 主键
+├── dan_level             # 对应等级
+├── title                 # 任务名称
+├── points                # 建议积分
+├── category              # 分类
+├── estimated_duration    # 预计时长
+├── is_default            # 是否默认启用
+└── created_at            # 创建时间
+```
+
+---
+
+## 五、勋章墙模块
+
+### 5.1 勋章列表
+
+| 勋章名称 | 解锁条件 | 图标 |
+|----------|----------|------|
+| 🌟 初次尝试 | 完成第一个任务 | ⭐ |
+| 🔥 三日火焰 | 连续3天按时完成 | 🔥 |
+| 🏆 一周达人 | 连续7天按时完成 | 🏆 |
+| 📚 百题斩 | 完成100个任务 | 📖 |
+| 🎯 精准打击 | 连续10个任务提前完成 | 🎯 |
+| 💪 坚持不懈 | 连续30天按时完成 | 💪 |
+| 🧠 专注大师 | 完成50次专注训练 | 🧠 |
+| ⭐ 积分富豪 | 累计获得1000积分 | 💰 |
+| 🏅 全勤宝宝 | 一周所有任务全部完成 | 🏅 |
+| 🎖️ 完美主义 | 单月任务完成率100% | 🎖️ |
+
+### 5.2 条件类型
+
+| 条件类型 | 说明 | 示例 |
+|----------|------|------|
+| `task_complete` | 完成任务数 | 完成100个任务 |
+| `streak_days` | 连续打卡天数 | 连续7天 |
+| `early_complete` | 提前完成数 | 连续10个提前完成 |
+| `focus_training` | 专注训练次数 | 完成50次 |
+| `total_points` | 累计积分 | 获得1000积分 |
+| `weekly_perfect` | 周全勤 | 一周全部完成 |
+
+### 5.3 数据结构
+
+```
+【勋章定义】
+├── id                    # 主键
+├── name                  # 勋章名称
+├── description           # 描述
+├── icon                  # 图标
+├── condition_type        # 条件类型
+├── condition_value       # 条件数值
+└── sort_order            # 排序
+
+【用户勋章】
+├── id                    # 主键
+├── child_id              # 孩子ID
+├── badge_id              # 勋章ID
+├── unlocked_at           # 解锁时间
+└── created_at            # 创建时间
+```
+
+---
+
+## 六、设计决策汇总
+
+| 决策点 | 选择 |
+|--------|------|
+| 连续打卡-无任务日 | 不计入连续 |
+| 连续打卡-补卡 | 不允许 |
+| 专注训练-满分奖励 | 5星额外+1分 |
+| AI审核-档位 | 三档(通过/疑似通过/需人工) |
+| DAN测评-解析失败 | 降级为手动录入 |
+| 勋章数量 | 10+个基础勋章 |
+
+---
+
+*本文档为P1增强模块功能设计。*

+ 37 - 0
miniprogram/app.js

@@ -0,0 +1,37 @@
+// app.js
+App({
+  globalData: {
+    baseUrl: 'http://localhost:8080/api',
+    token: '',
+    userInfo: null,
+    role: ''  // parent 或 child
+  },
+
+  onLaunch() {
+    // 检查登录状态
+    const token = wx.getStorageSync('token');
+    const role = wx.getStorageSync('role');
+    const userInfo = wx.getStorageSync('userInfo');
+    
+    if (token) {
+      this.globalData.token = token;
+      this.globalData.role = role;
+      this.globalData.userInfo = userInfo;
+    }
+  },
+
+  // 检查是否登录
+  checkLogin() {
+    return !!this.globalData.token;
+  },
+
+  // 退出登录
+  logout() {
+    this.globalData.token = '';
+    this.globalData.role = '';
+    this.globalData.userInfo = null;
+    wx.removeStorageSync('token');
+    wx.removeStorageSync('role');
+    wx.removeStorageSync('userInfo');
+  }
+})

+ 54 - 0
miniprogram/app.json

@@ -0,0 +1,54 @@
+{
+  "pages": [
+    "pages/index/index",
+    "pages/login/login",
+    "pages/parent-home/parent-home",
+    "pages/child-home/child-home",
+    "pages/task-list/task-list",
+    "pages/task-detail/task-detail",
+    "pages/task-create/task-create",
+    "pages/reward-list/reward-list",
+    "pages/points-log/points-log",
+    "pages/settings/settings",
+    "pages/child-edit/child-edit",
+    "pages/family-manage/family-manage",
+    "pages/achievements/achievements",
+    "pages/dan-assessment/dan-assessment",
+    "pages/help/help",
+    "pages/feedback/feedback"
+  ],
+  "window": {
+    "backgroundTextStyle": "light",
+    "navigationBarBackgroundColor": "#FF6B6B",
+    "navigationBarTitleText": "知行益家",
+    "navigationBarTextStyle": "white"
+  },
+  "tabBar": {
+    "color": "#999",
+    "selectedColor": "#FF6B6B",
+    "backgroundColor": "#fff",
+    "borderStyle": "black",
+    "list": [
+      {
+        "pagePath": "pages/parent-home/parent-home",
+        "text": "任务",
+        "iconPath": "images/tab-task.svg",
+        "selectedIconPath": "images/tab-task-active.svg"
+      },
+      {
+        "pagePath": "pages/reward-list/reward-list",
+        "text": "奖励",
+        "iconPath": "images/tab-reward.svg",
+        "selectedIconPath": "images/tab-reward-active.svg"
+      },
+      {
+        "pagePath": "pages/settings/settings",
+        "text": "我的",
+        "iconPath": "images/tab-me.svg",
+        "selectedIconPath": "images/tab-me-active.svg"
+      }
+    ]
+  },
+  "usingComponents": {},
+  "sitemapLocation": "sitemap.json"
+}

+ 45 - 0
miniprogram/app.wxss

@@ -0,0 +1,45 @@
+/* app.wxss */
+page {
+  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
+  font-size: 28rpx;
+  color: #333;
+  background-color: #f5f5f5;
+}
+
+.container {
+  box-sizing: border-box;
+}
+
+/* 通用按钮样式 */
+.btn-primary {
+  background: #FF6B6B;
+  color: #fff;
+}
+
+.btn-primary::after {
+  border: none;
+}
+
+/* 通用卡片样式 */
+.card {
+  background: #fff;
+  border-radius: 20rpx;
+  padding: 30rpx;
+  margin-bottom: 20rpx;
+}
+
+/* flex布局工具 */
+.flex {
+  display: flex;
+}
+
+.flex-center {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+
+.flex-between {
+  display: flex;
+  justify-content: space-between;
+}

+ 5 - 0
miniprogram/images/default-avatar.svg

@@ -0,0 +1,5 @@
+<svg width="120" height="120" viewBox="0 0 120 120" xmlns="http://www.w3.org/2000/svg">
+  <circle cx="60" cy="60" r="60" fill="#f5f5f5"/>
+  <circle cx="60" cy="50" r="25" fill="#e0e0e0"/>
+  <ellipse cx="60" cy="100" rx="40" ry="30" fill="#e0e0e0"/>
+</svg>

+ 8 - 0
miniprogram/images/empty-log.svg

@@ -0,0 +1,8 @@
+<svg width="200" height="200" viewBox="0 0 200 200" xmlns="http://www.w3.org/2000/svg">
+  <rect width="200" height="200" fill="#f5f5f5"/>
+  <rect x="50" y="50" width="100" height="100" rx="10" fill="#e0e0e0"/>
+  <line x1="70" y1="80" x2="130" y2="80" stroke="#ccc" stroke-width="2"/>
+  <line x1="70" y1="100" x2="130" y2="100" stroke="#ccc" stroke-width="2"/>
+  <line x1="70" y1="120" x2="130" y2="120" stroke="#ccc" stroke-width="2"/>
+  <text x="100" y="180" text-anchor="middle" font-family="Arial" font-size="14" fill="#999">暂无记录</text>
+</svg>

+ 6 - 0
miniprogram/images/empty-reward.svg

@@ -0,0 +1,6 @@
+<svg width="200" height="200" viewBox="0 0 200 200" xmlns="http://www.w3.org/2000/svg">
+  <rect width="200" height="200" fill="#f5f5f5"/>
+  <rect x="50" y="50" width="100" height="80" rx="10" fill="#e0e0e0"/>
+  <rect x="70" y="130" width="60" height="20" rx="10" fill="#e0e0e0"/>
+  <text x="100" y="180" text-anchor="middle" font-family="Arial" font-size="14" fill="#999">暂无奖励</text>
+</svg>

+ 6 - 0
miniprogram/images/empty-task.svg

@@ -0,0 +1,6 @@
+<svg width="200" height="200" viewBox="0 0 200 200" xmlns="http://www.w3.org/2000/svg">
+  <rect width="200" height="200" fill="#f5f5f5"/>
+  <circle cx="100" cy="80" r="40" fill="#e0e0e0"/>
+  <rect x="60" y="130" width="80" height="20" rx="10" fill="#e0e0e0"/>
+  <text x="100" y="180" text-anchor="middle" font-family="Arial" font-size="14" fill="#999">暂无任务</text>
+</svg>

+ 5 - 0
miniprogram/images/tab-me-active.svg

@@ -0,0 +1,5 @@
+<svg width="81" height="81" viewBox="0 0 81 81" xmlns="http://www.w3.org/2000/svg">
+  <rect width="81" height="81" fill="none"/>
+  <circle cx="40" cy="30" r="15" fill="#FF6B6B"/>
+  <ellipse cx="40" cy="60" rx="25" ry="15" fill="#FF6B6B"/>
+</svg>

+ 5 - 0
miniprogram/images/tab-me.svg

@@ -0,0 +1,5 @@
+<svg width="81" height="81" viewBox="0 0 81 81" xmlns="http://www.w3.org/2000/svg">
+  <rect width="81" height="81" fill="none"/>
+  <circle cx="40" cy="30" r="15" fill="#999"/>
+  <ellipse cx="40" cy="60" rx="25" ry="15" fill="#999"/>
+</svg>

+ 7 - 0
miniprogram/images/tab-reward-active.svg

@@ -0,0 +1,7 @@
+<svg width="81" height="81" viewBox="0 0 81 81" xmlns="http://www.w3.org/2000/svg">
+  <rect width="81" height="81" fill="none"/>
+  <rect x="20" y="30" width="41" height="31" rx="5" fill="#FF6B6B"/>
+  <rect x="30" y="20" width="21" height="15" rx="3" fill="#FF6B6B"/>
+  <line x1="40" y1="45" x2="40" y2="55" stroke="#fff" stroke-width="3"/>
+  <line x1="30" y1="50" x2="50" y2="50" stroke="#fff" stroke-width="3"/>
+</svg>

+ 7 - 0
miniprogram/images/tab-reward.svg

@@ -0,0 +1,7 @@
+<svg width="81" height="81" viewBox="0 0 81 81" xmlns="http://www.w3.org/2000/svg">
+  <rect width="81" height="81" fill="none"/>
+  <rect x="20" y="30" width="41" height="31" rx="5" fill="#999"/>
+  <rect x="30" y="20" width="21" height="15" rx="3" fill="#999"/>
+  <line x1="40" y1="45" x2="40" y2="55" stroke="#fff" stroke-width="3"/>
+  <line x1="30" y1="50" x2="50" y2="50" stroke="#fff" stroke-width="3"/>
+</svg>

+ 6 - 0
miniprogram/images/tab-task-active.svg

@@ -0,0 +1,6 @@
+<svg width="81" height="81" viewBox="0 0 81 81" xmlns="http://www.w3.org/2000/svg">
+  <rect width="81" height="81" fill="none"/>
+  <rect x="10" y="20" width="61" height="41" rx="5" fill="#FF6B6B"/>
+  <line x1="20" y1="35" x2="61" y2="35" stroke="#fff" stroke-width="3"/>
+  <line x1="20" y1="50" x2="50" y2="50" stroke="#fff" stroke-width="3"/>
+</svg>

+ 6 - 0
miniprogram/images/tab-task.svg

@@ -0,0 +1,6 @@
+<svg width="81" height="81" viewBox="0 0 81 81" xmlns="http://www.w3.org/2000/svg">
+  <rect width="81" height="81" fill="none"/>
+  <rect x="10" y="20" width="61" height="41" rx="5" fill="#999"/>
+  <line x1="20" y1="35" x2="61" y2="35" stroke="#fff" stroke-width="3"/>
+  <line x1="20" y1="50" x2="50" y2="50" stroke="#fff" stroke-width="3"/>
+</svg>

+ 54 - 0
miniprogram/pages/achievements/achievements.js

@@ -0,0 +1,54 @@
+// pages/achievements/achievements.js
+Page({
+  data: {
+    childId: 0,
+    childName: '',
+    loading: false,
+    achievements: [
+      { id: 1, name: '初次尝试', description: '完成第一个任务', icon: '⭐', unlocked: true, unlockedAt: '2026-03-25' },
+      { id: 2, name: '三日火焰', description: '连续3天按时完成', icon: '🔥', unlocked: true, unlockedAt: '2026-03-28' },
+      { id: 3, name: '一周达人', description: '连续7天按时完成', icon: '🏆', unlocked: false, condition: '连续打卡4天' },
+      { id: 4, name: '百题斩', description: '完成100个任务', icon: '📖', unlocked: false, condition: '已完成15个任务' },
+      { id: 5, name: '精准打击', description: '连续10个任务提前完成', icon: '🎯', unlocked: false, condition: '已提前完成3个任务' },
+      { id: 6, name: '坚持不懈', description: '连续30天按时完成', icon: '💪', unlocked: false, condition: '连续打卡4天' },
+      { id: 7, name: '专注大师', description: '完成50次专注训练', icon: '🧠', unlocked: false, condition: '已完成0次专注训练' },
+      { id: 8, name: '积分富豪', description: '累计获得1000积分', icon: '💰', unlocked: false, condition: '已获得120积分' },
+      { id: 9, name: '全勤宝宝', description: '一周所有任务全部完成', icon: '🏅', unlocked: false, condition: '本周完成3/5个任务' },
+      { id: 10, name: '完美主义', description: '单月任务完成率100%', icon: '🎖️', unlocked: false, condition: '本月完成率60%' }
+    ],
+    stats: {
+      totalTasks: 15,
+      completedTasks: 12,
+      totalPoints: 120,
+      streakDays: 4,
+      achievementsUnlocked: 2
+    }
+  },
+
+  onLoad(options) {
+    const childId = options.childId || 0;
+    const childName = options.childName || '';
+    this.setData({ childId, childName });
+    wx.setNavigationBarTitle({ title: childName ? `${childName}的成就` : '成就徽章' });
+    this.loadAchievements();
+  },
+
+  loadAchievements() {
+    this.setData({ loading: true });
+    
+    setTimeout(() => {
+      this.setData({ loading: false });
+    }, 500);
+  },
+
+  onAchievementTap(e) {
+    const achievementId = e.currentTarget.dataset.id;
+    const achievement = this.data.achievements.find(a => a.id === achievementId);
+    
+    wx.showModal({
+      title: achievement.name,
+      content: `${achievement.description}\n\n${achievement.unlocked ? `解锁时间:${achievement.unlockedAt}` : `进度:${achievement.condition}`}`,
+      showCancel: false
+    });
+  }
+});

+ 4 - 0
miniprogram/pages/achievements/achievements.json

@@ -0,0 +1,4 @@
+{
+  "navigationBarTitleText": "成就徽章",
+  "enablePullDownRefresh": false
+}

+ 69 - 0
miniprogram/pages/achievements/achievements.wxml

@@ -0,0 +1,69 @@
+<!--pages/achievements/achievements.wxml-->
+<view class="container">
+  <view class="loading" wx:if="{{loading}}">
+    <text>加载中...</text>
+  </view>
+
+  <view class="achievements-content" wx:else>
+    <!-- 统计信息 -->
+    <view class="stats-card">
+      <view class="stats-grid">
+        <view class="stat-item">
+          <text class="stat-value">{{stats.totalTasks}}</text>
+          <text class="stat-label">总任务</text>
+        </view>
+        <view class="stat-item">
+          <text class="stat-value">{{stats.completedTasks}}</text>
+          <text class="stat-label">已完成</text>
+        </view>
+        <view class="stat-item">
+          <text class="stat-value">{{stats.totalPoints}}</text>
+          <text class="stat-label">总积分</text>
+        </view>
+        <view class="stat-item">
+          <text class="stat-value">{{stats.streakDays}}</text>
+          <text class="stat-label">连续天数</text>
+        </view>
+        <view class="stat-item">
+          <text class="stat-value">{{stats.achievementsUnlocked}}</text>
+          <text class="stat-label">已解锁</text>
+        </view>
+      </view>
+    </view>
+
+    <!-- 成就列表 -->
+    <view class="achievements-list">
+      <view class="list-title">成就徽章</view>
+      
+      <view class="achievement-grid">
+        <view 
+          class="achievement-item {{item.unlocked ? 'unlocked' : 'locked'}}"
+          wx:for="{{achievements}}"
+          wx:key="id"
+          data-id="{{item.id}}"
+          bindtap="onAchievementTap"
+        >
+          <view class="achievement-icon">{{item.icon}}</view>
+          <view class="achievement-info">
+            <text class="achievement-name">{{item.name}}</text>
+            <text class="achievement-desc">{{item.description}}</text>
+          </view>
+          <view class="achievement-status">
+            <text class="status-text">{{item.unlocked ? '已解锁' : '未解锁'}}</text>
+          </view>
+        </view>
+      </view>
+    </view>
+
+    <!-- 提示信息 -->
+    <view class="tips-card">
+      <text class="tips-title">解锁技巧</text>
+      <view class="tips-content">
+        <text class="tip-item">✅ 每天按时完成任务可以获得连续打卡</text>
+        <text class="tip-item">✅ 提前完成任务可以解锁精准打击徽章</text>
+        <text class="tip-item">✅ 完成专注训练可以解锁专注大师徽章</text>
+        <text class="tip-item">✅ 积攒积分可以解锁积分富豪徽章</text>
+      </view>
+    </view>
+  </view>
+</view>

+ 152 - 0
miniprogram/pages/achievements/achievements.wxss

@@ -0,0 +1,152 @@
+/* pages/achievements/achievements.wxss */
+.container {
+  padding: 20rpx;
+  background-color: #f5f5f5;
+  min-height: 100vh;
+}
+
+.loading {
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  height: 400rpx;
+  color: #999;
+  font-size: 28rpx;
+}
+
+.stats-card {
+  background-color: #fff;
+  border-radius: 16rpx;
+  padding: 30rpx;
+  margin-bottom: 20rpx;
+  box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.05);
+}
+
+.stats-grid {
+  display: flex;
+  flex-wrap: wrap;
+  justify-content: space-around;
+}
+
+.stat-item {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  width: 30%;
+  margin-bottom: 20rpx;
+}
+
+.stat-value {
+  font-size: 48rpx;
+  font-weight: bold;
+  color: #FF6B6B;
+  margin-bottom: 10rpx;
+}
+
+.stat-label {
+  font-size: 24rpx;
+  color: #999;
+}
+
+.achievements-list {
+  background-color: #fff;
+  border-radius: 16rpx;
+  padding: 30rpx;
+  margin-bottom: 20rpx;
+  box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.05);
+}
+
+.list-title {
+  font-size: 32rpx;
+  font-weight: bold;
+  color: #333;
+  margin-bottom: 30rpx;
+}
+
+.achievement-grid {
+  display: flex;
+  flex-direction: column;
+}
+
+.achievement-item {
+  display: flex;
+  align-items: center;
+  padding: 20rpx;
+  border-radius: 12rpx;
+  margin-bottom: 20rpx;
+  background-color: #f9f9f9;
+}
+
+.achievement-item.unlocked {
+  background-color: #fff8e1;
+  border: 2rpx solid #FFD700;
+}
+
+.achievement-item.locked {
+  opacity: 0.7;
+}
+
+.achievement-icon {
+  font-size: 60rpx;
+  margin-right: 20rpx;
+}
+
+.achievement-info {
+  flex: 1;
+}
+
+.achievement-name {
+  font-size: 30rpx;
+  font-weight: bold;
+  color: #333;
+  display: block;
+  margin-bottom: 5rpx;
+}
+
+.achievement-desc {
+  font-size: 24rpx;
+  color: #666;
+}
+
+.achievement-status {
+  margin-left: 20rpx;
+}
+
+.status-text {
+  font-size: 24rpx;
+  color: #999;
+  padding: 4rpx 12rpx;
+  border-radius: 12rpx;
+  background-color: #f0f0f0;
+}
+
+.unlocked .status-text {
+  background-color: #4CAF50;
+  color: #fff;
+}
+
+.tips-card {
+  background-color: #fff;
+  border-radius: 16rpx;
+  padding: 30rpx;
+  box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.05);
+}
+
+.tips-title {
+  font-size: 32rpx;
+  font-weight: bold;
+  color: #333;
+  margin-bottom: 20rpx;
+  display: block;
+}
+
+.tips-content {
+  display: flex;
+  flex-direction: column;
+}
+
+.tip-item {
+  font-size: 26rpx;
+  color: #666;
+  margin-bottom: 10rpx;
+}

+ 110 - 0
miniprogram/pages/child-edit/child-edit.js

@@ -0,0 +1,110 @@
+// pages/child-edit/child-edit.js
+Page({
+  data: {
+    childId: 0,
+    child: null,
+    loading: false,
+    danLevels: [
+      { value: 'A1', label: 'A1 - 幼儿园小班' },
+      { value: 'A2', label: 'A2 - 幼儿园中班' },
+      { value: 'A3', label: 'A3 - 幼儿园大班' },
+      { value: 'B1', label: 'B1 - 小学一年级' },
+      { value: 'B2', label: 'B2 - 小学二年级' },
+      { value: 'B3', label: 'B3 - 小学三年级' },
+      { value: 'B4', label: 'B4 - 小学四年级' },
+      { value: 'B5', label: 'B5 - 小学五年级' },
+      { value: 'B6', label: 'B6 - 小学六年级' }
+    ],
+    themes: [
+      { value: 'default', label: '默认主题' },
+      { value: 'space', label: '太空主题' },
+      { value: 'ocean', label: '海洋主题' },
+      { value: 'forest', label: '森林主题' }
+    ]
+  },
+
+  onLoad(options) {
+    const childId = options.id || 0;
+    this.setData({ childId });
+    this.loadChildInfo();
+  },
+
+  loadChildInfo() {
+    this.setData({ loading: true });
+    
+    // 模拟数据
+    const mockChild = {
+      id: this.data.childId,
+      name: '小明',
+      age: 8,
+      grade: '小学二年级',
+      danLevel: 'B2',
+      theme: 'default',
+      penaltyEnabled: true
+    };
+
+    setTimeout(() => {
+      this.setData({
+        child: mockChild,
+        loading: false
+      });
+    }, 500);
+  },
+
+  onNameInput(e) {
+    this.setData({ 'child.name': e.detail.value });
+  },
+
+  onAgeInput(e) {
+    this.setData({ 'child.age': parseInt(e.detail.value) || 0 });
+  },
+
+  onGradeInput(e) {
+    this.setData({ 'child.grade': e.detail.value });
+  },
+
+  onDanLevelChange(e) {
+    this.setData({ 'child.danLevel': e.detail.value });
+  },
+
+  onThemeChange(e) {
+    this.setData({ 'child.theme': e.detail.value });
+  },
+
+  onPenaltyEnabledChange(e) {
+    this.setData({ 'child.penaltyEnabled': e.detail.value });
+  },
+
+  onSave() {
+    if (!this.data.child.name.trim()) {
+      wx.showToast({ title: '请输入孩子姓名', icon: 'none' });
+      return;
+    }
+
+    wx.showModal({
+      title: '确认保存',
+      content: '确定要保存孩子信息吗?',
+      success: (res) => {
+        if (res.confirm) {
+          this.doSave();
+        }
+      }
+    });
+  },
+
+  doSave() {
+    wx.showLoading({ title: '保存中...' });
+    
+    setTimeout(() => {
+      wx.hideLoading();
+      wx.showToast({
+        title: '保存成功',
+        icon: 'success'
+      });
+      
+      setTimeout(() => {
+        wx.navigateBack();
+      }, 1500);
+    }, 1000);
+  }
+});

+ 4 - 0
miniprogram/pages/child-edit/child-edit.json

@@ -0,0 +1,4 @@
+{
+  "navigationBarTitleText": "编辑孩子信息",
+  "enablePullDownRefresh": false
+}

+ 75 - 0
miniprogram/pages/child-edit/child-edit.wxml

@@ -0,0 +1,75 @@
+<!--pages/child-edit/child-edit.wxml-->
+<view class="container">
+  <view class="loading" wx:if="{{loading}}">
+    <text>加载中...</text>
+  </view>
+
+  <view class="edit-form" wx:else>
+    <view class="form-section">
+      <text class="section-title">基本信息</text>
+      
+      <view class="form-item">
+        <text class="form-label">姓名 <text class="required">*</text></text>
+        <input 
+          class="form-input"
+          placeholder="请输入孩子姓名"
+          value="{{child.name}}"
+          bindinput="onNameInput"
+        />
+      </view>
+
+      <view class="form-item">
+        <text class="form-label">年龄 <text class="required">*</text></text>
+        <input 
+          class="form-input"
+          type="number"
+          placeholder="请输入年龄"
+          value="{{child.age}}"
+          bindinput="onAgeInput"
+        />
+      </view>
+
+      <view class="form-item">
+        <text class="form-label">年级</text>
+        <input 
+          class="form-input"
+          placeholder="请输入年级(如:小学二年级)"
+          value="{{child.grade}}"
+          bindinput="onGradeInput"
+        />
+      </view>
+    </view>
+
+    <view class="form-section">
+      <text class="section-title">DAN测评等级</text>
+      <view class="form-item">
+        <text class="form-label">当前等级</text>
+        <picker mode="selector" range="{{danLevels}}" range-key="label" value="{{child.danLevel}}" bindchange="onDanLevelChange">
+          <view class="picker-value">{{child.danLevel}}</view>
+        </picker>
+      </view>
+      <text class="form-tip">DAN测评等级影响推荐任务类型和难度</text>
+    </view>
+
+    <view class="form-section">
+      <text class="section-title">个性化设置</text>
+      
+      <view class="form-item">
+        <text class="form-label">主题</text>
+        <picker mode="selector" range="{{themes}}" range-key="label" value="{{child.theme}}" bindchange="onThemeChange">
+          <view class="picker-value">{{child.theme}}</view>
+        </picker>
+      </view>
+
+      <view class="form-item">
+        <text class="form-label">扣分机制</text>
+        <switch checked="{{child.penaltyEnabled}}" bindchange="onPenaltyEnabledChange" color="#FF6B6B"/>
+      </view>
+      <text class="form-tip">6岁以下建议关闭扣分机制</text>
+    </view>
+
+    <view class="button-group">
+      <button class="save-btn" bindtap="onSave">保存信息</button>
+    </view>
+  </view>
+</view>

+ 92 - 0
miniprogram/pages/child-edit/child-edit.wxss

@@ -0,0 +1,92 @@
+/* pages/child-edit/child-edit.wxss */
+.container {
+  padding: 20rpx;
+  background-color: #f5f5f5;
+  min-height: 100vh;
+}
+
+.loading {
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  height: 400rpx;
+  color: #999;
+  font-size: 28rpx;
+}
+
+.form-section {
+  background-color: #fff;
+  border-radius: 16rpx;
+  padding: 30rpx;
+  margin-bottom: 20rpx;
+  box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.05);
+}
+
+.section-title {
+  font-size: 32rpx;
+  font-weight: bold;
+  color: #333;
+  display: block;
+  margin-bottom: 30rpx;
+}
+
+.form-item {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  padding: 20rpx 0;
+  border-bottom: 1rpx solid #f0f0f0;
+}
+
+.form-item:last-child {
+  border-bottom: none;
+}
+
+.form-label {
+  font-size: 30rpx;
+  color: #333;
+}
+
+.required {
+  color: #F44336;
+  margin-left: 10rpx;
+}
+
+.form-input {
+  flex: 1;
+  text-align: right;
+  font-size: 30rpx;
+  color: #666;
+  padding: 10rpx;
+}
+
+.picker-value {
+  font-size: 30rpx;
+  color: #666;
+  text-align: right;
+}
+
+.form-tip {
+  font-size: 24rpx;
+  color: #999;
+  display: block;
+  margin-top: 10rpx;
+}
+
+.button-group {
+  margin-top: 40rpx;
+  padding: 0 20rpx;
+}
+
+.save-btn {
+  background-color: #FF6B6B;
+  color: #fff;
+  border-radius: 50rpx;
+  padding: 24rpx;
+  font-size: 32rpx;
+  font-weight: bold;
+}
+
+.save-btn::after {
+  border: none;
+}

+ 33 - 0
miniprogram/pages/child-home/child-home.js

@@ -0,0 +1,33 @@
+// pages/child-home/child-home.js
+Page({
+  data: {
+    childName: '小明',
+    streak: 5,
+    points: 120,
+    todayTasks: [
+      { id: 1, title: '写作业', points: 2, deadline: '20:00', completed: false },
+      { id: 2, title: '练琴', points: 3, deadline: '19:00', completed: false },
+      { id: 3, title: '阅读20分钟', points: 2, deadline: '21:00', completed: true }
+    ],
+    currentWish: {
+      name: '乐高星球大战',
+      current: 320,
+      total: 500
+    }
+  },
+
+  onShow() {
+    this.loadData();
+  },
+
+  loadData() {
+    // TODO: 从后端加载数据
+  },
+
+  onTaskTap(e) {
+    const taskId = e.currentTarget.dataset.id;
+    wx.navigateTo({ 
+      url: `/pages/task-detail/task-detail?id=${taskId}` 
+    });
+  }
+})

+ 5 - 0
miniprogram/pages/child-home/child-home.json

@@ -0,0 +1,5 @@
+{
+  "navigationBarTitleText": "知行益家",
+  "navigationBarBackgroundColor": "#FFE66D",
+  "navigationBarTextStyle": "black"
+}

+ 49 - 0
miniprogram/pages/child-home/child-home.wxml

@@ -0,0 +1,49 @@
+<!--pages/child-home/child-home.wxml-->
+<view class="container">
+  <view class="greeting">
+    <text class="greeting-text">你好,{{childName}}!</text>
+  </view>
+
+  <view class="streak-card">
+    <view class="streak-info">
+      <text class="streak-fire">🔥</text>
+      <text class="streak-text">已连续 {{streak}} 天</text>
+    </view>
+    <view class="progress-bar">
+      <view class="progress-fill" style="width: {{streak/14*100}}%"></view>
+    </view>
+    <text class="streak-tip">下个里程碑:连续14天 (+30分)</text>
+  </view>
+
+  <view class="points-card">
+    <text class="points-label">💰 我的积分</text>
+    <text class="points-value">{{points}}</text>
+    
+    <view class="wish-card" wx:if="{{currentWish}}">
+      <text class="wish-name">🎯 {{currentWish.name}}</text>
+      <view class="wish-progress">
+        <view class="wish-bar">
+          <view class="wish-fill" style="width: {{currentWish.current/currentWish.total*100}}%"></view>
+        </view>
+        <text class="wish-text">{{currentWish.current}}/{{currentWish.total}}</text>
+      </view>
+    </view>
+  </view>
+
+  <view class="tasks-section">
+    <text class="section-title">📋 今日任务</text>
+    <view class="task-item {{item.completed ? 'completed' : ''}}" 
+          wx:for="{{todayTasks}}" wx:key="id" 
+          bindtap="onTaskTap" data-id="{{item.id}}">
+      <view class="task-status">
+        <text wx:if="{{item.completed}}">✅</text>
+        <text wx:else>⏳</text>
+      </view>
+      <view class="task-info">
+        <text class="task-name">{{item.title}}</text>
+        <text class="task-deadline">截止 {{item.deadline}}</text>
+      </view>
+      <text class="task-points">+{{item.points}}分</text>
+    </view>
+  </view>
+</view>

+ 177 - 0
miniprogram/pages/child-home/child-home.wxss

@@ -0,0 +1,177 @@
+/* pages/child-home/child-home.wxss */
+.container {
+  min-height: 100vh;
+  background: linear-gradient(180deg, #FFE66D 0%, #f5f5f5 30%);
+  padding: 20rpx;
+  padding-bottom: 120rpx;
+  box-sizing: border-box;
+}
+
+.greeting {
+  padding: 30rpx;
+}
+
+.greeting-text {
+  font-size: 40rpx;
+  font-weight: bold;
+  color: #333;
+}
+
+.streak-card {
+  background: #fff;
+  border-radius: 20rpx;
+  padding: 30rpx;
+  margin-bottom: 20rpx;
+}
+
+.streak-info {
+  display: flex;
+  align-items: center;
+  margin-bottom: 20rpx;
+}
+
+.streak-fire {
+  font-size: 48rpx;
+  margin-right: 15rpx;
+}
+
+.streak-text {
+  font-size: 36rpx;
+  font-weight: bold;
+  color: #FF6B6B;
+}
+
+.progress-bar {
+  height: 16rpx;
+  background: #eee;
+  border-radius: 8rpx;
+  overflow: hidden;
+  margin-bottom: 15rpx;
+}
+
+.progress-fill {
+  height: 100%;
+  background: linear-gradient(90deg, #FF6B6B, #FFE66D);
+  border-radius: 8rpx;
+}
+
+.streak-tip {
+  font-size: 22rpx;
+  color: #999;
+}
+
+.points-card {
+  background: #fff;
+  border-radius: 20rpx;
+  padding: 30rpx;
+  margin-bottom: 20rpx;
+  text-align: center;
+}
+
+.points-label {
+  font-size: 28rpx;
+  color: #666;
+  display: block;
+  margin-bottom: 15rpx;
+}
+
+.points-value {
+  font-size: 80rpx;
+  font-weight: bold;
+  color: #FFD700;
+  display: block;
+  margin-bottom: 20rpx;
+}
+
+.wish-card {
+  background: #f9f9f9;
+  border-radius: 16rpx;
+  padding: 20rpx;
+}
+
+.wish-name {
+  font-size: 28rpx;
+  color: #333;
+  display: block;
+  margin-bottom: 15rpx;
+}
+
+.wish-progress {
+  display: flex;
+  align-items: center;
+}
+
+.wish-bar {
+  flex: 1;
+  height: 16rpx;
+  background: #e0e0e0;
+  border-radius: 8rpx;
+  overflow: hidden;
+  margin-right: 15rpx;
+}
+
+.wish-fill {
+  height: 100%;
+  background: linear-gradient(90deg, #4CAF50, #8BC34A);
+  border-radius: 8rpx;
+}
+
+.wish-text {
+  font-size: 24rpx;
+  color: #666;
+}
+
+.tasks-section {
+  background: #fff;
+  border-radius: 20rpx;
+  padding: 30rpx;
+}
+
+.section-title {
+  font-size: 30rpx;
+  font-weight: bold;
+  color: #333;
+  display: block;
+  margin-bottom: 20rpx;
+}
+
+.task-item {
+  display: flex;
+  align-items: center;
+  padding: 25rpx;
+  background: #f9f9f9;
+  border-radius: 16rpx;
+  margin-bottom: 20rpx;
+}
+
+.task-item.completed {
+  opacity: 0.6;
+}
+
+.task-status {
+  font-size: 40rpx;
+  margin-right: 20rpx;
+}
+
+.task-info {
+  flex: 1;
+}
+
+.task-name {
+  font-size: 30rpx;
+  color: #333;
+  display: block;
+}
+
+.task-deadline {
+  font-size: 22rpx;
+  color: #999;
+  margin-top: 8rpx;
+  display: block;
+}
+
+.task-points {
+  font-size: 28rpx;
+  font-weight: bold;
+  color: #FFD700;
+}

+ 171 - 0
miniprogram/pages/family-manage/family-manage.js

@@ -0,0 +1,171 @@
+// pages/family-manage/family-manage.js
+Page({
+  data: {
+    family: null,
+    members: [],
+    loading: false,
+    isCreator: true
+  },
+
+  onLoad() {
+    this.loadFamilyInfo();
+  },
+
+  onShow() {
+    this.loadFamilyInfo();
+  },
+
+  loadFamilyInfo() {
+    this.setData({ loading: true });
+    
+    // 模拟数据
+    const mockFamily = {
+      id: 1,
+      name: '快乐家庭',
+      inviteCode: 'ABC123',
+      memberCount: 4,
+      createdAt: '2026-03-20 10:00'
+    };
+
+    const mockMembers = [
+      { id: 1, nickname: '爸爸', role: 'parent', avatar: '', isCreator: true },
+      { id: 2, nickname: '妈妈', role: 'parent', avatar: '', isCreator: false },
+      { id: 3, nickname: '小明', role: 'child', avatar: '', isCreator: false },
+      { id: 4, nickname: '小红', role: 'child', avatar: '', isCreator: false }
+    ];
+
+    setTimeout(() => {
+      this.setData({
+        family: mockFamily,
+        members: mockMembers,
+        loading: false
+      });
+    }, 500);
+  },
+
+  onEditFamilyName() {
+    wx.showModal({
+      title: '修改家庭名称',
+      editable: true,
+      placeholderText: '请输入新的家庭名称',
+      success: (res) => {
+        if (res.confirm && res.content) {
+          this.doUpdateFamilyName(res.content);
+        }
+      }
+    });
+  },
+
+  doUpdateFamilyName(name) {
+    wx.showLoading({ title: '修改中...' });
+    
+    setTimeout(() => {
+      wx.hideLoading();
+      this.setData({ 'family.name': name });
+      wx.showToast({
+        title: '修改成功',
+        icon: 'success'
+      });
+    }, 1000);
+  },
+
+  onCopyInviteCode() {
+    wx.setClipboardData({
+      data: this.data.family.inviteCode,
+      success: () => {
+        wx.showToast({
+          title: '邀请码已复制',
+          icon: 'success'
+        });
+      }
+    });
+  },
+
+  onRefreshInviteCode() {
+    wx.showModal({
+      title: '刷新邀请码',
+      content: '确定要刷新邀请码吗?旧邀请码将失效。',
+      success: (res) => {
+        if (res.confirm) {
+          this.doRefreshInviteCode();
+        }
+      }
+    });
+  },
+
+  doRefreshInviteCode() {
+    wx.showLoading({ title: '刷新中...' });
+    
+    setTimeout(() => {
+      wx.hideLoading();
+      const newCode = 'XYZ' + Math.floor(Math.random() * 1000);
+      this.setData({ 'family.inviteCode': newCode });
+      wx.showToast({
+        title: '刷新成功',
+        icon: 'success'
+      });
+    }, 1000);
+  },
+
+  onRemoveMember(e) {
+    const memberId = e.currentTarget.dataset.id;
+    const member = this.data.members.find(m => m.id === memberId);
+    
+    wx.showModal({
+      title: '移除成员',
+      content: `确定要移除成员「${member.nickname}」吗?`,
+      success: (res) => {
+        if (res.confirm) {
+          this.doRemoveMember(memberId);
+        }
+      }
+    });
+  },
+
+  doRemoveMember(memberId) {
+    wx.showLoading({ title: '移除中...' });
+    
+    setTimeout(() => {
+      wx.hideLoading();
+      const members = this.data.members.filter(m => m.id !== memberId);
+      this.setData({ 
+        members,
+        'family.memberCount': members.length
+      });
+      wx.showToast({
+        title: '移除成功',
+        icon: 'success'
+      });
+    }, 1000);
+  },
+
+  onLeaveFamily() {
+    wx.showModal({
+      title: '确认退出',
+      content: '确定要退出这个家庭吗?',
+      success: (res) => {
+        if (res.confirm) {
+          this.doLeaveFamily();
+        }
+      }
+    });
+  },
+
+  doLeaveFamily() {
+    wx.showLoading({ title: '退出中...' });
+    
+    setTimeout(() => {
+      wx.hideLoading();
+      wx.showToast({
+        title: '已退出家庭',
+        icon: 'success'
+      });
+      
+      setTimeout(() => {
+        wx.reLaunch({
+          url: '/pages/login/login'
+        });
+      }, 1500);
+    }, 1000);
+  }
+});

+ 4 - 0
miniprogram/pages/family-manage/family-manage.json

@@ -0,0 +1,4 @@
+{
+  "navigationBarTitleText": "家庭管理",
+  "enablePullDownRefresh": false
+}

+ 68 - 0
miniprogram/pages/family-manage/family-manage.wxml

@@ -0,0 +1,68 @@
+<!--pages/family-manage/family-manage.wxml-->
+<view class="container">
+  <view class="loading" wx:if="{{loading}}">
+    <text>加载中...</text>
+  </view>
+
+  <view class="family-info" wx:else>
+    <!-- 家庭基本信息 -->
+    <view class="info-card">
+      <view class="info-header">
+        <text class="info-title">家庭信息</text>
+        <text class="edit-btn" wx:if="{{isCreator}}" bindtap="onEditFamilyName">编辑</text>
+      </view>
+      
+      <view class="info-item">
+        <text class="info-label">家庭名称</text>
+        <text class="info-value">{{family.name}}</text>
+      </view>
+      
+      <view class="info-item">
+        <text class="info-label">邀请码</text>
+        <view class="invite-code">
+          <text class="code-text">{{family.inviteCode}}</text>
+          <text class="copy-btn" bindtap="onCopyInviteCode">复制</text>
+          <text class="refresh-btn" wx:if="{{isCreator}}" bindtap="onRefreshInviteCode">刷新</text>
+        </view>
+      </view>
+      
+      <view class="info-item">
+        <text class="info-label">成员数量</text>
+        <text class="info-value">{{family.memberCount}}人</text>
+      </view>
+      
+      <view class="info-item">
+        <text class="info-label">创建时间</text>
+        <text class="info-value">{{family.createdAt}}</text>
+      </view>
+    </view>
+
+    <!-- 家庭成员 -->
+    <view class="members-card">
+      <text class="card-title">家庭成员</text>
+      
+      <view class="member-list">
+        <view class="member-item" wx:for="{{members}}" wx:key="id">
+          <view class="member-avatar">
+            <image class="avatar-image" src="{{item.avatar || '/images/default-avatar.svg'}}" mode="aspectFill"></image>
+            <view class="creator-badge" wx:if="{{item.isCreator}}">创建者</view>
+          </view>
+          
+          <view class="member-info">
+            <text class="member-name">{{item.nickname}}</text>
+            <text class="member-role">{{item.role === 'parent' ? '家长' : '孩子'}}</text>
+          </view>
+          
+          <view class="member-actions" wx:if="{{isCreator && !item.isCreator}}">
+            <text class="remove-btn" data-id="{{item.id}}" bindtap="onRemoveMember">移除</text>
+          </view>
+        </view>
+      </view>
+    </view>
+
+    <!-- 操作按钮 -->
+    <view class="action-buttons">
+      <button class="leave-btn" bindtap="onLeaveFamily">退出家庭</button>
+    </view>
+  </view>
+</view>

+ 176 - 0
miniprogram/pages/family-manage/family-manage.wxss

@@ -0,0 +1,176 @@
+/* pages/family-manage/family-manage.wxss */
+.container {
+  padding: 20rpx;
+  background-color: #f5f5f5;
+  min-height: 100vh;
+}
+
+.loading {
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  height: 400rpx;
+  color: #999;
+  font-size: 28rpx;
+}
+
+.info-card, .members-card {
+  background-color: #fff;
+  border-radius: 16rpx;
+  padding: 30rpx;
+  margin-bottom: 20rpx;
+  box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.05);
+}
+
+.info-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 30rpx;
+}
+
+.info-title, .card-title {
+  font-size: 32rpx;
+  font-weight: bold;
+  color: #333;
+}
+
+.edit-btn {
+  font-size: 28rpx;
+  color: #FF6B6B;
+  padding: 10rpx 20rpx;
+  border: 2rpx solid #FF6B6B;
+  border-radius: 20rpx;
+}
+
+.info-item {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  padding: 20rpx 0;
+  border-bottom: 1rpx solid #f0f0f0;
+}
+
+.info-item:last-child {
+  border-bottom: none;
+}
+
+.info-label {
+  font-size: 28rpx;
+  color: #666;
+}
+
+.info-value {
+  font-size: 28rpx;
+  color: #333;
+  font-weight: bold;
+}
+
+.invite-code {
+  display: flex;
+  align-items: center;
+}
+
+.code-text {
+  font-size: 28rpx;
+  color: #FF6B6B;
+  font-weight: bold;
+  margin-right: 20rpx;
+}
+
+.copy-btn, .refresh-btn {
+  font-size: 24rpx;
+  color: #2196F3;
+  padding: 4rpx 12rpx;
+  border: 2rpx solid #2196F3;
+  border-radius: 16rpx;
+  margin-left: 10rpx;
+}
+
+.refresh-btn {
+  color: #FF9800;
+  border-color: #FF9800;
+}
+
+.member-list {
+  margin-top: 20rpx;
+}
+
+.member-item {
+  display: flex;
+  align-items: center;
+  padding: 20rpx 0;
+  border-bottom: 1rpx solid #f0f0f0;
+}
+
+.member-item:last-child {
+  border-bottom: none;
+}
+
+.member-avatar {
+  position: relative;
+  margin-right: 20rpx;
+}
+
+.avatar-image {
+  width: 80rpx;
+  height: 80rpx;
+  border-radius: 50%;
+  border: 2rpx solid #eee;
+}
+
+.creator-badge {
+  position: absolute;
+  bottom: -10rpx;
+  left: 50%;
+  transform: translateX(-50%);
+  background-color: #FF6B6B;
+  color: #fff;
+  font-size: 20rpx;
+  padding: 2rpx 10rpx;
+  border-radius: 10rpx;
+}
+
+.member-info {
+  flex: 1;
+}
+
+.member-name {
+  font-size: 30rpx;
+  font-weight: bold;
+  color: #333;
+  display: block;
+  margin-bottom: 5rpx;
+}
+
+.member-role {
+  font-size: 24rpx;
+  color: #999;
+}
+
+.remove-btn {
+  font-size: 28rpx;
+  color: #F44336;
+  padding: 10rpx 20rpx;
+  border: 2rpx solid #F44336;
+  border-radius: 20rpx;
+}
+
+.action-buttons {
+  margin-top: 40rpx;
+  padding: 0 20rpx;
+}
+
+.leave-btn {
+  background-color: #fff;
+  color: #F44336;
+  border: 2rpx solid #F44336;
+  border-radius: 50rpx;
+  padding: 24rpx;
+  font-size: 32rpx;
+  font-weight: bold;
+}
+
+.leave-btn::after {
+  border: none;
+}

Daži faili netika attēloti, jo izmaiņu fails ir pārāk liels