autosnippet 3.2.4 → 3.2.7

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 (67) hide show
  1. package/README.md +18 -5
  2. package/bin/cli.js +164 -145
  3. package/config/constitution.yaml +2 -0
  4. package/dashboard/dist/assets/{index-DNOHYBhy.css → index-BaGY7kJI.css} +1 -1
  5. package/dashboard/dist/assets/{index-6itPuGFl.js → index-DfHY_3ln.js} +25 -25
  6. package/dashboard/dist/index.html +2 -2
  7. package/lib/cli/CliLogger.js +78 -0
  8. package/lib/cli/SetupService.js +9 -718
  9. package/lib/cli/UpgradeService.js +23 -398
  10. package/lib/cli/deploy/FileDeployer.js +562 -0
  11. package/lib/cli/deploy/FileManifest.js +272 -0
  12. package/lib/external/mcp/McpServer.js +22 -26
  13. package/lib/external/mcp/autoApproveInjector.js +1 -0
  14. package/lib/external/mcp/handlers/bootstrap/BootstrapSession.js +5 -5
  15. package/lib/external/mcp/handlers/bootstrap/pipeline/EpisodicMemory.js +25 -3
  16. package/lib/external/mcp/handlers/bootstrap/pipeline/IncrementalBootstrap.js +6 -6
  17. package/lib/external/mcp/handlers/bootstrap/pipeline/ToolResultCache.js +4 -0
  18. package/lib/external/mcp/handlers/bootstrap/pipeline/dimension-configs.js +5 -5
  19. package/lib/external/mcp/handlers/bootstrap/pipeline/orchestrator.js +89 -44
  20. package/lib/external/mcp/handlers/consolidated.js +8 -9
  21. package/lib/external/mcp/handlers/dimension-complete-external.js +4 -4
  22. package/lib/external/mcp/handlers/guard.js +283 -5
  23. package/lib/external/mcp/handlers/task.js +361 -15
  24. package/lib/external/mcp/tools.js +32 -81
  25. package/lib/http/HttpServer.js +4 -0
  26. package/lib/http/routes/remote.js +1138 -0
  27. package/lib/http/routes/task.js +56 -0
  28. package/lib/infrastructure/database/migrations/003_add_remote_commands.js +27 -0
  29. package/lib/service/chat/AnalystAgent.js +12 -12
  30. package/lib/service/chat/ChatAgent.js +227 -545
  31. package/lib/service/chat/ChatAgentPrompts.js +9 -11
  32. package/lib/service/chat/ContextWindow.js +2 -296
  33. package/lib/service/chat/EpisodicConsolidator.js +15 -15
  34. package/lib/service/chat/ExplorationTracker.js +1262 -0
  35. package/lib/service/chat/HandoffProtocol.js +8 -9
  36. package/lib/service/chat/Memory.js +4 -0
  37. package/lib/service/chat/ProducerAgent.js +9 -6
  38. package/lib/service/chat/ProjectSemanticMemory.js +4 -0
  39. package/lib/service/chat/ReasoningTrace.js +182 -0
  40. package/lib/service/chat/WorkingMemory.js +4 -0
  41. package/lib/service/chat/memory/ActiveContext.js +910 -0
  42. package/lib/service/chat/memory/MemoryCoordinator.js +662 -0
  43. package/lib/service/chat/memory/PersistentMemory.js +450 -0
  44. package/lib/service/chat/memory/SessionStore.js +896 -0
  45. package/lib/service/chat/memory/index.js +13 -0
  46. package/lib/service/chat/tools/ast-graph.js +17 -16
  47. package/lib/service/cursor/AgentInstructionsGenerator.js +76 -47
  48. package/lib/service/cursor/FileProtection.js +4 -1
  49. package/lib/service/guard/GuardCheckEngine.js +10 -3
  50. package/lib/service/task/TaskGraphService.js +3 -3
  51. package/lib/shared/LanguageService.js +2 -1
  52. package/package.json +12 -1
  53. package/skills/autosnippet-intent/SKILL.md +1 -3
  54. package/skills/autosnippet-recipes/SKILL.md +1 -3
  55. package/templates/claude-code/commands/prime.md +19 -0
  56. package/templates/claude-code/hooks/autosnippet-session.sh +63 -0
  57. package/templates/claude-code/settings.json +21 -0
  58. package/templates/copilot-instructions.md +64 -177
  59. package/templates/cursor-hooks/commands/prime.md +12 -0
  60. package/templates/cursor-hooks/hooks/session-start.sh +10 -0
  61. package/templates/cursor-hooks/hooks.json +11 -0
  62. package/templates/cursor-rules/autosnippet-conventions.mdc +52 -3
  63. package/templates/cursor-rules/autosnippet-workflow.mdc +51 -27
  64. package/lib/external/mcp/handlers/decide.js +0 -109
  65. package/lib/external/mcp/handlers/ready.js +0 -42
  66. package/lib/service/chat/ReasoningLayer.js +0 -888
  67. package/templates/claude-hooks.yaml +0 -19
