team-anya-cli 0.1.1 → 0.1.2
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.
- package/package.json +1 -1
- package/apps/server/dist/loid/brief-assembler.js +0 -156
- package/apps/server/dist/loid/commitment-tracker.js +0 -236
- package/apps/server/dist/loid/dispatcher.js +0 -544
- package/apps/server/dist/loid/intent-classifier.js +0 -158
- package/apps/server/dist/loid/process-manager.js +0 -186
- package/packages/mcp-tools/dist/layer2/loid/task-review.js +0 -151
- package/packages/mcp-tools/dist/layer2/loid/workspace-cleanup.js +0 -7
- package/packages/mcp-tools/dist/layer2/loid/workspace-info.js +0 -31
- package/packages/mcp-tools/dist/layer2/loid/workspace-prepare.js +0 -12
- package/packages/mcp-tools/dist/layer2/yor/code-lint.js +0 -47
- package/packages/mcp-tools/dist/layer2/yor/code-test.js +0 -52
- package/packages/mcp-tools/dist/layer2/yor/git-add.js +0 -24
- package/packages/mcp-tools/dist/layer2/yor/git-commit.js +0 -24
- package/packages/mcp-tools/dist/layer2/yor/git-push.js +0 -64
package/package.json
CHANGED
|
@@ -1,156 +0,0 @@
|
|
|
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
|
|
@@ -1,236 +0,0 @@
|
|
|
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
|