From 1852a3bf9f15d8ece310dc2ec8fc81e6caefdf1f Mon Sep 17 00:00:00 2001 From: mac Date: Fri, 24 Jul 2026 14:34:56 +0800 Subject: [PATCH 1/3] feat(tooling): add unified quality check script --- scripts/check-all.sh | 49 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100755 scripts/check-all.sh diff --git a/scripts/check-all.sh b/scripts/check-all.sh new file mode 100755 index 0000000..4942fd9 --- /dev/null +++ b/scripts/check-all.sh @@ -0,0 +1,49 @@ +#!/usr/bin/env bash + +set -euo pipefail + +ROOT="$(cd "$(dirname "$0")/.." && pwd)" +TARGET="${1:-all}" + +case "$TARGET" in + all|backend|frontend) ;; + *) + echo "Usage: $0 [all|backend|frontend]" >&2 + exit 2 + ;; +esac + +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 + ( + 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 >/dev/null 2>&1; then + uv run alembic upgrade head + uv run alembic check + else + echo "warning: PostgreSQL unreachable; skipped alembic upgrade/check" >&2 + fi + ) +fi + +if [[ "$TARGET" == "all" || "$TARGET" == "frontend" ]]; then + npm --prefix "$ROOT/frontend" ci + npm --prefix "$ROOT/frontend" run check + npm --prefix "$ROOT/frontend" audit --audit-level=moderate +fi From e3d2c00a72003d56d01875718bd5ca0c74b36faa Mon Sep 17 00:00:00 2001 From: mac Date: Fri, 24 Jul 2026 14:35:19 +0800 Subject: [PATCH 2/3] docs: update project documentation --- README.md | 132 ++++++++++++++++++++++++++- docs/dependency-management.md | 36 ++++++++ docs/project-structure.md | 39 ++++++++ docs/skills/dev-standards/SKILL.md | 140 +++++++++++++++++++++++++++++ docs/skills/git-hooks/SKILL.md | 134 +++++++++++++++++++++++++++ 5 files changed, 480 insertions(+), 1 deletion(-) create mode 100644 docs/dependency-management.md create mode 100644 docs/project-structure.md create mode 100644 docs/skills/dev-standards/SKILL.md create mode 100644 docs/skills/git-hooks/SKILL.md diff --git a/README.md b/README.md index 7aa54f0..2397115 100644 --- a/README.md +++ b/README.md @@ -1 +1,131 @@ -# timeflow +# Timeflow + +**让时间真正流动起来的智能日程与目标助手。** + +Timeflow 面向需要同时管理日程、待办与长期目标的个人用户,把「记下要做什么」升级为「帮你安排、拆解、调整与复盘」。你可以用文字、语音或图片表达意图;系统理解后给出可执行的安排,并在计划偏离时帮助你重新排程。 + +> 当前仓库处于架构骨架与基础设施阶段:API、数据库迁移、质量门禁与 Agent / 业务目录边界已就绪,核心业务能力将按模块逐步落地。 + +--- + +## 产品定位 + +传统待办工具擅长记录,却很少帮你回答更难的问题: + +- 这件事该插在今天的哪一段空档? +- 一个模糊目标如何拆成可执行步骤? +- 突发冲突出现时,整周计划如何最小代价地重排? +- 执行一段时间后,该如何复盘并改进下一次安排? + +Timeflow 的目标是成为**时间决策的协同方**:不只存事项,还参与调度、拆解、重排与反馈闭环,让计划跟着真实生活持续流动。 + +--- + +## 核心能力 + +| 能力 | 说明 | +| --- | --- | +| 日程与待办调度 | 创建、调整日程与待办,结合已有安排给出可落地的时间建议 | +| 任务拆解 | 将模糊目标拆成可执行的子任务与阶段性计划 | +| 智能重排 | 在冲突、延期或优先级变化时,重新规划后续时间线 | +| 执行反馈 | 记录完成情况与偏差,为后续调度提供依据 | +| 周期复盘 | 对目标与时间使用进行回顾,沉淀可复用的改进建议 | +| 多模态输入 | 支持文字、语音(ASR)与图片(OCR)表达意图 | +| 日程视图 | 按时间展示日程、待办与目标任务,支持确认后的统一查看与操作 | + +消息提醒等端侧能力由移动端负责;后端聚焦理解、规划与事实数据管理。 + +--- + +## 目标架构(概览) + +Timeflow 的 Agent 协作采用「主入口 + 专项能力」的职责划分,并与非 Agent 业务边界并存。下图展示各模块的职责与协作关系: + +```text +用户(Android App) + │ + ▼ + 主 Agent(唯一对话 / 编排入口) + │ + ├── 日程待办 Agent + ├── 任务拆解 Agent + ├── 重排 Agent + ├── 复盘 Agent + └── 反馈 Agent + │ + ▼ + 公共事实数据 / 用户画像 / 对象存储 +``` + +- **主 Agent(目标职责)**:对外提供唯一 Agent HTTP 入口,负责理解意图、追问补全、确认写操作与结果编排。 +- **专项 Agent(目标职责)**:作为进程内能力包,专注各自领域推理,不直接对外暴露接口、不直接落库。 +- **Basic 业务**:身份、用户画像、OCR / ASR、用量管理等非对话型产品能力。 +- **Common 能力**:公共数据读写、LLM 调用、任务画像、对象存储与系统日志等横切支撑。 + +详细目录说明见 [docs/project-structure.md](docs/project-structure.md)。 + +--- + +## 技术栈 + +| 层级 | 选型 | +| --- | --- | +| 移动端 | Expo React Native(目标平台 **Android**)+ TypeScript | +| API | FastAPI + SQLAlchemy + Alembic | +| 数据 | PostgreSQL 16 | +| 工程 | uv(后端) / npm(前端),Docker Compose 本地栈 | + +运行时版本基线:Node.js `>=20.20.2 <21`、Python `>=3.11,<3.12`(Docker 使用 `3.11.15`)、uv `0.11.28`、PostgreSQL `16.4`。依赖约定见 [docs/dependency-management.md](docs/dependency-management.md)。 + +--- + +## 快速开始 + +```bash +# 1. 环境文件 +cp .env.example .env +cp backend/.env.example backend/.env +cp frontend/.env.example frontend/.env + +# 2. 安装依赖 +npm --prefix frontend ci +(cd backend && uv sync --locked --all-groups) + +# 3. 启动 PostgreSQL + API(容器会先执行 alembic upgrade head) +docker compose up --build +``` + +- API 健康检查: +- 仅启动移动端:`npm --prefix frontend run start`(Android:`npm --prefix frontend run android`) +- 仅本地启动 API:`cd backend && uv run alembic upgrade head && uv run uvicorn timeapp.main:app --reload` +- Android 模拟器默认通过 `10.0.2.2` 访问宿主机 API;真机请将 `EXPO_PUBLIC_API_URL` 改为开发机局域网 IP + +更多后端说明见 [backend/README.md](backend/README.md)。 + +--- + +## 质量与协作 + +```bash +bash scripts/check-all.sh # 全量 +bash scripts/check-all.sh backend # 仅后端 +bash scripts/check-all.sh frontend # 仅前端 +``` + +开发规范与硬性约束见 [docs/skills/dev-standards/SKILL.md](docs/skills/dev-standards/SKILL.md)。提交信息遵循 [Conventional Commits](https://www.conventionalcommits.org/zh-hans/v1.0.0/)。 + +--- + +## 文档索引 + +| 文档 | 内容 | +| --- | --- | +| [docs/project-structure.md](docs/project-structure.md) | 仓库目录与模块边界 | +| [docs/dependency-management.md](docs/dependency-management.md) | 依赖与锁文件规则 | +| [docs/skills/dev-standards/SKILL.md](docs/skills/dev-standards/SKILL.md) | 开发规范 | +| [docs/skills/git-hooks/SKILL.md](docs/skills/git-hooks/SKILL.md) | Git Hooks 启用指引 | +| [backend/README.md](backend/README.md) | API 本地启动与迁移 | + +--- + +Timeflow —— 让计划跟随生活流动,而不是让生活迁就一份过时的清单。 diff --git a/docs/dependency-management.md b/docs/dependency-management.md new file mode 100644 index 0000000..583b678 --- /dev/null +++ b/docs/dependency-management.md @@ -0,0 +1,36 @@ +# 依赖管理 + +## 运行时基线 + +| 范围 | 工具 | 版本基线 | 权威来源 | +| --- | --- | --- | --- | +| 前端运行时 | Node.js | `>=20.20.2 <21` | `frontend/package.json`(`engines`) | +| 前端包管理器 | npm | 10.8.2 | `frontend/package.json`(`engines` / `packageManager`) | +| 后端运行时 | Python | `>=3.11,<3.12`(Docker:3.11.15) | `backend/pyproject.toml`(版本范围)与 `backend/Dockerfile`(精确版本) | +| 后端包管理器 | uv | 0.11.28 | `backend/Dockerfile` | +| 开发数据库 | 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,或再引入第二套锁文件;`backend/Dockerfile` 仅使用 pip 安装固定版本的 uv 作为构建引导。 + +## 锁文件规则 + +- 修改 `frontend/package.json` 后必须提交 `frontend/package-lock.json` +- 修改 `backend/pyproject.toml` 后必须提交 `backend/uv.lock` +- 安装与质量检查使用 `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..0e5a05e --- /dev/null +++ b/docs/project-structure.md @@ -0,0 +1,39 @@ +# 项目结构 + +```text +timeflow/ +├── .env.example # Docker Compose 开发默认值 +├── 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/ # 数据、LLM、任务画像、对象存储与日志边界 +│ │ └── core/ # 配置与数据库基础设施 +│ └── tests/ +├── docs/ +│ ├── project-structure.md +│ ├── dependency-management.md +│ └── skills/ # 开发规范与 git hooks 指引 +│ ├── dev-standards/ +│ └── git-hooks/ +├── frontend/ +│ ├── .env.example # Android 模拟器 API 基址 +│ ├── package.json +│ ├── package-lock.json +│ └── src/ +│ ├── api/ +│ ├── constants/ +│ └── screens/ +├── docker-compose.yml # API 与 PostgreSQL 开发栈 +└── scripts/check-all.sh # 官方完工门禁 +``` + +`agents/` 及其主 Agent、五个子 Agent 目录保留为空(仅 `__init__.py`),实现文件将在数据库和接口设计完成后添加。`common/` 负责共享数据、LLM、任务画像、对象存储和系统日志;`basic/` 负责非 Agent 产品边界。消息推送由前端实现。 diff --git a/docs/skills/dev-standards/SKILL.md b/docs/skills/dev-standards/SKILL.md new file mode 100644 index 0000000..81a714f --- /dev/null +++ b/docs/skills/dev-standards/SKILL.md @@ -0,0 +1,140 @@ +--- +name: dev-standards +description: Timeflow 项目的开发规范与硬性约束。在本仓库中编写、修改、重构任何代码(backend FastAPI 或 frontend Expo RN)之前必须先阅读并遵守。涵盖架构分层、API 设计、数据库、前端结构、命名、注释语言、测试与安全底线,以及 AI 禁止行为清单。 +--- + +# Timeflow 开发规范 + +本文件是本仓库的最高开发约束。与本文件冲突的实现一律不允许产出。 +改完代码后必须运行检查(见「完工门禁」),检查不过不算完成。 + +## 项目概览 + +- `backend/`:FastAPI + SQLAlchemy + Alembic,Python `>=3.11,<3.12`,包管理用 **uv**(开发环境禁止 pip install / poetry) +- `frontend/`:Expo React Native + TypeScript strict,**目标平台为 Android**,包管理用 **npm**(禁止 yarn / pnpm),本地运行用 `npm --prefix frontend 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` + `alembic history`;若本机 PostgreSQL 可达则再跑 `alembic upgrade head` + `alembic check`;前端 `eslint` + `prettier --check` + `tsc --noEmit` + `npm audit --audit-level=moderate`。 +任何一项失败都必须修复后重跑,直到全绿。禁止用 `# noqa`、`# type: ignore`、`eslint-disable` 掩盖问题(确有必要时必须写明原因并在回复中向用户说明)。 + +## 后端架构(强制分层) + +后端按 Agent 边界组织,禁止把业务写进 `api/`、`core/` 或 `main.py`: + +```text +src/timeapp/ +├── agents/ # 主 Agent 与五个子 Agent 的目录边界,当前仅含空 __init__.py +│ ├── main_agent/ +│ ├── schedule_todo_agent/ +│ ├── task_breakdown_agent/ +│ ├── replanning_agent/ +│ ├── review_agent/ +│ └── feedback_agent/ +├── basic/ # 非 Agent 产品边界(手动业务、用户画像、OCR/ASR 等) +│ └── / # router / schemas / service / models(按需) +├── common/ # 跨 Agent 共享能力 +│ ├── data/ # 公共事实数据读写(专项 Agent 禁止直接调用) +│ ├── llm/ # 统一模型调用与提示词管理 +│ ├── task_profile/ # 任务级画像 +│ ├── object_storage/ # 图片与音频对象存储 +│ └── system_logs/ # 系统日志与业务审计 +├── api/ # 路由聚合、health、dependencies +└── core/ # Settings、DB engine / session / Base +``` + +- Agent 调用协议和实现文件尚未确定;新增 Agent 逻辑必须依据 Wiki 设计,通过独立功能提交实现 +- 目标架构中只有 `main_agent` 暴露 Agent HTTP 入口;专项 Agent 是进程内能力包,不直接读写数据库、不追问、不执行 `db_action` +- 对外 HTTP 路由必须在 `api/router.py` 中注册:`api_router.include_router(...)` +- 横切能力(认证、DB 会话)统一放 `api/dependencies.py`,业务包内禁止自建 +- 配置只能通过 `core/config.py` 的 `Settings` 读取,禁止在业务代码里直接 `os.environ` +- `basic//` 内部分层:`router` 只做 HTTP 编排,`service` 禁止出现 FastAPI 对象;包之间只允许调用对方 `service`,禁止跨包 import `router` / `models` +- ORM 模型放 `basic//models.py` 或经 `common/data/` 统一出口;专项 Agent 目录禁止出现 `models.py` 与数据库导入 + +## API 设计 + +- 路径用 kebab-case,与现有前缀保持一致;资源用复数名词,禁止动词路径(`POST /todos`,不是 `/create-todo`) +- 请求/响应必须定义 Pydantic 模型并声明 `response_model`,禁止返回裸 dict +- 状态码:创建 201、删除 204、404/409 等错误用 `HTTPException` 抛出,`detail` 用英文 +- 分页列表统一 query 参数 `limit`(默认 20,上限 100)+ `offset` + +## 数据库 + +- 公共 `Base`、engine、session 放 `core/db.py`;迁移脚本放 `backend/alembic/versions/` +- 表名 snake_case 复数(`todos`、`goal_plans`);所有表必须有 `id`、`created_at`、`updated_at` +- 任何模型变更必须生成 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) + +新代码按以下目录组织(目录不存在时按需创建): + +```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`,覆盖正常路径 + 至少一个错误路径 +- 改完必须实际运行 `cd backend && uv run pytest` 并通过;禁止提交只为凑数、无断言的测试 + +## 安全底线 + +- 密钥、token、连接串只能走环境变量(`TIMEAPP_` 前缀)+ `.env`(已 gitignore),新增变量必须同步更新 `.env.example`(放占位值) +- 禁止把 `.env`、真实密钥、用户数据写进代码、测试、文档 +- SQL 只能通过 ORM / 绑定参数,禁止字符串拼接 SQL +- 禁止 `print` 调试(ruff T20 会拦截);日志不得输出密码、token、个人敏感信息 +- 后端禁止吞异常(裸 `except: pass`);对外错误信息不暴露堆栈和内部路径 + +## AI 禁止行为清单 + +1. 禁止未经用户同意引入新依赖、新框架、新服务(改 `backend/pyproject.toml` / `frontend/package.json` 依赖前必须先说明理由并征得同意) +2. 禁止修改与当前任务无关的代码、重排无关 import、顺手重构 +3. 禁止留 TODO 空壳函数、`pass` 占位实现交差;做不完就明确告诉用户哪部分没做 +4. 禁止降低门禁:不得删除/放宽 ruff、mypy、eslint、tsconfig 的现有配置 +5. 未经用户明确授权,禁止执行 git init、commit、push、merge 或创建 PR;用户明确授权后,只能在授权范围内操作。启用 hooks 时按 `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): 新增待办创建接口`。 +启用拦截:用户提出需求后,按 `docs/skills/git-hooks/SKILL.md` 创建 hooks(pre-commit 检查改动端、commit-msg 校验格式、pre-push 全量检查)。 diff --git a/docs/skills/git-hooks/SKILL.md b/docs/skills/git-hooks/SKILL.md new file mode 100644 index 0000000..ccdda5f --- /dev/null +++ b/docs/skills/git-hooks/SKILL.md @@ -0,0 +1,134 @@ +--- +name: 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/hooks/` 下的这三个文件。 + +## 创建步骤 + +用文件写入工具将下面三个模板**原样**写入对应路径(已存在同名 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=ACMRD)" + +[[ -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、fixup 和 squash 等 git 自动生成的信息直接放行;revert 仍使用 `revert:`。 +if echo "$SUBJECT" | grep -qE '^(Merge|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` From 007d1ae0add58957935f1d40eff711937fa0539d Mon Sep 17 00:00:00 2001 From: mac Date: Fri, 24 Jul 2026 14:55:59 +0800 Subject: [PATCH 3/3] fix(tooling): harden local quality checks --- README.md | 2 ++ docs/dependency-management.md | 4 +++- docs/skills/dev-standards/SKILL.md | 4 +++- docs/skills/git-hooks/SKILL.md | 18 ++++++++++---- scripts/check-all.sh | 38 ++++++++++++++++++++---------- 5 files changed, 47 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index 2397115..647b80a 100644 --- a/README.md +++ b/README.md @@ -112,6 +112,8 @@ bash scripts/check-all.sh backend # 仅后端 bash scripts/check-all.sh frontend # 仅前端 ``` +数据库迁移验证只在显式设置 `TIMEAPP_CHECK_DATABASE_URL` 时执行,并且数据库名必须以 `_test` 或 `_check` 结尾。脚本不会对应用使用的 `TIMEAPP_DATABASE_URL` 执行迁移;一旦提供检查数据库地址,配置、连接或迁移错误都会使门禁失败。 + 开发规范与硬性约束见 [docs/skills/dev-standards/SKILL.md](docs/skills/dev-standards/SKILL.md)。提交信息遵循 [Conventional Commits](https://www.conventionalcommits.org/zh-hans/v1.0.0/)。 --- diff --git a/docs/dependency-management.md b/docs/dependency-management.md index 583b678..89a7c9e 100644 --- a/docs/dependency-management.md +++ b/docs/dependency-management.md @@ -14,13 +14,15 @@ - 前端依赖:`npm --prefix frontend ci` - 后端依赖:`cd backend && uv sync --locked --all-groups` -- 官方门禁:`bash scripts/check-all.sh`(或带 `backend` / `frontend` 参数);后端在 PostgreSQL 可达时含 `alembic upgrade head` + `alembic check` +- 官方门禁:`bash scripts/check-all.sh`(或带 `backend` / `frontend` 参数);显式设置 `TIMEAPP_CHECK_DATABASE_URL` 后,后端还会执行 `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,或再引入第二套锁文件;`backend/Dockerfile` 仅使用 pip 安装固定版本的 uv 作为构建引导。 +数据库迁移检查不得复用应用的 `TIMEAPP_DATABASE_URL`。检查数据库必须通过 `TIMEAPP_CHECK_DATABASE_URL` 显式提供,名称以 `_test` 或 `_check` 结尾,并且只用于可随时重建的数据;配置、连接或迁移失败会直接终止门禁。未设置时脚本会明确警告迁移检查未运行。 + ## 锁文件规则 - 修改 `frontend/package.json` 后必须提交 `frontend/package-lock.json` diff --git a/docs/skills/dev-standards/SKILL.md b/docs/skills/dev-standards/SKILL.md index 81a714f..c52fdd6 100644 --- a/docs/skills/dev-standards/SKILL.md +++ b/docs/skills/dev-standards/SKILL.md @@ -25,9 +25,11 @@ bash scripts/check-all.sh frontend bash scripts/check-all.sh ``` -门禁内容:后端 `ruff check` + `ruff format --check` + `mypy`(strict)+ `pytest` + `alembic history`;若本机 PostgreSQL 可达则再跑 `alembic upgrade head` + `alembic check`;前端 `eslint` + `prettier --check` + `tsc --noEmit` + `npm audit --audit-level=moderate`。 +门禁内容:后端 `ruff check` + `ruff format --check` + `mypy`(strict)+ `pytest` + `alembic history`;显式设置 `TIMEAPP_CHECK_DATABASE_URL` 后,再对该检查数据库执行 `alembic upgrade head` + `alembic check`;前端 `eslint` + `prettier --check` + `tsc --noEmit` + `npm audit --audit-level=moderate`。 任何一项失败都必须修复后重跑,直到全绿。禁止用 `# noqa`、`# type: ignore`、`eslint-disable` 掩盖问题(确有必要时必须写明原因并在回复中向用户说明)。 +迁移检查禁止使用应用的 `TIMEAPP_DATABASE_URL`,只能显式提供数据库名以 `_test` 或 `_check` 结尾的 `TIMEAPP_CHECK_DATABASE_URL`。该数据库必须是可随时重建的本地或测试数据库;一旦提供,配置、连接和迁移错误都必须使门禁失败。未提供时必须明确报告迁移检查未运行。 + ## 后端架构(强制分层) 后端按 Agent 边界组织,禁止把业务写进 `api/`、`core/` 或 `main.py`: diff --git a/docs/skills/git-hooks/SKILL.md b/docs/skills/git-hooks/SKILL.md index ccdda5f..99574fd 100644 --- a/docs/skills/git-hooks/SKILL.md +++ b/docs/skills/git-hooks/SKILL.md @@ -26,16 +26,26 @@ chmod +x .git/hooks/pre-commit .git/hooks/commit-msg .git/hooks/pre-push ```bash #!/usr/bin/env bash -# 提交前拦截:只检查本次提交涉及的端,检查不通过则禁止提交。 +# 提交前拦截:在暂存区快照中检查本次提交涉及的端,检查不通过则禁止提交。 set -uo pipefail -ROOT="$(git rev-parse --show-toplevel)" STAGED="$(git diff --cached --name-only --diff-filter=ACMRD)" [[ -z "$STAGED" ]] && exit 0 +CHECKOUT="$(mktemp -d)" || { + echo "[pre-commit] 无法创建暂存区检查目录。" + exit 1 +} +trap 'rm -rf "$CHECKOUT"' EXIT HUP INT TERM + +git checkout-index --all --force --prefix="$CHECKOUT/" || { + echo "[pre-commit] 无法导出暂存区快照。" + exit 1 +} + if echo "$STAGED" | grep -q '^backend/'; then - "$ROOT/scripts/check-all.sh" backend || { + "$CHECKOUT/scripts/check-all.sh" backend || { echo "" echo "[pre-commit] 后端检查未通过,提交已被拦截。" exit 1 @@ -43,7 +53,7 @@ if echo "$STAGED" | grep -q '^backend/'; then fi if echo "$STAGED" | grep -q '^frontend/'; then - "$ROOT/scripts/check-all.sh" frontend || { + "$CHECKOUT/scripts/check-all.sh" frontend || { echo "" echo "[pre-commit] 前端检查未通过,提交已被拦截。" exit 1 diff --git a/scripts/check-all.sh b/scripts/check-all.sh index 4942fd9..a51beb6 100755 --- a/scripts/check-all.sh +++ b/scripts/check-all.sh @@ -4,6 +4,7 @@ set -euo pipefail ROOT="$(cd "$(dirname "$0")/.." && pwd)" TARGET="${1:-all}" +CHECK_DATABASE_URL="${TIMEAPP_CHECK_DATABASE_URL:-}" case "$TARGET" in all|backend|frontend) ;; @@ -13,15 +14,31 @@ case "$TARGET" in ;; esac -database_reachable() { - uv run python -c ' -from sqlalchemy import create_engine, text -from timeapp.core.config import get_settings +run_database_checks() { + if [[ -z "$CHECK_DATABASE_URL" ]]; then + echo "warning: TIMEAPP_CHECK_DATABASE_URL is not set; skipped alembic upgrade/check" >&2 + return 0 + fi -engine = create_engine(get_settings().database_url, pool_pre_ping=True) -with engine.connect() as connection: - connection.execute(text("SELECT 1")) + TIMEAPP_CHECK_DATABASE_URL="$CHECK_DATABASE_URL" uv run python -c ' +import os + +from sqlalchemy.engine import make_url + +try: + url = make_url(os.environ["TIMEAPP_CHECK_DATABASE_URL"]) +except Exception: + raise SystemExit("TIMEAPP_CHECK_DATABASE_URL must be a valid SQLAlchemy URL") from None + +database = url.database or "" +if not url.drivername.startswith("postgresql"): + raise SystemExit("TIMEAPP_CHECK_DATABASE_URL must use PostgreSQL") +if not database.endswith(("_test", "_check")): + raise SystemExit("TIMEAPP_CHECK_DATABASE_URL database must end with _test or _check") ' + + TIMEAPP_DATABASE_URL="$CHECK_DATABASE_URL" uv run alembic upgrade head + TIMEAPP_DATABASE_URL="$CHECK_DATABASE_URL" uv run alembic check } if [[ "$TARGET" == "all" || "$TARGET" == "backend" ]]; then @@ -33,12 +50,7 @@ if [[ "$TARGET" == "all" || "$TARGET" == "backend" ]]; then uv run mypy uv run pytest uv run alembic history >/dev/null - if database_reachable >/dev/null 2>&1; then - uv run alembic upgrade head - uv run alembic check - else - echo "warning: PostgreSQL unreachable; skipped alembic upgrade/check" >&2 - fi + run_database_checks ) fi