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.
@@ -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: {} } });
@@ -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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "team-anya-cli",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "type": "module",
5
5
  "description": "Team Anya - AI 数字员工系统",
6
6
  "bin": {
@@ -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
- return projectUpsert(deps.db, deps.workspacePath, args);
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
- return projectRemove(deps.db, deps.workspacePath, args);
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