OpenCode 工作流

Money-Log

一个用 Electron + React + TypeScript 构建的轻量级桌面记账工具。同样的 OpenLogos 方法论,不同的 AI 工具与技术栈。

Electron React 18 TypeScript SQLite Zustand Tailwind CSS
4 场景
·
73 测试用例
·
100% 覆盖率
·
Gate 3.5: PASS
·
已部署
·
Smoke PASS
OpenCode 集成 使用 .opencode/plugins/openlogos.js,配合 commands/ hooks/,在终端里实现方法论驱动的开发。
1
WHY

需求 —— 谁需要它,为什么?

来源:logos/resources/prd/1-product-requirements/01-requirements.md

用户画像与痛点

上班族,24-50 岁 —— 月收入 5K-30K 元。没有主动记账的习惯,只会被动查看支付宝/微信的交易记录。需要快速记账、分类消费,以及消费趋势洞察。

P01 记不清钱花在哪 —— 交易记录分散,没有分类汇总
P02 想记账但现有应用太复杂 → 用一周就放弃
P03 每月按分类手动统计太繁琐
P04 共用电脑 —— 财务记录没有数据保护

场景概览

ID场景触发痛点优先级
S01快速记账用户打开应用记一笔P02P0
S02查看统计报表用户进入统计页P01, P03P0
S03管理消费分类用户管理分类P01P1
S04设置密码锁用户开启/关闭锁P04P1

验收标准 —— S01:快速记账

正常
GIVEN 用户已打开应用,所有分类都已展示
WHEN 用户选择「餐饮」,输入「35.5」,点击「记一笔」
THEN 显示「已记录」,分类保持在「餐饮」,金额清空,准备记下一笔
异常
GIVEN 用户已打开应用
WHEN 未选择分类,用户输入金额并点击「记一笔」
THEN 按钮置灰,或提示「请选择一个分类」
异常
GIVEN 用户选择了「娱乐」分类
WHEN 用户输入「abc」或「-50」,点击「记一笔」
THEN 提示「请输入有效金额」,不保存
2
WHAT

产品设计 —— 用户会看到什么、做什么?

来源:logos/resources/prd/2-product-design/1-feature-specs/01-main-design.md

页面架构

┌─────────────────────────────────────────┐
│            Money-Log Logo               │
├─────────────────────────────────────────┤
│                                         │
│   ┌─────────┐ ┌─────────┐ ┌─────────┐  │
│   │  Log    │ │  Stats  │ │ Settings│  │
│   │(default)│ │         │ │         │  │
│   └─────────┘ └─────────┘ └─────────┘  │
│                                         │
│  ┌─────────────────────────────────┐    │
│  │      [Page Content Area]        │    │
│  │                                 │    │
│  └─────────────────────────────────┘    │
│                                         │
└─────────────────────────────────────────┘

Window: 800 x 600 px (min: 600 x 500)
3 tabs: Log / Statistics / Settings

UI 原型 —— 桌面应用

来源:logos/resources/prd/2-product-design/2-page-design/01-prototype.html

包含全部 3 个标签页(记账 / 统计 / 设置)的交互式 HTML 原型 —— 在写任何代码之前就设计好了。

S01 交互 —— 快速记账流程

  1. 应用打开 → 显示默认记账页,含分类网格(8 个带 emoji 图标的预设分类)
  2. 用户点选一个分类 → 高亮选中项,其余变暗
  3. 用户用数字键盘输入金额 → 实时校验,过滤非数字字符
  4. 用户可选地添加备注(最多 200 字符)
  5. 点击「记一笔」→ 保存到 SQLite → 显示「已记录」提示 → 清空金额,保留分类
字段类型必填校验
分类图标网格选择必须选择一个
金额数字输入> 0,最大 999999.99
备注文本输入最多 200 字符
3
HOW

实现 —— 从架构到验证通过的代码

Step 0

架构概览

来源:logos/resources/prd/3-technical-plan/1-architecture/01-architecture-overview.md

