team-anya 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of team-anya might be problematic. Click here for more details.
- package/README.md +38 -0
- package/anya/prompts/execution-guides/git-delivery.md +38 -0
- package/anya/prompts/execution-guides/testing-and-self-heal.md +28 -0
- package/anya/prompts/protocols/brief-assembly.md +55 -0
- package/anya/prompts/protocols/report.md +175 -0
- package/anya/prompts/protocols/review.md +90 -0
- package/anya/prompts/task-claude-md.template.md +32 -0
- package/apps/server/dist/broker/cc-broker.js +257 -0
- package/apps/server/dist/cli.js +296 -0
- package/apps/server/dist/config.js +76 -0
- package/apps/server/dist/daemon.js +51 -0
- package/apps/server/dist/gateway/chat-sync.js +135 -0
- package/apps/server/dist/gateway/command-router.js +114 -0
- package/apps/server/dist/gateway/commands/cancel.js +32 -0
- package/apps/server/dist/gateway/commands/help.js +16 -0
- package/apps/server/dist/gateway/commands/index.js +26 -0
- package/apps/server/dist/gateway/commands/restart.js +34 -0
- package/apps/server/dist/gateway/commands/status.js +34 -0
- package/apps/server/dist/gateway/commands/tasks.js +33 -0
- package/apps/server/dist/gateway/feishu-sender.js +346 -0
- package/apps/server/dist/gateway/feishu-ws.js +254 -0
- package/apps/server/dist/gateway/http.js +994 -0
- package/apps/server/dist/gateway/media-downloader.js +149 -0
- package/apps/server/dist/gateway/message-events.js +10 -0
- package/apps/server/dist/gateway/message-intake.js +50 -0
- package/apps/server/dist/gateway/message-queue.js +104 -0
- package/apps/server/dist/gateway/session-reader.js +142 -0
- package/apps/server/dist/gateway/ws-push.js +115 -0
- package/apps/server/dist/loid/brain.js +104 -0
- package/apps/server/dist/loid/brief-assembler.js +156 -0
- package/apps/server/dist/loid/clarifier.js +162 -0
- package/apps/server/dist/loid/commitment-tracker.js +236 -0
- package/apps/server/dist/loid/context-builder.js +413 -0
- package/apps/server/dist/loid/dispatcher.js +544 -0
- package/apps/server/dist/loid/intent-classifier.js +158 -0
- package/apps/server/dist/loid/mcp-server.js +104 -0
- package/apps/server/dist/loid/memory-settler.js +189 -0
- package/apps/server/dist/loid/opportunity-manager.js +148 -0
- package/apps/server/dist/loid/process-manager.js +186 -0
- package/apps/server/dist/loid/profile-updater.js +179 -0
- package/apps/server/dist/loid/reporter.js +148 -0
- package/apps/server/dist/loid/schemas.js +117 -0
- package/apps/server/dist/loid/self-calibrator.js +314 -0
- package/apps/server/dist/loid/session-manager.js +217 -0
- package/apps/server/dist/loid/session.js +271 -0
- package/apps/server/dist/loid/worktree-manager.js +191 -0
- package/apps/server/dist/main.js +337 -0
- package/apps/server/dist/tracing/index.js +2 -0
- package/apps/server/dist/tracing/trace-context.js +92 -0
- package/apps/server/dist/types/message.js +2 -0
- package/apps/server/dist/yor/yor-mcp-server.js +104 -0
- package/apps/server/dist/yor/yor-orchestrator.js +233 -0
- package/apps/web/dist/assets/index-CHIT0Dya.css +1 -0
- package/apps/web/dist/assets/index-CJzAjoVH.js +798 -0
- package/apps/web/dist/index.html +13 -0
- package/package.json +42 -0
- package/packages/cc-client/dist/claude-code-backend.js +664 -0
- package/packages/cc-client/dist/index.js +2 -0
- package/packages/cc-client/package.json +11 -0
- package/packages/core/dist/constants.js +59 -0
- package/packages/core/dist/errors.js +35 -0
- package/packages/core/dist/index.js +7 -0
- package/packages/core/dist/office-init.js +97 -0
- package/packages/core/dist/scope/checker.js +114 -0
- package/packages/core/dist/scope/defaults.js +40 -0
- package/packages/core/dist/scope/index.js +3 -0
- package/packages/core/dist/state-machine.js +85 -0
- package/packages/core/dist/types/audit.js +12 -0
- package/packages/core/dist/types/backend.js +2 -0
- package/packages/core/dist/types/commitment.js +17 -0
- package/packages/core/dist/types/communication.js +18 -0
- package/packages/core/dist/types/index.js +8 -0
- package/packages/core/dist/types/opportunity.js +27 -0
- package/packages/core/dist/types/org.js +26 -0
- package/packages/core/dist/types/task.js +46 -0
- package/packages/core/package.json +10 -0
- package/packages/db/dist/client.js +69 -0
- package/packages/db/dist/index.js +603 -0
- package/packages/db/dist/schema/audit-events.js +13 -0
- package/packages/db/dist/schema/cc-sessions.js +14 -0
- package/packages/db/dist/schema/chats.js +33 -0
- package/packages/db/dist/schema/commitments.js +18 -0
- package/packages/db/dist/schema/communication-events.js +14 -0
- package/packages/db/dist/schema/index.js +12 -0
- package/packages/db/dist/schema/message-log.js +20 -0
- package/packages/db/dist/schema/opportunities.js +23 -0
- package/packages/db/dist/schema/org.js +36 -0
- package/packages/db/dist/schema/projects.js +23 -0
- package/packages/db/dist/schema/tasks.js +46 -0
- package/packages/db/dist/schema/trace-spans.js +19 -0
- package/packages/db/package.json +12 -0
- package/packages/db/src/migrations/0000_simple_magneto.sql +148 -0
- package/packages/db/src/migrations/0001_nifty_morph.sql +42 -0
- package/packages/db/src/migrations/0002_common_joshua_kane.sql +20 -0
- package/packages/db/src/migrations/0003_add_cc_sessions.sql +13 -0
- package/packages/db/src/migrations/0004_jittery_triathlon.sql +1 -0
- package/packages/db/src/migrations/meta/0000_snapshot.json +987 -0
- package/packages/db/src/migrations/meta/0001_snapshot.json +1280 -0
- package/packages/db/src/migrations/meta/0002_snapshot.json +1417 -0
- package/packages/db/src/migrations/meta/0004_snapshot.json +1505 -0
- package/packages/db/src/migrations/meta/_journal.json +41 -0
- package/packages/mcp-tools/dist/index.js +41 -0
- package/packages/mcp-tools/dist/layer1/audit-append.js +38 -0
- package/packages/mcp-tools/dist/layer1/audit-query.js +51 -0
- package/packages/mcp-tools/dist/layer1/memory-brief.js +168 -0
- package/packages/mcp-tools/dist/layer1/memory-context.js +124 -0
- package/packages/mcp-tools/dist/layer1/memory-digest.js +126 -0
- package/packages/mcp-tools/dist/layer1/memory-forget.js +108 -0
- package/packages/mcp-tools/dist/layer1/memory-learn.js +63 -0
- package/packages/mcp-tools/dist/layer1/memory-recall.js +287 -0
- package/packages/mcp-tools/dist/layer1/memory-reflect.js +80 -0
- package/packages/mcp-tools/dist/layer1/memory-remember.js +119 -0
- package/packages/mcp-tools/dist/layer1/memory-search.js +263 -0
- package/packages/mcp-tools/dist/layer1/memory-write.js +21 -0
- package/packages/mcp-tools/dist/layer1/org-lookup.js +47 -0
- package/packages/mcp-tools/dist/layer1/project-get.js +28 -0
- package/packages/mcp-tools/dist/layer1/project-list.js +20 -0
- package/packages/mcp-tools/dist/layer1/report-daily.js +68 -0
- package/packages/mcp-tools/dist/layer1/task-get.js +29 -0
- package/packages/mcp-tools/dist/layer1/task-update.js +34 -0
- package/packages/mcp-tools/dist/layer2/loid/decision-log.js +15 -0
- package/packages/mcp-tools/dist/layer2/loid/decision-no-action.js +15 -0
- package/packages/mcp-tools/dist/layer2/loid/delivery-create-pr.js +30 -0
- package/packages/mcp-tools/dist/layer2/loid/delivery-share.js +12 -0
- package/packages/mcp-tools/dist/layer2/loid/delivery-submit.js +77 -0
- package/packages/mcp-tools/dist/layer2/loid/delivery-upload.js +18 -0
- package/packages/mcp-tools/dist/layer2/loid/project-remove.js +16 -0
- package/packages/mcp-tools/dist/layer2/loid/project-upsert.js +33 -0
- package/packages/mcp-tools/dist/layer2/loid/task-dispatch.js +177 -0
- package/packages/mcp-tools/dist/layer2/loid/task-lookup.js +38 -0
- package/packages/mcp-tools/dist/layer2/loid/task-review.js +151 -0
- package/packages/mcp-tools/dist/layer2/loid/workspace-cleanup.js +7 -0
- package/packages/mcp-tools/dist/layer2/loid/workspace-info.js +31 -0
- package/packages/mcp-tools/dist/layer2/loid/workspace-prepare.js +12 -0
- package/packages/mcp-tools/dist/layer2/loid/yor-approve.js +8 -0
- package/packages/mcp-tools/dist/layer2/loid/yor-kill.js +7 -0
- package/packages/mcp-tools/dist/layer2/loid/yor-rework.js +7 -0
- package/packages/mcp-tools/dist/layer2/loid/yor-spawn.js +15 -0
- package/packages/mcp-tools/dist/layer2/loid/yor-status.js +8 -0
- package/packages/mcp-tools/dist/layer2/yor/code-lint.js +47 -0
- package/packages/mcp-tools/dist/layer2/yor/code-test.js +52 -0
- package/packages/mcp-tools/dist/layer2/yor/git-add.js +24 -0
- package/packages/mcp-tools/dist/layer2/yor/git-commit.js +24 -0
- package/packages/mcp-tools/dist/layer2/yor/git-push.js +64 -0
- package/packages/mcp-tools/dist/layer2/yor/task-block.js +11 -0
- package/packages/mcp-tools/dist/layer2/yor/task-deliver.js +35 -0
- package/packages/mcp-tools/dist/layer2/yor/task-progress.js +21 -0
- package/packages/mcp-tools/dist/layer3/adapters/feishu-adapter.js +191 -0
- package/packages/mcp-tools/dist/layer3/adapters/types.js +28 -0
- package/packages/mcp-tools/dist/layer3/channel-receive.js +11 -0
- package/packages/mcp-tools/dist/layer3/channel-send.js +90 -0
- package/packages/mcp-tools/dist/layer3/file-upload.js +44 -0
- package/packages/mcp-tools/dist/registry.js +779 -0
- package/packages/mcp-tools/package.json +13 -0
- package/workspace/.claude/settings.local.json +9 -0
- package/workspace/.mcp.json +12 -0
- package/workspace/CHARTER.md +73 -0
- package/workspace/CLAUDE.md +49 -0
- package/workspace/PROTOCOL.md +126 -0
- package/workspace/TOOLS.md +464 -0
- package/workspace/audit/.gitkeep +0 -0
- package/workspace/loid/CLAUDE.md +12 -0
- package/workspace/loid/PLAYBOOK.md +198 -0
- package/workspace/loid/PROFILE.md +78 -0
- package/workspace/memory/commitments/.gitkeep +0 -0
- package/workspace/memory/execution/.gitkeep +0 -0
- package/workspace/memory/people/.gitkeep +0 -0
- package/workspace/memory/projects/.gitkeep +0 -0
- package/workspace/memory/self/.gitkeep +0 -0
- package/workspace/reference/identity/.gitkeep +0 -0
- package/workspace/reference/org/escalation.yaml +24 -0
- package/workspace/reference/org/ownership.yaml +28 -0
- package/workspace/reports/.gitkeep +0 -0
- package/workspace/yor/CLAUDE.md +22 -0
- package/workspace/yor/PLAYBOOK.md +73 -0
- package/workspace/yor/PROFILE.md +52 -0
- package/workspace/yor/SELF-HEAL.md +39 -0
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
// ── 本地类型定义(避免对 mcp-tools 内部类型的直接依赖)──
|
|
2
|
+
// ── 粗略 token 估算 ──
|
|
3
|
+
function estimateTokens(text) {
|
|
4
|
+
// 粗略估算:中文 ~1.5 token/字,英文 ~0.25 token/word
|
|
5
|
+
// 简化为 1 token ≈ 3 字符
|
|
6
|
+
return Math.ceil(text.length / 3);
|
|
7
|
+
}
|
|
8
|
+
// ── BriefAssembler ──
|
|
9
|
+
export class BriefAssembler {
|
|
10
|
+
deps;
|
|
11
|
+
maxTokenBudget;
|
|
12
|
+
constructor(deps, options) {
|
|
13
|
+
this.deps = deps;
|
|
14
|
+
this.maxTokenBudget = options?.maxTokenBudget ?? 4000;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* 收集 brief 所需的全部数据
|
|
18
|
+
*/
|
|
19
|
+
async gatherData(taskId) {
|
|
20
|
+
// taskContext 是必须的,失败则直接抛出
|
|
21
|
+
const ctx = await this.deps.getTaskContext(taskId);
|
|
22
|
+
// 并行收集可选数据(失败安全降级)
|
|
23
|
+
const [memoryResult, orgResult] = await Promise.allSettled([
|
|
24
|
+
this.deps.searchMemory(ctx.task.title + ' ' + (ctx.task.objective ?? ''), ctx.task.project_id ?? undefined, ['pitfall', 'decision']),
|
|
25
|
+
this.deps.lookupOrg(ctx.task.project_id ?? undefined),
|
|
26
|
+
]);
|
|
27
|
+
const memories = memoryResult.status === 'fulfilled'
|
|
28
|
+
? memoryResult.value.results
|
|
29
|
+
: [];
|
|
30
|
+
const orgData = orgResult.status === 'fulfilled'
|
|
31
|
+
? orgResult.value
|
|
32
|
+
: { ownership: [], escalation_path: [] };
|
|
33
|
+
return {
|
|
34
|
+
task: ctx.task,
|
|
35
|
+
clarifications: ctx.clarifications,
|
|
36
|
+
memories,
|
|
37
|
+
ownership: orgData.ownership ?? [],
|
|
38
|
+
escalationPath: orgData.escalation_path ?? [],
|
|
39
|
+
executionGuidance: ctx.task.execution_guidance,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* 将收集到的数据渲染为 brief.md
|
|
44
|
+
*
|
|
45
|
+
* 只写"做什么、做到什么程度",不写"怎么做"
|
|
46
|
+
*
|
|
47
|
+
* Token 预算优先级:验收标准 > 踩坑 > 决策 > 联系方式
|
|
48
|
+
*/
|
|
49
|
+
renderBrief(data) {
|
|
50
|
+
const sections = [];
|
|
51
|
+
let usedTokens = 0;
|
|
52
|
+
// Header(最高优先级,不计预算)
|
|
53
|
+
const header = `# Brief: ${data.task.title}\n\n> Task ID: ${data.task.task_id}\n`;
|
|
54
|
+
usedTokens += estimateTokens(header);
|
|
55
|
+
// 1. What — 目标(最高优先级)
|
|
56
|
+
if (data.task.objective) {
|
|
57
|
+
const objectiveSection = `## What\n\n${data.task.objective}\n`;
|
|
58
|
+
sections.push({ priority: 0, content: objectiveSection });
|
|
59
|
+
}
|
|
60
|
+
// 2. DoD — 验收标准(最高优先级)
|
|
61
|
+
const criteria = this.parseAcceptanceCriteria(data.task.acceptance_criteria);
|
|
62
|
+
if (criteria.length > 0) {
|
|
63
|
+
const criteriaSection = `## DoD\n\n${criteria.map(c => `- ${c}`).join('\n')}\n`;
|
|
64
|
+
sections.push({ priority: 1, content: criteriaSection });
|
|
65
|
+
}
|
|
66
|
+
// 3. Why — 背景(高优先级)
|
|
67
|
+
if (data.task.context) {
|
|
68
|
+
let bgContent = data.task.context;
|
|
69
|
+
// 附加澄清信息
|
|
70
|
+
const answeredClarifications = data.clarifications.filter(c => c.answer);
|
|
71
|
+
if (answeredClarifications.length > 0) {
|
|
72
|
+
bgContent += '\n\n**补充澄清:**\n' +
|
|
73
|
+
answeredClarifications.map(c => `- Q: ${c.question}\n A: ${c.answer}`).join('\n');
|
|
74
|
+
}
|
|
75
|
+
const bgSection = `## Why\n\n${bgContent}\n`;
|
|
76
|
+
sections.push({ priority: 2, content: bgSection });
|
|
77
|
+
}
|
|
78
|
+
// 4. 执行指引(高优先级,介于背景与约束之间)
|
|
79
|
+
const guidanceText = data.executionGuidance || '将结果写入 scratch/result.md';
|
|
80
|
+
const guidanceSection = `## 执行指引\n\n${guidanceText}\n`;
|
|
81
|
+
sections.push({ priority: 2.5, content: guidanceSection });
|
|
82
|
+
// 5. Constraints — 约束条件(高优先级)
|
|
83
|
+
const constraints = [];
|
|
84
|
+
if (data.task.project_id) {
|
|
85
|
+
constraints.push(`项目: ${data.task.project_id}`);
|
|
86
|
+
}
|
|
87
|
+
if (data.ownership && data.ownership.length > 0) {
|
|
88
|
+
constraints.push(...data.ownership.map(o => `${o.target} → ${o.member_id} (${o.role})`));
|
|
89
|
+
}
|
|
90
|
+
if (data.escalationPath && data.escalationPath.length > 0) {
|
|
91
|
+
constraints.push(...data.escalationPath.map(e => `${e.after_minutes} 分钟后: ${e.action} → ${e.targets.join(', ')}`));
|
|
92
|
+
}
|
|
93
|
+
if (constraints.length > 0) {
|
|
94
|
+
const constraintsSection = `## Constraints\n\n${constraints.map(c => `- ${c}`).join('\n')}\n`;
|
|
95
|
+
sections.push({ priority: 3, content: constraintsSection });
|
|
96
|
+
}
|
|
97
|
+
// 7. 历史经验 — 踩坑(中优先级)
|
|
98
|
+
const pitfalls = data.memories.filter(m => m.type === 'pitfall');
|
|
99
|
+
if (pitfalls.length > 0) {
|
|
100
|
+
const pitfallSection = `## 历史踩坑\n\n${pitfalls.map(p => `- ${p.content}`).join('\n')}\n`;
|
|
101
|
+
sections.push({ priority: 4, content: pitfallSection });
|
|
102
|
+
}
|
|
103
|
+
// 8. 历史经验 — 决策(中低优先级)
|
|
104
|
+
const decisions = data.memories.filter(m => m.type === 'decision');
|
|
105
|
+
if (decisions.length > 0) {
|
|
106
|
+
const decisionSection = `## 历史决策\n\n${decisions.map(d => `- ${d.content}`).join('\n')}\n`;
|
|
107
|
+
sections.push({ priority: 5, content: decisionSection });
|
|
108
|
+
}
|
|
109
|
+
// 按优先级排序
|
|
110
|
+
sections.sort((a, b) => a.priority - b.priority);
|
|
111
|
+
// Token 预算控制
|
|
112
|
+
const result = [header];
|
|
113
|
+
for (const section of sections) {
|
|
114
|
+
const sectionTokens = estimateTokens(section.content);
|
|
115
|
+
if (usedTokens + sectionTokens <= this.maxTokenBudget) {
|
|
116
|
+
result.push(section.content);
|
|
117
|
+
usedTokens += sectionTokens;
|
|
118
|
+
}
|
|
119
|
+
else {
|
|
120
|
+
// 尝试截断
|
|
121
|
+
const remainingBudget = this.maxTokenBudget - usedTokens;
|
|
122
|
+
if (remainingBudget > 50) {
|
|
123
|
+
// 至少留 50 token 才截断
|
|
124
|
+
const maxChars = remainingBudget * 3;
|
|
125
|
+
const truncated = section.content.slice(0, maxChars) + '\n\n…(已截断)\n';
|
|
126
|
+
result.push(truncated);
|
|
127
|
+
usedTokens = this.maxTokenBudget;
|
|
128
|
+
}
|
|
129
|
+
break;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return result.join('\n');
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* 端到端方法:收集数据 + 渲染 brief
|
|
136
|
+
*/
|
|
137
|
+
async assembleBrief(taskId) {
|
|
138
|
+
const data = await this.gatherData(taskId);
|
|
139
|
+
return this.renderBrief(data);
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* 解析验收标准(JSON 数组字符串或 null)
|
|
143
|
+
*/
|
|
144
|
+
parseAcceptanceCriteria(ac) {
|
|
145
|
+
if (!ac)
|
|
146
|
+
return [];
|
|
147
|
+
try {
|
|
148
|
+
const parsed = JSON.parse(ac);
|
|
149
|
+
return Array.isArray(parsed) ? parsed : [];
|
|
150
|
+
}
|
|
151
|
+
catch {
|
|
152
|
+
return [];
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
//# sourceMappingURL=brief-assembler.js.map
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import { getTask, updateTask, addClarification, getClarifications, answerClarification } from '@team-anya/db';
|
|
2
|
+
import { TaskStatus } from '@team-anya/core';
|
|
3
|
+
// ── 问题模板 ──
|
|
4
|
+
const QUESTION_TEMPLATES = {
|
|
5
|
+
objective: [
|
|
6
|
+
'这个任务的具体目标是什么?希望达到什么效果?',
|
|
7
|
+
],
|
|
8
|
+
acceptance_criteria: [
|
|
9
|
+
'这个任务的验收标准是什么?怎样算完成?',
|
|
10
|
+
],
|
|
11
|
+
context: [
|
|
12
|
+
'能否提供更多背景信息?比如触发场景、影响范围、相关日志等?',
|
|
13
|
+
],
|
|
14
|
+
scope: [
|
|
15
|
+
'这个任务涉及哪个项目/仓库?影响范围有多大?',
|
|
16
|
+
],
|
|
17
|
+
};
|
|
18
|
+
// 精确型 sender 只问关键项(objective 和 acceptance_criteria 优先)
|
|
19
|
+
const CRITICAL_FIELDS = ['objective', 'acceptance_criteria'];
|
|
20
|
+
// ── Clarifier ──
|
|
21
|
+
export class Clarifier {
|
|
22
|
+
db;
|
|
23
|
+
getProfileStyle;
|
|
24
|
+
constructor(deps) {
|
|
25
|
+
this.db = deps.db;
|
|
26
|
+
this.getProfileStyle = deps.getProfileStyle;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* 检查任务需求的完整性(四要素:objective, acceptance_criteria, context, scope)
|
|
30
|
+
*/
|
|
31
|
+
checkCompleteness(taskData) {
|
|
32
|
+
const missing = [];
|
|
33
|
+
if (!taskData.objective || taskData.objective.trim() === '') {
|
|
34
|
+
missing.push('objective');
|
|
35
|
+
}
|
|
36
|
+
if (!taskData.acceptance_criteria || taskData.acceptance_criteria.trim() === '') {
|
|
37
|
+
missing.push('acceptance_criteria');
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
// 检查 JSON 数组是否为空
|
|
41
|
+
try {
|
|
42
|
+
const parsed = JSON.parse(taskData.acceptance_criteria);
|
|
43
|
+
if (Array.isArray(parsed) && parsed.length === 0) {
|
|
44
|
+
missing.push('acceptance_criteria');
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
// 非 JSON 格式,视为有值
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
if (!taskData.context || taskData.context.trim() === '') {
|
|
52
|
+
missing.push('context');
|
|
53
|
+
}
|
|
54
|
+
if (!taskData.project_id || taskData.project_id.trim() === '') {
|
|
55
|
+
missing.push('scope');
|
|
56
|
+
}
|
|
57
|
+
return {
|
|
58
|
+
isComplete: missing.length === 0,
|
|
59
|
+
missing,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* 根据缺失项生成澄清问题
|
|
64
|
+
* @param missing 缺失的要素名
|
|
65
|
+
* @param style sender 的沟通风格(verbose=全问, precise=只问关键项)
|
|
66
|
+
*/
|
|
67
|
+
generateQuestions(missing, style = 'verbose') {
|
|
68
|
+
if (missing.length === 0)
|
|
69
|
+
return [];
|
|
70
|
+
const fieldsToAsk = style === 'precise'
|
|
71
|
+
? missing.filter(m => CRITICAL_FIELDS.includes(m))
|
|
72
|
+
: missing;
|
|
73
|
+
// 精确型如果没有关键项缺失,仍至少问第一个缺失项
|
|
74
|
+
const effectiveFields = fieldsToAsk.length > 0 ? fieldsToAsk : [missing[0]];
|
|
75
|
+
const questions = [];
|
|
76
|
+
for (const field of effectiveFields) {
|
|
77
|
+
const templates = QUESTION_TEMPLATES[field];
|
|
78
|
+
if (templates) {
|
|
79
|
+
questions.push(...templates);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return questions;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* 对任务进行需求澄清
|
|
86
|
+
* - 完整 → 直接转为 READY
|
|
87
|
+
* - 不完整 → 转为 NEED_CLARIFICATION,生成问题并写入 DB
|
|
88
|
+
*/
|
|
89
|
+
async clarifyTask(taskId, senderId) {
|
|
90
|
+
const task = getTask(this.db, taskId);
|
|
91
|
+
if (!task) {
|
|
92
|
+
throw new Error(`任务不存在: ${taskId}`);
|
|
93
|
+
}
|
|
94
|
+
if (task.status !== TaskStatus.NEW) {
|
|
95
|
+
throw new Error(`任务状态为 ${task.status},只有 NEW 状态才能进行澄清`);
|
|
96
|
+
}
|
|
97
|
+
const check = this.checkCompleteness({
|
|
98
|
+
objective: task.objective,
|
|
99
|
+
acceptance_criteria: task.acceptance_criteria,
|
|
100
|
+
context: task.context,
|
|
101
|
+
project_id: task.project_id,
|
|
102
|
+
});
|
|
103
|
+
if (check.isComplete) {
|
|
104
|
+
// 直接转为 READY
|
|
105
|
+
updateTask(this.db, taskId, { status: TaskStatus.READY });
|
|
106
|
+
return {
|
|
107
|
+
needsClarification: false,
|
|
108
|
+
questions: [],
|
|
109
|
+
missing: [],
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
// 获取 sender 的沟通风格
|
|
113
|
+
let style = 'verbose';
|
|
114
|
+
if (senderId && this.getProfileStyle) {
|
|
115
|
+
try {
|
|
116
|
+
style = await this.getProfileStyle(senderId);
|
|
117
|
+
}
|
|
118
|
+
catch {
|
|
119
|
+
// 降级为 verbose
|
|
120
|
+
style = 'verbose';
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
const questions = this.generateQuestions(check.missing, style);
|
|
124
|
+
// 转为 NEED_CLARIFICATION
|
|
125
|
+
updateTask(this.db, taskId, { status: TaskStatus.NEED_CLARIFICATION });
|
|
126
|
+
// 将问题写入 clarifications 表
|
|
127
|
+
for (const question of questions) {
|
|
128
|
+
addClarification(this.db, {
|
|
129
|
+
task_id: taskId,
|
|
130
|
+
question,
|
|
131
|
+
asked_by: 'loid',
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
return {
|
|
135
|
+
needsClarification: true,
|
|
136
|
+
questions,
|
|
137
|
+
missing: check.missing,
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* 处理澄清回答
|
|
142
|
+
* - 更新 clarification 记录
|
|
143
|
+
* - 如果所有问题都已回答,将任务转为 READY
|
|
144
|
+
*/
|
|
145
|
+
async handleAnswer(clarificationId, answer, answeredBy) {
|
|
146
|
+
const updated = answerClarification(this.db, clarificationId, answer, answeredBy);
|
|
147
|
+
if (!updated) {
|
|
148
|
+
throw new Error(`澄清记录不存在: ${clarificationId}`);
|
|
149
|
+
}
|
|
150
|
+
const taskId = updated.task_id;
|
|
151
|
+
// 检查是否所有问题都已回答
|
|
152
|
+
const allClarifications = getClarifications(this.db, taskId);
|
|
153
|
+
const allAnswered = allClarifications.every(c => c.answer !== null);
|
|
154
|
+
if (allAnswered) {
|
|
155
|
+
const task = getTask(this.db, taskId);
|
|
156
|
+
if (task && task.status === TaskStatus.NEED_CLARIFICATION) {
|
|
157
|
+
updateTask(this.db, taskId, { status: TaskStatus.READY });
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
//# sourceMappingURL=clarifier.js.map
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
import { EventEmitter } from 'node:events';
|
|
2
|
+
import { createCommitment, getCommitment, getActiveCommitments, updateCommitment, } from '@team-anya/db';
|
|
3
|
+
/**
|
|
4
|
+
* 承诺追踪器:自动提取、追踪、检测逾期承诺
|
|
5
|
+
*/
|
|
6
|
+
export class CommitmentTracker extends EventEmitter {
|
|
7
|
+
db;
|
|
8
|
+
workspacePath;
|
|
9
|
+
intervalHandle = null;
|
|
10
|
+
constructor(deps) {
|
|
11
|
+
super();
|
|
12
|
+
this.db = deps.db;
|
|
13
|
+
this.workspacePath = deps.workspacePath;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* 从任务文本中提取承诺
|
|
17
|
+
* 识别模式:
|
|
18
|
+
* - "向 @xxx 承诺...(动作)"
|
|
19
|
+
* - "我会在...之前完成/交付..."
|
|
20
|
+
* - "承诺给 @xxx 在...之前..."
|
|
21
|
+
* - "保证给 @xxx 在...前..."
|
|
22
|
+
* - "告知 @xxx 我会在...之前..."
|
|
23
|
+
*/
|
|
24
|
+
async extractCommitments(task, context) {
|
|
25
|
+
const commitments = [];
|
|
26
|
+
const patterns = this.buildPatterns();
|
|
27
|
+
for (const pattern of patterns) {
|
|
28
|
+
const matches = context.matchAll(pattern.regex);
|
|
29
|
+
for (const match of matches) {
|
|
30
|
+
const extracted = pattern.extract(match, task.task_id);
|
|
31
|
+
if (extracted && !this.isDuplicate(commitments, extracted)) {
|
|
32
|
+
commitments.push(extracted);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
// 保存到数据库
|
|
37
|
+
const saved = [];
|
|
38
|
+
for (const c of commitments) {
|
|
39
|
+
const record = createCommitment(this.db, {
|
|
40
|
+
task_id: c.task_id,
|
|
41
|
+
promised_to: c.promised_to,
|
|
42
|
+
promise: c.promise,
|
|
43
|
+
deadline: c.deadline,
|
|
44
|
+
status: 'active',
|
|
45
|
+
});
|
|
46
|
+
saved.push({
|
|
47
|
+
id: record.id,
|
|
48
|
+
task_id: record.task_id,
|
|
49
|
+
promised_to: record.promised_to,
|
|
50
|
+
promise: record.promise,
|
|
51
|
+
deadline: record.deadline,
|
|
52
|
+
status: record.status,
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
return saved;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* 扫描逾期承诺,将其标记为 broken
|
|
59
|
+
*/
|
|
60
|
+
async checkOverdue() {
|
|
61
|
+
const active = getActiveCommitments(this.db);
|
|
62
|
+
const now = new Date();
|
|
63
|
+
const overdue = [];
|
|
64
|
+
for (const c of active) {
|
|
65
|
+
if (!c.deadline)
|
|
66
|
+
continue;
|
|
67
|
+
const deadlineDate = new Date(c.deadline);
|
|
68
|
+
if (deadlineDate < now) {
|
|
69
|
+
updateCommitment(this.db, c.id, {
|
|
70
|
+
status: 'broken',
|
|
71
|
+
break_reason: `逾期:deadline ${c.deadline} 已过`,
|
|
72
|
+
});
|
|
73
|
+
overdue.push({
|
|
74
|
+
id: c.id,
|
|
75
|
+
task_id: c.task_id,
|
|
76
|
+
promised_to: c.promised_to,
|
|
77
|
+
promise: c.promise,
|
|
78
|
+
deadline: c.deadline,
|
|
79
|
+
status: 'broken',
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
if (overdue.length > 0) {
|
|
84
|
+
this.emit('overdue', overdue);
|
|
85
|
+
}
|
|
86
|
+
return overdue;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* 标记承诺已兑现
|
|
90
|
+
*/
|
|
91
|
+
async fulfillCommitment(id) {
|
|
92
|
+
const existing = getCommitment(this.db, id);
|
|
93
|
+
if (!existing) {
|
|
94
|
+
throw new Error(`承诺不存在: id=${id}`);
|
|
95
|
+
}
|
|
96
|
+
updateCommitment(this.db, id, { status: 'fulfilled' });
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* 取消承诺
|
|
100
|
+
*/
|
|
101
|
+
async cancelCommitment(id, reason) {
|
|
102
|
+
const existing = getCommitment(this.db, id);
|
|
103
|
+
if (!existing) {
|
|
104
|
+
throw new Error(`承诺不存在: id=${id}`);
|
|
105
|
+
}
|
|
106
|
+
updateCommitment(this.db, id, {
|
|
107
|
+
status: 'cancelled',
|
|
108
|
+
break_reason: reason,
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* 启动定时逾期扫描
|
|
113
|
+
*/
|
|
114
|
+
startPeriodicCheck(intervalMs) {
|
|
115
|
+
this.stopPeriodicCheck();
|
|
116
|
+
this.intervalHandle = setInterval(async () => {
|
|
117
|
+
await this.checkOverdue();
|
|
118
|
+
}, intervalMs);
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* 停止定时扫描
|
|
122
|
+
*/
|
|
123
|
+
stopPeriodicCheck() {
|
|
124
|
+
if (this.intervalHandle) {
|
|
125
|
+
clearInterval(this.intervalHandle);
|
|
126
|
+
this.intervalHandle = null;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
// ── 私有方法 ──
|
|
130
|
+
buildPatterns() {
|
|
131
|
+
return [
|
|
132
|
+
// "向 @xxx 承诺在...之前..."
|
|
133
|
+
{
|
|
134
|
+
regex: /向\s*(@\w+)\s*承诺在?\s*(.+?)之前\s*(.+?)(?:[。,;\n]|$)/g,
|
|
135
|
+
extract: (m, taskId) => ({
|
|
136
|
+
task_id: taskId,
|
|
137
|
+
promised_to: m[1],
|
|
138
|
+
promise: m[3].trim(),
|
|
139
|
+
deadline: this.parseFuzzyDate(m[2].trim()),
|
|
140
|
+
status: 'active',
|
|
141
|
+
}),
|
|
142
|
+
},
|
|
143
|
+
// "承诺给 @xxx 在...之前交付/完成..."
|
|
144
|
+
{
|
|
145
|
+
regex: /承诺给?\s*(@\w+)\s*在?\s*(.+?)之前\s*(.+?)(?:[。,;\n]|$)/g,
|
|
146
|
+
extract: (m, taskId) => ({
|
|
147
|
+
task_id: taskId,
|
|
148
|
+
promised_to: m[1],
|
|
149
|
+
promise: m[3].trim(),
|
|
150
|
+
deadline: this.parseFuzzyDate(m[2].trim()),
|
|
151
|
+
status: 'active',
|
|
152
|
+
}),
|
|
153
|
+
},
|
|
154
|
+
// "保证给 @xxx 在...前完成..."
|
|
155
|
+
{
|
|
156
|
+
regex: /保证给?\s*(@\w+)\s*在?\s*(.+?)前\s*(.+?)(?:[。,;\n]|$)/g,
|
|
157
|
+
extract: (m, taskId) => ({
|
|
158
|
+
task_id: taskId,
|
|
159
|
+
promised_to: m[1],
|
|
160
|
+
promise: m[3].trim(),
|
|
161
|
+
deadline: this.parseFuzzyDate(m[2].trim()),
|
|
162
|
+
status: 'active',
|
|
163
|
+
}),
|
|
164
|
+
},
|
|
165
|
+
// "告知 @xxx 我会在...之前..."
|
|
166
|
+
{
|
|
167
|
+
regex: /告知\s*(@\w+)\s*我会在?\s*(.+?)之前\s*(.+?)(?:[。,;\n]|$)/g,
|
|
168
|
+
extract: (m, taskId) => ({
|
|
169
|
+
task_id: taskId,
|
|
170
|
+
promised_to: m[1],
|
|
171
|
+
promise: m[3].trim(),
|
|
172
|
+
deadline: this.parseFuzzyDate(m[2].trim()),
|
|
173
|
+
status: 'active',
|
|
174
|
+
}),
|
|
175
|
+
},
|
|
176
|
+
// "@xxx ...我会在...之前完成..." (generic pattern with @mention + deadline)
|
|
177
|
+
{
|
|
178
|
+
regex: /(@\w+)\s*.{0,20}我会在?\s*(.+?)之前\s*(完成|交付|修复|解决).+?(?:[。,;\n]|$)/g,
|
|
179
|
+
extract: (m, taskId) => ({
|
|
180
|
+
task_id: taskId,
|
|
181
|
+
promised_to: m[1],
|
|
182
|
+
promise: `${m[3]}${m[0].slice(m[0].indexOf(m[3]) + m[3].length).replace(/[。,;\n]+$/, '')}`.trim() || m[3],
|
|
183
|
+
deadline: this.parseFuzzyDate(m[2].trim()),
|
|
184
|
+
status: 'active',
|
|
185
|
+
}),
|
|
186
|
+
},
|
|
187
|
+
];
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* 解析模糊日期描述为 ISO 字符串
|
|
191
|
+
*/
|
|
192
|
+
parseFuzzyDate(text) {
|
|
193
|
+
// 精确日期格式
|
|
194
|
+
const dateMatch = text.match(/(\d{4}-\d{2}-\d{2})/);
|
|
195
|
+
if (dateMatch) {
|
|
196
|
+
return `${dateMatch[1]}T23:59:59.000Z`;
|
|
197
|
+
}
|
|
198
|
+
const now = new Date();
|
|
199
|
+
// "明天"
|
|
200
|
+
if (text.includes('明天')) {
|
|
201
|
+
const tomorrow = new Date(now);
|
|
202
|
+
tomorrow.setDate(tomorrow.getDate() + 1);
|
|
203
|
+
return `${tomorrow.toISOString().slice(0, 10)}T23:59:59.000Z`;
|
|
204
|
+
}
|
|
205
|
+
// "后天"
|
|
206
|
+
if (text.includes('后天')) {
|
|
207
|
+
const dayAfter = new Date(now);
|
|
208
|
+
dayAfter.setDate(dayAfter.getDate() + 2);
|
|
209
|
+
return `${dayAfter.toISOString().slice(0, 10)}T23:59:59.000Z`;
|
|
210
|
+
}
|
|
211
|
+
// "本周五" / "周五" / "本周X"
|
|
212
|
+
const weekDayMatch = text.match(/(?:本)?周([一二三四五六日天])/);
|
|
213
|
+
if (weekDayMatch) {
|
|
214
|
+
const dayMap = {
|
|
215
|
+
'一': 1, '二': 2, '三': 3, '四': 4,
|
|
216
|
+
'五': 5, '六': 6, '日': 0, '天': 0,
|
|
217
|
+
};
|
|
218
|
+
const targetDay = dayMap[weekDayMatch[1]];
|
|
219
|
+
const currentDay = now.getDay();
|
|
220
|
+
let daysUntil = targetDay - currentDay;
|
|
221
|
+
if (daysUntil <= 0)
|
|
222
|
+
daysUntil += 7;
|
|
223
|
+
const target = new Date(now);
|
|
224
|
+
target.setDate(target.getDate() + daysUntil);
|
|
225
|
+
return `${target.toISOString().slice(0, 10)}T23:59:59.000Z`;
|
|
226
|
+
}
|
|
227
|
+
return null;
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* 检查是否重复(同一人+同一承诺内容)
|
|
231
|
+
*/
|
|
232
|
+
isDuplicate(existing, candidate) {
|
|
233
|
+
return existing.some(e => e.promised_to === candidate.promised_to && e.promise === candidate.promise);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
//# sourceMappingURL=commitment-tracker.js.map
|