@@ -1,14 +1,15 @@
1
1
  /**
2
- * MCP Handler — autosnippet_task (Task CRUD)
2
+ * MCP Handler — autosnippet_task (Unified Task & Decision Management)
3
3
  *
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
4
+ * Operations:
5
+ * Session: prime (session entry loads decisions + ready tasks + stats)
6
+ * Tasks: create / ready / claim / close / fail / defer / progress
7
+ * show / list / stats / blocked / decompose / dep_add / dep_tree
8
+ * Decisions: record_decision / revise_decision / unpin_decision / list_decisions
9
9
  */
10
10
 
11
11
  import { envelope } from '../envelope.js';
12
+ // guard is independent — no guardState dependency in task lifecycle
12
13
 
13
14
  /**
14
15
  * 统一入口
@@ -18,23 +19,35 @@ import { envelope } from '../envelope.js';
18
19
  export async function taskHandler(ctx, args) {
19
20
  const taskService = ctx.container.get('taskGraphService');
20
21
 
22
+ let result;
21
23
  switch (args.operation) {
24
+ // ── Session ──
25
+ case 'prime':
26
+ return _prime(taskService, args);
27
+ // ── Task CRUD ──
22
28
  case 'create':
23
- return _create(taskService, args);
29
+ result = await _create(taskService, args);
30
+ break;
24
31
  case 'ready':
25
32
  return _ready(taskService, args);
26
33
  case 'claim':
27
- return _claim(taskService, args);
34
+ result = await _claim(taskService, args);
35
+ break;
28
36
  case 'close':
29
- return _close(taskService, args);
37
+ result = await _close(ctx, taskService, args);
38
+ break;
30
39
  case 'fail':
31
- return _fail(taskService, args);
40
+ result = await _fail(taskService, args);
41
+ break;
32
42
  case 'defer':
33
- return _defer(taskService, args);
43
+ result = await _defer(taskService, args);
44
+ break;
34
45
  case 'progress':
35
- return _progress(taskService, args);
46
+ result = await _progress(taskService, args);
47
+ break;
36
48
  case 'decompose':
37
- return _decompose(taskService, args);
49
+ result = await _decompose(taskService, args);
50
+ break;
38
51
  case 'show':
39
52
  return _show(taskService, args);
40
53
  case 'list':
@@ -47,13 +60,32 @@ export async function taskHandler(ctx, args) {
47
60
  return _depTree(taskService, args);
48
61
  case 'stats':
49
62
  return _stats(taskService);
63
+ // ── Decisions ──
64
+ case 'record_decision':
65
+ result = await _recordDecision(taskService, args);
66
+ break;
67
+ case 'revise_decision':
68
+ result = await _reviseDecision(taskService, args);
69
+ break;
70
+ case 'unpin_decision':
71
+ result = await _unpinDecision(taskService, args);
72
+ break;
73
+ case 'list_decisions':
74
+ return _listDecisions(taskService);
50
75
  default:
51
76
  return envelope({
52
77
  success: false,
53
- message: `Unknown operation: ${args.operation}. Use autosnippet_ready for session context, autosnippet_decide for decisions.`,
78
+ message: `Unknown operation: ${args.operation}. Valid: prime, ready, create, claim, close, fail, defer, progress, decompose, dep_add, dep_tree, stats, list, record_decision, revise_decision, unpin_decision, list_decisions.`,
54
79
  meta: { tool: 'autosnippet_task' },
55
80
  });
56
81
  }
82
+
83
+ // ── 飞书任务进度通知(异步非阻塞)──
84
+ _notifyTaskProgress(args.operation, args, result).catch((err) => {
85
+ process.stderr.write(`[MCP/Task] Notify error: ${err?.message}\n`);
86
+ });
87
+
88
+ return result;
57
89
  }
58
90
 
59
91
  // ── create ──
@@ -113,10 +145,11 @@ async function _claim(svc, args) {
113
145
 
114
146
  // ── close ──
115
147
 
116
- async function _close(svc, args) {
148
+ async function _close(ctx, svc, args) {
117
149
  if (!args.id) {
118
150
  return envelope({ success: false, message: 'id is required', meta: { tool: 'autosnippet_task' } });
119
151
  }
152
+
120
153
  const { task, newlyReady } = await svc.close(args.id, args.reason || 'Completed');
121
154
  return envelope({
122
155
  success: true,
@@ -281,3 +314,316 @@ async function _stats(svc) {
281
314
  meta: { tool: 'autosnippet_task' },
282
315
  });
283
316
  }
317
+
318
+ // ═══ Session (prime) ═══════════════════════════════════
319
+
320
+ async function _prime(svc, args) {
321
+ const result = await svc.prime({
322
+ limit: args.limit || 10,
323
+ withKnowledge: args.withKnowledge !== false,
324
+ });
325
+
326
+ const decisionCount = (result.decisions || []).length;
327
+ const staleCount = (result.staleDecisions || []).length;
328
+ const decisionTitles = (result.decisions || []).map((d) => d.title).join('; ');
329
+ const statsLine = `${result.inProgress.length} in-progress, ${result.ready.length} ready, ${result.stats.total} total`;
330
+
331
+ // ── Behavioral Rules Reminder (survives compaction) ──
332
+ result._taskRules = {
333
+ reminder: [
334
+ '📋 TASK RULES (MANDATORY):',
335
+ '🔑 YOU are the task operator — user speaks naturally, you translate to task operations. NEVER tell user to run task commands.',
336
+ '• MUST prime on EVERY message BEFORE anything else',
337
+ '• MUST create task for non-trivial work (≥2 files OR ≥10 lines)',
338
+ '• MUST claim before coding, close when done with meaningful reason',
339
+ '• MUST handle unfinished tasks before starting new work (ask user: Continue/Defer/Abandon)',
340
+ '• NEVER skip prime, NEVER start new work with open in_progress tasks',
341
+ '• NEVER leave tasks in in_progress when session ends — close or defer ALL',
342
+ '• When in doubt → create a task. When idle → ready()',
343
+ '• Session end → close all tasks, defer incomplete, verify zero in_progress',
344
+ ].join('\n'),
345
+ translationHint: [
346
+ 'User Says → You Run:',
347
+ '"fix bug"/"implement" → create→claim→code→close',
348
+ '"continue" → resume in-progress→close',
349
+ '"pause" → defer | "abandon" → fail | "break down" → decompose',
350
+ '"what\'s next" → ready() | "agreed" → record_decision',
351
+ 'Quick question → No task. Just answer.',
352
+ ].join('\n'),
353
+ };
354
+
355
+ let message;
356
+ if (decisionCount > 0) {
357
+ const stalePart = staleCount > 0 ? ` ${staleCount} stale.` : '';
358
+ message = `⚠️ ${decisionCount} ACTIVE DECISION(S): [${decisionTitles}].${stalePart} ${statsLine}.`;
359
+ } else {
360
+ message = `${statsLine}.`;
361
+ }
362
+
363
+ // ── Resume Prompt: 有 inProgress 任务时,提示 Agent 让用户选择 ──
364
+ if (result.inProgress.length > 0) {
365
+ const taskList = result.inProgress.map((t) => {
366
+ const age = t.updatedAt
367
+ ? `${Math.floor((Date.now() / 1000 - t.updatedAt) / 86400)}d ago`
368
+ : '';
369
+ return `• **${t.id}** — ${t.title}${age ? ` (${age})` : ''}`;
370
+ }).join('\n');
371
+
372
+ result._resumePrompt = {
373
+ instruction: [
374
+ 'There are unfinished tasks. You MUST present these options to the user BEFORE doing anything else:',
375
+ '',
376
+ '**Unfinished tasks:**',
377
+ taskList,
378
+ '',
379
+ 'Ask the user to choose:',
380
+ '1. **Continue** — resume the unfinished task(s)',
381
+ '2. **Defer** — pause it and work on something else',
382
+ '3. **Abandon** — close/fail it and start fresh',
383
+ '',
384
+ 'Wait for the user\'s answer. Do NOT auto-resume.',
385
+ ].join('\n'),
386
+ taskIds: result.inProgress.map((t) => t.id),
387
+ };
388
+
389
+ message += ` ⏸️ ${result.inProgress.length} unfinished task(s) — ask user before resuming.`;
390
+ }
391
+
392
+ return envelope({
393
+ success: true,
394
+ data: result,
395
+ message,
396
+ meta: { tool: 'autosnippet_task' },
397
+ });
398
+ }
399
+
400
+ // ═══ Decisions ═══════════════════════════════════════
401
+
402
+ async function _recordDecision(svc, args) {
403
+ if (!args.title) {
404
+ return envelope({ success: false, message: 'title is required', meta: { tool: 'autosnippet_task' } });
405
+ }
406
+ if (!args.description) {
407
+ return envelope({ success: false, message: 'description is required', meta: { tool: 'autosnippet_task' } });
408
+ }
409
+ const { task, isDuplicate } = await svc.recordDecision({
410
+ title: args.title,
411
+ description: args.description,
412
+ rationale: args.rationale || '',
413
+ tags: args.tags || [],
414
+ relatedTaskId: args.relatedTaskId || null,
415
+ });
416
+ return envelope({
417
+ success: true,
418
+ data: task.toJSON(),
419
+ message: isDuplicate
420
+ ? `⚠ Decision already recorded: ${task.id}`
421
+ : `✅ Decision pinned: ${task.id} — "${args.title}"`,
422
+ meta: { tool: 'autosnippet_task' },
423
+ });
424
+ }
425
+
426
+ async function _reviseDecision(svc, args) {
427
+ if (!args.id) {
428
+ return envelope({ success: false, message: 'id of old decision is required', meta: { tool: 'autosnippet_task' } });
429
+ }
430
+ if (!args.title) {
431
+ return envelope({ success: false, message: 'title of new decision is required', meta: { tool: 'autosnippet_task' } });
432
+ }
433
+ if (!args.description) {
434
+ return envelope({ success: false, message: 'description of new decision is required', meta: { tool: 'autosnippet_task' } });
435
+ }
436
+ const result = await svc.reviseDecision({
437
+ oldDecisionId: args.id,
438
+ title: args.title,
439
+ description: args.description,
440
+ rationale: args.rationale || '',
441
+ reason: args.reason || '',
442
+ });
443
+ return envelope({
444
+ success: true,
445
+ data: {
446
+ newDecision: result.newDecision.toJSON(),
447
+ superseded: result.oldDecisionId,
448
+ },
449
+ message: `✅ Decision revised: ${result.oldDecisionId} → ${result.newDecision.id}`,
450
+ meta: { tool: 'autosnippet_task' },
451
+ });
452
+ }
453
+
454
+ async function _unpinDecision(svc, args) {
455
+ if (!args.id) {
456
+ return envelope({ success: false, message: 'id is required', meta: { tool: 'autosnippet_task' } });
457
+ }
458
+ const task = await svc.unpinDecision(args.id, args.reason || '');
459
+ return envelope({
460
+ success: true,
461
+ data: task.toJSON(),
462
+ message: `Decision ${args.id} unpinned and closed`,
463
+ meta: { tool: 'autosnippet_task' },
464
+ });
465
+ }
466
+
467
+ async function _listDecisions(svc) {
468
+ const decisions = await svc.list({ status: 'pinned', taskType: 'decision' }, { limit: 50 });
469
+ return envelope({
470
+ success: true,
471
+ data: decisions.map((d) => d.toJSON()),
472
+ message: `${decisions.length} active decision(s)`,
473
+ meta: { tool: 'autosnippet_task' },
474
+ });
475
+ }
476
+
477
+ // ═══ 飞书任务进度通知(通过 API Server 中转)═══════════════
478
+ //
479
+ // MCP Server 与 API Server 是独立进程。
480
+ // 飞书 WSClient 连接在 API Server 中,因此通知需 HTTP 中转。
481
+ // ═══════════════════════════════════════════════════════════
482
+
483
+ const PRIORITY_LABELS = ['P0 紧急', 'P1 高', 'P2 中', 'P3 低', 'P4 微'];
484
+
485
+ /**
486
+ * 通过 API Server 的 /api/v1/remote/notify 发送飞书通知
487
+ * @param {string} text
488
+ * @returns {Promise<boolean>}
489
+ */
490
+ async function _sendLarkViaApi(text) {
491
+ try {
492
+ const port = process.env.PORT || 3000;
493
+ const resp = await fetch(`http://localhost:${port}/api/v1/remote/notify`, {
494
+ method: 'POST',
495
+ headers: { 'Content-Type': 'application/json' },
496
+ body: JSON.stringify({ text }),
497
+ signal: AbortSignal.timeout(5000),
498
+ });
499
+ if (!resp.ok) {
500
+ process.stderr.write(`[MCP/Task] Lark notify HTTP ${resp.status}\n`);
501
+ return false;
502
+ }
503
+ const body = await resp.json();
504
+ return body.success === true;
505
+ } catch (err) {
506
+ process.stderr.write(`[MCP/Task] Lark notify failed: ${err?.message}\n`);
507
+ return false;
508
+ }
509
+ }
510
+
511
+ /**
512
+ * 通过 API Server 截取 IDE 窗口截图并发送到飞书
513
+ * @param {string} [caption] — 可选文字说明
514
+ * @returns {Promise<boolean>}
515
+ */
516
+ async function _sendScreenshotViaApi(caption = '') {
517
+ try {
518
+ const port = process.env.PORT || 3000;
519
+ const resp = await fetch(`http://localhost:${port}/api/v1/remote/screenshot`, {
520
+ method: 'POST',
521
+ headers: { 'Content-Type': 'application/json' },
522
+ body: JSON.stringify({ caption }),
523
+ signal: AbortSignal.timeout(15000),
524
+ });
525
+ if (!resp.ok) {
526
+ process.stderr.write(`[MCP/Task] Screenshot HTTP ${resp.status}\n`);
527
+ return false;
528
+ }
529
+ const body = await resp.json();
530
+ return body.success === true;
531
+ } catch (err) {
532
+ process.stderr.write(`[MCP/Task] Screenshot failed: ${err?.message}\n`);
533
+ return false;
534
+ }
535
+ }
536
+
537
+ /**
538
+ * 根据任务操作向飞书发送进度通知(异步非阻塞)
539
+ * result 是 envelope() 返回的 { success, data, message, meta }
540
+ */
541
+ async function _notifyTaskProgress(operation, args, result) {
542
+ if (!result || result.success === false) return;
543
+
544
+ const data = result.data;
545
+ let text = '';
546
+
547
+ switch (operation) {
548
+ case 'create': {
549
+ const title = data?.title || args.title || '';
550
+ const id = data?.id || '';
551
+ const type = data?.taskType || args.taskType || 'task';
552
+ const pri = PRIORITY_LABELS[data?.priority ?? args.priority ?? 2] || 'P2';
553
+ const dup = result.message?.includes('Duplicate') || result.message?.startsWith('⚠') ? ' (重复)' : '';
554
+ text = `📋 新任务${dup}: ${id}\n${title}\n类型: ${type} | ${pri}`;
555
+ break;
556
+ }
557
+ case 'claim': {
558
+ const id = data?.id || args.id;
559
+ const title = data?.title || '';
560
+ text = `🔨 开始执行: ${id}\n${title}`;
561
+ break;
562
+ }
563
+ case 'close': {
564
+ const closed = data?.closed || data;
565
+ const title = closed?.title || '';
566
+ const id = closed?.id || args.id;
567
+ const reason = closed?.closeReason || args.reason || '';
568
+ const readyCount = data?.newlyReady?.length || 0;
569
+ const readyInfo = readyCount > 0 ? `\n→ ${readyCount} 个任务新就绪` : '';
570
+ text = `✅ 完成: ${id}\n${title}\n原因: ${reason}${readyInfo}`;
571
+ break;
572
+ }
573
+ case 'fail': {
574
+ const title = data?.title || '';
575
+ const id = data?.id || args.id;
576
+ const reason = data?.lastFailReason || args.reason || '未知';
577
+ const count = data?.failCount || 0;
578
+ text = `❌ 失败: ${id}\n${title}\n原因: ${reason}${count > 1 ? ` (第${count}次)` : ''}`;
579
+ break;
580
+ }
581
+ case 'defer': {
582
+ const id = data?.id || args.id;
583
+ const title = data?.title || '';
584
+ text = `⏸️ 暂缓: ${id} — ${title}`;
585
+ break;
586
+ }
587
+ case 'progress': {
588
+ const id = data?.id || args.id;
589
+ const note = args.reason || args.description || '';
590
+ text = note
591
+ ? `📝 进度: ${id}\n${note.slice(0, 200)}`
592
+ : `📝 进度: ${id}`;
593
+ break;
594
+ }
595
+ case 'decompose': {
596
+ const epicId = args.id;
597
+ const count = Array.isArray(data) ? data.length : 0;
598
+ const subTitles = Array.isArray(data)
599
+ ? data.slice(0, 5).map((t, i) => ` ${i + 1}. ${t.title || t.id}`).join('\n')
600
+ : '';
601
+ text = `📂 拆解: ${epicId} → ${count} 个子任务${subTitles ? '\n' + subTitles : ''}`;
602
+ break;
603
+ }
604
+ case 'record_decision': {
605
+ const title = data?.title || args.title || '';
606
+ text = `📌 决策: ${title}`;
607
+ break;
608
+ }
609
+ case 'revise_decision': {
610
+ const oldId = data?.superseded || args.id;
611
+ const newTitle = data?.newDecision?.title || args.title;
612
+ text = `🔄 决策更新: ${oldId} → ${newTitle}`;
613
+ break;
614
+ }
615
+ case 'unpin_decision': {
616
+ const id = data?.id || args.id;
617
+ text = `🔓 决策取消: ${id}`;
618
+ break;
619
+ }
620
+ default:
621
+ return;
622
+ }
623
+
624
+ if (text) {
625
+ await _sendLarkViaApi(text);
626
+ // 发送文字通知后,附带 IDE 窗口截图
627
+ await _sendScreenshotViaApi();
628
+ }
629
+ }
@@ -1,8 +1,8 @@
1
1
  /**
2
2
  * MCP 工具定义 — V3 整合版 (18 agent + 4 admin = 22 工具)
3
3
  *
4
- * 从 39 → 22 工具(参数路由合并同类工具 + 外部 Agent 冷启动新架构 + TaskGraph 三工具拆分)。
5
- * TaskGraph 拆分: autosnippet_ready + autosnippet_decide + autosnippet_task
4
+ * 从 39 → 22 工具(参数路由合并同类工具 + 外部 Agent 冷启动新架构 + TaskGraph 统一入口)。
5
+ * TaskGraph: autosnippet_task (统一入口,含 prime/decision/task CRUD)
6
6
  * 每个工具声明增加 tier 字段(agent / admin)。
7
7
  * tools.js 只包含 JSON Schema 声明 + Gateway 映射,不含业务逻辑。
8
8
  *
@@ -45,16 +45,7 @@ export const TOOL_GATEWAY_MAP = {
45
45
  autosnippet_submit_knowledge: { action: 'knowledge:create', resource: 'knowledge' },
46
46
  autosnippet_submit_knowledge_batch: { action: 'knowledge:create', resource: 'knowledge' },
47
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
- },
57
- // task 写操作(create/claim/close/fail/defer/decompose/dep_add)
48
+ // task 写操作(create/claim/close/fail/defer/decompose/dep_add + decision 写操作)
58
49
  autosnippet_task: {
59
50
  resolver: (args) =>
60
51
  ({
@@ -66,7 +57,10 @@ export const TOOL_GATEWAY_MAP = {
66
57
  progress: { action: 'task:update', resource: 'tasks' },
67
58
  decompose: { action: 'task:create', resource: 'tasks' },
68
59
  dep_add: { action: 'task:update', resource: 'tasks' },
69
- })[args?.operation] || null, // ready/show/list/blocked/dep_tree/stats 只读
60
+ record_decision: { action: 'task:create', resource: 'tasks' },
61
+ revise_decision: { action: 'task:update', resource: 'tasks' },
62
+ unpin_decision: { action: 'task:update', resource: 'tasks' },
63
+ })[args?.operation] || null, // prime/ready/show/list/blocked/dep_tree/stats/list_decisions 只读
70
64
  },
71
65
  // admin 工具
72
66
  autosnippet_enrich_candidates: { action: 'knowledge:update', resource: 'knowledge' },
@@ -216,32 +210,27 @@ export const TOOLS = [
216
210
  },
217
211
  },
218
212
 
219
- // 6. Guard 检查(2 → 1)
213
+ // 6. Guard 检查(统一入口)
220
214
  {
221
215
  name: 'autosnippet_guard',
222
216
  tier: 'agent',
223
- description: '代码规范检查。传 code=单文件检查,传 files[]=多文件批量审计(自动路由)。',
217
+ description:
218
+ '代码规范检查 & 质量门禁。\n' +
219
+ '• 无参数 → 自动从 git diff 检测增量文件并检查(编码后推荐用法)\n' +
220
+ '• files: ["path/to/file.m", ...] → 检查指定文件\n' +
221
+ '• code: "..." → 单文件内联检查\n' +
222
+ '每个 violation 内联 recipe 修复指南(doClause + coreCode),直接按指示修复后再次调用。',
224
223
  inputSchema: {
225
224
  type: 'object',
226
225
  properties: {
227
- code: { type: 'string', description: '待检查代码(单文件模式,与 files 二选一)' },
228
- language: { type: 'string', description: '编程语言' },
229
- filePath: { type: 'string', description: '文件路径(单文件模式)' },
230
226
  files: {
231
227
  type: 'array',
232
- items: {
233
- type: 'object',
234
- properties: { path: { type: 'string' }, content: { type: 'string' } },
235
- required: ['path'],
236
- },
237
- description: '文件列表(批量模式,与 code 二选一)',
238
- },
239
- scope: {
240
- type: 'string',
241
- enum: ['file', 'target', 'project'],
242
- default: 'project',
243
- description: '审计范围(批量模式)',
228
+ items: { type: 'string' },
229
+ description: '待检查文件路径列表(string[])。省略则自动从 git diff 检测增量文件。',
244
230
  },
231
+ code: { type: 'string', description: '待检查代码(单文件模式,与 files 二选一)' },
232
+ language: { type: 'string', description: '编程语言(单文件模式)' },
233
+ filePath: { type: 'string', description: '文件路径提示(单文件模式,用于语言检测)' },
245
234
  },
246
235
  required: [],
247
236
  },
@@ -538,68 +527,26 @@ export const TOOLS = [
538
527
  inputSchema: { type: 'object', properties: {}, required: [] },
539
528
  },
540
529
 
541
- // 13. autosnippet_readyAgent 会话入口(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
530
+ // 13. autosnippet_task统一任务管理(含 prime/decision/CRUD
586
531
  {
587
532
  name: 'autosnippet_task',
588
533
  tier: 'agent',
589
534
  description:
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.',
535
+ 'Unified task & decision management. Includes session context (prime), task CRUD, and decision persistence.\n' +
536
+ 'Call prime FIRST at every conversation start to load decisions + tasks + knowledge context.',
592
537
  inputSchema: {
593
538
  type: 'object',
594
539
  properties: {
595
540
  operation: {
596
541
  type: 'string',
597
542
  enum: [
598
- 'create', 'ready', 'claim', 'close', 'fail', 'defer',
543
+ 'prime', 'ready',
544
+ 'create', 'claim', 'close', 'fail', 'defer',
599
545
  'progress', 'show', 'list', 'stats',
600
546
  'blocked', 'decompose', 'dep_add', 'dep_tree',
547
+ 'record_decision', 'revise_decision', 'unpin_decision', 'list_decisions',
601
548
  ],
602
- description: 'Operation type',
549
+ description: 'prime=session entry (CALL FIRST) | ready=ready tasks | record_decision/revise_decision/unpin_decision/list_decisions=decision management | create/claim/close/fail/defer/progress/decompose=task CRUD',
603
550
  },
604
551
  title: { type: 'string', description: 'Task title (create)' },
605
552
  description: { type: 'string', description: 'Task description (create/progress)' },
@@ -612,8 +559,12 @@ export const TOOLS = [
612
559
  description: 'Task type (create)',
613
560
  },
614
561
  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)' },
562
+ id: { type: 'string', description: 'Task ID (claim/close/fail/defer/show/dep_add/dep_tree/progress/revise_decision/unpin_decision)' },
563
+ reason: { type: 'string', description: 'Reason (close/fail/defer/unpin_decision)' },
564
+ rationale: { type: 'string', description: 'Why this decision (record_decision/revise_decision)' },
565
+ tags: { type: 'array', items: { type: 'string' }, description: 'Classification tags (record_decision)' },
566
+ relatedTaskId: { type: 'string', description: 'Related task ID (record_decision)' },
567
+
617
568
  dependsOn: { type: 'string', description: 'Dependency target task ID (dep_add)' },
618
569
  depType: {
619
570
  type: 'string',
@@ -38,6 +38,7 @@ import skillsRouter from './routes/skills.js';
38
38
  import snippetRouter from './routes/snippets.js';
39
39
  import spmRouter from './routes/spm.js';
40
40
  import taskRouter from './routes/task.js';
41
+ import remoteRouter from './routes/remote.js';
41
42
  import violationsRouter from './routes/violations.js';
42
43
  import wikiRouter from './routes/wiki.js';
43
44
 
@@ -302,6 +303,9 @@ export class HttpServer {
302
303
  // Wiki 路由
303
304
  this.app.use(`${apiPrefix}/wiki`, wikiRouter);
304
305
 
306
+ // Remote 路由(飞书 Bot → IDE 远程指令桥接)
307
+ this.app.use(`${apiPrefix}/remote`, remoteRouter);
308
+
305
309
  // 根路径 — 返回 API 元信息(避免外部探测产生无意义 404)
306
310
  this.app.all('/', (req, res) => {
307
311
  res.json({