diff --git a/README.md b/README.md index 72bd52b..dee0866 100644 --- a/README.md +++ b/README.md @@ -1,136 +1,241 @@ -# FTB 项目管理系统 +# FTB 智能项目管理系统 -基于 Next.js + NestJS 的项目管理系统,包含产品管理、项目管理、版本管理、需求管理、加班记录、成员/角色管理等模块。 +FTB 是一个面向中小型研发团队的智能项目管理平台,核心目标是帮助产品/项目经理在一个页面判断:**开发完了吗 → 验收过了吗 → 还有多少 Bug → 能不能发布**。 + +系统核心层级: + +```text +产品 Product → 项目 Project → 版本 Version → 需求 / 开发任务 / 测试用例 / Bug +``` + +当前项目已经进入 **V2.1 服务端持久化阶段**:前端仍保留现有 Zustand store 数据形状,但业务主数据已切到 NestJS + PostgreSQL `app_data` 文档表,浏览器只保留登录会话。 + +## 开始前必读 + +新会话或新开发任务开始前,先读这 4 份项目文档,避免重复讨论已确定方案: + +- `docs/architecture.md`:整体架构、心智模型、关键设计原则 +- `docs/decisions.md`:关键设计决策记录,以及为什么这么做 +- `docs/workflow.md`:工作流程、协作偏好、命名规范 +- `docs/roadmap.md`:V1/V2/V3 路线图和已完成清单 + +## 核心心智模型 + +FTB 把需求、执行和质量三条线分开: + +| 维度 | 核心实体 | 说明 | +|------|----------|------| +| 语义线 Why | Requirement | 解释为什么做、功能范围是什么,不直接驱动流程 | +| 执行线 How | Version / DevTask / VersionPlan | 版本是执行主线,任务和计划归属版本 | +| 质量线 Quality | TestCase / Bug | 测试用例和 Bug 挂在版本上形成验收闭环 | + +关键原则: + +- 进度由状态和估时派生,不手填百分比。 +- 跨模块联动放在纯函数引擎层,例如 `linkage-engine.ts`、`workspace-engine.ts`。 +- DevTask 的 `submitted` 是开发终态,后续验收失败通过 Bug 流转。 +- TestCase 和 Bug 主归属版本,需求只是可选语义标签。 +- AI 只能写入带 `aiDraft: true` 的草案,用户编辑后才转正。 + +## 技术栈 + +| 层级 | 技术 | 说明 | +|------|------|------| +| 前端 | Next.js 14 + React 18 + TypeScript | App Router,管理后台以客户端组件为主 | +| UI | Tailwind CSS + Shadcn/ui 风格组件 | 紧凑信息密度,适合项目管理场景 | +| 状态 | Zustand | 每个业务域一个 store | +| 后端 | NestJS + Prisma | REST API、AppData 文档层、AI 配置和 AI 网关 | +| 数据库 | PostgreSQL | 当前主用 `app_data` JSONB,后续逐步关系化 | +| 缓存/实时预留 | Redis | 本地通过 Docker Compose 启动 | +| AI | Anthropic / OpenAI 格式 Provider | 支持官方和中转站配置 | +| 工程化 | pnpm workspace + Turborepo | Monorepo 任务编排 | ## 环境要求 | 工具 | 版本 | 说明 | |------|------|------| -| Node.js | >= 18.x | 推荐 20.x LTS | -| pnpm | >= 9.4.0 | 包管理器 | -| Git | >= 2.x | 版本控制 | - -> 后端(可选)还需要 PostgreSQL >= 15 和 Redis >= 7,目前前端独立运行不依赖后端。 - -## 安装 Node.js 和 pnpm - -```bash -# Windows - 下载安装包 -# https://nodejs.org/en/download - -# 安装 pnpm -npm install -g pnpm@9.4.0 -``` +| Node.js | >= 18.x | 推荐 20.x LTS,后端会优先使用 Node 20 的 `.env` 加载能力 | +| pnpm | 9.4.0 | 仓库锁定的包管理器版本 | +| Docker | 可选 | 用于本地启动 PostgreSQL 和 Redis | +| PostgreSQL | >= 15 | Docker Compose 默认使用 PostgreSQL 16 | +| Redis | >= 7 | 预留给缓存和实时能力 | ## 快速开始 ```bash -# 1. 克隆项目 -git clone https://github.com/chengnianbai/ftb-project-management.git -cd ftb-project-management - -# 2. 安装依赖 +# 1. 安装依赖 pnpm install -# 3. 启动前端开发服务器 -cd apps/web -npx next dev -p 3000 +# 2. 准备后端环境变量 +cp apps/server/.env.example apps/server/.env -# 4. 打开浏览器 -# http://localhost:3000 +# 3. 启动 PostgreSQL + Redis +docker-compose up -d + +# 4. 创建/同步数据库结构 +pnpm db:migrate + +# 5. 启动前端 + 后端 +pnpm dev ``` -## 项目结构 +启动后访问: +- 前端:`http://localhost:3000` +- 后端:`http://localhost:3001/api/v1` +- AI 配置探测端点:`http://localhost:3001/api/v1/config/ai` + +如果只想单独启动某一端: + +```bash +pnpm dev --filter=web +pnpm dev --filter=server ``` -ftb-project-management/ -├── apps/ -│ ├── web/ # 前端 (Next.js 14 + React 18) -│ │ ├── app/ # 页面路由 -│ │ │ ├── products/ # 产品管理 -│ │ │ ├── projects/ # 项目管理 -│ │ │ ├── versions/ # 版本管理 -│ │ │ ├── requirements/ # 需求管理 -│ │ │ ├── overtime/ # 加班记录 -│ │ │ └── admin/ # 管理(成员/角色) -│ │ ├── components/ # 通用组件 -│ │ ├── stores/ # Zustand 状态管理 -│ │ └── lib/ # 类型定义/工具函数 -│ └── server/ # 后端 (NestJS 10, 暂未启用) -│ ├── src/ -│ └── prisma/ # 数据库 Schema -├── packages/ -│ └── shared/ # 共享类型 -├── package.json # Monorepo 根配置 -├── pnpm-workspace.yaml # pnpm workspace -└── turbo.json # Turborepo 配置 -``` - -## 技术栈 - -**前端** -- Next.js 14 (App Router) -- React 18 -- TypeScript 5.5 -- Tailwind CSS -- Zustand (状态管理) -- Lucide React (图标) - -**后端(暂未启用)** -- NestJS 10 -- Prisma (ORM) -- PostgreSQL - -**工程化** -- pnpm workspace (Monorepo) -- Turborepo (任务编排) - -## 功能模块 - -| 模块 | 路径 | 状态 | -|------|------|------| -| 产品管理 | /products | 已完成 | -| 项目管理 | /projects | 已完成 | -| 版本管理 | /versions | 已完成 | -| 需求管理 | /requirements | 已完成 | -| 加班记录 | /overtime | 已完成 | -| 成员管理 | /admin/members | 已完成 | -| 角色管理 | /admin/roles | 已完成 | - -## 数据存储 - -当前阶段所有数据存储在浏览器 localStorage 中(Mock 模式),无需数据库即可体验完整功能。后续接入后端 API 后切换为数据库持久化。 ## 常用命令 ```bash -# 前端开发 -cd apps/web && npx next dev -p 3000 +# 全量开发 +pnpm dev -# TypeScript 类型检查 -cd apps/web && npx tsc --noEmit - -# 全量构建 +# 构建 pnpm build + +# 类型检查 +pnpm type-check + +# 测试 +pnpm test + +# ESLint +pnpm lint + +# Prisma +pnpm db:migrate +pnpm db:seed +pnpm db:studio + +# 本地基础服务 +docker-compose up -d +docker-compose down ``` -## 后端启动(可选) +## 项目结构 -如果需要启动后端 API: +```text +ftb-project-management/ +├── apps/ +│ ├── web/ # Next.js 前端 +│ │ ├── app/ # 页面路由 +│ │ │ ├── products/ # 产品列表和详情 +│ │ │ ├── projects/ # 项目列表和详情 +│ │ │ ├── versions/ # 版本列表和详情 +│ │ │ ├── requirements/ # 需求池 +│ │ │ ├── workspace/ # 与我相关工作台 +│ │ │ ├── xiaobao-warning/ # 小宝预警 +│ │ │ └── admin/ # 成员、角色、任务类型、AI 配置 +│ │ ├── components/ # 业务组件和通用组件 +│ │ ├── lib/ # 规则引擎、派生计算、API 封装 +│ │ └── stores/ # Zustand stores +│ └── server/ # NestJS 后端 +│ ├── src/modules/data/ # AppData 文档层:GET/PUT /api/v1/data/:key +│ ├── src/modules/ai/ # AI 网关、拆解、风险解读 +│ ├── src/modules/config/ # AI Provider 配置 +│ └── prisma/ # Prisma Schema +├── packages/ +│ └── shared/ # 前后端共享类型 +├── docs/ # 架构、决策、流程、路线图 +├── docker-compose.yml # PostgreSQL + Redis +├── pnpm-workspace.yaml +└── turbo.json +``` + +## 功能模块 + +| 模块 | 路径 | 当前状态 | +|------|------|----------| +| 产品管理 | `/products` | 已实现 | +| 项目管理 | `/projects` | 已实现,项目详情联动版本状态 | +| 版本管理 | `/versions` | 已实现,承载计划、开发、测试、Bug 和发布风险 | +| 需求池 | `/requirements` | 已实现,需求作为语义层和版本可选分组 | +| 与我相关 | `/workspace` | 已实现,聚合个人计划、任务、测试、Bug、日报 | +| 小宝预警 | `/xiaobao-warning` | 已实现首版,规则优先、AI 只做解释 | +| 加班记录 | `/overtime` | 已实现 | +| 成员/角色 | `/admin/members`、`/admin/roles` | 已实现 | +| 任务类型 | `/admin/categories` | 已实现,使用稳定语义码对接 AI | +| AI 配置 | `/admin/ai-config` | 已实现,支持多 Provider 配置 | + +## 数据持久化 + +当前阶段使用服务端通用文档层: + +- 后端接口:`GET/PUT /api/v1/data/:key` +- 数据库表:Prisma `AppData`,映射到 PostgreSQL `app_data` +- 前端读写:`apps/web/lib/server-data.ts` +- 浏览器存储:只保留登录态,不作为业务数据主存储 + +已纳入 AppData 的 key: + +```text +products-overview +requirements +version-plans +dev-tasks +test-cases +bugs +members +task-categories +task-worklogs +work-activities +xiaobao-risk-insights +xiaobao-risk-snapshots +xiaobao-warning-views +overtime +``` + +后续 V2.2 会把稳定的数据形状逐步拆成关系表和领域 CRUD API。拆表前不要恢复业务 localStorage 缓存,避免多端数据分叉。 + +## AI 与小宝预警 + +AI 能力当前以 NestJS 后端为入口: + +- AI Provider 配置在 `/admin/ai-config` 管理,服务端落盘到 `apps/server/data/ai-config.json`,环境变量 `ANTHROPIC_API_KEY` 作为兜底。 +- Prototype Decompose Agent 面向“产品方案原型 → 开发任务 / 测试用例草案”场景,所有写入必须带 `aiDraft: true` 和 `references[]`。 +- 小宝预警先由规则引擎计算风险分、趋势、静默风险、预测发版日和置信度;AI 只解释规则结果,不改写业务数据。 + +相关实现入口: + +- `apps/server/src/modules/ai/` +- `apps/web/lib/ai-decompose-*` +- `apps/web/lib/xiaobao-risk-*` +- `apps/web/components/version/AiDecomposeButton.tsx` +- `apps/web/components/xiaobao-warning/` + +## 开发约定 + +- 新增跨模块联动时,优先放到 `*-engine.ts` 或纯函数 helper,不在页面组件里散写规则。 +- 新增状态流转或完成条件时,优先建立 workflow helper。 +- 新增 AppData key 时,同时更新后端 `data-keys.ts` 和前端 `server-data.ts`。 +- 时间字段使用 ISO 时间戳;展示时统一格式化。 +- Commit 使用中文,格式:`类型(模块): 描述`,例如 `feat(版本): 增加小宝预警入口`。 +- 用户没有明确要求时不要自动 push。 + +## 验证建议 + +改动后至少运行: ```bash -# 1. 安装 PostgreSQL 并创建数据库 -createdb ftb_pm - -# 2. 配置环境变量 -cd apps/server -cp .env.example .env -# 编辑 .env 设置 DATABASE_URL - -# 3. 初始化数据库 -npx prisma migrate dev - -# 4. 启动 -npm run dev -# 后端运行在 http://localhost:3001 +pnpm type-check +``` + +涉及服务端数据持久化时额外运行: + +```bash +pnpm --filter server exec prisma validate --schema prisma/schema.prisma +``` + +涉及 UI 页面时,确保对应页面能返回 200,例如: + +```bash +curl http://localhost:3000/workspace ``` diff --git a/apps/web/app/wenfan-xiaobao/page.tsx b/apps/web/app/wenfan-xiaobao/page.tsx index 1843d20..7ac4b0e 100644 --- a/apps/web/app/wenfan-xiaobao/page.tsx +++ b/apps/web/app/wenfan-xiaobao/page.tsx @@ -62,7 +62,7 @@ const INITIAL_MESSAGES: ChatMessage[] = [ role: 'assistant', type: 'intro', content: - '我是问翻小宝,第一阶段只从内置帮助中心回答系统怎么用,不调用 AI,也不消耗模型 token。你可以问产品、项目、版本、需求池、开发任务、测试用例、Bug 和日志记录。', + '第一阶段只从内置帮助中心回答系统怎么用,不调用 AI,也不消耗模型 token。你可以问产品、项目、版本、需求池、开发任务、测试用例、Bug 和日志记录。', }, ]; @@ -337,7 +337,6 @@ function MessageRow({
-

