autosnippet 3.2.3 → 3.2.6

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 (64) hide show
  1. package/README.md +2 -4
  2. package/bin/cli.js +164 -145
  3. package/config/constitution.yaml +3 -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 -719
  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 +183 -9
  24. package/lib/external/mcp/tools.js +32 -81
  25. package/lib/http/routes/task.js +55 -0
  26. package/lib/service/chat/AnalystAgent.js +12 -12
  27. package/lib/service/chat/ChatAgent.js +227 -545
  28. package/lib/service/chat/ChatAgentPrompts.js +9 -11
  29. package/lib/service/chat/ContextWindow.js +2 -296
  30. package/lib/service/chat/EpisodicConsolidator.js +15 -15
  31. package/lib/service/chat/ExplorationTracker.js +1262 -0
  32. package/lib/service/chat/HandoffProtocol.js +8 -9
  33. package/lib/service/chat/Memory.js +4 -0
  34. package/lib/service/chat/ProducerAgent.js +9 -6
  35. package/lib/service/chat/ProjectSemanticMemory.js +4 -0
  36. package/lib/service/chat/ReasoningTrace.js +182 -0
  37. package/lib/service/chat/WorkingMemory.js +4 -0
  38. package/lib/service/chat/memory/ActiveContext.js +910 -0
  39. package/lib/service/chat/memory/MemoryCoordinator.js +662 -0
  40. package/lib/service/chat/memory/PersistentMemory.js +450 -0
  41. package/lib/service/chat/memory/SessionStore.js +896 -0
  42. package/lib/service/chat/memory/index.js +13 -0
  43. package/lib/service/chat/tools/ast-graph.js +17 -16
  44. package/lib/service/cursor/AgentInstructionsGenerator.js +75 -40
  45. package/lib/service/cursor/FileProtection.js +4 -1
  46. package/lib/service/guard/GuardCheckEngine.js +10 -3
  47. package/lib/service/task/TaskGraphService.js +3 -3
  48. package/lib/shared/LanguageService.js +2 -1
  49. package/package.json +1 -1
  50. package/skills/autosnippet-intent/SKILL.md +1 -3
  51. package/skills/autosnippet-recipes/SKILL.md +1 -3
  52. package/templates/claude-code/commands/prime.md +19 -0
  53. package/templates/claude-code/hooks/autosnippet-session.sh +63 -0
  54. package/templates/claude-code/settings.json +21 -0
  55. package/templates/copilot-instructions.md +66 -177
  56. package/templates/cursor-hooks/commands/prime.md +12 -0
  57. package/templates/cursor-hooks/hooks/session-start.sh +10 -0
  58. package/templates/cursor-hooks/hooks.json +11 -0
  59. package/templates/cursor-rules/autosnippet-conventions.mdc +52 -3
  60. package/templates/cursor-rules/autosnippet-workflow.mdc +51 -27
  61. package/lib/external/mcp/handlers/decide.js +0 -109
  62. package/lib/external/mcp/handlers/ready.js +0 -42
  63. package/lib/service/chat/ReasoningLayer.js +0 -888
  64. 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
  * 统一入口