graph TB
    subgraph Renderer["Renderer Process (React)"]
        UI["React Components
UI Layer"] State["Zustand Store
State Management"] IPC["IPC Bridge
Process Communication"] end subgraph Client["Client (Electron Main Process)"] Main["Main Process
Backend Logic"] DB["SQLite Database
Local Storage"] Store["electron-store
Config Storage"] end UI --> State State --> IPC IPC --> Main Main --> DB Main --> Store

UI 放在渲染进程(React + Zustand),数据放在主进程(better-sqlite3)。IPC 桥负责所有通信。密码锁配置存在 electron-store 中,与消费数据分开。

Step 1

场景 → 时序图

来源:logos/resources/prd/3-technical-plan/2-scenario-implementation/S01-快速记账.md

S01:快速记账 —— 时序图

sequenceDiagram
    participant U as User
    participant UI as React UI
    participant Store as Zustand Store
    participant IPC as IPC Bridge
    participant DB as SQLite Database

    U->>UI: Step 1: Select category
    UI->>UI: Step 2: Update selection — highlight active
    UI->>U: Step 3: Show selected state

    U->>UI: Step 4: Enter amount via keyboard
    UI->>UI: Step 5: Validate format — filter invalid chars
    UI->>UI: Step 6: Enable/disable button by validity
    UI->>U: Step 7: Display amount

    U->>UI: Step 8: Click "Log" button
    UI->>Store: Step 9: Submit {amount, category_id}
    Store->>IPC: Step 10: saveRecord(amount, category_id)
    IPC->>DB: Step 11: INSERT INTO records
    DB-->>IPC: Step 12: Return write result
    IPC-->>Store: Step 13: Return success/failure
    Store-->>UI: Step 14: Update state
    UI->>U: Step 15: Show "Recorded" toast

    UI->>UI: Step 16: Clear amount input
    UI->>UI: Step 17: Keep category selected
    UI->>U: Step 18: Ready for next entry

步骤

  1. 用户点选一个分类标签(例如「餐饮」)。
  2. React UI 更新选中状态,将当前分类视觉高亮。
  3. UI 向用户展示选中反馈。
  4. 用户通过键盘输入金额。
  5. React UI 校验输入格式,过滤非数字字符。
  6. React UI 根据输入是否有效启用/禁用「记一笔」按钮(未选分类或金额为 0 时禁用)。
  7. UI 实时显示已输入的金额。
  8. 用户点击「记一笔」按钮提交。
  9. Zustand Store 接收消费数据 amount, category_id
  10. Store 通过 IPC 调用主进程的 saveRecord()
  11. SQLite 执行 INSERT INTO records
  12. 数据库返回写入结果(成功或错误)。
  13. IPC 将结果返回给 Store。
  14. Store 更新状态,通知 UI。
  15. UI 显示「已记录」提示。
  16. React UI 清空金额输入框,准备记下一笔。
  17. React UI 保留分类选中,减少重复选择。
  18. UI 进入就绪状态,等待下一笔记账。
异常情况
EX-8.1 未选择分类 → 按钮禁用
EX-8.2 金额为 0 或为空 → 按钮禁用
EX-11.1 数据库写入失败 → 显示错误提示,保留输入
EX-11.2 金额超过 2 位小数 → 自动截断为 2 位
Step 2

API 规格 + 数据库 Schema

API 规格 —— Electron IPC 通道

来源:logos/resources/api/local-api.yaml

数据库 Schema —— SQLite 表

来源:logos/resources/database/schema.yaml

Step 3

测试用例设计(先于代码!)

来源:logos/resources/test/S01-test-cases.md

S01 测试用例 —— 单元测试(节选)

ID描述输入预期
UT-S01-01金额必填{}400:缺少必填字段
UT-S01-02金额必须 > 0{amount: 0}400:金额必须 > 0
UT-S01-05分类 ID 必须在数据库中存在category_id: 999400:分类不存在
UT-S01-06备注最多 200 字符"a".repeat(201)400:备注过长
UT-S01-10金额截断为 2 位小数12.345存储为 12.34
UT-S01-11过滤非数字字符"abc"过滤为空

S01 场景测试(节选)

ID描述覆盖
ST-S01-01完整记账流程:选择 → 输入 → 保存 → 提示Steps 1-18
ST-S01-02同一分类连续记账Steps 1-18
ST-S01-05无效金额格式被拒绝EX-5.1
共 73 条测试用例,覆盖 S01-S04,涵盖记账、统计、分类管理和密码锁 —— 全部在写下任何一行实现代码之前就设计好了。
Step 4

代码生成 —— 经由 OpenCode 的设计驱动实现

来源:src/main/database.js · src/renderer/pages/ · src/__tests__/

OpenCode —— 终端优先的 AI 编程

OpenCode 通过斜杠命令读取时序图和 API 规格,然后批量生成 Electron 主进程 + React 前端 + Vitest 测试。每个批次都闭环:业务代码 → 测试代码 → OpenLogos reporter。

数据层 —— Electron 主进程 → 对应 S01 Steps 10-13

src/main/database.jsJavaScript
class DatabaseManager {
  // S01 Steps 10-13: Save a new expense record
  saveRecord(amount, category_id, remark = null, created_at = null) {
    const amountInCents = Math.round(amount * 100);
    const timestamp = created_at || new Date().toISOString();
    this.db.run(
      `INSERT INTO records (amount, category_id, remark, created_at)
       VALUES (?, ?, ?, ?)`,
      [amountInCents, category_id, remark, timestamp]
    );
    const result = this.db.exec('SELECT last_insert_rowid() as id');
    this.save();
    return result[0].values[0][0];
  }
}

单元测试 + 场景测试 → 验证 S01 验收标准

src/__tests__/S01.test.tsTypeScript
describe('S01: Quick Logging - Unit Tests', () => {
  describe('UT-S01-01: Amount is required', () => {
    it('should reject empty amount', async () => {
      const data = {};
      const hasAmount = data.amount !== undefined;
      expect(hasAmount).toBe(false);
    });
  });

  describe('UT-S01-10: Amount truncated to 2 decimals', () => {
    it('should auto-truncate to 2 decimal places', () => {
      const amount = 12.345;
      const formatted = Math.floor(amount * 100) / 100;
      expect(formatted).toBe(12.34);
    });
  });
});

describe('S01: Quick Logging - Scenario Tests', () => {
  describe('ST-S01-01: Full logging flow', () => {
    it('should correctly convert amount to cents', () => {
      const amount = 35.5;
      const amountInCents = Math.round(amount * 100);
      expect(amountInCents).toBe(3550);
    });
  });
});
4React 页面
1Zustand Store
73测试用例
S01–S04全覆盖
Step 5

openlogos verify —— 自动化验收

来源:logos/resources/verify/acceptance-report.md

Gate 3.5: PASS
生成于 2026-04-09
73 已定义
73 已执行
73 已通过
0 失败
100% 覆盖率

设计期覆盖断言

Phase 1 正常验收标准:全部覆盖
Phase 1 异常验收标准:全部覆盖
EX 异常情况:全部覆盖
API 必填字段:全部覆盖
DB UNIQUE/CHECK 约束:全部覆盖
…… 横跨 S01-S04 测试用例

部署与 smoke 门禁

示例项目补齐部署完成标记,并通过部署后的最小健康检查。

Gate 3.8: PASS
部署状态 DEPLOY_DONE

部署执行已完成,状态由 OpenLogos 受控记录。

Smoke 状态 SMOKE_PASS

部署后 smoke 用例通过,可追溯到 smoke 报告。

Δ
DELTA

变更追踪 —— 每次迭代都洞察影响范围

传统的 AI 编程(「vibe coding」)只改代码,却忘了设计文档、API 规格和测试用例。久而久之,文档与现实脱节,变得毫无用处。OpenLogos 用一套结构化的 delta 工作流解决这一问题 —— 任何变更,无论多小,都必须评估它在整条产物链上的连锁影响。

1
proposal.md原因、类型、影响范围、概述
2
tasks.md按 Phase 划分的任务清单
3
Delta 文件逐任务产出变更
4
合并openlogos merge
5
代码 + 测试实现并验证
6
部署openlogos deploy-done
7
smokeopenlogos smoke
8
归档关闭提案

变更传播规则

变更类型决定了最小更新范围。一个看似微小的功能请求,可能会沿着整条链条层层传导:

变更类型
所需的最小更新
需求
需求设计场景API/DB测试代码
设计
需求设计场景API/DB测试代码
接口
需求设计场景API/DB测试代码
代码级
需求设计场景API/DB测试代码

真实示例 —— 功能:为记账增加备注

来源:logos/changes/archive/每天记账增加备注功能/proposal.md

为记账增加备注字段

功能 S01 —— 快速记账

需求:用户需要给消费添加备注(例如「火锅聚餐」「打车去医院」)。数据库列 remark 已经存在 —— 但前端 UI 没有输入框,IPC 通道也没有把参数传进去。

影响分析

需求无变更原始需求已提到可选的备注
产品设计受影响在快速记账面板的金额输入框下方加一个备注文本域
时序图受影响S01 Step 7 更新:用户填写备注 → 传给 IPC
API 规格受影响save-record 通道 —— 增加 remark 参数(字符串,可选,最多 200 字符)
数据库 Schema无变更records.remark 列已存在(TEXT,可空)
测试用例受影响新增 UT-S01-12(备注最多 200 字符),更新 ST-S01-01 以纳入备注流程
代码受影响渲染进程:增加文本域组件。主进程:将备注传入 DB 插入
结论:设计级变更 —— 从产品设计开始,向下贯穿场景、API、测试和代码。需求和数据库 Schema 不受影响。如果没有影响分析,开发者很可能只是加了 UI 字段,却忘了更新时序图、API 规格和测试用例。

更多已归档提案

Money-Log 有 8 个已归档的变更提案 —— 每一个都遵循相同的结构化 delta 工作流。

修复:密码未持久化

Bug 修复 S04

代码级修复 —— electron-store 的写入在关闭前未被 await。

月度消费明细视图

功能 S02

设计级 —— 在统计图表下方新增列表视图。

想亲手试试?

克隆仓库在本地运行 Money-Log,或用 OpenLogos 开启你自己的项目。