From b2de8ef6a998e9a6126041c29840200fd5b48a90 Mon Sep 17 00:00:00 2001 From: mac Date: Thu, 16 Jul 2026 19:14:49 +0800 Subject: [PATCH 1/3] docs: add project setup instructions --- README.md | 56 ++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 55 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 7aa54f0..3820fc1 100644 --- a/README.md +++ b/README.md @@ -1 +1,55 @@ -# timeflow +# Timeflow + +Timeflow 采用单仓库布局,包含 FastAPI 后端和 Expo React Native 移动端。 + +```text +backend/ # FastAPI API 服务 +frontend/ # Expo React Native 应用(TypeScript) +``` + +## 后端 + +```bash +cd backend +cp .env.example .env # 可选 +uv sync +uv run uvicorn timeapp.main:app --reload +``` + +API 健康检查: + +## 移动端 + +```bash +cd frontend +npm install +npm run start +``` + +目标平台为 Android,日常开发用 `npm run android` 在模拟器/真机上运行。 + +## 开发规范与质量检查 + +开发规范见 `.cursor/skills/timeflow-dev-standards/SKILL.md`(AI 会在每次改代码前强制加载)。 + +- 后端:Ruff(lint + format)、mypy strict、pytest,配置在 `backend/pyproject.toml` +- 前端:ESLint(expo 规则集)、Prettier、tsc,脚本在 `frontend/package.json` + +一键全量检查: + +```bash +bash scripts/check-all.sh # 前后端全部 +bash scripts/check-all.sh backend # 仅后端 +bash scripts/check-all.sh frontend # 仅前端 +``` + +## Git hooks(提交/推送拦截) + +hooks 不预置在仓库中。git 仓库就绪后,在 Cursor 里对 AI 说「启用 git hooks」,AI 会按 +`.cursor/skills/timeflow-git-hooks/SKILL.md` 中的模板把三个 hook 写入 `.git/hooks/` 并验证: + +- `pre-commit`:按改动范围跑对应端检查,不过不让提交 +- `commit-msg`:强制 [Conventional Commits](https://www.conventionalcommits.org/zh-hans/v1.0.0/) 的 `type(scope): 描述` 格式(feat/fix/docs/style/refactor/perf/test/build/ci/chore/revert) +- `pre-push`:全量检查通过才允许推送 + +停用:删除 `.git/hooks/` 下对应文件即可。 From 6a4b8c48038f352504041fa3fad60f51777a7f06 Mon Sep 17 00:00:00 2001 From: mac Date: Fri, 17 Jul 2026 09:22:21 +0800 Subject: [PATCH 2/3] feat(tooling): add quality check workflow --- .cursor/rules/enforce-dev-standards.mdc | 14 ++ .../skills/timeflow-dev-standards/SKILL.md | 124 ++++++++++++++++ .cursor/skills/timeflow-git-hooks/SKILL.md | 134 ++++++++++++++++++ README.md | 2 +- scripts/check-all.sh | 57 ++++++++ 5 files changed, 330 insertions(+), 1 deletion(-) create mode 100644 .cursor/rules/enforce-dev-standards.mdc create mode 100644 .cursor/skills/timeflow-dev-standards/SKILL.md create mode 100644 .cursor/skills/timeflow-git-hooks/SKILL.md create mode 100755 scripts/check-all.sh diff --git a/.cursor/rules/enforce-dev-standards.mdc b/.cursor/rules/enforce-dev-standards.mdc new file mode 100644 index 0000000..5f4dd9f --- /dev/null +++ b/.cursor/rules/enforce-dev-standards.mdc @@ -0,0 +1,14 @@ +--- +description: 强制在任何代码改动前加载 Timeflow 开发规范 skill +alwaysApply: true +--- + +# 开发规范强制加载 + +在本仓库编写、修改、重构任何代码之前,必须先用 Read 工具阅读 +`.cursor/skills/timeflow-dev-standards/SKILL.md` 并严格遵守其中全部约束。 + +- 改完代码必须运行 `bash scripts/check-all.sh`(或带 backend/frontend 参数),全绿才算完成 +- 禁止操作 git(init/commit/push/hooks),git 一律由用户执行 +- 未经用户同意禁止新增依赖 +- 与 SKILL.md 冲突的实现一律不允许产出 diff --git a/.cursor/skills/timeflow-dev-standards/SKILL.md b/.cursor/skills/timeflow-dev-standards/SKILL.md new file mode 100644 index 0000000..7dd8fa1 --- /dev/null +++ b/.cursor/skills/timeflow-dev-standards/SKILL.md @@ -0,0 +1,124 @@ +--- +name: timeflow-dev-standards +description: Timeflow 项目的开发规范与硬性约束。在本仓库中编写、修改、重构任何代码(backend FastAPI 或 frontend Expo RN)之前必须先阅读并遵守。涵盖架构分层、API 设计、数据库、前端结构、命名、注释语言、测试与安全底线,以及 AI 禁止行为清单。 +--- + +# Timeflow 开发规范 + +本文件是本仓库的最高开发约束。与本文件冲突的实现一律不允许产出。 +改完代码后必须运行检查(见「完工门禁」),检查不过不算完成。 + +## 项目概览 + +- `backend/`:FastAPI + SQLAlchemy + Alembic,Python ≥ 3.11,包管理用 **uv**(禁止 pip install / poetry) +- `frontend/`:Expo React Native + TypeScript strict,**目标平台为 Android**,包管理用 **npm**(禁止 yarn / pnpm),本地运行用 `npm run android` +- 后端包名 `timeapp`,src 布局:`backend/src/timeapp/` + +## 完工门禁(每次改完代码必须执行) + +```bash +# 只改了后端 +bash scripts/check-all.sh backend +# 只改了前端 +bash scripts/check-all.sh frontend +# 都改了 +bash scripts/check-all.sh +``` + +门禁内容:后端 `ruff check` + `ruff format --check` + `mypy`(strict)+ `pytest`;前端 `eslint` + `prettier --check` + `tsc --noEmit`。 +任何一项失败都必须修复后重跑,直到全绿。禁止用 `# noqa`、`# type: ignore`、`eslint-disable` 掩盖问题(确有必要时必须写明原因并在回复中向用户说明)。 + +## 后端架构(强制分层) + +新功能只能落在 `backend/src/timeapp/modules/<领域>/` 下,禁止写进 `api/`、`core/` 或 `main.py`: + +```text +modules/<领域>/ +├── __init__.py +├── router.py # 只做 HTTP 编排:解析请求、调 service、返回响应 +├── schemas.py # Pydantic 请求/响应模型 +├── service.py # 业务逻辑,禁止出现 FastAPI 对象(Request/Depends 等) +└── models.py # SQLAlchemy ORM 模型(需要持久化时) +``` + +- 新领域模块必须在 `api/router.py` 中注册:`api_router.include_router(<模块>.router)` +- 横切能力(认证、DB 会话)统一放 `api/dependencies.py`,模块内禁止自建 +- 配置只能通过 `core/config.py` 的 `Settings` 读取,禁止在业务代码里直接 `os.environ` +- 模块之间只允许调用对方的 `service` 层函数,禁止跨模块 import `router` / `models` + +## API 设计 + +- 路径用 kebab-case(`/goal-planning`),与现有模块前缀保持一致;资源用复数名词,禁止动词路径(`POST /todos`,不是 `/create-todo`) +- 请求/响应必须定义 Pydantic 模型并声明 `response_model`,禁止返回裸 dict +- 状态码:创建 201、删除 204、404/409 等错误用 `HTTPException` 抛出,`detail` 用英文 +- 分页列表统一 query 参数 `limit`(默认 20,上限 100)+ `offset` + +## 数据库 + +- ORM 模型放各模块 `models.py`,公共 `Base`、engine、session 放 `core/db.py`(首次建库时创建) +- 表名 snake_case 复数(`todos`、`goal_plans`);所有表必须有 `id`、`created_at`、`updated_at` +- 任何模型变更必须生成 Alembic 迁移,禁止手改历史迁移、禁止 `Base.metadata.create_all` 用于生产路径 +- 查询写在 service 层,禁止在 router 里直接操作 session + +## 前端结构(Expo RN) + +新代码按以下目录组织(目录不存在时按需创建): + +```text +frontend/src/ +├── screens/ # 页面级组件,PascalCase:HomeScreen.tsx +├── components/ # 可复用组件,PascalCase:TodoCard.tsx +├── hooks/ # 自定义 hook,camelCase:useTodos.ts +├── api/ # 后端 API 封装,统一 fetch 客户端 +├── types/ # 共享 TS 类型 +└── constants/ # 颜色、间距等设计常量 +``` + +- 一律函数组件 + hooks,禁止 class 组件;组件 props 必须有显式 TS 类型 +- 样式用 `StyleSheet.create`,禁止大段内联样式对象;颜色/间距取自 `constants/` +- 禁止 `any`(确需未知类型用 `unknown` 再收窄);tsconfig strict 不得关闭 +- 调用后端必须经过 `src/api/` 封装层,组件内禁止直接写 fetch/URL + +### Android 适配(目标平台) + +- 一切 UI 与交互以 Android 为准验收,禁止引入 iOS-only API(如 `ActionSheetIOS`) +- 阴影用 `elevation`,禁止只写 iOS 的 `shadow*` 系列样式 +- 必须处理 Android 物理返回键的页面退出逻辑(`BackHandler` 或导航库默认行为) +- 系统权限(通知、麦克风、相册等)在 `app.json` 的 `android.permissions` 中声明,并在代码中运行时请求 +- 刘海屏/状态栏适配用 `SafeAreaView`/safe-area 方案,不写死状态栏高度 + +## 命名与语言 + +- Python:模块/函数/变量 snake_case,类 PascalCase;TS:变量/函数 camelCase,组件/类型 PascalCase +- 注释和 docstring 用**中文**;标识符、日志、错误信息、commit scope 用**英文** +- 只写解释「为什么」的注释,禁止复述代码行为的废话注释 + +## 测试(硬性要求) + +- 新增或修改业务逻辑(service 层、非空壳 router、工具函数)必须同步补/改 pytest 测试,放 `backend/tests/test_<模块>.py` +- API 测试用 `fastapi.testclient.TestClient`,覆盖正常路径 + 至少一个错误路径 +- 改完必须实际运行 `uv run pytest` 并通过;禁止提交只为凑数、无断言的测试 + +## 安全底线 + +- 密钥、token、连接串只能走环境变量(`TIMEAPP_` 前缀)+ `.env`(已 gitignore),新增变量必须同步更新 `.env.example`(放占位值) +- 禁止把 `.env`、真实密钥、用户数据写进代码、测试、文档 +- SQL 只能通过 ORM / 绑定参数,禁止字符串拼接 SQL +- 禁止 `print` 调试(ruff T20 会拦截);日志不得输出密码、token、个人敏感信息 +- 后端禁止吞异常(裸 `except: pass`);对外错误信息不暴露堆栈和内部路径 + +## AI 禁止行为清单 + +1. 禁止未经用户同意引入新依赖、新框架、新服务(改 pyproject.toml / package.json 依赖前必须先说明理由并征得同意) +2. 禁止修改与当前任务无关的代码、重排无关 import、顺手重构 +3. 禁止留 TODO 空壳函数、`pass` 占位实现交差;做不完就明确告诉用户哪部分没做 +4. 禁止降低门禁:不得删除/放宽 ruff、mypy、eslint、tsconfig 的现有配置 +5. 禁止操作 git:不 init、不 commit、不 push,git 操作一律由用户自己执行。唯一例外:用户明确要求启用 hooks 时,按 `timeflow-git-hooks` skill 写 `.git/hooks/` 下的三个文件 +6. 禁止编造不存在的 API、库用法;不确定就先查证 +7. 禁止跳过「完工门禁」就宣称任务完成 + +## Commit 规范(供用户参考,hooks 强制) + +提交信息请遵循 Conventional Commits 规范,详见 。 +格式 `type(scope): 描述`,type 限 feat/fix/docs/style/refactor/perf/test/build/ci/chore/revert,scope 用英文模块名,描述可中文,标题 ≤ 72 字符。示例:`feat(scheduling): 新增待办创建接口`。 +启用拦截:用户提出需求后,按 `.cursor/skills/timeflow-git-hooks/SKILL.md` 创建 hooks(pre-commit 检查改动端、commit-msg 校验格式、pre-push 全量检查)。 diff --git a/.cursor/skills/timeflow-git-hooks/SKILL.md b/.cursor/skills/timeflow-git-hooks/SKILL.md new file mode 100644 index 0000000..e26210c --- /dev/null +++ b/.cursor/skills/timeflow-git-hooks/SKILL.md @@ -0,0 +1,134 @@ +--- +name: timeflow-git-hooks +description: 按模板为 Timeflow 仓库创建并启用 git hooks(pre-commit / commit-msg / pre-push 质量与提交信息拦截)。当用户要求安装、启用、创建、更新或修复 git hooks、提交拦截、push 拦截时使用。AI 依据本文件将 hook 脚本写入 .git/hooks/ 并验证。 +--- + +# Timeflow Git Hooks 创建 + +按本文件模板把三个 hook 写入 `.git/hooks/`,实现:提交前跑质量检查、提交信息强制 `type(scope): 描述`、推送前全量检查。 +提交信息遵循 Conventional Commits 规范,详见 。 + +## 前置条件 + +1. 仓库根目录必须存在 `.git/`。不存在时**停止**并提示用户先自行 `git init`,禁止代替用户执行。 +2. `scripts/check-all.sh` 必须存在且可执行(hooks 依赖它)。缺失时先修复此依赖再继续。 +3. 仅在用户明确要求启用/更新 hooks 时执行本流程;这是「禁止操作 git」约束的唯一例外,且例外范围仅限写 `.git/hooks/` 下的这三个文件。 + +## 创建步骤 + +用 Write 工具将下面三个模板**原样**写入对应路径(已存在同名 hook 时先向用户确认再覆盖),然后: + +```bash +chmod +x .git/hooks/pre-commit .git/hooks/commit-msg .git/hooks/pre-push +``` + +### 模板 1:`.git/hooks/pre-commit` + +```bash +#!/usr/bin/env bash +# 提交前拦截:只检查本次提交涉及的端,检查不通过则禁止提交。 +set -uo pipefail + +ROOT="$(git rev-parse --show-toplevel)" +STAGED="$(git diff --cached --name-only --diff-filter=ACMR)" + +[[ -z "$STAGED" ]] && exit 0 + +if echo "$STAGED" | grep -q '^backend/'; then + "$ROOT/scripts/check-all.sh" backend || { + echo "" + echo "[pre-commit] 后端检查未通过,提交已被拦截。" + exit 1 + } +fi + +if echo "$STAGED" | grep -q '^frontend/'; then + "$ROOT/scripts/check-all.sh" frontend || { + echo "" + echo "[pre-commit] 前端检查未通过,提交已被拦截。" + exit 1 + } +fi + +exit 0 +``` + +### 模板 2:`.git/hooks/commit-msg` + +```bash +#!/usr/bin/env bash +# 提交信息拦截:必须符合 Conventional Commits 格式。 +# 格式: type(scope)?: 描述 例如: feat(scheduling): 新增待办创建接口 +set -uo pipefail + +MSG_FILE="$1" +SUBJECT="$(head -n 1 "$MSG_FILE")" + +# merge/revert 等 git 自动生成的信息直接放行 +if echo "$SUBJECT" | grep -qE '^(Merge|Revert|fixup!|squash!)'; then + exit 0 +fi + +PATTERN='^(feat|fix|docs|style|refactor|perf|test|build|ci|chore|revert)(\([a-z0-9_-]+\))?(!)?: .+' + +if ! echo "$SUBJECT" | grep -qE "$PATTERN"; then + echo "[commit-msg] 提交信息不符合规范,已被拦截。" + echo "" + echo " 要求格式: type(scope): 描述" + echo " 允许类型: feat fix docs style refactor perf test build ci chore revert" + echo " 示例: feat(scheduling): 新增待办创建接口" + echo " fix(reminders): 修复提醒时间时区错误" + echo "" + echo " 当前信息: $SUBJECT" + exit 1 +fi + +if [[ "${#SUBJECT}" -gt 72 ]]; then + echo "[commit-msg] 标题超过 72 字符(当前 ${#SUBJECT}),请精简后重试。" + exit 1 +fi + +exit 0 +``` + +### 模板 3:`.git/hooks/pre-push` + +```bash +#!/usr/bin/env bash +# 推送前拦截:全量检查(后端 + 前端)通过后才允许 push。 +set -uo pipefail + +ROOT="$(git rev-parse --show-toplevel)" + +"$ROOT/scripts/check-all.sh" || { + echo "" + echo "[pre-push] 检查未通过,推送已被拦截。修复后重新 push。" + exit 1 +} + +exit 0 +``` + +## 验证步骤(创建后必须执行) + +不实际执行 commit/push,只做无副作用验证: + +```bash +# 1. 语法检查 +bash -n .git/hooks/pre-commit && bash -n .git/hooks/commit-msg && bash -n .git/hooks/pre-push + +# 2. commit-msg 逻辑:合法信息应放行、非法信息应拦截 +T=$(mktemp) +echo "feat(scheduling): 新增待办创建接口" > "$T" && bash .git/hooks/commit-msg "$T" # 应通过 +echo "随便改改" > "$T" && bash .git/hooks/commit-msg "$T" # 应拦截(退出码 1) +rm -f "$T" + +# 3. 确认可执行权限 +ls -l .git/hooks/pre-commit .git/hooks/commit-msg .git/hooks/pre-push +``` + +三项全部符合预期后,向用户报告 hooks 已启用;任何一项异常必须修复后重新验证。 + +## 停用方式(告知用户即可,不主动执行) + +删除对应文件即可:`rm .git/hooks/pre-commit .git/hooks/commit-msg .git/hooks/pre-push` diff --git a/README.md b/README.md index 3820fc1..c24caef 100644 --- a/README.md +++ b/README.md @@ -48,7 +48,7 @@ bash scripts/check-all.sh frontend # 仅前端 hooks 不预置在仓库中。git 仓库就绪后,在 Cursor 里对 AI 说「启用 git hooks」,AI 会按 `.cursor/skills/timeflow-git-hooks/SKILL.md` 中的模板把三个 hook 写入 `.git/hooks/` 并验证: -- `pre-commit`:按改动范围跑对应端检查,不过不让提交 +- `pre-commit`:按改动范围运行对应端检查;检查不通过时阻止提交 - `commit-msg`:强制 [Conventional Commits](https://www.conventionalcommits.org/zh-hans/v1.0.0/) 的 `type(scope): 描述` 格式(feat/fix/docs/style/refactor/perf/test/build/ci/chore/revert) - `pre-push`:全量检查通过才允许推送 diff --git a/scripts/check-all.sh b/scripts/check-all.sh new file mode 100755 index 0000000..104b27a --- /dev/null +++ b/scripts/check-all.sh @@ -0,0 +1,57 @@ +#!/usr/bin/env bash +# 全仓质量检查入口:后端 ruff/mypy/pytest + 前端 eslint/prettier/tsc。 +# 用法: scripts/check-all.sh [backend|frontend](不带参数则两端都跑) +set -uo pipefail + +ROOT="$(cd "$(dirname "$0")/.." && pwd)" +TARGET="${1:-all}" +FAILED=0 + +case "$TARGET" in + all | backend | frontend) ;; + *) + echo "用法: $0 [backend|frontend]" >&2 + exit 2 + ;; +esac + +run_step() { + local name="$1" + shift + echo "==> ${name}" + if ! "$@"; then + echo "!! ${name} 未通过" + FAILED=1 + fi +} + +if [[ "$TARGET" == "all" || "$TARGET" == "backend" ]]; then + if cd "$ROOT/backend"; then + run_step "backend: ruff check" uv run ruff check . + run_step "backend: ruff format" uv run ruff format --check . + run_step "backend: mypy" uv run mypy + run_step "backend: pytest" uv run pytest + else + echo "!! 找不到后端目录:$ROOT/backend" + FAILED=1 + fi +fi + +if [[ "$TARGET" == "all" || "$TARGET" == "frontend" ]]; then + if cd "$ROOT/frontend"; then + run_step "frontend: eslint" npm run --silent lint + run_step "frontend: prettier" npm run --silent format:check + run_step "frontend: tsc" npm run --silent typecheck + else + echo "!! 找不到前端目录:$ROOT/frontend" + FAILED=1 + fi +fi + +if [[ "$FAILED" -ne 0 ]]; then + echo "" + echo "检查未通过,请修复后重试。" + exit 1 +fi +echo "" +echo "全部检查通过。" From 43a90249643babd8c4be4f2a8ffdcb7f7f7b35bc Mon Sep 17 00:00:00 2001 From: mac Date: Tue, 21 Jul 2026 14:51:08 +0800 Subject: [PATCH 3/3] feat(tooling): update project documentation and developer workflow --- .../skills/timeflow-dev-standards/SKILL.md | 39 +++++---- .nvmrc | 1 + .python-version | 1 + README.md | 81 ++++++++++++------- docs/dependency-management.md | 36 +++++++++ docs/project-structure.md | 38 +++++++++ package-lock.json | 14 ++++ package.json | 18 +++++ scripts/check-all.sh | 70 +++++++--------- 9 files changed, 214 insertions(+), 84 deletions(-) create mode 100644 .nvmrc create mode 100644 .python-version create mode 100644 docs/dependency-management.md create mode 100644 docs/project-structure.md create mode 100644 package-lock.json create mode 100644 package.json diff --git a/.cursor/skills/timeflow-dev-standards/SKILL.md b/.cursor/skills/timeflow-dev-standards/SKILL.md index 7dd8fa1..aec26dd 100644 --- a/.cursor/skills/timeflow-dev-standards/SKILL.md +++ b/.cursor/skills/timeflow-dev-standards/SKILL.md @@ -25,40 +25,49 @@ bash scripts/check-all.sh frontend bash scripts/check-all.sh ``` -门禁内容:后端 `ruff check` + `ruff format --check` + `mypy`(strict)+ `pytest`;前端 `eslint` + `prettier --check` + `tsc --noEmit`。 +门禁内容:后端 `ruff check` + `ruff format --check` + `mypy`(strict)+ `pytest` + `alembic history`;若本机 PostgreSQL 可达则再跑 `alembic upgrade head` + `alembic check`(CI 始终跑这两项);前端 `eslint` + `prettier --check` + `tsc --noEmit` + `npm audit --audit-level=moderate`。 任何一项失败都必须修复后重跑,直到全绿。禁止用 `# noqa`、`# type: ignore`、`eslint-disable` 掩盖问题(确有必要时必须写明原因并在回复中向用户说明)。 ## 后端架构(强制分层) -新功能只能落在 `backend/src/timeapp/modules/<领域>/` 下,禁止写进 `api/`、`core/` 或 `main.py`: +后端按 Agent 边界组织,禁止把业务写进 `api/`、`core/` 或 `main.py`: ```text -modules/<领域>/ -├── __init__.py -├── router.py # 只做 HTTP 编排:解析请求、调 service、返回响应 -├── schemas.py # Pydantic 请求/响应模型 -├── service.py # 业务逻辑,禁止出现 FastAPI 对象(Request/Depends 等) -└── models.py # SQLAlchemy ORM 模型(需要持久化时) +src/timeapp/ +├── agents/ # 主 Agent 与专项 Agent +│ ├── main_agent/ # 唯一 Agent HTTP 入口:router / schemas / dispatcher +│ └── _agent/ # 内部能力包:schemas / service / prompts(禁止 router.py) +├── basic/ # 非 Agent 产品边界(手动业务、事项展示、OCR/ASR 等) +│ └── / # router / schemas / service / models(按需) +├── common/ # 跨 Agent 契约与共享能力 +│ ├── contracts/ # 统一响应等契约 +│ ├── data/ # 公共事实数据读写(专项 Agent 禁止直接调用) +│ ├── confirmation/ / questioning/ / llm/ / task_profile/ / context/ +├── api/ # 路由聚合、health、dependencies +└── core/ # Settings、DB engine / session / Base ``` -- 新领域模块必须在 `api/router.py` 中注册:`api_router.include_router(<模块>.router)` -- 横切能力(认证、DB 会话)统一放 `api/dependencies.py`,模块内禁止自建 +- 只有 `main_agent` 暴露 Agent HTTP 入口;专项 Agent 是进程内能力包,不直接读写数据库、不追问、不执行 `db_action` +- 对外 HTTP 路由(main_agent / basic)必须在 `api/router.py` 中注册:`api_router.include_router(...)` +- 横切能力(认证、DB 会话)统一放 `api/dependencies.py`,业务包内禁止自建 - 配置只能通过 `core/config.py` 的 `Settings` 读取,禁止在业务代码里直接 `os.environ` -- 模块之间只允许调用对方的 `service` 层函数,禁止跨模块 import `router` / `models` +- `basic//` 内部分层:`router` 只做 HTTP 编排,`service` 禁止出现 FastAPI 对象;包之间只允许调用对方 `service`,禁止跨包 import `router` / `models` +- ORM 模型放 `basic//models.py` 或经 `common/data/` 统一出口;专项 Agent 目录禁止出现 `models.py` 与数据库导入 ## API 设计 -- 路径用 kebab-case(`/goal-planning`),与现有模块前缀保持一致;资源用复数名词,禁止动词路径(`POST /todos`,不是 `/create-todo`) +- 路径用 kebab-case(`/main-agent`、`/unified-items`),与现有前缀保持一致;资源用复数名词,禁止动词路径(`POST /todos`,不是 `/create-todo`) - 请求/响应必须定义 Pydantic 模型并声明 `response_model`,禁止返回裸 dict - 状态码:创建 201、删除 204、404/409 等错误用 `HTTPException` 抛出,`detail` 用英文 - 分页列表统一 query 参数 `limit`(默认 20,上限 100)+ `offset` ## 数据库 -- ORM 模型放各模块 `models.py`,公共 `Base`、engine、session 放 `core/db.py`(首次建库时创建) +- 公共 `Base`、engine、session 放 `core/db.py`;迁移脚本放 `backend/alembic/versions/` - 表名 snake_case 复数(`todos`、`goal_plans`);所有表必须有 `id`、`created_at`、`updated_at` -- 任何模型变更必须生成 Alembic 迁移,禁止手改历史迁移、禁止 `Base.metadata.create_all` 用于生产路径 -- 查询写在 service 层,禁止在 router 里直接操作 session +- 任何模型变更必须生成 Alembic 迁移(`cd backend && uv run alembic revision --autogenerate -m "..."`),禁止手改历史迁移、禁止 `Base.metadata.create_all` 用于生产路径 +- 应用迁移:`cd backend && uv run alembic upgrade head` +- 查询写在 service / `common/data` 层,禁止在 router 里直接操作 session ## 前端结构(Expo RN) diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 0000000..ccc4c6c --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +20.20.2 diff --git a/.python-version b/.python-version new file mode 100644 index 0000000..ed7d51a --- /dev/null +++ b/.python-version @@ -0,0 +1 @@ +3.11.15 diff --git a/README.md b/README.md index c24caef..56d0cbd 100644 --- a/README.md +++ b/README.md @@ -1,55 +1,76 @@ # Timeflow -Timeflow 采用单仓库布局,包含 FastAPI 后端和 Expo React Native 移动端。 +Timeflow 是一个单仓库项目,包含 FastAPI 后端和 Expo React Native 移动端。 -```text -backend/ # FastAPI API 服务 -frontend/ # Expo React Native 应用(TypeScript) +## 固定版本 + +- Node.js `20.20.2` / npm `10.8.2`,见 `.nvmrc` 和 `package.json`。 +- Python `3.11.15` / uv `0.11.28`,见 `.python-version`、`backend/pyproject.toml` 和 `backend/uv.lock`。 +- PostgreSQL `16.4`,由根目录 Docker Compose 提供。 + +依赖使用规范见 [docs/dependency-management.md](docs/dependency-management.md), +完整目录设计见 [docs/project-structure.md](docs/project-structure.md)。 + +## 首次安装 + +```bash +cp .env.example .env +cp backend/.env.example backend/.env +cp frontend/.env.example frontend/.env +npm run install:frontend +npm run install:backend ``` -## 后端 +本地直接跑 API(不用 Compose)时,先确保 PostgreSQL 可用,再应用迁移: ```bash -cd backend -cp .env.example .env # 可选 -uv sync -uv run uvicorn timeapp.main:app --reload +cd backend && uv run alembic upgrade head ``` -API 健康检查: +## 启动 + +启动 PostgreSQL 和 API(容器入口会先执行 `alembic upgrade head`,再启动 uvicorn): + +```bash +docker compose up --build +``` -## 移动端 +需要 Docker Desktop 4+(内含 Docker Compose v2)。 +Android 模拟器使用 `frontend/.env.example` 中的 `10.0.2.2` 访问宿主机 API; +真机调试时,将 `EXPO_PUBLIC_API_URL` 改为开发机器的局域网 IP。 + +只启动移动端: ```bash -cd frontend -npm install -npm run start +npm run dev:frontend ``` -目标平台为 Android,日常开发用 `npm run android` 在模拟器/真机上运行。 +本地只启动 API(需已执行过迁移): -## 开发规范与质量检查 +```bash +npm run dev:backend +``` -开发规范见 `.cursor/skills/timeflow-dev-standards/SKILL.md`(AI 会在每次改代码前强制加载)。 +API 健康检查: -- 后端:Ruff(lint + format)、mypy strict、pytest,配置在 `backend/pyproject.toml` -- 前端:ESLint(expo 规则集)、Prettier、tsc,脚本在 `frontend/package.json` +## 质量检查 -一键全量检查: +官方门禁(改完代码后优先使用;后端在 PostgreSQL 可达时会跑 Alembic upgrade/check): ```bash -bash scripts/check-all.sh # 前后端全部 -bash scripts/check-all.sh backend # 仅后端 -bash scripts/check-all.sh frontend # 仅前端 +bash scripts/check-all.sh +# 或:bash scripts/check-all.sh backend +# 或:bash scripts/check-all.sh frontend ``` -## Git hooks(提交/推送拦截) +等价的分项命令: -hooks 不预置在仓库中。git 仓库就绪后,在 Cursor 里对 AI 说「启用 git hooks」,AI 会按 -`.cursor/skills/timeflow-git-hooks/SKILL.md` 中的模板把三个 hook 写入 `.git/hooks/` 并验证: +```bash +npm run check:frontend +npm run check:backend +npm --prefix frontend audit --audit-level=moderate +``` -- `pre-commit`:按改动范围运行对应端检查;检查不通过时阻止提交 -- `commit-msg`:强制 [Conventional Commits](https://www.conventionalcommits.org/zh-hans/v1.0.0/) 的 `type(scope): 描述` 格式(feat/fix/docs/style/refactor/perf/test/build/ci/chore/revert) -- `pre-push`:全量检查通过才允许推送 +后端使用 `uv` 管理依赖,前端使用 `npm` 管理依赖。两种语言使用各自原生的锁文件,根目录脚本只负责统一入口。 -停用:删除 `.git/hooks/` 下对应文件即可。 +后端按 Agent 边界组织:`agents/` 放主 Agent 和专项 Agent,`common/` 放公共数据、确认、反问、LLM、任务级画像、上下文和统一响应契约,`basic/` 放手动业务与 OCR/ASR。只有主 Agent 暴露 Agent HTTP 入口;专项 Agent 是内部能力包,不直接读写数据库,统一响应契约见 `backend/src/timeapp/common/contracts/agent_response.py`。 diff --git a/docs/dependency-management.md b/docs/dependency-management.md new file mode 100644 index 0000000..3219100 --- /dev/null +++ b/docs/dependency-management.md @@ -0,0 +1,36 @@ +# 依赖管理 + +## 运行时基线 + +| 范围 | 工具 | 固定版本 | 权威来源 | +| --- | --- | --- | --- | +| 前端运行时 | Node.js | 20.20.2 | `.nvmrc` | +| 前端包管理器 | npm | 10.8.2 | 根目录与 `frontend/package.json` | +| 后端运行时 | Python | 3.11.15 | `.python-version` | +| 后端包管理器 | uv | 0.11.28 | Dockerfile 与 CI workflow | +| 开发数据库 | PostgreSQL | 16.4 | `docker-compose.yml` | + +## 允许的命令 + +- 前端依赖:`npm --prefix frontend ci` +- 后端依赖:`cd backend && uv sync --locked --all-groups` +- 官方门禁:`bash scripts/check-all.sh`(或带 `backend` / `frontend` 参数);后端在 PostgreSQL 可达时含 `alembic upgrade head` + `alembic check` +- 前端质量与安全:`npm --prefix frontend run check` 与 `npm --prefix frontend audit --audit-level=moderate` +- 后端质量:`cd backend && uv run ruff check . && uv run ruff format --check . && uv run mypy && uv run pytest && uv run alembic history` +- 数据库迁移:`cd backend && uv run alembic upgrade head`;模型变更后 `uv run alembic revision --autogenerate -m "..."` + +禁止在本仓库使用 yarn、pnpm、`pip install`、Poetry,或再引入第二套锁文件。 + +## 锁文件规则 + +- 修改 `frontend/package.json` 后必须提交 `frontend/package-lock.json` +- 修改 `backend/pyproject.toml` 后必须提交 `backend/uv.lock` +- 安装与 CI 使用 `npm ci` 和 `uv sync --locked`,二者不得改写锁文件 +- 运行时依赖与开发依赖保持在各自声明的分区中 + +## 更新流程 + +1. 只改对应 manifest 里的一处依赖声明。 +2. 只重新生成该包管理器的锁文件。 +3. 跑对应端的质量检查与安全审计(优先 `bash scripts/check-all.sh`)。 +4. 若改了 Expo 相关依赖,提交前跑 Expo Doctor 与 Android export。 diff --git a/docs/project-structure.md b/docs/project-structure.md new file mode 100644 index 0000000..ff38acb --- /dev/null +++ b/docs/project-structure.md @@ -0,0 +1,38 @@ +# 项目结构 + +```text +timeflow/ +├── .env.example # Docker Compose 开发默认值 +├── .github/workflows/ci.yml # 质量、安全、导出与容器检查 +├── backend/ +│ ├── Dockerfile +│ ├── docker-entrypoint.sh # 启动前执行 alembic upgrade head +│ ├── alembic.ini # Alembic 配置(连接串来自 Settings) +│ ├── alembic/ # 迁移环境与 versions/ +│ ├── pyproject.toml +│ ├── uv.lock +│ ├── README.md +│ ├── src/timeapp/ +│ │ ├── agents/ # 主 Agent 与专项 Agent 骨架 +│ │ ├── api/ # HTTP 路由聚合与健康检查 +│ │ ├── basic/ # 手动业务与 OCR/ASR 边界 +│ │ ├── common/ # 契约与共享能力边界 +│ │ └── core/ # 配置与数据库基础设施 +│ └── tests/ +├── docs/ +│ ├── dependency-management.md +│ └── project-structure.md +├── frontend/ +│ ├── .env.example # Android 模拟器 API 基址 +│ ├── package.json +│ ├── package-lock.json +│ └── src/ +│ ├── api/ +│ ├── constants/ +│ └── screens/ +├── docker-compose.yml # API 与 PostgreSQL 开发栈 +├── package.json # 仅跨项目统一命令入口 +└── scripts/check-all.sh # 官方完工门禁 +``` + +`agents/` 只放编排与能力框架,不包含已实现的 LLM、确认、CRUD 或数据库业务逻辑。`common/` 负责跨 Agent 契约与后续共享能力;`basic/` 负责非 Agent 产品边界。 diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..aed3543 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,14 @@ +{ + "name": "timeflow", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "timeflow", + "engines": { + "node": ">=20.20.2 <21", + "npm": ">=10.8.2 <11" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..06a80b9 --- /dev/null +++ b/package.json @@ -0,0 +1,18 @@ +{ + "name": "timeflow", + "private": true, + "engines": { + "node": ">=20.20.2 <21", + "npm": ">=10.8.2 <11" + }, + "packageManager": "npm@10.8.2", + "scripts": { + "check": "npm run check:frontend && npm run check:backend", + "check:frontend": "npm --prefix frontend run check", + "check:backend": "cd backend && uv run ruff check . && uv run ruff format --check . && uv run mypy && uv run pytest && uv run alembic history >/dev/null", + "install:frontend": "npm --prefix frontend ci", + "install:backend": "cd backend && uv sync --locked --all-groups", + "dev:frontend": "npm --prefix frontend run start", + "dev:backend": "cd backend && uv run uvicorn timeapp.main:app --reload" + } +} diff --git a/scripts/check-all.sh b/scripts/check-all.sh index 104b27a..0dd5718 100755 --- a/scripts/check-all.sh +++ b/scripts/check-all.sh @@ -1,57 +1,49 @@ #!/usr/bin/env bash -# 全仓质量检查入口:后端 ruff/mypy/pytest + 前端 eslint/prettier/tsc。 -# 用法: scripts/check-all.sh [backend|frontend](不带参数则两端都跑) -set -uo pipefail + +set -euo pipefail ROOT="$(cd "$(dirname "$0")/.." && pwd)" TARGET="${1:-all}" -FAILED=0 case "$TARGET" in - all | backend | frontend) ;; + all|backend|frontend) ;; *) - echo "用法: $0 [backend|frontend]" >&2 + echo "Usage: $0 [all|backend|frontend]" >&2 exit 2 ;; esac -run_step() { - local name="$1" - shift - echo "==> ${name}" - if ! "$@"; then - echo "!! ${name} 未通过" - FAILED=1 - fi +database_reachable() { + uv run python -c ' +from sqlalchemy import create_engine, text +from timeapp.core.config import get_settings + +engine = create_engine(get_settings().database_url, pool_pre_ping=True) +with engine.connect() as connection: + connection.execute(text("SELECT 1")) +' } if [[ "$TARGET" == "all" || "$TARGET" == "backend" ]]; then - if cd "$ROOT/backend"; then - run_step "backend: ruff check" uv run ruff check . - run_step "backend: ruff format" uv run ruff format --check . - run_step "backend: mypy" uv run mypy - run_step "backend: pytest" uv run pytest - else - echo "!! 找不到后端目录:$ROOT/backend" - FAILED=1 - fi + ( + cd "$ROOT/backend" + uv sync --locked --all-groups + uv run ruff check . + uv run ruff format --check . + uv run mypy + uv run pytest + uv run alembic history >/dev/null + if database_reachable; then + uv run alembic upgrade head + uv run alembic check + else + echo "warning: PostgreSQL unreachable; skipped alembic upgrade/check (CI still runs them)" >&2 + fi + ) fi if [[ "$TARGET" == "all" || "$TARGET" == "frontend" ]]; then - if cd "$ROOT/frontend"; then - run_step "frontend: eslint" npm run --silent lint - run_step "frontend: prettier" npm run --silent format:check - run_step "frontend: tsc" npm run --silent typecheck - else - echo "!! 找不到前端目录:$ROOT/frontend" - FAILED=1 - fi -fi - -if [[ "$FAILED" -ne 0 ]]; then - echo "" - echo "检查未通过,请修复后重试。" - exit 1 + npm --prefix "$ROOT/frontend" ci + npm --prefix "$ROOT/frontend" run check + npm --prefix "$ROOT/frontend" audit --audit-level=moderate fi -echo "" -echo "全部检查通过。"