@@ -19,6 +20,10 @@ export async function taskHandler(ctx, args) {
19
20
  const taskService = ctx.container.get('taskGraphService');
20
21
 
21
22
  switch (args.operation) {
23
+ // ── Session ──
24
+ case 'prime':
25
+ return _prime(taskService, args);
26
+ // ── Task CRUD ──
22
27
  case 'create':
23
28
  return _create(taskService, args);
24
29
  case 'ready':
@@ -26,7 +31,7 @@ export async function taskHandler(ctx, args) {
26
31
  case 'claim':
27
32
  return _claim(taskService, args);
28
33
  case 'close':
29
- return _close(taskService, args);
34
+ return _close(ctx, taskService, args);
30
35
  case 'fail':
31
36
  return _fail(taskService, args);
32
37
  case 'defer':
@@ -47,10 +52,19 @@ export async function taskHandler(ctx, args) {
47
52
  return _depTree(taskService, args);
48
53
  case 'stats':
49
54
  return _stats(taskService);
55
+ // ── Decisions ──
56
+ case 'record_decision':
57
+ return _recordDecision(taskService, args);
58
+ case 'revise_decision':
59
+ return _reviseDecision(taskService, args);
60
+ case 'unpin_decision':
61
+ return _unpinDecision(taskService, args);
62
+ case 'list_decisions':
63
+ return _listDecisions(taskService);
50
64
  default:
51
65
  return envelope({
52
66
  success: false,
53
- message: `Unknown operation: ${args.operation}. Use autosnippet_ready for session context, autosnippet_decide for decisions.`,
67
+ 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
68
  meta: { tool: 'autosnippet_task' },
55
69
  });
56
70
  }
@@ -113,10 +127,11 @@ async function _claim(svc, args) {
113
127
 
114
128
  // ── close ──
115
129
 
116
- async function _close(svc, args) {
130
+ async function _close(ctx, svc, args) {
117
131
  if (!args.id) {
118
132
  return envelope({ success: false, message: 'id is required', meta: { tool: 'autosnippet_task' } });
119
133
  }
134
+
120
135
  const { task, newlyReady } = await svc.close(args.id, args.reason || 'Completed');
121
136
  return envelope({
122
137
  success: true,
@@ -281,3 +296,162 @@ async function _stats(svc) {
281
296
  meta: { tool: 'autosnippet_task' },
282
297
  });
283
298
  }
299
+
300
+ // ═══ Session (prime) ═══════════════════════════════════
301
+
302
+ async function _prime(svc, args) {
303
+ const result = await svc.prime({
304
+ limit: args.limit || 10,
305
+ withKnowledge: args.withKnowledge !== false,
306
+ });
307
+
308
+ const decisionCount = (result.decisions || []).length;
309
+ const staleCount = (result.staleDecisions || []).length;
310
+ const decisionTitles = (result.decisions || []).map((d) => d.title).join('; ');
311
+ const statsLine = `${result.inProgress.length} in-progress, ${result.ready.length} ready, ${result.stats.total} total`;
312
+
313
+ // ── Behavioral Rules Reminder (survives compaction) ──
314
+ result._taskRules = {
315
+ reminder: [
316
+ '📋 TASK RULES (MANDATORY):',
317
+ '🔑 YOU are the task operator — user speaks naturally, you translate to task operations. NEVER tell user to run task commands.',
318
+ '• MUST prime on EVERY message BEFORE anything else',
319
+ '• MUST create task for non-trivial work (≥2 files OR ≥10 lines)',
320
+ '• MUST claim before coding, close when done with meaningful reason',
321
+ '• MUST handle unfinished tasks before starting new work (ask user: Continue/Defer/Abandon)',
322
+ '• NEVER skip prime, NEVER start new work with open in_progress tasks',
323
+ '• NEVER leave tasks in in_progress when session ends — close or defer ALL',
324
+ '• When in doubt → create a task. When idle → ready()',
325
+ '• Session end → close all tasks, defer incomplete, verify zero in_progress',
326
+ ].join('\n'),
327
+ translationHint: [
328
+ 'User Says → You Run:',
329
+ '"fix bug"/"implement" → create→claim→code→close',
330
+ '"continue" → resume in-progress→close',
331
+ '"pause" → defer | "abandon" → fail | "break down" → decompose',
332
+ '"what\'s next" → ready() | "agreed" → record_decision',
333
+ 'Quick question → No task. Just answer.',
334
+ ].join('\n'),
335
+ };
336
+
337
+ let message;
338
+ if (decisionCount > 0) {
339
+ const stalePart = staleCount > 0 ? ` ${staleCount} stale.` : '';
340
+ message = `⚠️ ${decisionCount} ACTIVE DECISION(S): [${decisionTitles}].${stalePart} ${statsLine}.`;
341
+ } else {
342
+ message = `${statsLine}.`;
343
+ }
344
+
345
+ // ── Resume Prompt: 有 inProgress 任务时,提示 Agent 让用户选择 ──
346
+ if (result.inProgress.length > 0) {
347
+ const taskList = result.inProgress.map((t) => {
348
+ const age = t.updatedAt
349
+ ? `${Math.floor((Date.now() / 1000 - t.updatedAt) / 86400)}d ago`
350
+ : '';
351
+ return `• **${t.id}** — ${t.title}${age ? ` (${age})` : ''}`;
352
+ }).join('\n');
353
+
354
+ result._resumePrompt = {
355
+ instruction: [
356
+ 'There are unfinished tasks. You MUST present these options to the user BEFORE doing anything else:',
357
+ '',
358
+ '**Unfinished tasks:**',
359
+ taskList,
360
+ '',
361
+ 'Ask the user to choose:',
362
+ '1. **Continue** — resume the unfinished task(s)',
363
+ '2. **Defer** — pause it and work on something else',
364
+ '3. **Abandon** — close/fail it and start fresh',
365
+ '',
366
+ 'Wait for the user\'s answer. Do NOT auto-resume.',
367
+ ].join('\n'),
368
+ taskIds: result.inProgress.map((t) => t.id),
369
+ };
370
+
371
+ message += ` ⏸️ ${result.inProgress.length} unfinished task(s) — ask user before resuming.`;
372
+ }
373
+
374
+ return envelope({
375
+ success: true,
376
+ data: result,
377
+ message,
378
+ meta: { tool: 'autosnippet_task' },
379
+ });
380
+ }
381
+
382
+ // ═══ Decisions ═══════════════════════════════════════
383
+
384
+ async function _recordDecision(svc, args) {
385
+ if (!args.title) {
386
+ return envelope({ success: false, message: 'title is required', meta: { tool: 'autosnippet_task' } });
387
+ }
388
+ if (!args.description) {
389
+ return envelope({ success: false, message: 'description is required', meta: { tool: 'autosnippet_task' } });
390
+ }
391
+ const { task, isDuplicate } = await svc.recordDecision({
392
+ title: args.title,
393
+ description: args.description,
394
+ rationale: args.rationale || '',
395
+ tags: args.tags || [],
396
+ relatedTaskId: args.relatedTaskId || null,
397
+ });
398
+ return envelope({
399
+ success: true,
400
+ data: task.toJSON(),
401
+ message: isDuplicate
402
+ ? `⚠ Decision already recorded: ${task.id}`
403
+ : `✅ Decision pinned: ${task.id} — "${args.title}"`,
404
+ meta: { tool: 'autosnippet_task' },
405
+ });
406
+ }
407
+
408
+ async function _reviseDecision(svc, args) {
409
+ if (!args.id) {
410
+ return envelope({ success: false, message: 'id of old decision is required', meta: { tool: 'autosnippet_task' } });
411
+ }
412
+ if (!args.title) {
413
+ return envelope({ success: false, message: 'title of new decision is required', meta: { tool: 'autosnippet_task' } });
414
+ }
415
+ if (!args.description) {
416
+ return envelope({ success: false, message: 'description of new decision is required', meta: { tool: 'autosnippet_task' } });
417
+ }
418
+ const result = await svc.reviseDecision({
419
+ oldDecisionId: args.id,
420
+ title: args.title,
421
+ description: args.description,
422
+ rationale: args.rationale || '',
423
+ reason: args.reason || '',
424
+ });
425
+ return envelope({
426
+ success: true,
427
+ data: {
428
+ newDecision: result.newDecision.toJSON(),
429
+ superseded: result.oldDecisionId,
430
+ },
431
+ message: `✅ Decision revised: ${result.oldDecisionId} → ${result.newDecision.id}`,
432
+ meta: { tool: 'autosnippet_task' },
433
+ });
434
+ }
435
+
436
+ async function _unpinDecision(svc, args) {
437
+ if (!args.id) {
438
+ return envelope({ success: false, message: 'id is required', meta: { tool: 'autosnippet_task' } });
439
+ }
440
+ const task = await svc.unpinDecision(args.id, args.reason || '');
441
+ return envelope({
442
+ success: true,
443
+ data: task.toJSON(),
444
+ message: `Decision ${args.id} unpinned and closed`,
445
+ meta: { tool: 'autosnippet_task' },
446
+ });
447
+ }
448
+
449
+ async function _listDecisions(svc) {
450
+ const decisions = await svc.list({ status: 'pinned', taskType: 'decision' }, { limit: 50 });
451
+ return envelope({
452
+ success: true,
453
+ data: decisions.map((d) => d.toJSON()),
454
+ message: `${decisions.length} active decision(s)`,
455
+ meta: { tool: 'autosnippet_task' },
456
+ });
457
+ }
@@ -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',
@@ -208,6 +208,57 @@ async function _prime(svc) {
208
208
  const decisionTitles = (result.decisions || []).map((d) => d.title).join('; ');
209
209
  const statsLine = `${result.inProgress.length} in-progress, ${result.ready.length} ready, ${result.stats.total} total`;
210
210
 
211
+ // ── Behavioral Rules Reminder (synced with MCP handler) ──
212
+ result._taskRules = {
213
+ reminder: [
214
+ '📋 TASK RULES (MANDATORY):',
215
+ '🔑 YOU are the task operator — user speaks naturally, you translate to task operations. NEVER tell user to run task commands.',
216
+ '• MUST prime on EVERY message BEFORE anything else',
217
+ '• MUST create task for non-trivial work (≥2 files OR ≥10 lines)',
218
+ '• MUST claim before coding, close when done with meaningful reason',
219
+ '• MUST handle unfinished tasks before starting new work (ask user: Continue/Defer/Abandon)',
220
+ '• NEVER skip prime, NEVER start new work with open in_progress tasks',
221
+ '• NEVER leave tasks in in_progress when session ends — close or defer ALL',
222
+ '• When in doubt → create a task. When idle → ready()',
223
+ '• Session end → close all tasks, defer incomplete, verify zero in_progress',
224
+ ].join('\n'),
225
+ translationHint: [
226
+ 'User Says → You Run:',
227
+ '"fix bug"/"implement" → create→claim→code→close',
228
+ '"continue" → resume in-progress→close',
229
+ '"pause" → defer | "abandon" → fail | "break down" → decompose',
230
+ '"what\'s next" → ready() | "agreed" → record_decision',
231
+ 'Quick question → No task. Just answer.',
232
+ ].join('\n'),
233
+ };
234
+
235
+ // ── Resume Prompt: 有 inProgress 任务时,提示 Agent 让用户选择 ──
236
+ if (result.inProgress.length > 0) {
237
+ const taskList = result.inProgress.map((t) => {
238
+ const age = t.updatedAt
239
+ ? `${Math.floor((Date.now() / 1000 - t.updatedAt) / 86400)}d ago`
240
+ : '';
241
+ return `• **${t.id}** — ${t.title}${age ? ` (${age})` : ''}`;
242
+ }).join('\n');
243
+
244
+ result._resumePrompt = {
245
+ instruction: [
246
+ 'There are unfinished tasks. You MUST present these options to the user BEFORE doing anything else:',
247
+ '',
248
+ '**Unfinished tasks:**',
249
+ taskList,
250
+ '',
251
+ 'Ask the user to choose:',
252
+ '1. **Continue** — resume the unfinished task(s)',
253
+ '2. **Defer** — pause it and work on something else',
254
+ '3. **Abandon** — close/fail it and start fresh',
255
+ '',
256
+ 'Wait for the user\'s answer. Do NOT auto-resume.',
257
+ ].join('\n'),
258
+ taskIds: result.inProgress.map((t) => t.id),
259
+ };
260
+ }
261
+
211
262
  let message;
212
263
  if (decisionCount > 0) {
213
264
  const stalePart = staleCount > 0 ? ` ${staleCount} stale.` : '';
@@ -216,6 +267,10 @@ async function _prime(svc) {
216
267
  message = `${statsLine}.`;
217
268
  }
218
269
 
270
+ if (result.inProgress.length > 0) {
271
+ message += ` ⏸️ ${result.inProgress.length} unfinished task(s) — ask user before resuming.`;
272
+ }
273
+
219
274
  return {
220
275
  success: true,
221
276
  data: result,
@@ -74,7 +74,7 @@ const ANALYST_TOOLS = [
74
74
  ];
75
75
 
76
76
  // ──────────────────────────────────────────────────────────────────
77
- // Analyst 预算 — 自由探索,不需要 PhaseRouter
77
+ // Analyst 预算 — 使用 analyst 策略(自由探索,无阶段约束)
78
78
  // ──────────────────────────────────────────────────────────────────
79
79
 
80
80
  const ANALYST_BUDGET = {
@@ -283,7 +283,7 @@ export class AnalystAgent {
283
283
  dimConfig,
284
284
  projectInfo,
285
285
  options.dimensionContext,
286
- options.episodicMemory, // v4.0: EpisodicMemory 增强
286
+ options.memoryCoordinator?.getSessionStore(), // v5.0: SessionStore 提供跨维度上下文
287
287
  options.semanticMemory, // v4.1: ProjectSemanticMemory 历史记忆
288
288
  options.codeEntityGraph // Phase E: CodeEntityGraph 代码实体图谱
289
289
  );
@@ -308,24 +308,24 @@ export class AnalystAgent {
308
308
  budget: ANALYST_BUDGET,
309
309
  systemPromptOverride: ANALYST_SYSTEM_PROMPT,
310
310
  allowedTools: ANALYST_TOOLS,
311
- disablePhaseRouter: true,
311
+ strategy: 'analyst',
312
312
  temperature: 0.4,
313
313
  dimensionMeta: {
314
314
  id: dimId,
315
315
  outputType: 'analysis',
316
316
  allowedKnowledgeTypes: dimConfig.allowedKnowledgeTypes || [],
317
317
  },
318
- // v4.0: Agent Memory 注入
319
- workingMemory: options.workingMemory || undefined,
320
- episodicMemory: options.episodicMemory || undefined,
321
- toolResultCache: options.toolResultCache || undefined,
318
+ // v5.0: 统一 MemoryCoordinator
319
+ memoryCoordinator: options.memoryCoordinator || undefined,
322
320
  });
323
321
 
324
- // 构建 AnalysisReport/AnalysisArtifact
325
- // v2: WorkingMemory 可用时使用 buildAnalysisArtifact (包含 evidenceMap/findings/negativeSignals)
326
- // v1: 回退到 buildAnalysisReport (仅 text + referencedFiles)
327
- const report = options.workingMemory
328
- ? buildAnalysisArtifact(result, dimId, this.#projectGraph, options.workingMemory)
322
+ // v5.0: 当有 MemoryCoordinator 时使用 ActiveContext 的 distill 结果
323
+ // 构建 AnalysisArtifact (包含 evidenceMap/findings/negativeSignals)
324
+ // 使用显式 scopeId 确保并行执行安全
325
+ const analystScopeId = `${dimId}:analyst`;
326
+ const ac = options.memoryCoordinator?.getActiveContext(analystScopeId);
327
+ const report = ac
328
+ ? buildAnalysisArtifact(result, dimId, this.#projectGraph, ac)
329
329
  : buildAnalysisReport(result, dimId, this.#projectGraph);
330
330
 
331
331
  // 附加推理链数据(如果 ChatAgent 返回了 ReasoningTrace)