问翻小宝

{message.type === 'intro' &&

{message.content}

} {message.type === 'article' && } {message.type === 'fallback' && ( diff --git a/apps/web/app/xiaobao-warning/page.tsx b/apps/web/app/xiaobao-warning/page.tsx index 68e9f7c..5f73fee 100644 --- a/apps/web/app/xiaobao-warning/page.tsx +++ b/apps/web/app/xiaobao-warning/page.tsx @@ -16,6 +16,7 @@ import { filterXiaobaoRiskWarnings, formatRemainingWork, isXiaobaoWarningUpdated, + shouldSkipXiaobaoRiskInsightRequestForReadState, sanitizeRiskInsight, } from '@/lib/xiaobao-warning-view'; import { formatDateTime } from '@/lib/format'; @@ -90,6 +91,7 @@ function XiaobaoWarningContent() { useEffect(() => { risks.forEach((risk) => { + if (shouldSkipXiaobaoRiskInsightRequestForReadState(risk, readStates, user?.id, readStateLoaded)) return; const previous = findPreviousRiskSnapshot(snapshots, risk.versionId, today); const key = buildXiaobaoRiskInsightPendingKey(risk); if (!shouldRequestRiskInsightWithRequestGate({ @@ -123,11 +125,14 @@ function XiaobaoWarningContent() { insights, insightRequestAttempts, pendingInsightKeys, + readStateLoaded, + readStates, riskDataLoaded, risks, saveInsight, snapshots, today, + user?.id, ]); const risksWithInsight = useMemo(() => risks.map((risk) => attachXiaobaoRiskSuggestion(risk, { diff --git a/apps/web/components/bug/BugDetailDrawer.tsx b/apps/web/components/bug/BugDetailDrawer.tsx index bd0f6d3..b673854 100644 --- a/apps/web/components/bug/BugDetailDrawer.tsx +++ b/apps/web/components/bug/BugDetailDrawer.tsx @@ -95,45 +95,45 @@ export function BugDetailDrawer({ bugId, onClose, contextLabel, readOnly = false }; return ( -
-
e.stopPropagation()}> +
+
e.stopPropagation()}> {contextLabel && (
{contextLabel}
)}
-
- {bug.bugNo} - {bug.title} +
+ {bug.bugNo} + {bug.title}
- +
-
+
{/* 关联链路 */} -
+
关联链路
{tc && ( -
+
{tc.caseNo} - {tc.title} + {tc.title}
)} {requirement && ( -
+
{requirement.code} - {requirement.title} + {requirement.title}
)}
{/* 状态 & 操作 */} -
+
状态 & 操作
-
+
{BUG_SEVERITY_LABEL[bug.severity]} {bug.priority} @@ -189,9 +189,9 @@ export function BugDetailDrawer({ bugId, onClose, contextLabel, readOnly = false
{/* 基本信息 */} -
+
基本信息
-
+
计划修复:{bug.plannedFixAt ? formatDateTime(bug.plannedFixAt) : '待排期'}
提交人:{reporterName}
修复人:{assigneeName}
@@ -202,14 +202,14 @@ export function BugDetailDrawer({ bugId, onClose, contextLabel, readOnly = false
{/* 描述 */} -
+
Bug 描述

{bug.description}

{/* 截图 */} {bug.images && bug.images.length > 0 && ( -
+
截图附件
{bug.images.map((src, i) => ( @@ -221,7 +221,7 @@ export function BugDetailDrawer({ bugId, onClose, contextLabel, readOnly = false {/* 修复说明 */} {bug.resolution && ( -
+
修复说明

{bug.resolution}

diff --git a/apps/web/components/dev-task/DevTaskDetailDrawer.tsx b/apps/web/components/dev-task/DevTaskDetailDrawer.tsx index 0e5d09c..0ecf2ad 100644 --- a/apps/web/components/dev-task/DevTaskDetailDrawer.tsx +++ b/apps/web/components/dev-task/DevTaskDetailDrawer.tsx @@ -196,19 +196,19 @@ export function DevTaskDetailDrawer({ taskId, allTaskIds, onClose, contextLabel, }; return ( -
-
e.stopPropagation()}> +
+
e.stopPropagation()}> {contextLabel && (
{contextLabel}
)}
-
- {task.taskNo} - {task.title} +
+ {task.taskNo} + {task.title}
-
+
{!readOnly && task.status !== 'submitted' && ( )} @@ -233,22 +233,22 @@ export function DevTaskDetailDrawer({ taskId, allTaskIds, onClose, contextLabel,
)} -
+
{requirement && ( -
+
关联需求
-
+
{requirement.code} - {requirement.title} + {requirement.title}
)} -
+
状态 & 操作
-
+
{DEV_TASK_STATUS_LABEL[task.status]} @@ -295,7 +295,7 @@ export function DevTaskDetailDrawer({ taskId, allTaskIds, onClose, contextLabel,
{needsClaim ? '领取并填写计划' : '填写计划'}
-
+
{task.status !== 'todo' && task.status !== 'submitted' && !readOnly && ( -
+
今日进展