feat: P0-4看板组件 + P0-5数据模型 + P0-6 HR管理员 + P0-7经验管理员 + P0-9检查清单引擎

This commit is contained in:
2026-04-11 18:53:55 +08:00
parent 107a972647
commit 8df4ea3c30
7 changed files with 924 additions and 26 deletions

View File

@@ -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 | ⬜ |

View File

@@ -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<string, { label: string; color: string }> = {
must: { label: 'M - 必须', color: 'red' },
should: { label: 'S - 应该', color: 'orange' },
could: { label: 'C - 可以', color: 'blue' },
};
const ASSIGNEE_MAP: Record<string, { label: string; color: string }> = {
user: { label: '👤 人工', color: 'arcoblue' },
ai_agent: { label: '🤖 AI', color: 'purple' },
};
const KanbanBoard: React.FC<KanbanBoardProps> = ({ tasks: initialTasks, onTaskMove, onTaskAdd }) => {
const [tasks, setTasks] = useState<Task[]>(initialTasks || []);
const [dragId, setDragId] = useState<string | null>(null);
const [addModalVisible, setAddModalVisible] = useState(false);
const [addStatus, setAddStatus] = useState<TaskStatus>('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 (
<div style={{ padding: '0 24px' }}>
<div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: 16 }}>
<Title heading={5} style={{ margin: 0 }}></Title>
<Button type="primary" size="small" onClick={() => setAddModalVisible(true)}>
+
</Button>
</div>
<div style={{ display: 'flex', gap: 16, minHeight: 400 }}>
{COLUMNS.map((col) => {
const colTasks = getTasksByStatus(col.key);
return (
<div
key={col.key}
onDrop={() => handleDrop(col.key)}
onDragOver={handleDragOver}
style={{
flex: 1,
background: '#f7f8fa',
borderRadius: 8,
padding: 12,
minHeight: 300,
}}
>
<div style={{ marginBottom: 12, fontWeight: 600, fontSize: 14 }}>
{col.title} ({colTasks.length})
</div>
{colTasks.map((task) => (
<Card
key={task.id}
draggable
onDragStart={() => handleDragStart(task.id)}
size="small"
style={{
marginBottom: 8,
cursor: 'grab',
borderLeft: `3px solid ${task.assignee === 'ai_agent' ? '#722ed1' : '#165dff'}`,
}}
hoverable
>
<Text bold style={{ fontSize: 13 }}>{task.title}</Text>
<div style={{ marginTop: 8 }}>
<Space size={4}>
<Tag size="small" color={PRIORITY_MAP[task.priority]?.color}>
{PRIORITY_MAP[task.priority]?.label}
</Tag>
<Tag size="small" color={ASSIGNEE_MAP[task.assignee]?.color}>
{ASSIGNEE_MAP[task.assignee]?.label}
</Tag>
</Space>
</div>
{task.description && (
<Text style={{ fontSize: 12, color: '#86909c', marginTop: 4, display: 'block' }}>
{task.description}
</Text>
)}
</Card>
))}
{colTasks.length === 0 && (
<Text style={{ color: '#c9cdd4', fontSize: 12 }}></Text>
)}
</div>
);
})}
</div>
<Modal
title="添加任务"
visible={addModalVisible}
onOk={handleAddTask}
onCancel={() => setAddModalVisible(false)}
>
<div style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>
<Input
placeholder="任务标题"
value={newTitle}
onChange={setNewTitle}
/>
<Select
value={newPriority}
onChange={(v) => setNewPriority(v)}
style={{ width: '100%' }}
>
<Select.Option value="must">M - </Select.Option>
<Select.Option value="should">S - </Select.Option>
<Select.Option value="could">C - </Select.Option>
</Select>
<Select
value={addStatus}
onChange={(v) => setAddStatus(v)}
style={{ width: '100%' }}
>
<Select.Option value="todo"></Select.Option>
<Select.Option value="in_progress"></Select.Option>
<Select.Option value="done"></Select.Option>
</Select>
</div>
</Modal>
</div>
);
};
export default KanbanBoard;

1
src/components/index.ts Normal file
View File

@@ -0,0 +1 @@
export { default as KanbanBoard } from './KanbanBoard';

94
src/lib/checklists.ts Normal file
View File

@@ -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<ChecklistItem, 'checked'>[] = [
// --- 启动阶段 ---
{ 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,
};
}

View File

@@ -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<string, unknown>;
outputPattern: Record<string, unknown>;
score: number; // 1-5 average
successCount: number;
failCount: number;
learnedAt: string;
}
export interface ExperienceRecord {
taskId: string;
agentId: string;
atomicType: AtomicTaskType;
input: Record<string, unknown>;
output: Record<string, unknown>;
score: number;
durationMs: number;
model: string;
success: boolean;
timestamp: string;
}
/**
* 经验管理员
*/
export class ExperienceManager {
private executionLog: ExecutionLog[] = [];
private decisionLog: DecisionLog[] = [];
private knowledgeBase: Map<string, KnowledgeEntry> = 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<DecisionLog, 'id' | 'createdAt'>): 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<string, number>;
avgScore: number;
} {
const entries = Array.from(this.knowledgeBase.values());
const byType: Record<string, number> = {};
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(),
});
}
}
}

310
src/lib/hr-manager.ts Normal file
View File

@@ -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<string, string>;
outputSchema: Record<string, string>;
}
/**
* 原子任务类型库
*/
export const ATOMIC_TASK_TYPES: Record<AtomicTaskType, AtomicTaskDef> = {
[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<string, unknown>;
outputFormat: Record<string, unknown>;
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<string, unknown>): 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<string, string> = {
[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<string, unknown>): 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',
},
];
}
}

127
src/lib/models.ts Normal file
View File

@@ -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<string, unknown>;
output?: Record<string, unknown>;
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<string, unknown>;
output: Record<string, unknown>;
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<Task, 'id' | 'createdAt' | 'updatedAt'>): Promise<Task>;
getTask(id: string): Promise<Task | null>;
getTasksByProject(projectId: string): Promise<Task[]>;
getTasksByStatus(projectId: string, status: TaskStatus): Promise<Task[]>;
updateTask(id: string, updates: Partial<Task>): Promise<Task>;
deleteTask(id: string): Promise<void>;
moveTask(id: string, status: TaskStatus): Promise<Task>;
}
export interface IProjectStore {
createProject(project: Omit<Project, 'id' | 'createdAt' | 'updatedAt'>): Promise<Project>;
getProject(id: string): Promise<Project | null>;
updateProject(id: string, updates: Partial<Project>): Promise<Project>;
listProjects(): Promise<Project[]>;
}