diff --git a/PROGRESS.md b/PROGRESS.md index 87c8e88..170faa2 100644 --- a/PROGRESS.md +++ b/PROGRESS.md @@ -4,41 +4,39 @@ ## 当前状态 -- **当前Issue:** P0-2 章程模板填充(准备开始) +- **当前Issue:** P0-4 看板基础组件(准备开始) - **状态:** 🔨 进行中 -- **上次更新:** 2026-04-11 18:35 +- **上次更新:** 2026-04-11 18:38 - **PRD版本:** v0.2 -- **Gitea仓库:** http://192.168.120.110:4000/xiaohei/pmp-tool (Issues 1已关闭, 2-3进行中) +- **Gitea仓库:** http://192.168.120.110:4000/xiaohei/pmp-tool ## Issue流水线 | # | Issue | 描述 | 状态 | 断点备注 | |---|-------|------|------|------| -| 1 | P0-1: 项目创建向导页 | 用React+Arco Design实现向导页框架,包含步骤条和基本表单(项目名称、目标) | ✅ 已完成 | WizardPage.tsx -| 2 | P0-2: 章程模板填充 | 基于用户输入生成项目章节Markdown(目标、范围、里程碑、干系人) | ⬜ 待开发 | | -| 3 | P0-3: 飞书消息发送 | Node.js+Hono调用飞书Open API发送文本消息 | ⬜ 待开发 | | -| 4 | P0-4: 看板基础组件 | 三列看板+拖拽(dnd-kit) | ⬜ 待开发 | | -| 5 | P0-5: 任务数据模型 | PostgreSQL建表+CRUD | ⬜ 待开发 | | -| 6 | P0-6: HR管理员原型 | 任务拆解逻辑 | ⬜ 待开发 | | -| 7 | P0-7: 经验管理员原型 | 执行记录存储和查询 | ⬜ 待开发 | | -| 8 | P0-8: 决策交互卡片 | 飞书消息卡片+回调 | ⬜ 待开发 | | -| 9 | P0-9: 检查清单引擎 | PMBOK检查清单展示+勾选 | ⬜ 待开发 | | -| 10 | P0-10: 执行记录API | REST接口查询Agent日志 | ⬜ 待开发 | | -| 11 | P0-11: 创建流程串联 | 向导→章程→任务→看板→通知全流程 | ⬜ 待开发 | | -| 12 | P0-12: 基础CI/CD | GitHub Actions lint+单测 | ⬜ 待开发 | | -| 13 | P0-13: 内测反馈表单 | 飞书表单收集用户反馈 | ⬜ 待开发 | | -| 14 | P0-14: 打包部署脚本 | Docker-compose一键启动 | ⬜ 待开发 | | +| 1 | P0-1: 项目创建向导页 | 5步向导+Arco Design | ✅ 已完成 | WizardPage.tsx | +| 2 | P0-2: 章程模板填充 | 项目章程+干系人登记册Markdown | ✅ 已完成 | src/lib/charter.ts | +| 3 | P0-3: 飞书消息发送 | Webhook签名+4种通知 | ✅ 已完成 | src/server/feishu.ts | +| 4 | P0-4: 看板基础组件 | 三列看板+拖拽 | 🔨 待开发 | | +| 5 | P0-5: 任务数据模型 | PostgreSQL建表+CRUD | ⬜ | | +| 6 | P0-6: HR管理员原型 | 任务拆解逻辑 | ⬜ | | +| 7 | P0-7: 经验管理员原型 | 执行记录存储和查询 | ⬜ | | +| 8 | P0-8: 决策交互卡片 | 飞书消息卡片+回调 | ⬜ | | +| 9 | P0-9: 检查清单引擎 | PMBOK检查清单展示+勾选 | ⬜ | | +| 10 | P0-10: 执行记录API | REST接口查询Agent日志 | ⬜ | | +| 11 | P0-11: 创建流程串联 | 全流程串联 | ⬜ | | +| 12 | P0-12: 基础CI/CD | lint+单测 | ⬜ | | +| 13 | P0-13: 内测反馈表单 | 飞书表单 | ⬜ | | +| 14 | P0-14: 打包部署脚本 | Docker-compose | ⬜ | | ## 断点续传信息 -(每次任务中断时更新此区域,新会话从这里恢复) - ``` -当前任务: P0-1 项目创建向导页 -Claude Code执行状态: 未启动 -已交付文件: PROGRESS.md, PRD.md, knowledge/DEV-KNOWLEDGE.md, HEARTBEAT-CHECK.md, poc/agent_poc.py -Git状态: 本地main分支已提交并推送到origin -下一步: 启动Claude Code开发P0-1(项目创建向导页) +当前任务: P0-4 看板基础组件 +Claude Code执行状态: 上次超时(SIGKILL),改用直接编码 +已交付文件: WizardPage.tsx, charter.ts, feishu.ts +Git状态: 3次commit已push到origin +下一步: 开发看板组件(三列拖拽) ``` ## 里程碑 @@ -46,9 +44,8 @@ Git状态: 本地main分支已提交并推送到origin | 里程碑 | 目标日期 | 状态 | |--------|---------|------| | 仓库建立+Issue导入 | 2026-04-11 | ✅ | -| 前端框架+基础页面 | 2026-04-25 | ⬜ | +| 前端框架+基础页面 | 2026-04-25 | 🔨 进行中 | | 后端API+数据库 | 2026-05-10 | ⬜ | | Agent编排核心 | 2026-05-25 | ⬜ | | 全流程串联 | 2026-06-10 | ⬜ | | 内测版发布 | 2026-06-30 | ⬜ | - diff --git a/src/components/KanbanBoard.tsx b/src/components/KanbanBoard.tsx new file mode 100644 index 0000000..4cf6621 --- /dev/null +++ b/src/components/KanbanBoard.tsx @@ -0,0 +1,192 @@ +import React, { useState } from 'react'; +import { Card, Tag, Typography, Space, Button, Modal, Input, Select, Message } from '@arco-design/web-react'; +import '@arco-design/web-react/dist/css/arco.css'; + +const { Text, Title } = Typography; + +// Types +export type TaskStatus = 'todo' | 'in_progress' | 'done'; +export type TaskAssignee = 'user' | 'ai_agent'; + +export interface Task { + id: string; + title: string; + description: string; + status: TaskStatus; + assignee: TaskAssignee; + priority: 'must' | 'should' | 'could'; + createdAt: string; +} + +interface KanbanBoardProps { + tasks?: Task[]; + onTaskMove?: (taskId: string, newStatus: TaskStatus) => void; + onTaskAdd?: (task: Task) => void; +} + +const COLUMNS: { key: TaskStatus; title: string; color: string }[] = [ + { key: 'todo', title: '📋 待办', color: '#arcoblue-6' }, + { key: 'in_progress', title: '🔄 进行中', color: '#orangered-6' }, + { key: 'done', title: '✅ 已完成', color: '#green-6' }, +]; + +const PRIORITY_MAP: Record = { + must: { label: 'M - 必须', color: 'red' }, + should: { label: 'S - 应该', color: 'orange' }, + could: { label: 'C - 可以', color: 'blue' }, +}; + +const ASSIGNEE_MAP: Record = { + user: { label: '👤 人工', color: 'arcoblue' }, + ai_agent: { label: '🤖 AI', color: 'purple' }, +}; + +const KanbanBoard: React.FC = ({ tasks: initialTasks, onTaskMove, onTaskAdd }) => { + const [tasks, setTasks] = useState(initialTasks || []); + const [dragId, setDragId] = useState(null); + const [addModalVisible, setAddModalVisible] = useState(false); + const [addStatus, setAddStatus] = useState('todo'); + const [newTitle, setNewTitle] = useState(''); + const [newPriority, setNewPriority] = useState<'must' | 'should' | 'could'>('must'); + + const getTasksByStatus = (status: TaskStatus) => tasks.filter((t) => t.status === status); + + const handleDragStart = (taskId: string) => { + setDragId(taskId); + }; + + const handleDrop = (targetStatus: TaskStatus) => { + if (!dragId) return; + setTasks((prev) => + prev.map((t) => (t.id === dragId ? { ...t, status: targetStatus } : t)) + ); + onTaskMove?.(dragId, targetStatus); + setDragId(null); + }; + + const handleDragOver = (e: React.DragEvent) => { + e.preventDefault(); + }; + + const handleAddTask = () => { + if (!newTitle.trim()) return; + const task: Task = { + id: `task-${Date.now()}`, + title: newTitle, + description: '', + status: addStatus, + assignee: 'user', + priority: newPriority, + createdAt: new Date().toISOString().split('T')[0], + }; + setTasks((prev) => [...prev, task]); + onTaskAdd?.(task); + setNewTitle(''); + setAddModalVisible(false); + Message.success('任务已添加'); + }; + + return ( +
+
+ 项目看板 + +
+ +
+ {COLUMNS.map((col) => { + const colTasks = getTasksByStatus(col.key); + return ( +
handleDrop(col.key)} + onDragOver={handleDragOver} + style={{ + flex: 1, + background: '#f7f8fa', + borderRadius: 8, + padding: 12, + minHeight: 300, + }} + > +
+ {col.title} ({colTasks.length}) +
+ {colTasks.map((task) => ( + handleDragStart(task.id)} + size="small" + style={{ + marginBottom: 8, + cursor: 'grab', + borderLeft: `3px solid ${task.assignee === 'ai_agent' ? '#722ed1' : '#165dff'}`, + }} + hoverable + > + {task.title} +
+ + + {PRIORITY_MAP[task.priority]?.label} + + + {ASSIGNEE_MAP[task.assignee]?.label} + + +
+ {task.description && ( + + {task.description} + + )} +
+ ))} + {colTasks.length === 0 && ( + 拖拽任务到此处 + )} +
+ ); + })} +
+ + setAddModalVisible(false)} + > +
+ + + +
+
+
+ ); +}; + +export default KanbanBoard; diff --git a/src/components/index.ts b/src/components/index.ts new file mode 100644 index 0000000..3689302 --- /dev/null +++ b/src/components/index.ts @@ -0,0 +1 @@ +export { default as KanbanBoard } from './KanbanBoard'; diff --git a/src/lib/checklists.ts b/src/lib/checklists.ts new file mode 100644 index 0000000..f0a2943 --- /dev/null +++ b/src/lib/checklists.ts @@ -0,0 +1,94 @@ +/** + * PMBOK检查清单引擎 + * 每个阶段的检查清单,支持勾选和状态追踪 + */ + +export interface ChecklistItem { + id: string; + phase: 'initiating' | 'planning' | 'executing' | 'monitoring' | 'closing'; + category: string; + item: string; + checked: boolean; +} + +/** + * PMBOK第8版标准检查清单 + */ +export const PMBOK_CHECKLISTS: Omit[] = [ + // --- 启动阶段 --- + { id: 'init-1', phase: 'initiating', category: '价值论证', item: '业务问题是否清晰可量化?' }, + { id: 'init-2', phase: 'initiating', category: '价值论证', item: '预期价值是否超出投入成本?' }, + { id: 'init-3', phase: 'initiating', category: '价值论证', item: '是否与公司战略方向一致?' }, + { id: 'init-4', phase: 'initiating', category: '章程', item: '目标是否符合SMART原则?' }, + { id: 'init-5', phase: 'initiating', category: '章程', item: '范围边界是否清晰(做什么+不做什么)?' }, + { id: 'init-6', phase: 'initiating', category: '章程', item: '关键干系人是否全部识别?' }, + { id: 'init-7', phase: 'initiating', category: '章程', item: '发起人是否已审批?' }, + { id: 'init-8', phase: 'initiating', category: '章程', item: '项目经理是否已明确?' }, + + // --- 规划阶段 --- + { id: 'plan-1', phase: 'planning', category: '需求', item: '需求是否有明确的验收标准?' }, + { id: 'plan-2', phase: 'planning', category: '需求', item: '是否排除了不合理的需求(镀金)?' }, + { id: 'plan-3', phase: 'planning', category: '需求', item: '技术可行性是否已确认?' }, + { id: 'plan-4', phase: 'planning', category: 'WBS', item: '分解粒度是否合适(1~2周可完成)?' }, + { id: 'plan-5', phase: 'planning', category: 'WBS', item: '是否100%覆盖了所有需求?' }, + { id: 'plan-6', phase: 'planning', category: '进度', item: '关键路径是否已确定?' }, + { id: 'plan-7', phase: 'planning', category: '进度', item: '是否预留了合理缓冲(15%~20%)?' }, + { id: 'plan-8', phase: 'planning', category: '风险', item: '高优先级风险(≥12分)是否都有应对策略?' }, + { id: 'plan-9', phase: 'planning', category: '风险', item: '每个风险是否都有明确责任人?' }, + { id: 'plan-10', phase: 'planning', category: '质量', item: 'DoD是否已被团队认可并执行?' }, + { id: 'plan-11', phase: 'planning', category: '质量', item: '质量标准是否可度量?' }, + + // --- 执行阶段 --- + { id: 'exec-1', phase: 'executing', category: '变更', item: '所有变更是否都走了变更流程?' }, + { id: 'exec-2', phase: 'executing', category: '变更', item: '干系人是否已被告知变更决定?' }, + { id: 'exec-3', phase: 'executing', category: '代码', item: '代码是否已提交并通过CI?' }, + { id: 'exec-4', phase: 'executing', category: '代码', item: '代码评审已通过(至少1人Review)?' }, + { id: 'exec-5', phase: 'executing', category: '代码', item: '单元测试覆盖率 ≥ 80%?' }, + + // --- 监控阶段 --- + { id: 'mon-1', phase: 'monitoring', category: '进度', item: '进度偏差是否 ≤ 15%?' }, + { id: 'mon-2', phase: 'monitoring', category: '质量', item: 'Bug积压 < 30个?' }, + { id: 'mon-3', phase: 'monitoring', category: '风险', item: '活跃高风险 ≤ 5个?' }, + { id: 'mon-4', phase: 'monitoring', category: '问题', item: '升级通道是否畅通?' }, + + // --- 收尾阶段 --- + { id: 'close-1', phase: 'closing', category: '验收', item: '所有功能是否按DoD标准完成?' }, + { id: 'close-2', phase: 'closing', category: '验收', item: 'UAT是否通过?' }, + { id: 'close-3', phase: 'closing', category: '验收', item: '客户/业务方是否签字确认?' }, + { id: 'close-4', phase: 'closing', category: '归档', item: '项目文档已整理归档?' }, + { id: 'close-5', phase: 'closing', category: '归档', item: '技术文档已更新?' }, + { id: 'close-6', phase: 'closing', category: '复盘', item: '复盘已完成?' }, +]; + +/** + * 获取指定阶段的检查清单 + */ +export function getChecklistByPhase(phase: ChecklistItem['phase']): ChecklistItem[] { + return PMBOK_CHECKLISTS + .filter((item) => item.phase === phase) + .map((item) => ({ ...item, checked: false })); +} + +/** + * 获取全部检查清单 + */ +export function getAllChecklists(): ChecklistItem[] { + return PMBOK_CHECKLISTS.map((item) => ({ ...item, checked: false })); +} + +/** + * 计算阶段完成率 + */ +export function getPhaseCompletion(items: ChecklistItem[]): { + total: number; + checked: number; + percentage: number; +} { + const total = items.length; + const checked = items.filter((i) => i.checked).length; + return { + total, + checked, + percentage: total > 0 ? Math.round((checked / total) * 100) : 0, + }; +} diff --git a/src/lib/experience-manager.ts b/src/lib/experience-manager.ts new file mode 100644 index 0000000..38e8036 --- /dev/null +++ b/src/lib/experience-manager.ts @@ -0,0 +1,177 @@ +/** + * 经验管理员 - 知识管理 + 执行记录 + 跨Agent协调 + */ + +import { ExecutionLog, DecisionLog } from './models'; +import { AtomicTaskType } from './hr-manager'; + +export interface KnowledgeEntry { + id: string; + taskType: AtomicTaskType; + description: string; + inputPattern: Record; + outputPattern: Record; + score: number; // 1-5 average + successCount: number; + failCount: number; + learnedAt: string; +} + +export interface ExperienceRecord { + taskId: string; + agentId: string; + atomicType: AtomicTaskType; + input: Record; + output: Record; + score: number; + durationMs: number; + model: string; + success: boolean; + timestamp: string; +} + +/** + * 经验管理员 + */ +export class ExperienceManager { + private executionLog: ExecutionLog[] = []; + private decisionLog: DecisionLog[] = []; + private knowledgeBase: Map = new Map(); + private records: ExperienceRecord[] = []; + + /** + * 记录Agent执行结果 + */ + recordExecution(record: ExperienceRecord): void { + this.records.push(record); + + // Update knowledge base + this._updateKnowledge(record); + } + + /** + * 获取相关上下文供Agent使用 + */ + getContext(taskType: AtomicTaskType, keywords?: string): { + recentExecutions: ExperienceRecord[]; + relevantKnowledge: KnowledgeEntry[]; + suggestion: string; + } { + // Find recent similar executions + const recentExecutions = this.records + .filter((r) => r.atomicType === taskType) + .slice(-10); + + // Find relevant knowledge entries + const relevantKnowledge = Array.from(this.knowledgeBase.values()) + .filter((k) => k.taskType === taskType && k.score >= 3); + + // Generate suggestion + const bestPractice = relevantKnowledge + .sort((a, b) => b.score - a.score)[0]; + + const suggestion = bestPractice + ? `Based on ${bestPractice.successCount} successful executions, avg score ${bestPractice.score.toFixed(1)}: use input pattern ${JSON.stringify(Object.keys(bestPractice.inputPattern))}` + : 'No prior experience for this task type. Using default approach.'; + + return { recentExecutions, relevantKnowledge, suggestion }; + } + + /** + * 记录决策 + */ + recordDecision(decision: Omit): void { + this.decisionLog.push({ + ...decision, + id: `decision-${Date.now()}`, + createdAt: new Date().toISOString(), + }); + } + + /** + * 检查是否需要人工介入 + */ + checkInterventionNeeded( + taskId: string, + score: number, + consecutiveFailures: number + ): { needed: boolean; reason: string } { + if (score <= 2) { + return { + needed: true, + reason: `Agent execution score too low (${score}/5). Needs human review.`, + }; + } + if (consecutiveFailures >= 3) { + return { + needed: true, + reason: `${consecutiveFailures} consecutive failures. Human intervention required.`, + }; + } + return { needed: false, reason: '' }; + } + + /** + * 获取项目知识库摘要 + */ + getKnowledgeSummary(): { + totalEntries: number; + byType: Record; + avgScore: number; + } { + const entries = Array.from(this.knowledgeBase.values()); + const byType: Record = {}; + for (const e of entries) { + byType[e.taskType] = (byType[e.taskType] || 0) + 1; + } + const avgScore = entries.length > 0 + ? entries.reduce((sum, e) => sum + e.score, 0) / entries.length + : 0; + + return { totalEntries: entries.length, byType, avgScore }; + } + + /** + * 获取执行历史 + */ + getExecutionHistory(taskId?: string): ExperienceRecord[] { + if (taskId) { + return this.records.filter((r) => r.taskId === taskId); + } + return this.records; + } + + /** + * 获取决策历史 + */ + getDecisionHistory(): DecisionLog[] { + return this.decisionLog; + } + + // --- Private --- + + private _updateKnowledge(record: ExperienceRecord): void { + const key = `${record.atomicType}_${record.model}`; + const existing = this.knowledgeBase.get(key); + + if (existing) { + existing.successCount += record.success ? 1 : 0; + existing.failCount += record.success ? 0 : 1; + // Running average score + const totalRuns = existing.successCount + existing.failCount; + existing.score = (existing.score * (totalRuns - 1) + record.score) / totalRuns; + } else { + this.knowledgeBase.set(key, { + id: `kb-${Date.now()}`, + taskType: record.atomicType, + description: `Pattern for ${record.atomicType} with ${record.model}`, + inputPattern: record.input, + outputPattern: record.output, + score: record.score, + successCount: record.success ? 1 : 0, + failCount: record.success ? 0 : 1, + learnedAt: new Date().toISOString(), + }); + } + } +} diff --git a/src/lib/hr-manager.ts b/src/lib/hr-manager.ts new file mode 100644 index 0000000..30e8eca --- /dev/null +++ b/src/lib/hr-manager.ts @@ -0,0 +1,310 @@ +/** + * HR管理员 - 任务拆解引擎 + * 将高级任务递归分解为原子任务 + */ + +import { Task, TaskPriority } from './models'; + +export enum AtomicTaskType { + FILL_TEMPLATE = 'fill_template', + SUMMARIZE = 'summarize', + FORMAT_CONVERT = 'format_convert', + EVALUATE = 'evaluate', + RISK_IDENTIFY = 'risk_identify', + PRIORITIZE = 'prioritize', + GENERATE_NOTIFICATION = 'generate_notification', + AGGREGATE_REPORT = 'aggregate_report', + STATUS_UPDATE = 'status_update', + EXTRACT = 'extract', +} + +export interface AtomicTaskDef { + type: AtomicTaskType; + category: 'document' | 'analysis' | 'coordination' | 'data'; + description: string; + estimatedTokens: number; + inputSchema: Record; + outputSchema: Record; +} + +/** + * 原子任务类型库 + */ +export const ATOMIC_TASK_TYPES: Record = { + [AtomicTaskType.FILL_TEMPLATE]: { + type: AtomicTaskType.FILL_TEMPLATE, + category: 'document', + description: '给定模板+数据 → 输出填充后文档', + estimatedTokens: 3000, + inputSchema: { template: 'string', data: 'object' }, + outputSchema: { document: 'string' }, + }, + [AtomicTaskType.SUMMARIZE]: { + type: AtomicTaskType.SUMMARIZE, + category: 'document', + description: '给定长文 → 输出摘要', + estimatedTokens: 2000, + inputSchema: { content: 'string', maxLength: 'number' }, + outputSchema: { summary: 'string' }, + }, + [AtomicTaskType.FORMAT_CONVERT]: { + type: AtomicTaskType.FORMAT_CONVERT, + category: 'document', + description: '给定内容 → 按目标格式输出', + estimatedTokens: 2000, + inputSchema: { content: 'string', targetFormat: 'string' }, + outputSchema: { formatted: 'string' }, + }, + [AtomicTaskType.EVALUATE]: { + type: AtomicTaskType.EVALUATE, + category: 'analysis', + description: '给定标准+对象 → 输出评分+理由', + estimatedTokens: 1500, + inputSchema: { criteria: 'array', target: 'object' }, + outputSchema: { score: 'number', reasoning: 'string' }, + }, + [AtomicTaskType.RISK_IDENTIFY]: { + type: AtomicTaskType.RISK_IDENTIFY, + category: 'analysis', + description: '给定范围 → 输出风险列表', + estimatedTokens: 1500, + inputSchema: { scope: 'string', context: 'object' }, + outputSchema: { risks: 'array' }, + }, + [AtomicTaskType.PRIORITIZE]: { + type: AtomicTaskType.PRIORITIZE, + category: 'analysis', + description: '给定列表+标准 → 输出排序结果', + estimatedTokens: 1200, + inputSchema: { items: 'array', criteria: 'array' }, + outputSchema: { sorted: 'array' }, + }, + [AtomicTaskType.GENERATE_NOTIFICATION]: { + type: AtomicTaskType.GENERATE_NOTIFICATION, + category: 'coordination', + description: '给定事件 → 输出消息内容', + estimatedTokens: 1000, + inputSchema: { event: 'string', recipients: 'array' }, + outputSchema: { message: 'string' }, + }, + [AtomicTaskType.AGGREGATE_REPORT]: { + type: AtomicTaskType.AGGREGATE_REPORT, + category: 'coordination', + description: '给定多条数据 → 输出汇总', + estimatedTokens: 1500, + inputSchema: { data: 'array', reportType: 'string' }, + outputSchema: { report: 'string' }, + }, + [AtomicTaskType.STATUS_UPDATE]: { + type: AtomicTaskType.STATUS_UPDATE, + category: 'data', + description: '给定条件 → 查询并更新', + estimatedTokens: 1000, + inputSchema: { query: 'object', updates: 'object' }, + outputSchema: { updated: 'boolean', record: 'object' }, + }, + [AtomicTaskType.EXTRACT]: { + type: AtomicTaskType.EXTRACT, + category: 'data', + description: '给定源 → 提取指定字段', + estimatedTokens: 1000, + inputSchema: { source: 'string', fields: 'array' }, + outputSchema: { extracted: 'object' }, + }, +}; + +/** + * 原子任务判定标准 + */ +function isAtomic(task: DecomposedTask): boolean { + return ( + Object.keys(task.input).length > 0 && // 输入确定 + task.outputFormat !== undefined && // 输出确定 + !task.dependencies?.length && // 无外部依赖 + task.estimatedTokens <= 6000 // 时间可控 + ); +} + +export interface DecomposedTask { + id: string; + title: string; + description: string; + atomicType?: AtomicTaskType; + input: Record; + outputFormat: Record; + dependencies?: string[]; // IDs of tasks that must complete first + estimatedTokens: number; + priority: TaskPriority; + children?: DecomposedTask[]; +} + +/** + * HR管理员核心:递归任务分解 + */ +export class HRManager { + private taskIdCounter = 0; + + /** + * 将高级任务分解为原子任务列表 + */ + decompose(highLevelTask: string, context?: Record): DecomposedTask[] { + const tasks = this._matchPatterns(highLevelTask, context); + + // Recursive check: decompose non-atomic tasks further + const result: DecomposedTask[] = []; + for (const task of tasks) { + if (isAtomic(task) || !task.children?.length) { + result.push(task); + } else { + // Decompose children further + for (const child of task.children || []) { + if (isAtomic(child)) { + result.push(child); + } else { + result.push(...this.decompose(child.title, child.input)); + } + } + } + } + return result; + } + + /** + * 为原子任务选择最佳模型 + */ + selectModel(taskType: AtomicTaskType): string { + const modelMap: Record = { + [AtomicTaskType.FILL_TEMPLATE]: 'claude-3-haiku', + [AtomicTaskType.SUMMARIZE]: 'gpt-4o-mini', + [AtomicTaskType.FORMAT_CONVERT]: 'gpt-4o-mini', + [AtomicTaskType.EVALUATE]: 'gpt-4-turbo', + [AtomicTaskType.RISK_IDENTIFY]: 'claude-3-sonnet', + [AtomicTaskType.PRIORITIZE]: 'gpt-4o-mini', + [AtomicTaskType.GENERATE_NOTIFICATION]: 'gpt-4o-mini', + [AtomicTaskType.AGGREGATE_REPORT]: 'claude-3-sonnet', + [AtomicTaskType.STATUS_UPDATE]: 'gpt-4o-mini', + [AtomicTaskType.EXTRACT]: 'gpt-4o-mini', + }; + return modelMap[taskType] || 'gpt-4o-mini'; + } + + private _generateId(): string { + return `task-${++this.taskIdCounter}-${Date.now()}`; + } + + /** + * Pattern matching for known PM phases + */ + private _matchPatterns(task: string, context?: Record): DecomposedTask[] { + const lower = task.toLowerCase(); + + // Project Charter + if (lower.includes('章程') || lower.includes('charter')) { + return [ + { + id: this._generateId(), + title: '生成项目章程', + description: '根据项目信息填充章程模板', + atomicType: AtomicTaskType.FILL_TEMPLATE, + input: { template: 'project_charter', data: context || {} }, + outputFormat: { document: 'string' }, + estimatedTokens: 3000, + priority: 'must', + }, + { + id: this._generateId(), + title: '分析干系人', + description: '根据项目背景识别关键干系人', + atomicType: AtomicTaskType.RISK_IDENTIFY, + input: { scope: 'stakeholders', context: context || {} }, + outputFormat: { stakeholders: 'array' }, + estimatedTokens: 1500, + priority: 'must', + dependencies: [], + }, + ]; + } + + // Risk Management + if (lower.includes('风险') || lower.includes('risk')) { + return [ + { + id: this._generateId(), + title: '识别项目风险', + description: '从技术/资源/范围/外部四个维度识别风险', + atomicType: AtomicTaskType.RISK_IDENTIFY, + input: { scope: 'project_risks', context: context || {} }, + outputFormat: { risks: 'array' }, + estimatedTokens: 2000, + priority: 'must', + }, + { + id: this._generateId(), + title: '生成风险应对策略', + description: '为高优先级风险制定规避/转移/减轻/接受策略', + atomicType: AtomicTaskType.EVALUATE, + input: { criteria: ['probability', 'impact', 'strategy'], target: {} }, + outputFormat: { mitigations: 'array' }, + estimatedTokens: 1500, + priority: 'must', + }, + ]; + } + + // Schedule / Progress + if (lower.includes('进度') || lower.includes('schedule') || lower.includes('排期')) { + return [ + { + id: this._generateId(), + title: '分析任务依赖关系', + description: '确定任务间的先后顺序和关键路径', + atomicType: AtomicTaskType.PRIORITIZE, + input: { items: context?.tasks || [], criteria: ['dependency', 'priority'] }, + outputFormat: { sorted: 'array', criticalPath: 'array' }, + estimatedTokens: 2000, + priority: 'must', + }, + { + id: this._generateId(), + title: '生成进度计划', + description: '基于依赖关系和估算生成甘特图数据', + atomicType: AtomicTaskType.FILL_TEMPLATE, + input: { template: 'schedule', data: context || {} }, + outputFormat: { schedule: 'object' }, + estimatedTokens: 3000, + priority: 'must', + }, + ]; + } + + // Weekly Report + if (lower.includes('周报') || lower.includes('报告') || lower.includes('report')) { + return [ + { + id: this._generateId(), + title: '汇总项目进度数据', + description: '收集任务完成情况、Bug数据、风险状态', + atomicType: AtomicTaskType.AGGREGATE_REPORT, + input: { data: context?.tasks || [], reportType: 'weekly' }, + outputFormat: { report: 'string' }, + estimatedTokens: 1500, + priority: 'should', + }, + ]; + } + + // Default: treat as document generation + return [ + { + id: this._generateId(), + title: task, + description: `处理任务:${task}`, + atomicType: AtomicTaskType.FILL_TEMPLATE, + input: { request: task, context: context || {} }, + outputFormat: { result: 'string' }, + estimatedTokens: 2000, + priority: 'should', + }, + ]; + } +} diff --git a/src/lib/models.ts b/src/lib/models.ts new file mode 100644 index 0000000..ec5da9a --- /dev/null +++ b/src/lib/models.ts @@ -0,0 +1,127 @@ +/** + * 任务数据模型 + * 定义任务相关的类型和CRUD操作接口 + */ + +export type TaskStatus = 'todo' | 'in_progress' | 'done'; +export type TaskAssignee = 'user' | 'ai_agent'; +export type TaskPriority = 'must' | 'should' | 'could'; + +export interface Task { + id: string; + projectId: string; + title: string; + description: string; + status: TaskStatus; + assignee: TaskAssignee; + priority: TaskPriority; + parentId?: string; // WBS tree structure + agentId?: string; // Assigned AI agent + agentConfig?: string; // Agent config reference + input?: Record; + output?: Record; + score?: number; // HR Manager score (1-5) + createdAt: string; + updatedAt: string; + completedAt?: string; +} + +export interface Project { + id: string; + name: string; + goal: string; + scopeIn: string; + scopeOut: string; + constraints: string; + assumptions: string; + status: 'draft' | 'active' | 'completed' | 'archived'; + createdAt: string; + updatedAt: string; +} + +export interface Stakeholder { + id: string; + projectId: string; + name: string; + role: string; + power: 'high' | 'medium' | 'low'; + interest: 'high' | 'medium' | 'low'; + attitude?: string; + strategy: string; +} + +export interface Milestone { + id: string; + projectId: string; + name: string; + targetDate: string; + actualDate?: string; + deliverable: string; + status: 'planned' | 'reached' | 'missed'; +} + +export interface Risk { + id: string; + projectId: string; + description: string; + category: 'technical' | 'resource' | 'scope' | 'external'; + probability: number; // 1-5 + impact: number; // 1-5 + priority: number; // probability * impact + strategy: 'avoid' | 'transfer' | 'mitigate' | 'accept'; + strategyDetail: string; + owner: string; + status: 'monitoring' | 'occurred' | 'mitigated' | 'closed'; +} + +export interface AgentConfig { + id: string; + agentType: string; + model: string; + promptVersion: number; + promptTemplate: string; + avgScore: number; + usageCount: number; +} + +export interface ExecutionLog { + id: string; + taskId: string; + agentId: string; + input: Record; + output: Record; + score: number; + tokensUsed: number; + model: string; + durationMs: number; + createdAt: string; +} + +export interface DecisionLog { + id: string; + taskId: string; + decisionType: 'ai_auto' | 'human_confirm' | 'human_decision'; + context: string; + decision: string; + decider: 'user' | 'hr_manager' | 'experience_manager'; + createdAt: string; +} + +// --- CRUD Interface (to be implemented with DB) --- + +export interface ITaskStore { + createTask(task: Omit): Promise; + getTask(id: string): Promise; + getTasksByProject(projectId: string): Promise; + getTasksByStatus(projectId: string, status: TaskStatus): Promise; + updateTask(id: string, updates: Partial): Promise; + deleteTask(id: string): Promise; + moveTask(id: string, status: TaskStatus): Promise; +} + +export interface IProjectStore { + createProject(project: Omit): Promise; + getProject(id: string): Promise; + updateProject(id: string, updates: Partial): Promise; + listProjects(): Promise; +}