autosnippet 3.2.1 → 3.2.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/README.md +23 -0
- package/config/default.json +7 -0
- package/dashboard/dist/assets/{icons-18VxiaCT.js → icons-pSac4wYO.js} +101 -96
- package/dashboard/dist/assets/{index-CRH5Umim.js → index-6itPuGFl.js} +45 -45
- package/dashboard/dist/assets/index-DNOHYBhy.css +1 -0
- package/dashboard/dist/index.html +3 -3
- package/lib/cli/SetupService.js +245 -46
- package/lib/domain/knowledge/KnowledgeEntry.js +11 -0
- package/lib/domain/task/Task.js +32 -2
- package/lib/domain/task/TaskDependency.js +1 -0
- package/lib/external/mcp/McpServer.js +180 -6
- package/lib/external/mcp/handlers/bootstrap/shared/bootstrap-phases.js +2 -1
- package/lib/external/mcp/handlers/decide.js +109 -0
- package/lib/external/mcp/handlers/ready.js +42 -0
- package/lib/external/mcp/handlers/system.js +12 -0
- package/lib/external/mcp/handlers/task.js +7 -19
- package/lib/external/mcp/tools.js +83 -42
- package/lib/http/routes/knowledge.js +10 -10
- package/lib/http/routes/task.js +81 -1
- package/lib/http/utils/routeHelpers.js +30 -0
- package/lib/infrastructure/config/Paths.js +18 -8
- package/lib/repository/task/TaskRepository.impl.js +3 -1
- package/lib/service/cursor/AgentInstructionsGenerator.js +6 -4
- package/lib/service/knowledge/KnowledgeService.js +12 -1
- package/lib/service/task/TaskGraphService.js +243 -3
- package/package.json +1 -1
- package/skills/autosnippet-intent/SKILL.md +3 -1
- package/skills/autosnippet-recipes/SKILL.md +3 -1
- package/templates/claude-hooks.yaml +1 -0
- package/templates/copilot-instructions.md +48 -13
- package/templates/cursor-rules/autosnippet-conventions.mdc +11 -0
- package/templates/cursor-rules/autosnippet-workflow.mdc +16 -7
- package/templates/guard-ci.yml +22 -0
- package/templates/pre-commit-guard.sh +2 -1
- package/dashboard/dist/assets/index-BJiuaVPD.css +0 -1
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Model Context Protocol (stdio transport)
|
|
5
5
|
* 提供给 IDE AI Agent (Cursor/VSCode Copilot) 的工具集
|
|
6
6
|
*
|
|
7
|
-
* V3.1 整合:39 →
|
|
7
|
+
* V3.1 整合:39 → 22 工具(18 agent + 4 admin)
|
|
8
8
|
* 通过 ASD_MCP_TIER 环境变量控制可见工具集(agent/admin)
|
|
9
9
|
*
|
|
10
10
|
* 冷启动双路径:
|
|
@@ -38,7 +38,11 @@ import * as systemHandlers from './handlers/system.js';
|
|
|
38
38
|
// ─── External Agent Bootstrap 新 handler ──────────────────────
|
|
39
39
|
|
|
40
40
|
import { bootstrapExternal } from './handlers/bootstrap-external.js';
|
|
41
|
-
import {
|
|
41
|
+
import { decideHandler } from './handlers/decide.js';
|
|
42
|
+
import { dimensionComplete } from './handlers/dimension-complete-external.js';
|
|
43
|
+
import { readyHandler } from './handlers/ready.js';
|
|
44
|
+
import { taskHandler } from './handlers/task.js';
|
|
45
|
+
import { wikiFinalize, wikiPlan } from './handlers/wiki-external.js';
|
|
42
46
|
|
|
43
47
|
// ─── McpServer 类 ─────────────────────────────────────────────
|
|
44
48
|
|
|
@@ -49,11 +53,34 @@ export class McpServer {
|
|
|
49
53
|
this.bootstrap = options.bootstrap || null;
|
|
50
54
|
this.server = null;
|
|
51
55
|
this._startedAt = Date.now();
|
|
56
|
+
|
|
57
|
+
// ── P0: Decision 注入缓存 ──
|
|
58
|
+
this._decisionCache = {
|
|
59
|
+
decisions: [], // [{ id, title }]
|
|
60
|
+
fetchedAt: 0, // timestamp ms
|
|
61
|
+
ttl: 60_000, // 60s TTL
|
|
62
|
+
_pending: null, // 防并发重复查询的 pending promise
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
// ── P3: Session 管理 ──
|
|
66
|
+
this._session = {
|
|
67
|
+
id: `ses-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 6)}`,
|
|
68
|
+
startedAt: Date.now(),
|
|
69
|
+
readyCalled: false,
|
|
70
|
+
toolCallCount: 0,
|
|
71
|
+
toolsUsed: new Set(),
|
|
72
|
+
lastActivityAt: Date.now(),
|
|
73
|
+
};
|
|
52
74
|
}
|
|
53
75
|
|
|
54
76
|
/** 共享上下文对象,传给所有 handler */
|
|
55
77
|
get _ctx() {
|
|
56
|
-
return {
|
|
78
|
+
return {
|
|
79
|
+
container: this.container,
|
|
80
|
+
logger: this.logger,
|
|
81
|
+
startedAt: this._startedAt,
|
|
82
|
+
session: this._session,
|
|
83
|
+
};
|
|
57
84
|
}
|
|
58
85
|
|
|
59
86
|
async initialize() {
|
|
@@ -160,6 +187,9 @@ export class McpServer {
|
|
|
160
187
|
const wrapped = wrapHandler(name, handler);
|
|
161
188
|
const result = await wrapped(ctx, args);
|
|
162
189
|
|
|
190
|
+
// ── P0+P3: Decision 注入 + Session 追踪 ──
|
|
191
|
+
await this._injectDecisions(name, result);
|
|
192
|
+
|
|
163
193
|
// ── 首次成功 tool call → 标记 autoApprove(one-shot) ──
|
|
164
194
|
// 用户已手动授权了至少一个工具,标记后下次 MCP 启动注入 autoApprove
|
|
165
195
|
if (!this._autoApproveMarked) {
|
|
@@ -167,19 +197,159 @@ export class McpServer {
|
|
|
167
197
|
try {
|
|
168
198
|
const projectRoot = process.env.ASD_PROJECT_DIR || process.cwd();
|
|
169
199
|
markAutoApproveNeeded(projectRoot, this.logger);
|
|
170
|
-
} catch {
|
|
200
|
+
} catch {
|
|
201
|
+
/* non-blocking */
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
return result;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// ─── P0: Decision 自动注入 ────────────────────────────
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* 在工具返回结果中注入 decisions 摘要 + 更新 session 统计
|
|
212
|
+
*
|
|
213
|
+
* 策略:
|
|
214
|
+
* - ready: 刷新缓存,不额外注入(response 本身含 decisions)
|
|
215
|
+
* - decide 写操作: invalidate 缓存(下次查询拉最新)
|
|
216
|
+
* - 其他工具: 注入 _activeDecisions 摘要
|
|
217
|
+
* - 首次未调 ready 的工具: 注入更强提醒
|
|
218
|
+
*
|
|
219
|
+
* @param {string} toolName
|
|
220
|
+
* @param {object} result — handler 返回的 envelope 对象
|
|
221
|
+
*/
|
|
222
|
+
async _injectDecisions(toolName, result) {
|
|
223
|
+
// ── P3: Session 统计 ──
|
|
224
|
+
this._session.toolCallCount++;
|
|
225
|
+
this._session.toolsUsed.add(toolName);
|
|
226
|
+
this._session.lastActivityAt = Date.now();
|
|
227
|
+
|
|
228
|
+
// 1) ready 工具:刷新缓存 + 标记 session
|
|
229
|
+
if (toolName === 'autosnippet_ready') {
|
|
230
|
+
this._session.readyCalled = true;
|
|
231
|
+
this._refreshCacheFromReady(result);
|
|
232
|
+
return result;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// 2) decide 写操作:invalidate 缓存(record/revise/unpin 改变了 decisions)
|
|
236
|
+
if (toolName === 'autosnippet_decide') {
|
|
237
|
+
this._decisionCache.fetchedAt = 0;
|
|
238
|
+
this._decisionCache._pending = null;
|
|
239
|
+
return result;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// 3) autosnippet_task 也不注入(task 操作返回值已足够,避免 token 膨胀)
|
|
243
|
+
if (toolName === 'autosnippet_task') {
|
|
244
|
+
return result;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// 4) 对非 ready/decide 工具:注入 decisions 摘要
|
|
248
|
+
const decisions = await this._getDecisionsSummary();
|
|
249
|
+
if (decisions.length > 0 && typeof result === 'object' && result !== null) {
|
|
250
|
+
result._activeDecisions = decisions;
|
|
251
|
+
|
|
252
|
+
// P3: 如果 ready 从未被调用,注入更强提醒
|
|
253
|
+
if (!this._session.readyCalled) {
|
|
254
|
+
result._decisionReminder =
|
|
255
|
+
'⚠️ You have NOT called autosnippet_ready() yet this session. ' +
|
|
256
|
+
'These decisions may affect your work. Call autosnippet_ready() for full context.';
|
|
257
|
+
} else {
|
|
258
|
+
result._decisionReminder =
|
|
259
|
+
'Respect these team decisions. Call autosnippet_decide({ operation: "list" }) for full details.';
|
|
260
|
+
}
|
|
171
261
|
}
|
|
172
262
|
|
|
173
263
|
return result;
|
|
174
264
|
}
|
|
175
265
|
|
|
266
|
+
/**
|
|
267
|
+
* 获取 decisions 摘要(带缓存 + 防并发)
|
|
268
|
+
* @private
|
|
269
|
+
* @returns {Promise<Array<{id: string, title: string}>>}
|
|
270
|
+
*/
|
|
271
|
+
async _getDecisionsSummary() {
|
|
272
|
+
const cache = this._decisionCache;
|
|
273
|
+
const now = Date.now();
|
|
274
|
+
|
|
275
|
+
// 缓存有效(包括缓存了"空 decisions"的情况),直接返回
|
|
276
|
+
if (cache.fetchedAt > 0 && now - cache.fetchedAt < cache.ttl) {
|
|
277
|
+
return cache.decisions;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// 防并发:如果有正在进行的查询,等它完成
|
|
281
|
+
if (cache._pending) {
|
|
282
|
+
try {
|
|
283
|
+
return await cache._pending;
|
|
284
|
+
} catch {
|
|
285
|
+
return cache.decisions; // 降级返回旧缓存
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// 发起新查询
|
|
290
|
+
cache._pending = this._fetchDecisionsSummary();
|
|
291
|
+
try {
|
|
292
|
+
const result = await cache._pending;
|
|
293
|
+
return result;
|
|
294
|
+
} finally {
|
|
295
|
+
cache._pending = null;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* 从 DB 查询 decisions 摘要(仅 id + title)
|
|
301
|
+
* @private
|
|
302
|
+
*/
|
|
303
|
+
async _fetchDecisionsSummary() {
|
|
304
|
+
const cache = this._decisionCache;
|
|
305
|
+
try {
|
|
306
|
+
const taskService = this.container?.get('taskGraphService');
|
|
307
|
+
if (!taskService) {
|
|
308
|
+
return cache.decisions;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// 使用 service 公共 API(不直接访问 repo)
|
|
312
|
+
const pinned = await taskService.list(
|
|
313
|
+
{ status: 'pinned', taskType: 'decision' },
|
|
314
|
+
{ limit: 50 }
|
|
315
|
+
);
|
|
316
|
+
cache.decisions = pinned.map((d) => ({
|
|
317
|
+
id: d.id,
|
|
318
|
+
title: d.title,
|
|
319
|
+
}));
|
|
320
|
+
cache.fetchedAt = Date.now();
|
|
321
|
+
} catch (err) {
|
|
322
|
+
// 查询失败不阻塞,保留旧缓存
|
|
323
|
+
this.logger.debug('_fetchDecisionsSummary error', { error: err.message });
|
|
324
|
+
}
|
|
325
|
+
return cache.decisions;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
/**
|
|
329
|
+
* 从 ready 响应结果中刷新缓存(避免额外 DB 查询)
|
|
330
|
+
* @private
|
|
331
|
+
*/
|
|
332
|
+
_refreshCacheFromReady(readyResult) {
|
|
333
|
+
try {
|
|
334
|
+
// readyResult 是 envelope({ data: { decisions: [...] } })
|
|
335
|
+
const decisions = readyResult?.data?.decisions || [];
|
|
336
|
+
this._decisionCache.decisions = decisions.map((d) => ({
|
|
337
|
+
id: d.id,
|
|
338
|
+
title: d.title,
|
|
339
|
+
}));
|
|
340
|
+
this._decisionCache.fetchedAt = Date.now();
|
|
341
|
+
} catch {
|
|
342
|
+
/* ignore */
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
176
346
|
/**
|
|
177
347
|
* 解析工具名到 handler 函数(V3 整合版)
|
|
178
348
|
* @private
|
|
179
349
|
*/
|
|
180
350
|
_resolveHandler(name) {
|
|
181
351
|
const HANDLER_MAP = {
|
|
182
|
-
// ── Agent 层 (
|
|
352
|
+
// ── Agent 层 (18) ──
|
|
183
353
|
autosnippet_health: (ctx) => systemHandlers.health(ctx),
|
|
184
354
|
autosnippet_capabilities: () => systemHandlers.capabilities(),
|
|
185
355
|
autosnippet_search: (ctx, args) => consolidated.consolidatedSearch(ctx, args),
|
|
@@ -192,6 +362,8 @@ export class McpServer {
|
|
|
192
362
|
knowledgeHandlers.submitKnowledgeBatch(ctx, args),
|
|
193
363
|
autosnippet_save_document: (ctx, args) => knowledgeHandlers.saveDocument(ctx, args),
|
|
194
364
|
autosnippet_skill: (ctx, args) => consolidated.consolidatedSkill(ctx, args),
|
|
365
|
+
autosnippet_ready: (ctx, args) => readyHandler(ctx, args),
|
|
366
|
+
autosnippet_decide: (ctx, args) => decideHandler(ctx, args),
|
|
195
367
|
autosnippet_task: (ctx, args) => taskHandler(ctx, args),
|
|
196
368
|
// ── External Agent Bootstrap (v3.1) ──
|
|
197
369
|
autosnippet_bootstrap: (ctx, _args) => bootstrapExternal(ctx),
|
|
@@ -270,7 +442,9 @@ export class McpServer {
|
|
|
270
442
|
const projectRoot = process.env.ASD_PROJECT_DIR || process.cwd();
|
|
271
443
|
try {
|
|
272
444
|
applyPendingAutoApprove(projectRoot, this.logger);
|
|
273
|
-
} catch {
|
|
445
|
+
} catch {
|
|
446
|
+
/* non-blocking */
|
|
447
|
+
}
|
|
274
448
|
|
|
275
449
|
const transport = new StdioServerTransport();
|
|
276
450
|
await this.server.connect(transport);
|
|
@@ -542,7 +542,8 @@ export async function runAllPhases(projectRoot, ctx, options = {}) {
|
|
|
542
542
|
// ── Phase 1: 文件收集 ──
|
|
543
543
|
const p1Start = Date.now();
|
|
544
544
|
const phase1 = await runPhase1_FileCollection(projectRoot, ctx.logger, options);
|
|
545
|
-
|
|
545
|
+
let { allFiles, allTargets, discoverer, langStats } = phase1;
|
|
546
|
+
|
|
546
547
|
if (report) report.phases.fileCollection = { fileCount: allFiles.length, targetCount: allTargets.length, ms: Date.now() - p1Start };
|
|
547
548
|
|
|
548
549
|
if (allFiles.length === 0) {
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP Handler — autosnippet_decide
|
|
3
|
+
*
|
|
4
|
+
* 决策管理独立入口。Agent 与用户达成共识时调用。
|
|
5
|
+
* 操作:record / revise / unpin / list
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { envelope } from '../envelope.js';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* @param {object} ctx — { container }
|
|
12
|
+
* @param {object} args — { operation, title, description, rationale, tags, relatedTaskId, id, reason }
|
|
13
|
+
*/
|
|
14
|
+
export async function decideHandler(ctx, args) {
|
|
15
|
+
const taskService = ctx.container.get('taskGraphService');
|
|
16
|
+
const op = args.operation || 'list';
|
|
17
|
+
|
|
18
|
+
switch (op) {
|
|
19
|
+
case 'record':
|
|
20
|
+
return _record(taskService, args);
|
|
21
|
+
case 'revise':
|
|
22
|
+
return _revise(taskService, args);
|
|
23
|
+
case 'unpin':
|
|
24
|
+
return _unpin(taskService, args);
|
|
25
|
+
case 'list':
|
|
26
|
+
return _list(taskService);
|
|
27
|
+
default:
|
|
28
|
+
return envelope({
|
|
29
|
+
success: false,
|
|
30
|
+
message: `Unknown decide operation: ${op}. Use: record, revise, unpin, list`,
|
|
31
|
+
meta: { tool: 'autosnippet_decide' },
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async function _record(svc, args) {
|
|
37
|
+
if (!args.title) {
|
|
38
|
+
return envelope({ success: false, message: 'title is required', meta: { tool: 'autosnippet_decide' } });
|
|
39
|
+
}
|
|
40
|
+
if (!args.description) {
|
|
41
|
+
return envelope({ success: false, message: 'description is required', meta: { tool: 'autosnippet_decide' } });
|
|
42
|
+
}
|
|
43
|
+
const { task, isDuplicate } = await svc.recordDecision({
|
|
44
|
+
title: args.title,
|
|
45
|
+
description: args.description,
|
|
46
|
+
rationale: args.rationale || '',
|
|
47
|
+
tags: args.tags || [],
|
|
48
|
+
relatedTaskId: args.relatedTaskId || null,
|
|
49
|
+
});
|
|
50
|
+
return envelope({
|
|
51
|
+
success: true,
|
|
52
|
+
data: task.toJSON(),
|
|
53
|
+
message: isDuplicate
|
|
54
|
+
? `⚠ Decision already recorded: ${task.id}`
|
|
55
|
+
: `✅ Decision pinned: ${task.id} — "${args.title}"`,
|
|
56
|
+
meta: { tool: 'autosnippet_decide' },
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async function _revise(svc, args) {
|
|
61
|
+
if (!args.id) {
|
|
62
|
+
return envelope({ success: false, message: 'id of old decision is required', meta: { tool: 'autosnippet_decide' } });
|
|
63
|
+
}
|
|
64
|
+
if (!args.title) {
|
|
65
|
+
return envelope({ success: false, message: 'title of new decision is required', meta: { tool: 'autosnippet_decide' } });
|
|
66
|
+
}
|
|
67
|
+
if (!args.description) {
|
|
68
|
+
return envelope({ success: false, message: 'description of new decision is required', meta: { tool: 'autosnippet_decide' } });
|
|
69
|
+
}
|
|
70
|
+
const result = await svc.reviseDecision({
|
|
71
|
+
oldDecisionId: args.id,
|
|
72
|
+
title: args.title,
|
|
73
|
+
description: args.description,
|
|
74
|
+
rationale: args.rationale || '',
|
|
75
|
+
reason: args.reason || '',
|
|
76
|
+
});
|
|
77
|
+
return envelope({
|
|
78
|
+
success: true,
|
|
79
|
+
data: {
|
|
80
|
+
newDecision: result.newDecision.toJSON(),
|
|
81
|
+
superseded: result.oldDecisionId,
|
|
82
|
+
},
|
|
83
|
+
message: `✅ Decision revised: ${result.oldDecisionId} → ${result.newDecision.id}`,
|
|
84
|
+
meta: { tool: 'autosnippet_decide' },
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
async function _unpin(svc, args) {
|
|
89
|
+
if (!args.id) {
|
|
90
|
+
return envelope({ success: false, message: 'id is required', meta: { tool: 'autosnippet_decide' } });
|
|
91
|
+
}
|
|
92
|
+
const task = await svc.unpinDecision(args.id, args.reason || '');
|
|
93
|
+
return envelope({
|
|
94
|
+
success: true,
|
|
95
|
+
data: task.toJSON(),
|
|
96
|
+
message: `Decision ${args.id} unpinned and closed`,
|
|
97
|
+
meta: { tool: 'autosnippet_decide' },
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
async function _list(svc) {
|
|
102
|
+
const decisions = await svc.list({ status: 'pinned', taskType: 'decision' }, { limit: 50 });
|
|
103
|
+
return envelope({
|
|
104
|
+
success: true,
|
|
105
|
+
data: decisions.map(d => d.toJSON()),
|
|
106
|
+
message: `${decisions.length} active decision(s)`,
|
|
107
|
+
meta: { tool: 'autosnippet_decide' },
|
|
108
|
+
});
|
|
109
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP Handler — autosnippet_ready
|
|
3
|
+
*
|
|
4
|
+
* Agent 的首要入口:加载项目上下文 + 就绪任务 + 团队决策。
|
|
5
|
+
* 等同于 Beads 的 `bd ready`,但含知识桥接和决策持久化。
|
|
6
|
+
*
|
|
7
|
+
* 无参数即可调用。Agent 看到工具名就知道"先看看有什么可做的"。
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { envelope } from '../envelope.js';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* @param {object} ctx — { container }
|
|
14
|
+
* @param {object} args — { limit?, withKnowledge? }
|
|
15
|
+
*/
|
|
16
|
+
export async function readyHandler(ctx, args = {}) {
|
|
17
|
+
const taskService = ctx.container.get('taskGraphService');
|
|
18
|
+
const result = await taskService.prime({
|
|
19
|
+
limit: args.limit || 10,
|
|
20
|
+
withKnowledge: args.withKnowledge !== false,
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
const decisionCount = (result.decisions || []).length;
|
|
24
|
+
const staleCount = (result.staleDecisions || []).length;
|
|
25
|
+
const decisionTitles = (result.decisions || []).map((d) => d.title).join('; ');
|
|
26
|
+
const statsLine = `${result.inProgress.length} in-progress, ${result.ready.length} ready, ${result.stats.total} total`;
|
|
27
|
+
|
|
28
|
+
let message;
|
|
29
|
+
if (decisionCount > 0) {
|
|
30
|
+
const stalePart = staleCount > 0 ? ` ${staleCount} stale.` : '';
|
|
31
|
+
message = `⚠️ ${decisionCount} ACTIVE DECISION(S): [${decisionTitles}].${stalePart} ${statsLine}.`;
|
|
32
|
+
} else {
|
|
33
|
+
message = `${statsLine}.`;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return envelope({
|
|
37
|
+
success: true,
|
|
38
|
+
data: result,
|
|
39
|
+
message,
|
|
40
|
+
meta: { tool: 'autosnippet_ready' },
|
|
41
|
+
});
|
|
42
|
+
}
|
|
@@ -133,6 +133,18 @@ export async function health(ctx) {
|
|
|
133
133
|
checks,
|
|
134
134
|
services: ctx.container.getServiceNames(),
|
|
135
135
|
knowledgeBase,
|
|
136
|
+
// P3: Session 信息
|
|
137
|
+
...(ctx.session
|
|
138
|
+
? {
|
|
139
|
+
session: {
|
|
140
|
+
id: ctx.session.id,
|
|
141
|
+
readyCalled: ctx.session.readyCalled,
|
|
142
|
+
toolCallCount: ctx.session.toolCallCount,
|
|
143
|
+
toolsUsed: Array.from(ctx.session.toolsUsed),
|
|
144
|
+
durationMs: Date.now() - ctx.session.startedAt,
|
|
145
|
+
},
|
|
146
|
+
}
|
|
147
|
+
: {}),
|
|
136
148
|
...(issues.length ? { issues } : {}),
|
|
137
149
|
...(actionHints.length ? { actionHints } : {}),
|
|
138
150
|
},
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* MCP Handler —
|
|
2
|
+
* MCP Handler — autosnippet_task (Task CRUD)
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* Operations: create / decompose / claim / close / fail / defer / progress
|
|
5
|
+
* ready / show / list / blocked / dep_add / dep_tree / stats
|
|
6
|
+
*
|
|
7
|
+
* Session entry point → autosnippet_ready
|
|
8
|
+
* Decision management → autosnippet_decide
|
|
7
9
|
*/
|
|
8
10
|
|
|
9
11
|
import { envelope } from '../envelope.js';
|
|
@@ -31,8 +33,6 @@ export async function taskHandler(ctx, args) {
|
|
|
31
33
|
return _defer(taskService, args);
|
|
32
34
|
case 'progress':
|
|
33
35
|
return _progress(taskService, args);
|
|
34
|
-
case 'prime':
|
|
35
|
-
return _prime(taskService);
|
|
36
36
|
case 'decompose':
|
|
37
37
|
return _decompose(taskService, args);
|
|
38
38
|
case 'show':
|
|
@@ -50,7 +50,7 @@ export async function taskHandler(ctx, args) {
|
|
|
50
50
|
default:
|
|
51
51
|
return envelope({
|
|
52
52
|
success: false,
|
|
53
|
-
message: `Unknown operation: ${args.operation}
|
|
53
|
+
message: `Unknown operation: ${args.operation}. Use autosnippet_ready for session context, autosnippet_decide for decisions.`,
|
|
54
54
|
meta: { tool: 'autosnippet_task' },
|
|
55
55
|
});
|
|
56
56
|
}
|
|
@@ -175,18 +175,6 @@ async function _progress(svc, args) {
|
|
|
175
175
|
});
|
|
176
176
|
}
|
|
177
177
|
|
|
178
|
-
// ── prime — 会话恢复 ──
|
|
179
|
-
|
|
180
|
-
async function _prime(svc) {
|
|
181
|
-
const result = await svc.prime({ withKnowledge: true });
|
|
182
|
-
return envelope({
|
|
183
|
-
success: true,
|
|
184
|
-
data: result,
|
|
185
|
-
message: `${result.inProgress.length} in-progress, ${result.ready.length} ready, ${result.stats.total} total`,
|
|
186
|
-
meta: { tool: 'autosnippet_task' },
|
|
187
|
-
});
|
|
188
|
-
}
|
|
189
|
-
|
|
190
178
|
// ── decompose ──
|
|
191
179
|
|
|
192
180
|
async function _decompose(svc, args) {
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* MCP 工具定义 — V3 整合版 (
|
|
2
|
+
* MCP 工具定义 — V3 整合版 (18 agent + 4 admin = 22 工具)
|
|
3
3
|
*
|
|
4
|
-
* 从 39 →
|
|
4
|
+
* 从 39 → 22 工具(参数路由合并同类工具 + 外部 Agent 冷启动新架构 + TaskGraph 三工具拆分)。
|
|
5
|
+
* TaskGraph 拆分: autosnippet_ready + autosnippet_decide + autosnippet_task
|
|
5
6
|
* 每个工具声明增加 tier 字段(agent / admin)。
|
|
6
7
|
* tools.js 只包含 JSON Schema 声明 + Gateway 映射,不含业务逻辑。
|
|
7
8
|
*
|
|
@@ -44,6 +45,15 @@ export const TOOL_GATEWAY_MAP = {
|
|
|
44
45
|
autosnippet_submit_knowledge: { action: 'knowledge:create', resource: 'knowledge' },
|
|
45
46
|
autosnippet_submit_knowledge_batch: { action: 'knowledge:create', resource: 'knowledge' },
|
|
46
47
|
autosnippet_save_document: { action: 'knowledge:create', resource: 'knowledge' },
|
|
48
|
+
// decide 写操作(record/revise/unpin)
|
|
49
|
+
autosnippet_decide: {
|
|
50
|
+
resolver: (args) =>
|
|
51
|
+
({
|
|
52
|
+
record: { action: 'task:create', resource: 'tasks' },
|
|
53
|
+
revise: { action: 'task:update', resource: 'tasks' },
|
|
54
|
+
unpin: { action: 'task:update', resource: 'tasks' },
|
|
55
|
+
})[args?.operation] || null, // list 只读
|
|
56
|
+
},
|
|
47
57
|
// task 写操作(create/claim/close/fail/defer/decompose/dep_add)
|
|
48
58
|
autosnippet_task: {
|
|
49
59
|
resolver: (args) =>
|
|
@@ -56,7 +66,7 @@ export const TOOL_GATEWAY_MAP = {
|
|
|
56
66
|
progress: { action: 'task:update', resource: 'tasks' },
|
|
57
67
|
decompose: { action: 'task:create', resource: 'tasks' },
|
|
58
68
|
dep_add: { action: 'task:update', resource: 'tasks' },
|
|
59
|
-
})[args?.operation] || null, // ready/
|
|
69
|
+
})[args?.operation] || null, // ready/show/list/blocked/dep_tree/stats 只读
|
|
60
70
|
},
|
|
61
71
|
// admin 工具
|
|
62
72
|
autosnippet_enrich_candidates: { action: 'knowledge:update', resource: 'knowledge' },
|
|
@@ -67,7 +77,7 @@ export const TOOL_GATEWAY_MAP = {
|
|
|
67
77
|
|
|
68
78
|
export const TOOLS = [
|
|
69
79
|
// ══════════════════════════════════════════════════════
|
|
70
|
-
// Tier: agent — Agent 核心工具集 (
|
|
80
|
+
// Tier: agent — Agent 核心工具集 (18 个)
|
|
71
81
|
// ══════════════════════════════════════════════════════
|
|
72
82
|
|
|
73
83
|
// 1. 健康检查
|
|
@@ -528,68 +538,99 @@ export const TOOLS = [
|
|
|
528
538
|
inputSchema: { type: 'object', properties: {}, required: [] },
|
|
529
539
|
},
|
|
530
540
|
|
|
531
|
-
// 13.
|
|
541
|
+
// 13. autosnippet_ready — Agent 会话入口(like beads_ready)
|
|
542
|
+
{
|
|
543
|
+
name: 'autosnippet_ready',
|
|
544
|
+
tier: 'agent',
|
|
545
|
+
description:
|
|
546
|
+
'Session entry point. Call FIRST at conversation start to load project context, active decisions, and available tasks.\n' +
|
|
547
|
+
'Returns: in-progress tasks, ready tasks (with knowledge context), pinned decisions, and stats.',
|
|
548
|
+
inputSchema: {
|
|
549
|
+
type: 'object',
|
|
550
|
+
properties: {
|
|
551
|
+
limit: { type: 'number', default: 10, description: 'Max ready tasks to return' },
|
|
552
|
+
withKnowledge: { type: 'boolean', default: true, description: 'Attach knowledge context to ready tasks' },
|
|
553
|
+
},
|
|
554
|
+
required: [],
|
|
555
|
+
},
|
|
556
|
+
},
|
|
557
|
+
|
|
558
|
+
// 14. autosnippet_decide — 决策管理
|
|
559
|
+
{
|
|
560
|
+
name: 'autosnippet_decide',
|
|
561
|
+
tier: 'agent',
|
|
562
|
+
description:
|
|
563
|
+
'Decision management. Record, revise or unpin project decisions that persist across conversations.\n' +
|
|
564
|
+
'Call record when you and user reach agreement. Decisions are shown in every autosnippet_ready call.',
|
|
565
|
+
inputSchema: {
|
|
566
|
+
type: 'object',
|
|
567
|
+
properties: {
|
|
568
|
+
operation: {
|
|
569
|
+
type: 'string',
|
|
570
|
+
enum: ['record', 'revise', 'unpin', 'list'],
|
|
571
|
+
description: 'record=pin new decision, revise=supersede old, unpin=close, list=show active',
|
|
572
|
+
},
|
|
573
|
+
title: { type: 'string', description: 'Decision title (record/revise)' },
|
|
574
|
+
description: { type: 'string', description: 'Decision description (record/revise)' },
|
|
575
|
+
rationale: { type: 'string', description: 'Why this decision (record/revise)' },
|
|
576
|
+
tags: { type: 'array', items: { type: 'string' }, description: 'Classification tags (record)' },
|
|
577
|
+
relatedTaskId: { type: 'string', description: 'Related task ID (record)' },
|
|
578
|
+
id: { type: 'string', description: 'Decision ID (revise/unpin)' },
|
|
579
|
+
reason: { type: 'string', description: 'Reason for revision/unpin' },
|
|
580
|
+
},
|
|
581
|
+
required: ['operation'],
|
|
582
|
+
},
|
|
583
|
+
},
|
|
584
|
+
|
|
585
|
+
// 15. autosnippet_task — 任务 CRUD
|
|
532
586
|
{
|
|
533
587
|
name: 'autosnippet_task',
|
|
534
588
|
tier: 'agent',
|
|
535
589
|
description:
|
|
536
|
-
'
|
|
537
|
-
'
|
|
538
|
-
'ready 返回任务 + 相关知识上下文。prime 恢复会话状态。',
|
|
590
|
+
'Task CRUD operations. Create tasks, claim work, close completed, track dependencies.\n' +
|
|
591
|
+
'For session context use autosnippet_ready. For decisions use autosnippet_decide.',
|
|
539
592
|
inputSchema: {
|
|
540
593
|
type: 'object',
|
|
541
594
|
properties: {
|
|
542
595
|
operation: {
|
|
543
596
|
type: 'string',
|
|
544
597
|
enum: [
|
|
545
|
-
'create',
|
|
546
|
-
'
|
|
547
|
-
'
|
|
548
|
-
'close',
|
|
549
|
-
'fail',
|
|
550
|
-
'defer',
|
|
551
|
-
'progress',
|
|
552
|
-
'show',
|
|
553
|
-
'list',
|
|
554
|
-
'blocked',
|
|
555
|
-
'decompose',
|
|
556
|
-
'dep_add',
|
|
557
|
-
'dep_tree',
|
|
558
|
-
'prime',
|
|
559
|
-
'stats',
|
|
598
|
+
'create', 'ready', 'claim', 'close', 'fail', 'defer',
|
|
599
|
+
'progress', 'show', 'list', 'stats',
|
|
600
|
+
'blocked', 'decompose', 'dep_add', 'dep_tree',
|
|
560
601
|
],
|
|
561
|
-
description: '
|
|
602
|
+
description: 'Operation type',
|
|
562
603
|
},
|
|
563
|
-
title: { type: 'string', description: '
|
|
564
|
-
description: { type: 'string', description: '
|
|
565
|
-
design: { type: 'string', description: '
|
|
566
|
-
acceptance: { type: 'string', description: '
|
|
567
|
-
priority: { type: 'number', description: '
|
|
604
|
+
title: { type: 'string', description: 'Task title (create)' },
|
|
605
|
+
description: { type: 'string', description: 'Task description (create/progress)' },
|
|
606
|
+
design: { type: 'string', description: 'Design notes (create)' },
|
|
607
|
+
acceptance: { type: 'string', description: 'Acceptance criteria (create)' },
|
|
608
|
+
priority: { type: 'number', description: 'Priority 0-4, 0=highest (create)' },
|
|
568
609
|
taskType: {
|
|
569
610
|
type: 'string',
|
|
570
611
|
enum: ['epic', 'task', 'bug', 'chore'],
|
|
571
|
-
description: '
|
|
612
|
+
description: 'Task type (create)',
|
|
572
613
|
},
|
|
573
|
-
parentId: { type: 'string', description: '
|
|
574
|
-
id: { type: 'string', description: '
|
|
575
|
-
reason: { type: 'string', description: '
|
|
576
|
-
dependsOn: { type: 'string', description: '
|
|
614
|
+
parentId: { type: 'string', description: 'Parent task ID (create subtask)' },
|
|
615
|
+
id: { type: 'string', description: 'Task ID (claim/close/fail/defer/show/dep_add/dep_tree/progress)' },
|
|
616
|
+
reason: { type: 'string', description: 'Reason (close/fail/defer)' },
|
|
617
|
+
dependsOn: { type: 'string', description: 'Dependency target task ID (dep_add)' },
|
|
577
618
|
depType: {
|
|
578
619
|
type: 'string',
|
|
579
|
-
enum: ['blocks', 'parent-child', 'waits-for', 'discovered-from', 'related', 'knowledge-ref'],
|
|
620
|
+
enum: ['blocks', 'parent-child', 'waits-for', 'discovered-from', 'related', 'knowledge-ref', 'supersedes'],
|
|
580
621
|
default: 'blocks',
|
|
581
|
-
description: '
|
|
622
|
+
description: 'Dependency type (dep_add)',
|
|
582
623
|
},
|
|
583
|
-
limit: { type: 'number', default: 10, description: '
|
|
624
|
+
limit: { type: 'number', default: 10, description: 'Result limit (ready/list)' },
|
|
584
625
|
status: {
|
|
585
626
|
type: 'string',
|
|
586
|
-
enum: ['open', 'in_progress', 'deferred', 'closed'],
|
|
587
|
-
description: '
|
|
627
|
+
enum: ['open', 'in_progress', 'deferred', 'closed', 'pinned'],
|
|
628
|
+
description: 'Status filter (list)',
|
|
588
629
|
},
|
|
589
630
|
withKnowledge: {
|
|
590
631
|
type: 'boolean',
|
|
591
632
|
default: true,
|
|
592
|
-
description: '
|
|
633
|
+
description: 'Attach knowledge context (ready)',
|
|
593
634
|
},
|
|
594
635
|
subtasks: {
|
|
595
636
|
type: 'array',
|
|
@@ -600,11 +641,11 @@ export const TOOLS = [
|
|
|
600
641
|
description: { type: 'string' },
|
|
601
642
|
priority: { type: 'number' },
|
|
602
643
|
taskType: { type: 'string' },
|
|
603
|
-
blockedByIndex: { type: 'number', description: '
|
|
644
|
+
blockedByIndex: { type: 'number', description: 'Index in subtasks array that blocks this subtask' },
|
|
604
645
|
},
|
|
605
646
|
required: ['title'],
|
|
606
647
|
},
|
|
607
|
-
description: '
|
|
648
|
+
description: 'Subtasks list (decompose)',
|
|
608
649
|
},
|
|
609
650
|
},
|
|
610
651
|
required: ['operation'],
|