autosnippet 3.2.2 → 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.
Files changed (32) hide show
  1. package/README.md +23 -0
  2. package/config/default.json +7 -0
  3. package/dashboard/dist/assets/{icons-18VxiaCT.js → icons-pSac4wYO.js} +101 -96
  4. package/dashboard/dist/assets/{index-CRH5Umim.js → index-6itPuGFl.js} +45 -45
  5. package/dashboard/dist/assets/index-DNOHYBhy.css +1 -0
  6. package/dashboard/dist/index.html +3 -3
  7. package/lib/cli/SetupService.js +93 -25
  8. package/lib/domain/knowledge/KnowledgeEntry.js +11 -0
  9. package/lib/domain/task/Task.js +32 -2
  10. package/lib/domain/task/TaskDependency.js +1 -0
  11. package/lib/external/mcp/McpServer.js +180 -6
  12. package/lib/external/mcp/handlers/bootstrap/shared/bootstrap-phases.js +2 -1
  13. package/lib/external/mcp/handlers/decide.js +109 -0
  14. package/lib/external/mcp/handlers/ready.js +42 -0
  15. package/lib/external/mcp/handlers/system.js +12 -0
  16. package/lib/external/mcp/handlers/task.js +7 -19
  17. package/lib/external/mcp/tools.js +83 -42
  18. package/lib/http/routes/knowledge.js +10 -10
  19. package/lib/http/routes/task.js +81 -1
  20. package/lib/http/utils/routeHelpers.js +30 -0
  21. package/lib/repository/task/TaskRepository.impl.js +3 -1
  22. package/lib/service/cursor/AgentInstructionsGenerator.js +6 -4
  23. package/lib/service/knowledge/KnowledgeService.js +12 -1
  24. package/lib/service/task/TaskGraphService.js +243 -3
  25. package/package.json +1 -1
  26. package/skills/autosnippet-intent/SKILL.md +3 -1
  27. package/skills/autosnippet-recipes/SKILL.md +3 -1
  28. package/templates/copilot-instructions.md +47 -14
  29. package/templates/cursor-rules/autosnippet-conventions.mdc +11 -0
  30. package/templates/cursor-rules/autosnippet-workflow.mdc +16 -7
  31. package/templates/guard-ci.yml +21 -0
  32. 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 → 20 工具(16 agent + 4 admin)
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 { dimensionComplete } from './handlers/dimension-complete-external.js';import { taskHandler } from './handlers/task.js';import { wikiFinalize, wikiPlan } from './handlers/wiki-external.js';
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 { container: this.container, logger: this.logger, startedAt: this._startedAt };
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 { /* non-blocking */ }
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 层 (16) ──
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 { /* non-blocking */ }
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
- const { allFiles, allTargets, discoverer, langStats } = phase1;
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 — TaskGraph 任务管理
2
+ * MCP Handler — autosnippet_task (Task CRUD)
3
3
  *
4
- * 操作路由:
5
- * create / decompose / claim / close / fail / defer / progress
6
- * ready / prime / show / list / blocked / dep_add / dep_tree / stats
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 整合版 (16 agent + 4 admin = 20 工具)
2
+ * MCP 工具定义 — V3 整合版 (18 agent + 4 admin = 22 工具)
3
3
  *
4
- * 从 39 → 20 工具(参数路由合并同类工具 + 外部 Agent 冷启动新架构 + TaskGraph)。
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/prime/show/list/blocked/dep_tree/stats 只读
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 核心工具集 (16 个)
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. TaskGraph 任务管理
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
- '用于 Agent 自主拆解和执行复杂多步骤工作。' +
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
- 'ready',
547
- 'claim',
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: '任务标题(create' },
564
- description: { type: 'string', description: '任务描述(create/progress' },
565
- design: { type: 'string', description: '设计说明(create' },
566
- acceptance: { type: 'string', description: '验收标准(create' },
567
- priority: { type: 'number', description: '优先级 0-4, 0=最高(create' },
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: '任务类型(create',
612
+ description: 'Task type (create)',
572
613
  },
573
- parentId: { type: 'string', description: '父任务 IDcreate 子任务)' },
574
- id: { type: 'string', description: '任务 IDclaim/close/fail/defer/show/dep_add/dep_tree/progress' },
575
- reason: { type: 'string', description: '原因(close/fail/defer' },
576
- dependsOn: { type: 'string', description: '依赖的任务 IDdep_add' },
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: '依赖类型(dep_add',
622
+ description: 'Dependency type (dep_add)',
582
623
  },
583
- limit: { type: 'number', default: 10, description: '结果数量限制(ready/list' },
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: '状态过滤(list',
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: '是否附加知识上下文(ready',
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: ' subtasks 数组中哪个 index 的子任务阻塞' },
644
+ blockedByIndex: { type: 'number', description: 'Index in subtasks array that blocks this subtask' },
604
645
  },
605
646
  required: ['title'],
606
647
  },
607
- description: '子任务列表(decompose',
648
+ description: 'Subtasks list (decompose)',
608
649
  },
609
650
  },
610
651
  required: ['operation'],