team-anya-cli 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.
Files changed (163) hide show
  1. package/README.md +38 -0
  2. package/anya/prompts/execution-guides/git-delivery.md +38 -0
  3. package/anya/prompts/execution-guides/testing-and-self-heal.md +28 -0
  4. package/anya/prompts/protocols/brief-assembly.md +55 -0
  5. package/anya/prompts/protocols/report.md +175 -0
  6. package/anya/prompts/protocols/review.md +90 -0
  7. package/anya/prompts/task-claude-md.template.md +32 -0
  8. package/apps/server/dist/broker/cc-broker.js +257 -0
  9. package/apps/server/dist/cli.js +296 -0
  10. package/apps/server/dist/config.js +76 -0
  11. package/apps/server/dist/daemon.js +51 -0
  12. package/apps/server/dist/gateway/chat-sync.js +135 -0
  13. package/apps/server/dist/gateway/command-router.js +114 -0
  14. package/apps/server/dist/gateway/commands/cancel.js +32 -0
  15. package/apps/server/dist/gateway/commands/help.js +16 -0
  16. package/apps/server/dist/gateway/commands/index.js +26 -0
  17. package/apps/server/dist/gateway/commands/restart.js +34 -0
  18. package/apps/server/dist/gateway/commands/status.js +34 -0
  19. package/apps/server/dist/gateway/commands/tasks.js +33 -0
  20. package/apps/server/dist/gateway/feishu-sender.js +346 -0
  21. package/apps/server/dist/gateway/feishu-ws.js +254 -0
  22. package/apps/server/dist/gateway/http.js +994 -0
  23. package/apps/server/dist/gateway/media-downloader.js +149 -0
  24. package/apps/server/dist/gateway/message-events.js +10 -0
  25. package/apps/server/dist/gateway/message-intake.js +50 -0
  26. package/apps/server/dist/gateway/message-queue.js +104 -0
  27. package/apps/server/dist/gateway/session-reader.js +142 -0
  28. package/apps/server/dist/gateway/ws-push.js +115 -0
  29. package/apps/server/dist/loid/brain.js +104 -0
  30. package/apps/server/dist/loid/clarifier.js +162 -0
  31. package/apps/server/dist/loid/context-builder.js +413 -0
  32. package/apps/server/dist/loid/mcp-server.js +104 -0
  33. package/apps/server/dist/loid/memory-settler.js +189 -0
  34. package/apps/server/dist/loid/opportunity-manager.js +148 -0
  35. package/apps/server/dist/loid/profile-updater.js +179 -0
  36. package/apps/server/dist/loid/reporter.js +148 -0
  37. package/apps/server/dist/loid/schemas.js +117 -0
  38. package/apps/server/dist/loid/self-calibrator.js +314 -0
  39. package/apps/server/dist/loid/session-manager.js +217 -0
  40. package/apps/server/dist/loid/session.js +271 -0
  41. package/apps/server/dist/loid/worktree-manager.js +191 -0
  42. package/apps/server/dist/main.js +337 -0
  43. package/apps/server/dist/tracing/index.js +2 -0
  44. package/apps/server/dist/tracing/trace-context.js +92 -0
  45. package/apps/server/dist/types/message.js +2 -0
  46. package/apps/server/dist/yor/yor-mcp-server.js +104 -0
  47. package/apps/server/dist/yor/yor-orchestrator.js +233 -0
  48. package/apps/web/dist/assets/index-CHIT0Dya.css +1 -0
  49. package/apps/web/dist/assets/index-CJzAjoVH.js +798 -0
  50. package/apps/web/dist/index.html +13 -0
  51. package/package.json +42 -0
  52. package/packages/cc-client/dist/claude-code-backend.js +664 -0
  53. package/packages/cc-client/dist/index.js +2 -0
  54. package/packages/cc-client/package.json +11 -0
  55. package/packages/core/dist/constants.js +59 -0
  56. package/packages/core/dist/errors.js +35 -0
  57. package/packages/core/dist/index.js +7 -0
  58. package/packages/core/dist/office-init.js +97 -0
  59. package/packages/core/dist/scope/checker.js +114 -0
  60. package/packages/core/dist/scope/defaults.js +40 -0
  61. package/packages/core/dist/scope/index.js +3 -0
  62. package/packages/core/dist/state-machine.js +85 -0
  63. package/packages/core/dist/types/audit.js +12 -0
  64. package/packages/core/dist/types/backend.js +2 -0
  65. package/packages/core/dist/types/commitment.js +17 -0
  66. package/packages/core/dist/types/communication.js +18 -0
  67. package/packages/core/dist/types/index.js +8 -0
  68. package/packages/core/dist/types/opportunity.js +27 -0
  69. package/packages/core/dist/types/org.js +26 -0
  70. package/packages/core/dist/types/task.js +46 -0
  71. package/packages/core/package.json +10 -0
  72. package/packages/db/dist/client.js +69 -0
  73. package/packages/db/dist/index.js +603 -0
  74. package/packages/db/dist/schema/audit-events.js +13 -0
  75. package/packages/db/dist/schema/cc-sessions.js +14 -0
  76. package/packages/db/dist/schema/chats.js +33 -0
  77. package/packages/db/dist/schema/commitments.js +18 -0
  78. package/packages/db/dist/schema/communication-events.js +14 -0
  79. package/packages/db/dist/schema/index.js +12 -0
  80. package/packages/db/dist/schema/message-log.js +20 -0
  81. package/packages/db/dist/schema/opportunities.js +23 -0
  82. package/packages/db/dist/schema/org.js +36 -0
  83. package/packages/db/dist/schema/projects.js +23 -0
  84. package/packages/db/dist/schema/tasks.js +46 -0
  85. package/packages/db/dist/schema/trace-spans.js +19 -0
  86. package/packages/db/package.json +12 -0
  87. package/packages/db/src/migrations/0000_simple_magneto.sql +148 -0
  88. package/packages/db/src/migrations/0001_nifty_morph.sql +42 -0
  89. package/packages/db/src/migrations/0002_common_joshua_kane.sql +20 -0
  90. package/packages/db/src/migrations/0003_add_cc_sessions.sql +13 -0
  91. package/packages/db/src/migrations/0004_jittery_triathlon.sql +1 -0
  92. package/packages/db/src/migrations/meta/0000_snapshot.json +987 -0
  93. package/packages/db/src/migrations/meta/0001_snapshot.json +1280 -0
  94. package/packages/db/src/migrations/meta/0002_snapshot.json +1417 -0
  95. package/packages/db/src/migrations/meta/0004_snapshot.json +1505 -0
  96. package/packages/db/src/migrations/meta/_journal.json +41 -0
  97. package/packages/mcp-tools/dist/index.js +41 -0
  98. package/packages/mcp-tools/dist/layer1/audit-append.js +38 -0
  99. package/packages/mcp-tools/dist/layer1/audit-query.js +51 -0
  100. package/packages/mcp-tools/dist/layer1/memory-brief.js +168 -0
  101. package/packages/mcp-tools/dist/layer1/memory-context.js +124 -0
  102. package/packages/mcp-tools/dist/layer1/memory-digest.js +126 -0
  103. package/packages/mcp-tools/dist/layer1/memory-forget.js +108 -0
  104. package/packages/mcp-tools/dist/layer1/memory-learn.js +63 -0
  105. package/packages/mcp-tools/dist/layer1/memory-recall.js +287 -0
  106. package/packages/mcp-tools/dist/layer1/memory-reflect.js +80 -0
  107. package/packages/mcp-tools/dist/layer1/memory-remember.js +119 -0
  108. package/packages/mcp-tools/dist/layer1/memory-search.js +263 -0
  109. package/packages/mcp-tools/dist/layer1/memory-write.js +21 -0
  110. package/packages/mcp-tools/dist/layer1/org-lookup.js +47 -0
  111. package/packages/mcp-tools/dist/layer1/project-get.js +28 -0
  112. package/packages/mcp-tools/dist/layer1/project-list.js +20 -0
  113. package/packages/mcp-tools/dist/layer1/report-daily.js +68 -0
  114. package/packages/mcp-tools/dist/layer1/task-get.js +29 -0
  115. package/packages/mcp-tools/dist/layer1/task-update.js +34 -0
  116. package/packages/mcp-tools/dist/layer2/loid/decision-log.js +15 -0
  117. package/packages/mcp-tools/dist/layer2/loid/decision-no-action.js +15 -0
  118. package/packages/mcp-tools/dist/layer2/loid/delivery-create-pr.js +30 -0
  119. package/packages/mcp-tools/dist/layer2/loid/delivery-share.js +12 -0
  120. package/packages/mcp-tools/dist/layer2/loid/delivery-submit.js +77 -0
  121. package/packages/mcp-tools/dist/layer2/loid/delivery-upload.js +18 -0
  122. package/packages/mcp-tools/dist/layer2/loid/project-remove.js +16 -0
  123. package/packages/mcp-tools/dist/layer2/loid/project-upsert.js +33 -0
  124. package/packages/mcp-tools/dist/layer2/loid/task-dispatch.js +177 -0
  125. package/packages/mcp-tools/dist/layer2/loid/task-lookup.js +38 -0
  126. package/packages/mcp-tools/dist/layer2/loid/yor-approve.js +8 -0
  127. package/packages/mcp-tools/dist/layer2/loid/yor-kill.js +7 -0
  128. package/packages/mcp-tools/dist/layer2/loid/yor-rework.js +7 -0
  129. package/packages/mcp-tools/dist/layer2/loid/yor-spawn.js +15 -0
  130. package/packages/mcp-tools/dist/layer2/loid/yor-status.js +8 -0
  131. package/packages/mcp-tools/dist/layer2/yor/task-block.js +11 -0
  132. package/packages/mcp-tools/dist/layer2/yor/task-deliver.js +35 -0
  133. package/packages/mcp-tools/dist/layer2/yor/task-progress.js +21 -0
  134. package/packages/mcp-tools/dist/layer3/adapters/feishu-adapter.js +191 -0
  135. package/packages/mcp-tools/dist/layer3/adapters/types.js +28 -0
  136. package/packages/mcp-tools/dist/layer3/channel-receive.js +11 -0
  137. package/packages/mcp-tools/dist/layer3/channel-send.js +90 -0
  138. package/packages/mcp-tools/dist/layer3/file-upload.js +44 -0
  139. package/packages/mcp-tools/dist/registry.js +779 -0
  140. package/packages/mcp-tools/package.json +13 -0
  141. package/workspace/.claude/settings.local.json +9 -0
  142. package/workspace/.mcp.json +12 -0
  143. package/workspace/CHARTER.md +73 -0
  144. package/workspace/CLAUDE.md +49 -0
  145. package/workspace/PROTOCOL.md +126 -0
  146. package/workspace/TOOLS.md +464 -0
  147. package/workspace/audit/.gitkeep +0 -0
  148. package/workspace/loid/CLAUDE.md +12 -0
  149. package/workspace/loid/PLAYBOOK.md +198 -0
  150. package/workspace/loid/PROFILE.md +78 -0
  151. package/workspace/memory/commitments/.gitkeep +0 -0
  152. package/workspace/memory/execution/.gitkeep +0 -0
  153. package/workspace/memory/people/.gitkeep +0 -0
  154. package/workspace/memory/projects/.gitkeep +0 -0
  155. package/workspace/memory/self/.gitkeep +0 -0
  156. package/workspace/reference/identity/.gitkeep +0 -0
  157. package/workspace/reference/org/escalation.yaml +24 -0
  158. package/workspace/reference/org/ownership.yaml +28 -0
  159. package/workspace/reports/.gitkeep +0 -0
  160. package/workspace/yor/CLAUDE.md +22 -0
  161. package/workspace/yor/PLAYBOOK.md +73 -0
  162. package/workspace/yor/PROFILE.md +52 -0
  163. package/workspace/yor/SELF-HEAL.md +39 -0
