team-anya-cli 0.1.1 → 0.1.3
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/apps/server/dist/cli.js +1 -1
- package/apps/server/dist/loid/mcp-server.js +1 -0
- package/apps/server/dist/main.js +1 -0
- package/package.json +1 -1
- package/packages/mcp-tools/dist/layer2/loid/task-dispatch.js +19 -0
- package/packages/mcp-tools/dist/registry.js +6 -2
- 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/apps/server/dist/cli.js
CHANGED
|
@@ -201,7 +201,7 @@ program
|
|
|
201
201
|
});
|
|
202
202
|
console.log('\n Anya Setup - 交互式配置向导\n');
|
|
203
203
|
// ── 1. ANYA_HOME ──
|
|
204
|
-
const anyaHome = resolveHome(await ask('数据目录 ANYA_HOME', opts.home ?? '~/.anya'));
|
|
204
|
+
const anyaHome = resolveHome(await ask('数据目录 ANYA_HOME', opts.home ?? process.env.ANYA_HOME ?? '~/.anya'));
|
|
205
205
|
// 创建目录结构
|
|
206
206
|
const dirs = ['data', 'data/logs', 'office', 'repos', 'media/feishu'];
|
|
207
207
|
for (const dir of dirs)
|
|
@@ -15,6 +15,7 @@ function createMcpServer(deps) {
|
|
|
15
15
|
onMessageSent: deps.onMessageSent,
|
|
16
16
|
yorOrchestrator: deps.yorOrchestrator,
|
|
17
17
|
getProjectConfig: deps.getProjectConfig,
|
|
18
|
+
onProjectChanged: deps.onProjectChanged,
|
|
18
19
|
};
|
|
19
20
|
const router = createToolRouter('loid', routerDeps);
|
|
20
21
|
const server = new Server({ name: 'loid-mcp', version: '3.0.0' }, { capabilities: { tools: {} } });
|
package/apps/server/dist/main.js
CHANGED
|
@@ -173,6 +173,7 @@ export async function buildServer() {
|
|
|
173
173
|
logger: app.log,
|
|
174
174
|
yorOrchestrator,
|
|
175
175
|
getProjectConfig: (projectId) => worktreeManager.getProjectConfig(projectId),
|
|
176
|
+
onProjectChanged: () => worktreeManager.invalidateCache(),
|
|
176
177
|
},
|
|
177
178
|
});
|
|
178
179
|
await loidBrain.init();
|
package/package.json
CHANGED
|
@@ -47,6 +47,25 @@ export async function taskDispatch(deps, input) {
|
|
|
47
47
|
const project = deps.getProjectConfig
|
|
48
48
|
? await deps.getProjectConfig(input.project_id)
|
|
49
49
|
: undefined;
|
|
50
|
+
// 传了 project_id 但未在注册表中找到 → 直接 BLOCKED,不静默降级为 adhoc
|
|
51
|
+
if (input.project_id && project?.mode === 'adhoc') {
|
|
52
|
+
logger?.info(`[anya:pipeline] [Loid] 创建任务 ${taskId} "${input.title}" | project=${input.project_id} 未注册,BLOCKED`);
|
|
53
|
+
assertTransition(TaskStatus.NEW, TaskStatus.BLOCKED);
|
|
54
|
+
updateTask(db, taskId, { status: TaskStatus.BLOCKED });
|
|
55
|
+
insertAuditEvent(db, {
|
|
56
|
+
event_type: 'task_status_changed',
|
|
57
|
+
actor: 'loid',
|
|
58
|
+
task_id: taskId,
|
|
59
|
+
summary: `NEW → BLOCKED: 项目 "${input.project_id}" 未在注册表中找到,请先通过 project.upsert 注册`,
|
|
60
|
+
detail: JSON.stringify({ from: 'NEW', to: 'BLOCKED', reason: 'project_not_found', project_id: input.project_id }),
|
|
61
|
+
});
|
|
62
|
+
return {
|
|
63
|
+
task_id: taskId,
|
|
64
|
+
status: 'blocked',
|
|
65
|
+
brief_path: briefPath,
|
|
66
|
+
error: `项目 "${input.project_id}" 未在注册表中找到。请先调用 project.upsert 注册项目及其仓库配置,然后重新派工。`,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
50
69
|
logger?.info(`[anya:pipeline] [Loid] 创建任务 ${taskId} "${input.title}"${input.project_id ? ` | project=${input.project_id} mode=${project?.mode}` : ''}`);
|
|
51
70
|
// 5. 自动准备工作区
|
|
52
71
|
const workspaceResult = await prepareWorkspace(taskId, taskDir, project, logger);
|
|
@@ -644,11 +644,15 @@ export function createToolRouter(role, deps) {
|
|
|
644
644
|
// Layer 2: Loid 专属 - 项目管理
|
|
645
645
|
case 'project.upsert': {
|
|
646
646
|
const { projectUpsert } = await import('./layer2/loid/project-upsert.js');
|
|
647
|
-
|
|
647
|
+
const result = await projectUpsert(deps.db, deps.workspacePath, args);
|
|
648
|
+
deps.onProjectChanged?.();
|
|
649
|
+
return result;
|
|
648
650
|
}
|
|
649
651
|
case 'project.remove': {
|
|
650
652
|
const { projectRemove } = await import('./layer2/loid/project-remove.js');
|
|
651
|
-
|
|
653
|
+
const result = await projectRemove(deps.db, deps.workspacePath, args);
|
|
654
|
+
deps.onProjectChanged?.();
|
|
655
|
+
return result;
|
|
652
656
|
}
|
|
653
657
|
// Layer 2: Loid 专属 - yor/delivery
|
|
654
658
|
case 'yor.spawn': {
|
|
@@ -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
|