ai-dev-analytics 1.0.0-beta.11
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/LICENSE +21 -0
- package/README.md +422 -0
- package/README.zh-CN.md +422 -0
- package/dist/cli/commands/dashboard.d.ts +2 -0
- package/dist/cli/commands/dashboard.d.ts.map +1 -0
- package/dist/cli/commands/dashboard.js +70 -0
- package/dist/cli/commands/dashboard.js.map +1 -0
- package/dist/cli/commands/init.d.ts +2 -0
- package/dist/cli/commands/init.d.ts.map +1 -0
- package/dist/cli/commands/init.js +367 -0
- package/dist/cli/commands/init.js.map +1 -0
- package/dist/cli/commands/log.d.ts +2 -0
- package/dist/cli/commands/log.d.ts.map +1 -0
- package/dist/cli/commands/log.js +438 -0
- package/dist/cli/commands/log.js.map +1 -0
- package/dist/cli/commands/migrate.d.ts +2 -0
- package/dist/cli/commands/migrate.d.ts.map +1 -0
- package/dist/cli/commands/migrate.js +300 -0
- package/dist/cli/commands/migrate.js.map +1 -0
- package/dist/cli/commands/reindex.d.ts +10 -0
- package/dist/cli/commands/reindex.d.ts.map +1 -0
- package/dist/cli/commands/reindex.js +94 -0
- package/dist/cli/commands/reindex.js.map +1 -0
- package/dist/cli/commands/report.d.ts +2 -0
- package/dist/cli/commands/report.d.ts.map +1 -0
- package/dist/cli/commands/report.js +219 -0
- package/dist/cli/commands/report.js.map +1 -0
- package/dist/cli/commands/rules.d.ts +2 -0
- package/dist/cli/commands/rules.d.ts.map +1 -0
- package/dist/cli/commands/rules.js +143 -0
- package/dist/cli/commands/rules.js.map +1 -0
- package/dist/cli/commands/start.d.ts +2 -0
- package/dist/cli/commands/start.d.ts.map +1 -0
- package/dist/cli/commands/start.js +173 -0
- package/dist/cli/commands/start.js.map +1 -0
- package/dist/cli/commands/status.d.ts +2 -0
- package/dist/cli/commands/status.d.ts.map +1 -0
- package/dist/cli/commands/status.js +74 -0
- package/dist/cli/commands/status.js.map +1 -0
- package/dist/cli/commands/update.d.ts +2 -0
- package/dist/cli/commands/update.d.ts.map +1 -0
- package/dist/cli/commands/update.js +146 -0
- package/dist/cli/commands/update.js.map +1 -0
- package/dist/cli/index.d.ts +3 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +85 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/mcp/server.d.ts +10 -0
- package/dist/mcp/server.d.ts.map +1 -0
- package/dist/mcp/server.js +704 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/schemas/run-json.d.ts +258 -0
- package/dist/schemas/run-json.d.ts.map +1 -0
- package/dist/schemas/run-json.js +22 -0
- package/dist/schemas/run-json.js.map +1 -0
- package/dist/server/api.d.ts +30 -0
- package/dist/server/api.d.ts.map +1 -0
- package/dist/server/api.js +239 -0
- package/dist/server/api.js.map +1 -0
- package/dist/server/index.d.ts +2 -0
- package/dist/server/index.d.ts.map +1 -0
- package/dist/server/index.js +228 -0
- package/dist/server/index.js.map +1 -0
- package/dist/utils/display.d.ts +12 -0
- package/dist/utils/display.d.ts.map +1 -0
- package/dist/utils/display.js +52 -0
- package/dist/utils/display.js.map +1 -0
- package/dist/utils/fs.d.ts +9 -0
- package/dist/utils/fs.d.ts.map +1 -0
- package/dist/utils/fs.js +43 -0
- package/dist/utils/fs.js.map +1 -0
- package/dist/utils/git.d.ts +4 -0
- package/dist/utils/git.d.ts.map +1 -0
- package/dist/utils/git.js +18 -0
- package/dist/utils/git.js.map +1 -0
- package/dist/utils/guide.d.ts +18 -0
- package/dist/utils/guide.d.ts.map +1 -0
- package/dist/utils/guide.js +172 -0
- package/dist/utils/guide.js.map +1 -0
- package/dist/utils/paths.d.ts +25 -0
- package/dist/utils/paths.d.ts.map +1 -0
- package/dist/utils/paths.js +45 -0
- package/dist/utils/paths.js.map +1 -0
- package/dist/utils/rules.d.ts +44 -0
- package/dist/utils/rules.d.ts.map +1 -0
- package/dist/utils/rules.js +208 -0
- package/dist/utils/rules.js.map +1 -0
- package/dist/utils/run-data.d.ts +37 -0
- package/dist/utils/run-data.d.ts.map +1 -0
- package/dist/utils/run-data.js +389 -0
- package/dist/utils/run-data.js.map +1 -0
- package/dist/utils/tokens.d.ts +26 -0
- package/dist/utils/tokens.d.ts.map +1 -0
- package/dist/utils/tokens.js +173 -0
- package/dist/utils/tokens.js.map +1 -0
- package/package.json +49 -0
- package/src/assets/skills/audit.md +98 -0
- package/src/assets/skills/bug-fixer.md +48 -0
- package/src/assets/skills/code-generator.md +82 -0
- package/src/assets/skills/commit-code.md +67 -0
- package/src/assets/skills/dashboard-generator.md +65 -0
- package/src/assets/skills/dev-flower.md +85 -0
- package/src/assets/skills/deviation-recorder.md +82 -0
- package/src/assets/skills/docx-to-markdown.md +69 -0
- package/src/assets/skills/mcp-reviewer.md +43 -0
- package/src/assets/skills/requirement-analyzer.md +103 -0
- package/src/assets/skills/rules-evolver.md +47 -0
- package/src/assets/skills/self-reviewer.md +53 -0
- package/src/assets/skills/task-splitter.md +58 -0
- package/src/assets/skills/workflow-orchestrator.md +209 -0
- package/src/assets/templates/demo-run.json +910 -0
- package/src/assets/templates/run.json +63 -0
- package/src/dashboard/assets/index-C-E7m3g4.css +1 -0
- package/src/dashboard/assets/index-CsU3dyCj.js +111 -0
- package/src/dashboard/demo/overview.json +71 -0
- package/src/dashboard/demo/run.json +2667 -0
- package/src/dashboard/demo/runs.json +19 -0
- package/src/dashboard/index.html +13 -0
|
@@ -0,0 +1,704 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AIDA MCP Server
|
|
3
|
+
*
|
|
4
|
+
* Implements MCP (Model Context Protocol) over stdio using JSON-RPC 2.0.
|
|
5
|
+
* Zero dependencies — raw stdin/stdout processing.
|
|
6
|
+
*
|
|
7
|
+
* Provides tools for AI agents to silently collect development data.
|
|
8
|
+
*/
|
|
9
|
+
import { execSync } from 'node:child_process';
|
|
10
|
+
import { writeJson } from '../utils/fs.js';
|
|
11
|
+
import { SEVERITY_VALUES, BUG_SOURCE_VALUES, ROOT_CAUSE_VALUES, DEVIATION_CAT_VALUES, RULE_CATEGORIES, } from '../schemas/run-json.js';
|
|
12
|
+
import { now, nextId, addEvent, addTimeline, saveRunData, ensureRunJson as ensureRunJsonShared, } from '../utils/run-data.js';
|
|
13
|
+
import { collectClaudeTokens, collectClaudeTokensBetween } from '../utils/tokens.js';
|
|
14
|
+
import { getBranchName, getDevName } from '../utils/git.js';
|
|
15
|
+
import { addRule, buildRuleViews } from '../utils/rules.js';
|
|
16
|
+
function sendResponse(res) {
|
|
17
|
+
const json = JSON.stringify(res);
|
|
18
|
+
const msg = `Content-Length: ${Buffer.byteLength(json)}\r\n\r\n${json}`;
|
|
19
|
+
process.stdout.write(msg);
|
|
20
|
+
}
|
|
21
|
+
function sendResult(id, result) {
|
|
22
|
+
sendResponse({ jsonrpc: '2.0', id, result });
|
|
23
|
+
}
|
|
24
|
+
function sendError(id, code, message) {
|
|
25
|
+
sendResponse({ jsonrpc: '2.0', id, error: { code, message } });
|
|
26
|
+
}
|
|
27
|
+
// ─── Project Root ────────────────────────────────────────
|
|
28
|
+
let projectRoot = process.cwd();
|
|
29
|
+
function ensureRunJson() {
|
|
30
|
+
return ensureRunJsonShared(projectRoot);
|
|
31
|
+
}
|
|
32
|
+
function save(path, data) {
|
|
33
|
+
saveRunData(path, data, projectRoot);
|
|
34
|
+
}
|
|
35
|
+
// ─── Token Auto-Collection ───────────────────────────────
|
|
36
|
+
/**
|
|
37
|
+
* Collect tokens from Claude Code session since run started,
|
|
38
|
+
* and update run.json cost data.
|
|
39
|
+
*/
|
|
40
|
+
function syncTokenUsage(path, data) {
|
|
41
|
+
try {
|
|
42
|
+
const startTime = data.meta?.startTime;
|
|
43
|
+
if (!startTime)
|
|
44
|
+
return;
|
|
45
|
+
const usage = collectClaudeTokens(projectRoot, startTime);
|
|
46
|
+
if (!usage)
|
|
47
|
+
return;
|
|
48
|
+
if (!data.cost)
|
|
49
|
+
data.cost = {};
|
|
50
|
+
data.cost.totalTokens = usage.totalTokens;
|
|
51
|
+
// Store detailed breakdown
|
|
52
|
+
data.cost.tokenDetail = {
|
|
53
|
+
inputTokens: usage.inputTokens,
|
|
54
|
+
outputTokens: usage.outputTokens,
|
|
55
|
+
cacheCreationTokens: usage.cacheCreationTokens,
|
|
56
|
+
cacheReadTokens: usage.cacheReadTokens,
|
|
57
|
+
};
|
|
58
|
+
writeJson(path, data);
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
// Non-critical: don't break data collection if token reading fails
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Collect tokens for a specific task time range.
|
|
66
|
+
* Returns total tokens consumed during the task.
|
|
67
|
+
*/
|
|
68
|
+
function getTaskTokens(startTime, endTime) {
|
|
69
|
+
try {
|
|
70
|
+
const usage = collectClaudeTokensBetween(projectRoot, startTime, endTime);
|
|
71
|
+
return usage?.totalTokens || 0;
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
return 0;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
// ─── MCP Tool Definitions ────────────────────────────────
|
|
78
|
+
const TOOLS = [
|
|
79
|
+
{
|
|
80
|
+
name: 'aida_task_start',
|
|
81
|
+
description: '当你开始一个新任务或功能开发时调用。在接到用户需求、开始编码前调用。每个任务的完整数据采集流程:1) aida_task_start 2) 编码 3) aida_log_files 4) aida_log_review 5) aida_task_done。多个子任务必须每个都单独 start/done。',
|
|
82
|
+
inputSchema: {
|
|
83
|
+
type: 'object',
|
|
84
|
+
properties: {
|
|
85
|
+
title: { type: 'string', description: '任务标题,简洁描述要做什么' },
|
|
86
|
+
stage: { type: 'string', description: '所属模块或阶段,如 Authentication, UI, API 等' },
|
|
87
|
+
},
|
|
88
|
+
required: ['title'],
|
|
89
|
+
},
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
name: 'aida_task_done',
|
|
93
|
+
description: '当你完成一个任务后调用。标记任务为已完成,自动计算耗时。',
|
|
94
|
+
inputSchema: {
|
|
95
|
+
type: 'object',
|
|
96
|
+
properties: {
|
|
97
|
+
taskId: { type: 'string', description: '任务ID,如 TASK-01。如不确定,可调用 aida_status 查看。' },
|
|
98
|
+
},
|
|
99
|
+
required: ['taskId'],
|
|
100
|
+
},
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
name: 'aida_log_bug',
|
|
104
|
+
description: '当你在开发或测试中发现 bug 时调用。记录 bug 信息。',
|
|
105
|
+
inputSchema: {
|
|
106
|
+
type: 'object',
|
|
107
|
+
properties: {
|
|
108
|
+
title: { type: 'string', description: 'Bug 描述' },
|
|
109
|
+
severity: { type: 'string', enum: [...SEVERITY_VALUES], description: '严重程度:critical/high/medium/low,默认 medium' },
|
|
110
|
+
source: { type: 'string', enum: [...BUG_SOURCE_VALUES], description: '发现来源:self-review/user-feedback/testing,默认 self-review' },
|
|
111
|
+
},
|
|
112
|
+
required: ['title'],
|
|
113
|
+
},
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
name: 'aida_bug_fix',
|
|
117
|
+
description: '当你修复了一个 bug 后调用。标记 bug 为已修复。',
|
|
118
|
+
inputSchema: {
|
|
119
|
+
type: 'object',
|
|
120
|
+
properties: {
|
|
121
|
+
bugId: { type: 'string', description: 'Bug ID,如 BUG-01' },
|
|
122
|
+
fix: { type: 'string', description: '修复方案简述' },
|
|
123
|
+
},
|
|
124
|
+
required: ['bugId'],
|
|
125
|
+
},
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
name: 'aida_log_review',
|
|
129
|
+
description: '当你完成一轮代码审查后调用。记录审查结果。',
|
|
130
|
+
inputSchema: {
|
|
131
|
+
type: 'object',
|
|
132
|
+
properties: {
|
|
133
|
+
taskId: { type: 'string', description: '关联的任务ID' },
|
|
134
|
+
result: { type: 'string', enum: ['pass', 'fail'], description: '审查结果:pass 或 fail' },
|
|
135
|
+
issues: { type: 'string', description: '发现的问题,逗号分隔。通过时不填。' },
|
|
136
|
+
scope: { type: 'string', description: '审查覆盖的文件或模块范围' },
|
|
137
|
+
},
|
|
138
|
+
required: ['result'],
|
|
139
|
+
},
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
name: 'aida_log_deviation',
|
|
143
|
+
description: '当 AI 产出与用户预期不符时调用。记录偏差用于后续分析。当 rootCause 为 rule-missing 时,修复后如果属于项目级技术规范(非业务逻辑),应询问用户是否沉淀为规则。',
|
|
144
|
+
inputSchema: {
|
|
145
|
+
type: 'object',
|
|
146
|
+
properties: {
|
|
147
|
+
title: { type: 'string', description: '偏差描述' },
|
|
148
|
+
rootCause: { type: 'string', enum: [...ROOT_CAUSE_VALUES], description: '根因分类' },
|
|
149
|
+
category: { type: 'string', enum: [...DEVIATION_CAT_VALUES], description: '偏差类别' },
|
|
150
|
+
},
|
|
151
|
+
required: ['title'],
|
|
152
|
+
},
|
|
153
|
+
},
|
|
154
|
+
{
|
|
155
|
+
name: 'aida_log_files',
|
|
156
|
+
description: '记录文件变更。无需传参,自动扫描 git diff 获取变更文件列表和行数。在完成一轮代码修改后调用。',
|
|
157
|
+
inputSchema: {
|
|
158
|
+
type: 'object',
|
|
159
|
+
properties: {},
|
|
160
|
+
},
|
|
161
|
+
},
|
|
162
|
+
{
|
|
163
|
+
name: 'aida_highlight',
|
|
164
|
+
description: '记录值得关注的亮点,如性能提升、架构优化等。',
|
|
165
|
+
inputSchema: {
|
|
166
|
+
type: 'object',
|
|
167
|
+
properties: {
|
|
168
|
+
content: { type: 'string', description: '亮点内容描述' },
|
|
169
|
+
},
|
|
170
|
+
required: ['content'],
|
|
171
|
+
},
|
|
172
|
+
},
|
|
173
|
+
{
|
|
174
|
+
name: 'aida_status',
|
|
175
|
+
description: '查看当前开发运行的状态:任务列表、bug 数量、进度等。在需要了解当前进度时调用。',
|
|
176
|
+
inputSchema: {
|
|
177
|
+
type: 'object',
|
|
178
|
+
properties: {},
|
|
179
|
+
},
|
|
180
|
+
},
|
|
181
|
+
{
|
|
182
|
+
name: 'aida_log_rule',
|
|
183
|
+
description: '沉淀项目规则。当偏差的 rootCause 为 rule-missing 且修复方案属于项目级技术规范(非业务逻辑)时,询问用户同意后调用此工具沉淀规则。仅限:公共组件使用规范、API 调用规范、参数传递规范、代码风格/架构规范。禁止沉淀业务逻辑。',
|
|
184
|
+
inputSchema: {
|
|
185
|
+
type: 'object',
|
|
186
|
+
properties: {
|
|
187
|
+
content: { type: 'string', description: '规则内容,简洁描述项目规范' },
|
|
188
|
+
category: { type: 'string', enum: [...RULE_CATEGORIES], description: '规则分类' },
|
|
189
|
+
sourceDeviation: { type: 'string', description: '关联的偏差 ID,如 DEV-01' },
|
|
190
|
+
},
|
|
191
|
+
required: ['content', 'category'],
|
|
192
|
+
},
|
|
193
|
+
},
|
|
194
|
+
];
|
|
195
|
+
// ─── MCP Prompts ─────────────────────────────────────────
|
|
196
|
+
const PROMPTS = [
|
|
197
|
+
{
|
|
198
|
+
name: 'aida-guide',
|
|
199
|
+
description: 'AIDA 数据采集使用指南',
|
|
200
|
+
},
|
|
201
|
+
];
|
|
202
|
+
const PROMPT_CONTENT = `你拥有 AIDA 数据采集工具,必须在开发过程中的关键节点调用,确保数据完整性。
|
|
203
|
+
|
|
204
|
+
每个任务的完整流程:
|
|
205
|
+
1. 开始前 → aida_task_start(传入标题和所属模块)
|
|
206
|
+
2. 编码完成 → aida_log_files(自动扫描 git diff,无需传参)
|
|
207
|
+
3. 自检代码 → aida_log_review(pass/fail + 问题列表)
|
|
208
|
+
4. 任务完成 → aida_task_done(传入任务 ID)
|
|
209
|
+
|
|
210
|
+
过程中遇到以下情况必须立即记录:
|
|
211
|
+
- 发现 Bug → aida_log_bug,修复后 → aida_bug_fix
|
|
212
|
+
- 用户指出 AI 产出偏差 → aida_log_deviation(传入描述、rootCause、category)
|
|
213
|
+
- 值得记录的亮点 → aida_highlight
|
|
214
|
+
|
|
215
|
+
偏差规则沉淀:
|
|
216
|
+
- 当 aida_log_deviation 的 rootCause 为 rule-missing 时,修复后判断是否属于项目级技术规范(非业务逻辑)
|
|
217
|
+
- 如果是,询问用户是否沉淀为规则,用户同意后调用 aida_log_rule 工具(传入 content、category、sourceDeviation)
|
|
218
|
+
|
|
219
|
+
多任务场景下,每个子任务都必须单独 aida_task_start 和 aida_task_done。`;
|
|
220
|
+
// ─── Tool Handlers ───────────────────────────────────────
|
|
221
|
+
function handleTaskStart(args) {
|
|
222
|
+
const { path, data } = ensureRunJson();
|
|
223
|
+
const id = nextId(data.tasks, 'TASK');
|
|
224
|
+
const task = {
|
|
225
|
+
taskId: id,
|
|
226
|
+
title: args.title,
|
|
227
|
+
status: 'in-progress',
|
|
228
|
+
stageName: args.stage || 'default',
|
|
229
|
+
prdPhase: '',
|
|
230
|
+
acceptance: '',
|
|
231
|
+
createdAt: now(),
|
|
232
|
+
startedAt: now(),
|
|
233
|
+
completedAt: null,
|
|
234
|
+
};
|
|
235
|
+
data.tasks.push(task);
|
|
236
|
+
data.summary.totalTasks = data.tasks.length;
|
|
237
|
+
data.context.currentTaskId = id;
|
|
238
|
+
addEvent(data, 'task_created', { taskId: id });
|
|
239
|
+
addEvent(data, 'task_started', { taskId: id });
|
|
240
|
+
addTimeline(data, 'task', `${id}: ${args.title}`);
|
|
241
|
+
save(path, data);
|
|
242
|
+
return { success: true, taskId: id, message: `${id} 已记录并开始: ${args.title}` };
|
|
243
|
+
}
|
|
244
|
+
function handleTaskDone(args) {
|
|
245
|
+
const { path, data } = ensureRunJson();
|
|
246
|
+
const task = data.tasks.find(t => t.taskId === args.taskId);
|
|
247
|
+
if (!task)
|
|
248
|
+
return { success: false, message: `任务 ${args.taskId} 未找到` };
|
|
249
|
+
task.status = 'done';
|
|
250
|
+
task.completedAt = now();
|
|
251
|
+
if (!task.startedAt)
|
|
252
|
+
task.startedAt = task.createdAt || task.completedAt;
|
|
253
|
+
data.summary.completedTasks = data.tasks.filter(t => t.status === 'done').length;
|
|
254
|
+
// Auto-collect tokens for this task
|
|
255
|
+
let taskTokens = 0;
|
|
256
|
+
if (task.startedAt && task.completedAt) {
|
|
257
|
+
taskTokens = getTaskTokens(task.startedAt, task.completedAt);
|
|
258
|
+
if (taskTokens > 0) {
|
|
259
|
+
task.tokensConsumed = taskTokens;
|
|
260
|
+
// Add to cost breakdown
|
|
261
|
+
if (!data.cost)
|
|
262
|
+
data.cost = {};
|
|
263
|
+
if (!data.cost.tokenBreakdown)
|
|
264
|
+
data.cost.tokenBreakdown = [];
|
|
265
|
+
data.cost.tokenBreakdown.push({
|
|
266
|
+
stage: `task:${args.taskId}`,
|
|
267
|
+
tokens: taskTokens,
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
addEvent(data, 'task_completed', { taskId: args.taskId, tokensConsumed: taskTokens });
|
|
272
|
+
addTimeline(data, 'task-done', `${args.taskId}: ${task.title}`);
|
|
273
|
+
save(path, data);
|
|
274
|
+
// Sync total token usage from session
|
|
275
|
+
syncTokenUsage(path, data);
|
|
276
|
+
const tokenMsg = taskTokens > 0 ? ` (${taskTokens} tokens)` : '';
|
|
277
|
+
return { success: true, message: `${args.taskId} 已完成${tokenMsg}` };
|
|
278
|
+
}
|
|
279
|
+
function handleLogBug(args) {
|
|
280
|
+
const severity = args.severity || 'medium';
|
|
281
|
+
const source = args.source || 'self-review';
|
|
282
|
+
const { path, data } = ensureRunJson();
|
|
283
|
+
const id = nextId(data.bugs, 'BUG');
|
|
284
|
+
const bug = {
|
|
285
|
+
bugId: id,
|
|
286
|
+
title: args.title,
|
|
287
|
+
severity: severity,
|
|
288
|
+
source: source,
|
|
289
|
+
status: 'open',
|
|
290
|
+
files: [],
|
|
291
|
+
fix: null,
|
|
292
|
+
taskId: data.context.currentTaskId || null,
|
|
293
|
+
reportedAt: now(),
|
|
294
|
+
fixedAt: null,
|
|
295
|
+
};
|
|
296
|
+
data.bugs.push(bug);
|
|
297
|
+
data.summary.bugCount = data.bugs.length;
|
|
298
|
+
addEvent(data, 'bug_created', { bugId: id });
|
|
299
|
+
addTimeline(data, 'bug', `${id}: ${args.title}`);
|
|
300
|
+
save(path, data);
|
|
301
|
+
return { success: true, bugId: id, message: `${id} 已记录: ${args.title} [${severity}]` };
|
|
302
|
+
}
|
|
303
|
+
function handleBugFix(args) {
|
|
304
|
+
const { path, data } = ensureRunJson();
|
|
305
|
+
const bug = data.bugs.find(b => b.bugId === args.bugId);
|
|
306
|
+
if (!bug)
|
|
307
|
+
return { success: false, message: `Bug ${args.bugId} 未找到` };
|
|
308
|
+
bug.status = 'fixed';
|
|
309
|
+
bug.fixedAt = now();
|
|
310
|
+
if (args.fix)
|
|
311
|
+
bug.fix = args.fix;
|
|
312
|
+
// Auto-collect tokens for bug fix
|
|
313
|
+
let bugTokens = 0;
|
|
314
|
+
if (bug.reportedAt && bug.fixedAt) {
|
|
315
|
+
bugTokens = getTaskTokens(bug.reportedAt, bug.fixedAt);
|
|
316
|
+
if (bugTokens > 0) {
|
|
317
|
+
bug.tokensConsumed = bugTokens;
|
|
318
|
+
if (!data.cost)
|
|
319
|
+
data.cost = {};
|
|
320
|
+
if (!data.cost.tokenBreakdown)
|
|
321
|
+
data.cost.tokenBreakdown = [];
|
|
322
|
+
data.cost.tokenBreakdown.push({
|
|
323
|
+
stage: `bugfix:${args.bugId}`,
|
|
324
|
+
tokens: bugTokens,
|
|
325
|
+
});
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
addEvent(data, 'bug_fixed', { bugId: args.bugId, tokensConsumed: bugTokens });
|
|
329
|
+
addTimeline(data, 'bug-fix', `${args.bugId}: ${bug.title}`);
|
|
330
|
+
save(path, data);
|
|
331
|
+
syncTokenUsage(path, data);
|
|
332
|
+
const tokenMsg = bugTokens > 0 ? ` (${bugTokens} tokens)` : '';
|
|
333
|
+
return { success: true, message: `${args.bugId} 已修复${tokenMsg}` };
|
|
334
|
+
}
|
|
335
|
+
function handleLogReview(args) {
|
|
336
|
+
const result = args.result || 'pass';
|
|
337
|
+
const { path, data } = ensureRunJson();
|
|
338
|
+
const id = nextId(data.reviews, 'REV');
|
|
339
|
+
const issues = args.issues ? args.issues.split(',').map((s) => s.trim()).filter(Boolean) : [];
|
|
340
|
+
const review = {
|
|
341
|
+
reviewId: id,
|
|
342
|
+
taskId: args.taskId || data.context.currentTaskId || null,
|
|
343
|
+
result: result,
|
|
344
|
+
issueCount: issues.length,
|
|
345
|
+
scope: args.scope || '',
|
|
346
|
+
reviewedAt: now(),
|
|
347
|
+
issues,
|
|
348
|
+
};
|
|
349
|
+
data.reviews.push(review);
|
|
350
|
+
data.summary.reviewCount = data.reviews.length;
|
|
351
|
+
data.summary.reviewPassCount = data.reviews.filter(r => r.result === 'pass').length;
|
|
352
|
+
data.summary.reviewFailCount = data.reviews.filter(r => r.result === 'fail').length;
|
|
353
|
+
addEvent(data, 'review_created', { reviewId: id, result });
|
|
354
|
+
addTimeline(data, 'review', `${id}: ${result}`);
|
|
355
|
+
save(path, data);
|
|
356
|
+
syncTokenUsage(path, data);
|
|
357
|
+
return { success: true, reviewId: id, message: `${id}: ${result}` };
|
|
358
|
+
}
|
|
359
|
+
function handleLogDeviation(args) {
|
|
360
|
+
const rootCause = args.rootCause || 'other';
|
|
361
|
+
const category = args.category || 'other';
|
|
362
|
+
const { path, data } = ensureRunJson();
|
|
363
|
+
const id = nextId(data.deviations, 'DEV');
|
|
364
|
+
const deviation = {
|
|
365
|
+
deviationId: id,
|
|
366
|
+
title: args.title,
|
|
367
|
+
rootCauseCategory: rootCause,
|
|
368
|
+
deviationCategory: category,
|
|
369
|
+
aiOutput: '',
|
|
370
|
+
expectedOutput: '',
|
|
371
|
+
files: [],
|
|
372
|
+
ruleSedimented: null,
|
|
373
|
+
detectedAt: now(),
|
|
374
|
+
fixedAt: null,
|
|
375
|
+
};
|
|
376
|
+
data.deviations.push(deviation);
|
|
377
|
+
data.summary.deviationCount = data.deviations.length;
|
|
378
|
+
addEvent(data, 'deviation_created', { deviationId: id });
|
|
379
|
+
addTimeline(data, 'deviation', `${id}: ${args.title}`);
|
|
380
|
+
save(path, data);
|
|
381
|
+
const result = { success: true, deviationId: id, message: `${id} 已记录: ${args.title}` };
|
|
382
|
+
// When rootCause is rule-missing, check for pattern and hint rule sedimentation
|
|
383
|
+
if (rootCause === 'rule-missing') {
|
|
384
|
+
const sameCategoryCount = data.deviations.filter(d => d.deviationCategory === category && d.rootCauseCategory === 'rule-missing').length;
|
|
385
|
+
if (sameCategoryCount >= 2) {
|
|
386
|
+
result.ruleHint = `同类偏差已出现 ${sameCategoryCount} 次(${category} / rule-missing)。如果修复方案属于项目级技术规范(非业务逻辑),请询问用户是否沉淀为规则:调用 aida_log_rule 工具,传入 content="<规则描述>" category="${category}" sourceDeviation="${id}"`;
|
|
387
|
+
}
|
|
388
|
+
else {
|
|
389
|
+
result.ruleHint = `rootCause 为 rule-missing,修复后如果属于项目级技术规范(非业务逻辑),请询问用户是否沉淀为规则:调用 aida_log_rule 工具,传入 content="<规则描述>" category="${category}" sourceDeviation="${id}"`;
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
return result;
|
|
393
|
+
}
|
|
394
|
+
function handleLogFiles() {
|
|
395
|
+
const { path, data } = ensureRunJson();
|
|
396
|
+
// Auto-scan git diff
|
|
397
|
+
let diffOutput = '';
|
|
398
|
+
try {
|
|
399
|
+
diffOutput = execSync('git diff --stat HEAD', { cwd: projectRoot, encoding: 'utf-8' });
|
|
400
|
+
}
|
|
401
|
+
catch {
|
|
402
|
+
// If no HEAD (first commit), try against empty tree
|
|
403
|
+
try {
|
|
404
|
+
diffOutput = execSync('git diff --stat --cached', { cwd: projectRoot, encoding: 'utf-8' });
|
|
405
|
+
}
|
|
406
|
+
catch {
|
|
407
|
+
return { success: true, message: '没有检测到文件变更', filesLogged: 0 };
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
if (!diffOutput.trim()) {
|
|
411
|
+
return { success: true, message: '没有检测到文件变更', filesLogged: 0 };
|
|
412
|
+
}
|
|
413
|
+
// Parse git diff --stat output
|
|
414
|
+
// Format: " src/foo.ts | 10 ++++------" or " src/bar.ts | 5 +++++"
|
|
415
|
+
const lines = diffOutput.split('\n').filter(l => l.includes('|'));
|
|
416
|
+
let totalAdded = 0;
|
|
417
|
+
let totalRemoved = 0;
|
|
418
|
+
const filesLogged = [];
|
|
419
|
+
for (const line of lines) {
|
|
420
|
+
const match = line.match(/^\s*(.+?)\s*\|\s*(\d+)\s*([+-]*)/);
|
|
421
|
+
if (!match)
|
|
422
|
+
continue;
|
|
423
|
+
const filePath = match[1].trim();
|
|
424
|
+
const changes = parseInt(match[2]) || 0;
|
|
425
|
+
const indicators = match[3] || '';
|
|
426
|
+
// Count + and - in the indicators
|
|
427
|
+
const plusCount = (indicators.match(/\+/g) || []).length;
|
|
428
|
+
const minusCount = (indicators.match(/-/g) || []).length;
|
|
429
|
+
const total = plusCount + minusCount;
|
|
430
|
+
let linesAdded = 0;
|
|
431
|
+
let linesRemoved = 0;
|
|
432
|
+
if (total > 0) {
|
|
433
|
+
linesAdded = Math.round(changes * plusCount / total);
|
|
434
|
+
linesRemoved = Math.round(changes * minusCount / total);
|
|
435
|
+
}
|
|
436
|
+
else {
|
|
437
|
+
linesAdded = changes;
|
|
438
|
+
}
|
|
439
|
+
// Determine change type
|
|
440
|
+
let changeType = 'modified';
|
|
441
|
+
if (linesRemoved === 0 && linesAdded > 0)
|
|
442
|
+
changeType = 'created';
|
|
443
|
+
const existing = data.files.find(f => f.path === filePath);
|
|
444
|
+
if (existing) {
|
|
445
|
+
existing.changeCount = (existing.changeCount || 1) + 1;
|
|
446
|
+
existing.linesAdded += linesAdded;
|
|
447
|
+
existing.linesRemoved += linesRemoved;
|
|
448
|
+
existing.lastModified = now();
|
|
449
|
+
}
|
|
450
|
+
else {
|
|
451
|
+
data.files.push({
|
|
452
|
+
path: filePath,
|
|
453
|
+
changeType,
|
|
454
|
+
linesAdded,
|
|
455
|
+
linesRemoved,
|
|
456
|
+
changeCount: 1,
|
|
457
|
+
lastModified: now(),
|
|
458
|
+
});
|
|
459
|
+
}
|
|
460
|
+
totalAdded += linesAdded;
|
|
461
|
+
totalRemoved += linesRemoved;
|
|
462
|
+
filesLogged.push(filePath);
|
|
463
|
+
}
|
|
464
|
+
data.summary.filesChanged = data.files.length;
|
|
465
|
+
data.summary.linesAdded = data.files.reduce((s, f) => s + (f.linesAdded || 0), 0);
|
|
466
|
+
data.summary.linesRemoved = data.files.reduce((s, f) => s + (f.linesRemoved || 0), 0);
|
|
467
|
+
addEvent(data, 'files_scanned', { count: filesLogged.length });
|
|
468
|
+
save(path, data);
|
|
469
|
+
return {
|
|
470
|
+
success: true,
|
|
471
|
+
filesLogged: filesLogged.length,
|
|
472
|
+
linesAdded: totalAdded,
|
|
473
|
+
linesRemoved: totalRemoved,
|
|
474
|
+
message: `${filesLogged.length} files logged (+${totalAdded} -${totalRemoved})`,
|
|
475
|
+
};
|
|
476
|
+
}
|
|
477
|
+
function handleHighlight(args) {
|
|
478
|
+
const { path, data } = ensureRunJson();
|
|
479
|
+
const highlight = {
|
|
480
|
+
content: args.content,
|
|
481
|
+
source: 'auto',
|
|
482
|
+
createdAt: now(),
|
|
483
|
+
};
|
|
484
|
+
data.highlights.push(highlight);
|
|
485
|
+
addEvent(data, 'highlight_added', { content: args.content });
|
|
486
|
+
save(path, data);
|
|
487
|
+
return { success: true, message: `亮点已记录: ${args.content}` };
|
|
488
|
+
}
|
|
489
|
+
function handleStatus() {
|
|
490
|
+
try {
|
|
491
|
+
const { path, data } = ensureRunJson();
|
|
492
|
+
const s = data.summary;
|
|
493
|
+
const tasks = data.tasks.map(t => ({ id: t.taskId, title: t.title, status: t.status }));
|
|
494
|
+
const openBugs = data.bugs.filter(b => b.status === 'open').map(b => ({ id: b.bugId, title: b.title, severity: b.severity }));
|
|
495
|
+
// Real-time token sync
|
|
496
|
+
syncTokenUsage(path, data);
|
|
497
|
+
return {
|
|
498
|
+
branch: data.meta.branch,
|
|
499
|
+
developer: data.meta.developer,
|
|
500
|
+
status: data.meta.status,
|
|
501
|
+
summary: {
|
|
502
|
+
totalTasks: s.totalTasks,
|
|
503
|
+
completedTasks: s.completedTasks,
|
|
504
|
+
bugCount: s.bugCount,
|
|
505
|
+
deviationCount: s.deviationCount,
|
|
506
|
+
filesChanged: s.filesChanged,
|
|
507
|
+
},
|
|
508
|
+
tokenUsage: {
|
|
509
|
+
totalTokens: data.cost?.totalTokens || 0,
|
|
510
|
+
detail: data.cost?.tokenDetail || null,
|
|
511
|
+
},
|
|
512
|
+
currentTaskId: data.context.currentTaskId,
|
|
513
|
+
tasks,
|
|
514
|
+
openBugs,
|
|
515
|
+
};
|
|
516
|
+
}
|
|
517
|
+
catch (e) {
|
|
518
|
+
return { error: e.message };
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
function handleLogRule(args) {
|
|
522
|
+
const { path, data } = ensureRunJson();
|
|
523
|
+
const branch = getBranchName();
|
|
524
|
+
const dev = getDevName();
|
|
525
|
+
const category = args.category || 'general';
|
|
526
|
+
const content = args.content;
|
|
527
|
+
// Write to project-level registry with fingerprint dedup
|
|
528
|
+
const { entry, isDuplicate } = addRule(projectRoot, {
|
|
529
|
+
content,
|
|
530
|
+
category,
|
|
531
|
+
branch,
|
|
532
|
+
deviation: args.sourceDeviation || null,
|
|
533
|
+
author: dev,
|
|
534
|
+
status: 'active',
|
|
535
|
+
});
|
|
536
|
+
if (isDuplicate) {
|
|
537
|
+
return { success: true, message: `规则已存在: ${entry.id}(fingerprint 重复)`, ruleId: entry.id, isDuplicate: true };
|
|
538
|
+
}
|
|
539
|
+
// Also record in run.json.rules[] for per-run tracking
|
|
540
|
+
const localId = nextId(data.rules, 'RULE');
|
|
541
|
+
data.rules.push({
|
|
542
|
+
ruleId: localId,
|
|
543
|
+
file: `rules.json#${entry.id}`,
|
|
544
|
+
content,
|
|
545
|
+
sourceDeviation: args.sourceDeviation || null,
|
|
546
|
+
sedimentedAt: now(),
|
|
547
|
+
});
|
|
548
|
+
data.summary.rulesSedimented = data.rules.filter(r => r.status !== 'pending').length;
|
|
549
|
+
addEvent(data, 'rule_sedimented', { ruleId: localId, registryId: entry.id });
|
|
550
|
+
addTimeline(data, 'rule', `${localId}: ${content.substring(0, 50)}`);
|
|
551
|
+
save(path, data);
|
|
552
|
+
// Rebuild markdown views so AI can read rules next session
|
|
553
|
+
buildRuleViews(projectRoot);
|
|
554
|
+
return { success: true, ruleId: entry.id, message: `规则已沉淀: ${entry.id} [${category}] ${content.substring(0, 60)}` };
|
|
555
|
+
}
|
|
556
|
+
// ─── MCP Request Router ─────────────────────────────────
|
|
557
|
+
function handleRequest(req) {
|
|
558
|
+
const { id, method, params } = req;
|
|
559
|
+
switch (method) {
|
|
560
|
+
case 'initialize':
|
|
561
|
+
sendResult(id, {
|
|
562
|
+
protocolVersion: '2024-11-05',
|
|
563
|
+
capabilities: {
|
|
564
|
+
tools: {},
|
|
565
|
+
prompts: {},
|
|
566
|
+
},
|
|
567
|
+
serverInfo: {
|
|
568
|
+
name: 'aida',
|
|
569
|
+
version: '1.0.0',
|
|
570
|
+
},
|
|
571
|
+
});
|
|
572
|
+
break;
|
|
573
|
+
case 'notifications/initialized':
|
|
574
|
+
// No response needed for notifications
|
|
575
|
+
break;
|
|
576
|
+
case 'tools/list':
|
|
577
|
+
sendResult(id, { tools: TOOLS });
|
|
578
|
+
break;
|
|
579
|
+
case 'tools/call': {
|
|
580
|
+
const toolName = params?.name;
|
|
581
|
+
const args = params?.arguments || {};
|
|
582
|
+
let result;
|
|
583
|
+
try {
|
|
584
|
+
switch (toolName) {
|
|
585
|
+
case 'aida_task_start':
|
|
586
|
+
result = handleTaskStart(args);
|
|
587
|
+
break;
|
|
588
|
+
case 'aida_task_done':
|
|
589
|
+
result = handleTaskDone(args);
|
|
590
|
+
break;
|
|
591
|
+
case 'aida_log_bug':
|
|
592
|
+
result = handleLogBug(args);
|
|
593
|
+
break;
|
|
594
|
+
case 'aida_bug_fix':
|
|
595
|
+
result = handleBugFix(args);
|
|
596
|
+
break;
|
|
597
|
+
case 'aida_log_review':
|
|
598
|
+
result = handleLogReview(args);
|
|
599
|
+
break;
|
|
600
|
+
case 'aida_log_deviation':
|
|
601
|
+
result = handleLogDeviation(args);
|
|
602
|
+
break;
|
|
603
|
+
case 'aida_log_files':
|
|
604
|
+
result = handleLogFiles();
|
|
605
|
+
break;
|
|
606
|
+
case 'aida_highlight':
|
|
607
|
+
result = handleHighlight(args);
|
|
608
|
+
break;
|
|
609
|
+
case 'aida_status':
|
|
610
|
+
result = handleStatus();
|
|
611
|
+
break;
|
|
612
|
+
case 'aida_log_rule':
|
|
613
|
+
result = handleLogRule(args);
|
|
614
|
+
break;
|
|
615
|
+
default:
|
|
616
|
+
sendError(id, -32601, `Unknown tool: ${toolName}`);
|
|
617
|
+
return;
|
|
618
|
+
}
|
|
619
|
+
sendResult(id, {
|
|
620
|
+
content: [{ type: 'text', text: JSON.stringify(result) }],
|
|
621
|
+
});
|
|
622
|
+
}
|
|
623
|
+
catch (e) {
|
|
624
|
+
sendResult(id, {
|
|
625
|
+
content: [{ type: 'text', text: JSON.stringify({ error: e.message }) }],
|
|
626
|
+
isError: true,
|
|
627
|
+
});
|
|
628
|
+
}
|
|
629
|
+
break;
|
|
630
|
+
}
|
|
631
|
+
case 'prompts/list':
|
|
632
|
+
sendResult(id, { prompts: PROMPTS });
|
|
633
|
+
break;
|
|
634
|
+
case 'prompts/get': {
|
|
635
|
+
const promptName = params?.name;
|
|
636
|
+
if (promptName === 'aida-guide') {
|
|
637
|
+
sendResult(id, {
|
|
638
|
+
messages: [{
|
|
639
|
+
role: 'user',
|
|
640
|
+
content: { type: 'text', text: PROMPT_CONTENT },
|
|
641
|
+
}],
|
|
642
|
+
});
|
|
643
|
+
}
|
|
644
|
+
else {
|
|
645
|
+
sendError(id, -32601, `Unknown prompt: ${promptName}`);
|
|
646
|
+
}
|
|
647
|
+
break;
|
|
648
|
+
}
|
|
649
|
+
case 'ping':
|
|
650
|
+
sendResult(id, {});
|
|
651
|
+
break;
|
|
652
|
+
default:
|
|
653
|
+
if (id !== undefined) {
|
|
654
|
+
sendError(id, -32601, `Method not found: ${method}`);
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
// ─── Stdio Transport ─────────────────────────────────────
|
|
659
|
+
export function startMcpServer() {
|
|
660
|
+
let buffer = '';
|
|
661
|
+
process.stdin.setEncoding('utf-8');
|
|
662
|
+
process.stdin.on('data', (chunk) => {
|
|
663
|
+
buffer += chunk;
|
|
664
|
+
// Parse Content-Length based messages
|
|
665
|
+
while (true) {
|
|
666
|
+
const headerEnd = buffer.indexOf('\r\n\r\n');
|
|
667
|
+
if (headerEnd === -1)
|
|
668
|
+
break;
|
|
669
|
+
const header = buffer.substring(0, headerEnd);
|
|
670
|
+
const match = header.match(/Content-Length:\s*(\d+)/i);
|
|
671
|
+
if (!match) {
|
|
672
|
+
// Try to parse as raw JSON (some clients don't send headers)
|
|
673
|
+
const nlIdx = buffer.indexOf('\n');
|
|
674
|
+
if (nlIdx === -1)
|
|
675
|
+
break;
|
|
676
|
+
const line = buffer.substring(0, nlIdx).trim();
|
|
677
|
+
buffer = buffer.substring(nlIdx + 1);
|
|
678
|
+
if (line) {
|
|
679
|
+
try {
|
|
680
|
+
const req = JSON.parse(line);
|
|
681
|
+
handleRequest(req);
|
|
682
|
+
}
|
|
683
|
+
catch { /* skip malformed */ }
|
|
684
|
+
}
|
|
685
|
+
continue;
|
|
686
|
+
}
|
|
687
|
+
const contentLength = parseInt(match[1]);
|
|
688
|
+
const bodyStart = headerEnd + 4;
|
|
689
|
+
if (buffer.length < bodyStart + contentLength)
|
|
690
|
+
break;
|
|
691
|
+
const body = buffer.substring(bodyStart, bodyStart + contentLength);
|
|
692
|
+
buffer = buffer.substring(bodyStart + contentLength);
|
|
693
|
+
try {
|
|
694
|
+
const req = JSON.parse(body);
|
|
695
|
+
handleRequest(req);
|
|
696
|
+
}
|
|
697
|
+
catch { /* skip malformed */ }
|
|
698
|
+
}
|
|
699
|
+
});
|
|
700
|
+
process.stdin.on('end', () => {
|
|
701
|
+
process.exit(0);
|
|
702
|
+
});
|
|
703
|
+
}
|
|
704
|
+
//# sourceMappingURL=server.js.map
|