@@ -0,0 +1,189 @@
1
+ // ── MemorySettler ──
2
+ /**
3
+ * 记忆沉淀器
4
+ *
5
+ * 任务完成(DONE)后自动触发,分析 execution-log.jsonl 和 result.md,
6
+ * 将经过验证的结论写入长期记忆。
7
+ *
8
+ * 沉淀规则:
9
+ * - self_heal 且 result=fixed → pitfalls.md
10
+ * - decision 事件 → decisions.md
11
+ * - collaborator_discovery → people/{ref}.md
12
+ * - result.md 中的"经验教训"section → pitfalls.md
13
+ */
14
+ export class MemorySettler {
15
+ deps;
16
+ constructor(deps) {
17
+ this.deps = deps;
18
+ }
19
+ async settle(taskId, taskDir) {
20
+ const result = {
21
+ pitfalls: [],
22
+ decisions: [],
23
+ collaborators: [],
24
+ totalSettled: 0,
25
+ errors: [],
26
+ };
27
+ // 1. 读取 execution-log.jsonl
28
+ const logEntries = await this.readExecutionLog(taskDir);
29
+ // 2. 读取 result.md
30
+ const resultMd = await this.deps.readFile(`${taskDir}/result.md`);
31
+ // 3. 提取 pitfalls(自愈记录,仅 result=fixed)
32
+ const pitfalls = this.extractPitfalls(logEntries);
33
+ // 4. 从 result.md 提取额外经验教训
34
+ const lessonsFromResult = this.extractLessonsFromResult(resultMd);
35
+ for (const lesson of lessonsFromResult) {
36
+ if (!pitfalls.includes(lesson)) {
37
+ pitfalls.push(lesson);
38
+ }
39
+ }
40
+ // 5. 提取 decisions
41
+ const decisions = this.extractDecisions(logEntries);
42
+ // 6. 提取 collaborator discoveries
43
+ const collaborators = this.extractCollaborators(logEntries);
44
+ // 7. 写入记忆(使用新版 memoryRemember)
45
+ for (const pitfall of pitfalls) {
46
+ await this.safeRemember(result, {
47
+ category: 'project',
48
+ target: this.deps.projectId,
49
+ sub_type: 'pitfall',
50
+ content: pitfall,
51
+ source: taskId,
52
+ importance: 0.7,
53
+ });
54
+ }
55
+ for (const decision of decisions) {
56
+ await this.safeRemember(result, {
57
+ category: 'project',
58
+ target: this.deps.projectId,
59
+ sub_type: 'decision',
60
+ content: decision,
61
+ source: taskId,
62
+ importance: 0.6,
63
+ });
64
+ }
65
+ for (const collab of collaborators) {
66
+ await this.safeRemember(result, {
67
+ category: 'people',
68
+ target: collab.ref,
69
+ content: collab.detail,
70
+ source: taskId,
71
+ importance: 0.5,
72
+ });
73
+ }
74
+ result.pitfalls = pitfalls;
75
+ result.decisions = decisions;
76
+ result.collaborators = collaborators;
77
+ result.totalSettled = pitfalls.length + decisions.length + collaborators.length;
78
+ // 8. 审计记录
79
+ try {
80
+ await this.deps.auditAppend({
81
+ event_type: 'memory_settled',
82
+ actor: 'loid',
83
+ task_id: taskId,
84
+ summary: `沉淀 ${result.totalSettled} 条记忆 (${pitfalls.length} pitfalls, ${decisions.length} decisions, ${collaborators.length} collaborators)`,
85
+ detail: {
86
+ pitfalls: pitfalls.length,
87
+ decisions: decisions.length,
88
+ collaborators: collaborators.length,
89
+ },
90
+ });
91
+ }
92
+ catch {
93
+ result.errors.push('审计记录写入失败');
94
+ }
95
+ return result;
96
+ }
97
+ // ── 内部方法 ──
98
+ async readExecutionLog(taskDir) {
99
+ const raw = await this.deps.readFile(`${taskDir}/execution-log.jsonl`);
100
+ if (!raw)
101
+ return [];
102
+ const entries = [];
103
+ for (const line of raw.split('\n')) {
104
+ const trimmed = line.trim();
105
+ if (!trimmed)
106
+ continue;
107
+ try {
108
+ entries.push(JSON.parse(trimmed));
109
+ }
110
+ catch {
111
+ // 解析失败的行跳过
112
+ }
113
+ }
114
+ return entries;
115
+ }
116
+ extractPitfalls(entries) {
117
+ const seen = new Set();
118
+ const pitfalls = [];
119
+ for (const entry of entries) {
120
+ if (entry.event !== 'self_heal')
121
+ continue;
122
+ if (entry.result !== 'fixed')
123
+ continue;
124
+ if (!entry.detail)
125
+ continue;
126
+ const key = entry.detail;
127
+ if (seen.has(key))
128
+ continue;
129
+ seen.add(key);
130
+ pitfalls.push(entry.detail);
131
+ }
132
+ return pitfalls;
133
+ }
134
+ extractLessonsFromResult(resultMd) {
135
+ if (!resultMd)
136
+ return [];
137
+ const lessons = [];
138
+ // 查找 "## 经验教训" section
139
+ const sectionPattern = /##\s*经验教训\s*\n([\s\S]*?)(?=\n##\s|\n$|$)/;
140
+ const match = sectionPattern.exec(resultMd);
141
+ if (!match)
142
+ return [];
143
+ const sectionContent = match[1];
144
+ // 解析列表项
145
+ const listPattern = /^[-*]\s+(.+)$/gm;
146
+ let listMatch;
147
+ while ((listMatch = listPattern.exec(sectionContent)) !== null) {
148
+ const lesson = listMatch[1].trim();
149
+ if (lesson) {
150
+ lessons.push(lesson);
151
+ }
152
+ }
153
+ return lessons;
154
+ }
155
+ extractDecisions(entries) {
156
+ const decisions = [];
157
+ for (const entry of entries) {
158
+ if (entry.event !== 'decision')
159
+ continue;
160
+ if (!entry.detail)
161
+ continue;
162
+ decisions.push(entry.detail);
163
+ }
164
+ return decisions;
165
+ }
166
+ extractCollaborators(entries) {
167
+ const collaborators = [];
168
+ for (const entry of entries) {
169
+ if (entry.event !== 'collaborator_discovery')
170
+ continue;
171
+ if (!entry.ref || !entry.detail)
172
+ continue;
173
+ collaborators.push({
174
+ ref: entry.ref,
175
+ detail: entry.detail,
176
+ });
177
+ }
178
+ return collaborators;
179
+ }
180
+ async safeRemember(result, input) {
181
+ try {
182
+ await this.deps.memoryRemember(input);
183
+ }
184
+ catch (e) {
185
+ result.errors.push(`写入记忆 [${input.category}/${input.target ?? ''}] 失败: ${e.message}`);
186
+ }
187
+ }
188
+ }
189
+ //# sourceMappingURL=memory-settler.js.map
@@ -0,0 +1,148 @@
1
+ /**
2
+ * @deprecated 此模块已废弃,核心流程改为 Loid 自主判断。
3
+ * 保留此文件仅为兼容 HTTP API,新代码请勿使用。
4
+ */
5
+ import { randomUUID } from 'node:crypto';
6
+ import { createOpportunity, createTask, getOpportunity, getAllOpportunities, getOpportunitiesByStatus, updateOpportunity, getTodayMaxSequence, } from '@team-anya/db';
7
+ import { TaskStatus, IntentLevel, generateTaskId } from '@team-anya/core';
8
+ // ── 关键词列表(复用 intent-classifier 的关键词) ──
9
+ const RISK_WORDS = [
10
+ '故障', '报警', '错误', '崩溃', '超时', '500', 'oom', 'panic',
11
+ '宕机', '挂了', '异常', '告警', '失败', 'error', 'crash', 'timeout',
12
+ 'fatal', 'outage', 'down',
13
+ ];
14
+ const TASK_VERBS = [
15
+ '修复', '优化', '排查', '添加', '实现', '删除', '重构', '升级',
16
+ 'fix', 'add', 'implement', 'delete', 'remove', 'refactor', 'upgrade',
17
+ 'optimize', 'debug', 'resolve', 'migrate',
18
+ ];
19
+ const IMPACT_WORDS = [
20
+ '用户', '线上', '生产', '核心', '关键', '影响', '所有', '全部',
21
+ 'production', 'critical', 'users', 'all', 'major',
22
+ ];
23
+ // ── OpportunityManager ──
24
+ export class OpportunityManager {
25
+ db;
26
+ workspacePath;
27
+ logger;
28
+ constructor(deps) {
29
+ this.db = deps.db;
30
+ this.workspacePath = deps.workspacePath;
31
+ this.logger = deps.logger ?? { info: console.log, error: console.error };
32
+ }
33
+ /**
34
+ * 从意图分级结果创建机会
35
+ * 仅处理 L2_OPPORTUNITY 级别的消息
36
+ */
37
+ createFromIntent(input) {
38
+ if (input.intentLevel !== IntentLevel.L2_OPPORTUNITY) {
39
+ return null;
40
+ }
41
+ const scores = this.scoreOpportunity(input.content);
42
+ const id = `opp-${randomUUID().slice(0, 8)}`;
43
+ const opp = createOpportunity(this.db, {
44
+ id,
45
+ summary: input.content,
46
+ status: 'detected',
47
+ source_ref: input.sourceRef ?? null,
48
+ score_impact: scores.impact,
49
+ score_urgency: scores.urgency,
50
+ score_feasibility: scores.feasibility,
51
+ score_permission: scores.permission,
52
+ total_score: scores.total,
53
+ });
54
+ this.logger.info(`机会创建: ${id} (score=${scores.total.toFixed(2)})`);
55
+ return opp;
56
+ }
57
+ /**
58
+ * 4 维评分
59
+ * - impact: 影响范围(是否涉及用户/线上/核心功能)
60
+ * - urgency: 紧急程度(是否有风险词/故障词)
61
+ * - feasibility: 可行性(是否有明确的任务动词)
62
+ * - permission: 权限适配性(Anya 是否能处理,默认中等)
63
+ */
64
+ scoreOpportunity(content) {
65
+ const lower = content.toLowerCase();
66
+ // impact: 影响范围
67
+ const impactHits = IMPACT_WORDS.filter(w => lower.includes(w.toLowerCase())).length;
68
+ const impact = Math.min(1, 0.3 + impactHits * 0.15);
69
+ // urgency: 紧急程度
70
+ const urgencyHits = RISK_WORDS.filter(w => lower.includes(w.toLowerCase())).length;
71
+ const urgency = Math.min(1, 0.1 + urgencyHits * 0.25);
72
+ // feasibility: 可行性
73
+ const feasibilityHits = TASK_VERBS.filter(w => lower.includes(w.toLowerCase())).length;
74
+ const feasibility = Math.min(1, 0.3 + feasibilityHits * 0.2);
75
+ // permission: 默认中等
76
+ const permission = 0.5;
77
+ // total: 加权平均
78
+ const total = impact * 0.3 + urgency * 0.3 + feasibility * 0.25 + permission * 0.15;
79
+ return { impact, urgency, feasibility, permission, total };
80
+ }
81
+ /**
82
+ * 认领机会:转为 confirmed,创建关联 task
83
+ */
84
+ confirm(opportunityId) {
85
+ const opp = getOpportunity(this.db, opportunityId);
86
+ if (!opp) {
87
+ throw new Error(`机会 ${opportunityId} 不存在`);
88
+ }
89
+ if (opp.status !== 'detected') {
90
+ throw new Error(`只有 detected 状态的机会可以认领,当前状态: ${opp.status}`);
91
+ }
92
+ // 创建关联任务
93
+ const seq = getTodayMaxSequence(this.db) + 1;
94
+ const taskId = generateTaskId(seq);
95
+ const task = createTask(this.db, {
96
+ task_id: taskId,
97
+ title: opp.summary,
98
+ status: TaskStatus.NEW,
99
+ source_type: 'opportunity',
100
+ source_ref: opportunityId,
101
+ });
102
+ // 更新 opportunity
103
+ const updated = updateOpportunity(this.db, opportunityId, {
104
+ status: 'confirmed',
105
+ converted_task_id: taskId,
106
+ claimed_at: new Date().toISOString(),
107
+ });
108
+ this.logger.info(`机会认领: ${opportunityId} → task ${taskId}`);
109
+ return { opportunity: updated, task };
110
+ }
111
+ /**
112
+ * 拒绝机会
113
+ */
114
+ reject(opportunityId, _reason) {
115
+ const opp = getOpportunity(this.db, opportunityId);
116
+ if (!opp) {
117
+ throw new Error(`机会 ${opportunityId} 不存在`);
118
+ }
119
+ const updated = updateOpportunity(this.db, opportunityId, {
120
+ status: 'rejected',
121
+ });
122
+ this.logger.info(`机会拒绝: ${opportunityId}`);
123
+ return updated;
124
+ }
125
+ /**
126
+ * 查询机会列表(支持 status 和 min_score 过滤)
127
+ */
128
+ getOpportunities(filter) {
129
+ let results;
130
+ if (filter.status) {
131
+ results = getOpportunitiesByStatus(this.db, filter.status);
132
+ }
133
+ else {
134
+ results = getAllOpportunities(this.db);
135
+ }
136
+ if (filter.min_score !== undefined) {
137
+ results = results.filter(o => (o.total_score ?? 0) >= filter.min_score);
138
+ }
139
+ return results;
140
+ }
141
+ /**
142
+ * 获取单个机会
143
+ */
144
+ getOpportunity(opportunityId) {
145
+ return getOpportunity(this.db, opportunityId);
146
+ }
147
+ }
148
+ //# sourceMappingURL=opportunity-manager.js.map
@@ -0,0 +1,179 @@
1
+ // ── ProfileUpdater ──
2
+ /**
3
+ * 协作者画像自动更新
4
+ *
5
+ * 从任务交互中提取协作者特征,增量更新 memory/org/people/{person}.md
6
+ *
7
+ * 提取维度:
8
+ * - 响应速度:从 clarification_response 中统计平均响应时间
9
+ * - 沟通风格:从交互标签中聚合
10
+ * - 技术偏好:从 review_feedback 标签中提取
11
+ * - Review 反馈模式:统计 review 次数和情感倾向
12
+ */
13
+ export class ProfileUpdater {
14
+ deps;
15
+ constructor(deps) {
16
+ this.deps = deps;
17
+ }
18
+ /**
19
+ * 更新单人画像
20
+ */
21
+ async updateProfile(personId, interactions) {
22
+ const profile = { personId };
23
+ if (interactions.length === 0) {
24
+ return profile;
25
+ }
26
+ // 读取已有画像
27
+ const existingContent = await this.deps.readFile(`memory/people/${personId}.md`);
28
+ const existingProfile = existingContent ? this.parseExistingProfile(existingContent) : null;
29
+ // 提取响应速度
30
+ const responseTimes = interactions
31
+ .filter(i => i.type === 'clarification_response' && i.responseTimeMinutes !== undefined)
32
+ .map(i => i.responseTimeMinutes);
33
+ if (responseTimes.length > 0) {
34
+ const newAvg = responseTimes.reduce((a, b) => a + b, 0) / responseTimes.length;
35
+ const newCount = responseTimes.length;
36
+ if (existingProfile?.responseSpeed) {
37
+ // 合并历史数据
38
+ const oldTotal = existingProfile.responseSpeed.avgMinutes * existingProfile.responseSpeed.sampleCount;
39
+ const newTotal = newAvg * newCount;
40
+ const totalCount = existingProfile.responseSpeed.sampleCount + newCount;
41
+ profile.responseSpeed = {
42
+ avgMinutes: (oldTotal + newTotal) / totalCount,
43
+ sampleCount: totalCount,
44
+ };
45
+ }
46
+ else {
47
+ profile.responseSpeed = {
48
+ avgMinutes: newAvg,
49
+ sampleCount: newCount,
50
+ };
51
+ }
52
+ }
53
+ // 提取沟通风格
54
+ const styleTags = interactions
55
+ .filter(i => i.tags && i.tags.length > 0)
56
+ .flatMap(i => i.tags);
57
+ if (styleTags.length > 0) {
58
+ profile.communicationStyle = [...new Set(styleTags)];
59
+ }
60
+ // 提取技术偏好
61
+ const techInteractions = interactions.filter(i => i.type === 'review_feedback');
62
+ const techPrefs = [];
63
+ for (const interaction of techInteractions) {
64
+ if (interaction.detail) {
65
+ techPrefs.push(interaction.detail);
66
+ }
67
+ }
68
+ if (techPrefs.length > 0) {
69
+ profile.technicalPreferences = techPrefs;
70
+ }
71
+ // Review 反馈模式
72
+ const reviews = interactions.filter(i => i.type === 'review_feedback');
73
+ if (reviews.length > 0) {
74
+ const sentiments = {};
75
+ for (const r of reviews) {
76
+ const s = r.sentiment ?? 'neutral';
77
+ sentiments[s] = (sentiments[s] ?? 0) + 1;
78
+ }
79
+ profile.reviewPatterns = {
80
+ totalReviews: reviews.length,
81
+ sentiments,
82
+ };
83
+ }
84
+ // 构建历史记录
85
+ profile.history = interactions.map(i => {
86
+ const date = i.timestamp.slice(0, 10);
87
+ return `- ${date}: [${i.taskId}] ${i.type}${i.detail ? ` - ${i.detail}` : ''}`;
88
+ });
89
+ // 渲染并写入
90
+ const rendered = this.renderProfile(profile, existingContent);
91
+ try {
92
+ await this.deps.memoryRemember({
93
+ category: 'people',
94
+ target: personId,
95
+ content: rendered,
96
+ importance: 0.5,
97
+ });
98
+ }
99
+ catch {
100
+ // 写入失败不影响返回
101
+ }
102
+ return profile;
103
+ }
104
+ /**
105
+ * 批量更新多人画像
106
+ */
107
+ async updateProfiles(interactions) {
108
+ // 按 personId 分组
109
+ const grouped = new Map();
110
+ for (const interaction of interactions) {
111
+ const existing = grouped.get(interaction.personId) ?? [];
112
+ existing.push(interaction);
113
+ grouped.set(interaction.personId, existing);
114
+ }
115
+ const results = {};
116
+ for (const [personId, personInteractions] of grouped) {
117
+ results[personId] = await this.updateProfile(personId, personInteractions);
118
+ }
119
+ return results;
120
+ }
121
+ // ── 内部方法 ──
122
+ parseExistingProfile(content) {
123
+ const profile = {};
124
+ // 解析响应速度
125
+ const avgMatch = /平均响应:\s*(\d+(?:\.\d+)?)\s*分钟/.exec(content);
126
+ const countMatch = /样本数:\s*(\d+)/.exec(content);
127
+ if (avgMatch && countMatch) {
128
+ profile.responseSpeed = {
129
+ avgMinutes: parseFloat(avgMatch[1]),
130
+ sampleCount: parseInt(countMatch[1], 10),
131
+ };
132
+ }
133
+ return profile;
134
+ }
135
+ renderProfile(profile, existingContent) {
136
+ const sections = [];
137
+ sections.push(`# ${profile.personId}`);
138
+ // 保留已有的基本信息 section
139
+ if (existingContent) {
140
+ const basicInfoMatch = /## 基本信息\n([\s\S]*?)(?=\n##\s|$)/.exec(existingContent);
141
+ if (basicInfoMatch) {
142
+ sections.push(`\n## 基本信息\n${basicInfoMatch[1].trimEnd()}`);
143
+ }
144
+ }
145
+ // 响应速度
146
+ if (profile.responseSpeed) {
147
+ sections.push(`\n## 响应速度\n- 平均响应: ${Math.round(profile.responseSpeed.avgMinutes * 10) / 10} 分钟\n- 样本数: ${profile.responseSpeed.sampleCount}`);
148
+ }
149
+ // 沟通风格
150
+ if (profile.communicationStyle && profile.communicationStyle.length > 0) {
151
+ sections.push(`\n## 沟通风格\n${profile.communicationStyle.map(s => `- ${s}`).join('\n')}`);
152
+ }
153
+ // 技术偏好
154
+ if (profile.technicalPreferences && profile.technicalPreferences.length > 0) {
155
+ sections.push(`\n## 技术偏好\n${profile.technicalPreferences.map(p => `- ${p}`).join('\n')}`);
156
+ }
157
+ // Review 模式
158
+ if (profile.reviewPatterns) {
159
+ const sentimentStr = Object.entries(profile.reviewPatterns.sentiments)
160
+ .map(([k, v]) => `${k}: ${v}`)
161
+ .join(', ');
162
+ sections.push(`\n## Review 模式\n- 总 Review 数: ${profile.reviewPatterns.totalReviews}\n- 情感分布: ${sentimentStr}`);
163
+ }
164
+ // 历史记录
165
+ if (profile.history && profile.history.length > 0) {
166
+ // 保留已有历史
167
+ let existingHistory = '';
168
+ if (existingContent) {
169
+ const historyMatch = /## 历史记录\n([\s\S]*?)(?=\n##\s|$)/.exec(existingContent);
170
+ if (historyMatch) {
171
+ existingHistory = historyMatch[1].trimEnd() + '\n';
172
+ }
173
+ }
174
+ sections.push(`\n## 历史记录\n${existingHistory}${profile.history.join('\n')}`);
175
+ }
176
+ return sections.join('\n') + '\n';
177
+ }
178
+ }
179
+ //# sourceMappingURL=profile-updater.js.map
@@ -0,0 +1,148 @@
1
+ import { join } from 'node:path';
2
+ // ── 进行中状态集合 ──
3
+ const IN_PROGRESS_STATUSES = new Set(['IN_PROGRESS', 'TESTING', 'FIXING']);
4
+ const PR_STATUSES = new Set(['PR_OPEN', 'WAITING_REVIEW']);
5
+ // ── Reporter ──
6
+ export class Reporter {
7
+ deps;
8
+ config;
9
+ constructor(deps, config) {
10
+ this.deps = deps;
11
+ this.config = config;
12
+ }
13
+ /**
14
+ * 生成日报 Markdown 内容
15
+ */
16
+ async generateDailyReport(date) {
17
+ const allTasks = this.deps.getAllTasks();
18
+ const commitments = this.deps.getActiveCommitments();
19
+ const opportunities = this.deps.getOpenOpportunities();
20
+ const auditEvents = this.deps.getAuditEventsByDate(date);
21
+ const lines = [];
22
+ const done = allTasks.filter((t) => t.status === 'DONE' && t.updated_at?.startsWith(date));
23
+ const prOpen = allTasks.filter((t) => PR_STATUSES.has(t.status));
24
+ const inProgress = allTasks.filter((t) => IN_PROGRESS_STATUSES.has(t.status));
25
+ const blocked = allTasks.filter((t) => t.status === 'BLOCKED');
26
+ // 开头一句话总结
27
+ const totalActive = inProgress.length + prOpen.length;
28
+ if (done.length > 0 && blocked.length === 0) {
29
+ lines.push(`今天完成了 ${done.length} 个任务${totalActive > 0 ? `,还有 ${totalActive} 个在跑` : ''}。\n`);
30
+ }
31
+ else if (done.length > 0 && blocked.length > 0) {
32
+ lines.push(`今天完成了 ${done.length} 个任务,但有 ${blocked.length} 个卡住了。\n`);
33
+ }
34
+ else if (blocked.length > 0) {
35
+ lines.push(`有 ${blocked.length} 个任务卡住了,需要关注。\n`);
36
+ }
37
+ else if (totalActive > 0) {
38
+ lines.push(`${totalActive} 个任务进行中。\n`);
39
+ }
40
+ // 完成的
41
+ if (done.length > 0) {
42
+ lines.push('**已完成**');
43
+ for (const t of done) {
44
+ const pr = t.pr_url ? ` → ${t.pr_url}` : '';
45
+ lines.push(`- ${t.title} (${t.task_id})${pr}`);
46
+ }
47
+ lines.push('');
48
+ }
49
+ // 等 Review 的
50
+ if (prOpen.length > 0) {
51
+ lines.push('**等 Review**');
52
+ for (const t of prOpen) {
53
+ const pr = t.pr_url ? ` → ${t.pr_url}` : '';
54
+ lines.push(`- ${t.title} (${t.task_id})${pr}`);
55
+ }
56
+ lines.push('');
57
+ }
58
+ // 进行中的
59
+ if (inProgress.length > 0) {
60
+ lines.push('**进行中**');
61
+ for (const t of inProgress) {
62
+ lines.push(`- ${t.title} (${t.task_id})`);
63
+ }
64
+ lines.push('');
65
+ }
66
+ // 卡住的(重点标出)
67
+ if (blocked.length > 0) {
68
+ lines.push('**卡住了**');
69
+ for (const t of blocked) {
70
+ lines.push(`- ${t.title} (${t.task_id}) — ${t.blocked_reason ?? '原因待查'}`);
71
+ }
72
+ lines.push('');
73
+ }
74
+ // 承诺追踪
75
+ if (commitments.length > 0) {
76
+ lines.push('**还欠着的**');
77
+ for (const c of commitments) {
78
+ const deadline = c.deadline ? `,截止 ${c.deadline}` : '';
79
+ lines.push(`- ${c.promise} (答应 ${c.promised_to} 的${deadline})`);
80
+ }
81
+ lines.push('');
82
+ }
83
+ // 机会
84
+ if (opportunities.length > 0) {
85
+ lines.push('**发现了几个可以做的事**');
86
+ for (const o of opportunities) {
87
+ lines.push(`- ${o.summary}`);
88
+ }
89
+ lines.push('');
90
+ }
91
+ return lines.join('\n');
92
+ }
93
+ /**
94
+ * 将日报写入文件
95
+ */
96
+ async saveDailyReport(date, content) {
97
+ const reportsDir = join(this.config.workspacePath, 'reports');
98
+ await this.deps.mkdirp(reportsDir);
99
+ const filePath = join(reportsDir, `daily-${date}.md`);
100
+ await this.deps.writeFile(filePath, content);
101
+ return filePath;
102
+ }
103
+ /**
104
+ * 端到端:生成 + 写文件 + 飞书推送
105
+ */
106
+ async publishDailyReport(date) {
107
+ const content = await this.generateDailyReport(date);
108
+ // 写文件(总是执行)
109
+ await this.saveDailyReport(date, content);
110
+ // 飞书推送(可选,失败不影响文件写入)
111
+ if (this.config.feishuDailyReportChatId) {
112
+ try {
113
+ await this.deps.sendDailyReport({
114
+ chatId: this.config.feishuDailyReportChatId,
115
+ reportContent: content,
116
+ date,
117
+ });
118
+ }
119
+ catch {
120
+ // 飞书发送失败不影响日报文件保存
121
+ }
122
+ }
123
+ }
124
+ /**
125
+ * 实时告警:任务 BLOCKED
126
+ */
127
+ async notifyBlocked(params) {
128
+ await this.deps.sendBlockedAlert({
129
+ targetUserIds: params.targetUserIds,
130
+ taskId: params.taskId,
131
+ reason: params.reason,
132
+ });
133
+ }
134
+ /**
135
+ * 任务完成通知
136
+ */
137
+ async notifyTaskDone(params) {
138
+ if (!params.chatId)
139
+ return;
140
+ await this.deps.sendTaskDone({
141
+ chatId: params.chatId,
142
+ taskId: params.taskId,
143
+ prUrl: params.prUrl,
144
+ summary: params.summary,
145
+ });
146
+ }
147
+ }
148
+ //# sourceMappingURL=reporter.js.map