autosnippet 3.3.3 → 3.3.5

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 (34) hide show
  1. package/README.md +176 -81
  2. package/config/constitution.yaml +2 -0
  3. package/dist/lib/cli/KnowledgeSyncService.d.ts +5 -1
  4. package/dist/lib/cli/KnowledgeSyncService.js +5 -2
  5. package/dist/lib/domain/knowledge/values/Stats.d.ts +1 -1
  6. package/dist/lib/domain/knowledge/values/Stats.js +2 -2
  7. package/dist/lib/external/mcp/handlers/consolidated.js +178 -0
  8. package/dist/lib/external/mcp/handlers/task.js +36 -14
  9. package/dist/lib/external/mcp/tools.js +2 -1
  10. package/dist/lib/injection/modules/InfraModule.js +4 -1
  11. package/dist/lib/injection/modules/KnowledgeModule.js +23 -0
  12. package/dist/lib/repository/evolution/ProposalRepository.d.ts +99 -0
  13. package/dist/lib/repository/evolution/ProposalRepository.js +255 -0
  14. package/dist/lib/service/bootstrap/UiStartupTasks.d.ts +17 -4
  15. package/dist/lib/service/bootstrap/UiStartupTasks.js +53 -5
  16. package/dist/lib/service/evolution/DecayDetector.d.ts +4 -3
  17. package/dist/lib/service/evolution/DecayDetector.js +97 -22
  18. package/dist/lib/service/evolution/KnowledgeMetabolism.d.ts +4 -2
  19. package/dist/lib/service/evolution/KnowledgeMetabolism.js +29 -2
  20. package/dist/lib/service/evolution/ProposalExecutor.d.ts +62 -0
  21. package/dist/lib/service/evolution/ProposalExecutor.js +360 -0
  22. package/dist/lib/service/evolution/StagingManager.js +5 -3
  23. package/dist/lib/service/guard/GuardCrossFileChecks.js +2 -0
  24. package/dist/lib/service/guard/ReverseGuard.d.ts +1 -1
  25. package/dist/lib/service/guard/ReverseGuard.js +32 -2
  26. package/dist/lib/service/knowledge/SourceRefReconciler.d.ts +2 -0
  27. package/dist/lib/service/knowledge/SourceRefReconciler.js +48 -0
  28. package/dist/lib/service/task/IntentExtractor.d.ts +3 -1
  29. package/dist/lib/service/task/IntentExtractor.js +30 -10
  30. package/dist/lib/service/task/PrimeSearchPipeline.js +67 -12
  31. package/dist/lib/shared/schemas/mcp-tools.d.ts +2 -0
  32. package/dist/lib/shared/schemas/mcp-tools.js +9 -1
  33. package/package.json +1 -1
  34. package/templates/instructions/conventions.md +4 -2
@@ -220,6 +220,7 @@ export async function enhancedSubmitKnowledge(ctx, args) {
220
220
  const source = args.source || 'mcp';
221
221
  const dimensionId = args.dimensionId;
222
222
  const clientId = args.client_id;
223
+ const supersedes = args.supersedes;
223
224
  // ── Step 1: 限流 ──
224
225
  const { checkRecipeSave } = await import('#http/middleware/RateLimiter.js');
225
226
  const { resolveProjectRoot } = await import('#shared/resolveProjectRoot.js');
@@ -283,6 +284,7 @@ export async function enhancedSubmitKnowledge(ctx, args) {
283
284
  // ── Step 3: 融合分析(统一对所有有效条目运行) ──
284
285
  const submittableItems = [];
285
286
  const blockedItems = [];
287
+ const createdProposals = [];
286
288
  if (skipConsolidation) {
287
289
  submittableItems.push(...validItems);
288
290
  }
@@ -307,11 +309,37 @@ export async function enhancedSubmitKnowledge(ctx, args) {
307
309
  content: v.item.content,
308
310
  }));
309
311
  const batchAdvice = advisor.analyzeBatch(candidates);
312
+ // 尝试获取 ProposalRepository 以创建 Proposal(降级容忍)
313
+ let proposalRepo = null;
314
+ try {
315
+ proposalRepo = ctx.container.get('proposalRepository') ?? null;
316
+ }
317
+ catch {
318
+ /* ProposalRepository 未注册,降级为旧的 blocked 模式 */
319
+ }
310
320
  for (const { index: adviceIdx, advice } of batchAdvice.items) {
311
321
  const validEntry = validItems[adviceIdx];
312
322
  if (advice.action === 'create') {
313
323
  submittableItems.push(validEntry);
314
324
  }
325
+ else if (proposalRepo &&
326
+ (advice.action === 'merge' ||
327
+ advice.action === 'reorganize' ||
328
+ advice.action === 'insufficient')) {
329
+ // 创建 Proposal 而非简单 block — 系统后续自动处理
330
+ const proposal = _createProposalFromAdvice(proposalRepo, advice, validEntry.item);
331
+ if (proposal) {
332
+ createdProposals.push(proposal);
333
+ }
334
+ else {
335
+ // Proposal 创建失败(可能去重)→ 仍作为 blocked 返回
336
+ blockedItems.push({
337
+ index: validEntry.index,
338
+ title: validEntry.item.title || '(untitled)',
339
+ consolidation: advice,
340
+ });
341
+ }
342
+ }
315
343
  else {
316
344
  blockedItems.push({
317
345
  index: validEntry.index,
@@ -390,6 +418,63 @@ export async function enhancedSubmitKnowledge(ctx, args) {
390
418
  });
391
419
  }
392
420
  }
421
+ // ── Step 4b: Supersede 提案创建 ──
422
+ // 当 Agent 声明 supersedes 旧 Recipe 时,创建 supersede Proposal
423
+ if (supersedes && successIds.length > 0) {
424
+ let proposalRepo = null;
425
+ try {
426
+ proposalRepo = ctx.container.get('proposalRepository') ?? null;
427
+ }
428
+ catch {
429
+ /* ProposalRepository 未注册,跳过 */
430
+ }
431
+ if (proposalRepo) {
432
+ // 验证旧 Recipe 存在
433
+ const oldRecipeExists = (() => {
434
+ try {
435
+ const db = ctx.container.get('database');
436
+ if (!db) {
437
+ return false;
438
+ }
439
+ const rawDb = db.getDb();
440
+ const row = rawDb
441
+ .prepare('SELECT id FROM knowledge_entries WHERE id = ?')
442
+ .get(supersedes);
443
+ return row !== undefined;
444
+ }
445
+ catch {
446
+ return false;
447
+ }
448
+ })();
449
+ if (oldRecipeExists) {
450
+ const proposal = proposalRepo.create({
451
+ type: 'supersede',
452
+ targetRecipeId: supersedes,
453
+ relatedRecipeIds: successIds,
454
+ confidence: 0.8,
455
+ source: 'ide-agent',
456
+ description: `Agent 声明新 Recipe [${successIds.join(', ')}] 替代旧 Recipe [${supersedes}]。观察窗口内将对比新旧表现。`,
457
+ evidence: [
458
+ {
459
+ snapshotAt: Date.now(),
460
+ newRecipeIds: successIds,
461
+ declaredBy: 'agent',
462
+ },
463
+ ],
464
+ });
465
+ if (proposal) {
466
+ createdProposals.push({
467
+ proposalId: proposal.id,
468
+ type: 'supersede',
469
+ targetRecipe: { id: supersedes, title: supersedes },
470
+ status: proposal.status,
471
+ expiresAt: proposal.expiresAt,
472
+ message: `已创建替代提案:新 Recipe 将在观察窗口到期后自动替代旧 Recipe [${supersedes}]。`,
473
+ });
474
+ }
475
+ }
476
+ }
477
+ }
393
478
  // ── Step 5: 构建统一响应 ──
394
479
  const data = {
395
480
  count: successCount,
@@ -417,6 +502,13 @@ export async function enhancedSubmitKnowledge(ctx, args) {
417
502
  message: `${blockedItems.length} 条因融合分析被阻塞(与已有 Recipe 重叠或实质性不足)。设 skipConsolidation: true 可跳过。`,
418
503
  };
419
504
  }
505
+ if (createdProposals.length > 0) {
506
+ data.proposals = createdProposals;
507
+ data.proposalSummary = {
508
+ proposalCount: createdProposals.length,
509
+ message: `${createdProposals.length} 条已创建进化提案,系统将在观察窗口到期后自动执行。无需额外操作。`,
510
+ };
511
+ }
420
512
  const allOk = successCount === items.length;
421
513
  return envelope({
422
514
  success: successCount > 0,
@@ -467,3 +559,89 @@ function _trackRejection(ctx, item, dimensionId) {
467
559
  /* best effort */
468
560
  }
469
561
  }
562
+ // ── Proposal 创建辅助函数 ───────────────────────────
563
+ /**
564
+ * 将 ConsolidationAdvisor 分析结果转为 evolution_proposals 记录。
565
+ *
566
+ * merge → Proposal(type: merge, target: 已有 Recipe)
567
+ * reorganize → Proposal(type: reorganize, 高风险 → pending 等开发者确认)
568
+ * insufficient → Proposal(type: enhance, target: 最相似 Recipe)
569
+ */
570
+ function _createProposalFromAdvice(repo, advice, candidateItem) {
571
+ const evidence = [
572
+ {
573
+ snapshotAt: Date.now(),
574
+ candidateTitle: candidateItem.title,
575
+ candidateCategory: candidateItem.category,
576
+ analysisReason: advice.reason,
577
+ mergeDirection: advice.mergeDirection,
578
+ },
579
+ ];
580
+ if (advice.action === 'merge' && advice.targetRecipe) {
581
+ const proposal = repo.create({
582
+ type: 'merge',
583
+ targetRecipeId: advice.targetRecipe.id,
584
+ confidence: advice.confidence,
585
+ source: 'ide-agent',
586
+ description: advice.reason,
587
+ evidence,
588
+ });
589
+ if (!proposal) {
590
+ return null;
591
+ }
592
+ return {
593
+ proposalId: proposal.id,
594
+ type: 'merge',
595
+ targetRecipe: { id: advice.targetRecipe.id, title: advice.targetRecipe.title },
596
+ status: proposal.status,
597
+ expiresAt: proposal.expiresAt,
598
+ message: `已为「${advice.targetRecipe.title}」创建融合提案,${proposal.status === 'observing' ? '观察窗口 72h 后自动执行' : '等待开发者确认'}。`,
599
+ };
600
+ }
601
+ if (advice.action === 'reorganize' && advice.reorganizeTargets?.length) {
602
+ const target = advice.reorganizeTargets[0];
603
+ const proposal = repo.create({
604
+ type: 'reorganize',
605
+ targetRecipeId: target.id,
606
+ relatedRecipeIds: advice.reorganizeTargets.slice(1).map((t) => t.id),
607
+ confidence: advice.confidence,
608
+ source: 'ide-agent',
609
+ description: advice.reason,
610
+ evidence,
611
+ });
612
+ if (!proposal) {
613
+ return null;
614
+ }
615
+ return {
616
+ proposalId: proposal.id,
617
+ type: 'reorganize',
618
+ targetRecipe: { id: target.id, title: target.title },
619
+ status: proposal.status,
620
+ expiresAt: proposal.expiresAt,
621
+ message: `已为 ${advice.reorganizeTargets.length} 条 Recipe 创建重组提案,需开发者在 Dashboard 确认。`,
622
+ };
623
+ }
624
+ if (advice.action === 'insufficient' && advice.coveredBy?.length) {
625
+ const target = advice.coveredBy[0];
626
+ const proposal = repo.create({
627
+ type: 'enhance',
628
+ targetRecipeId: target.id,
629
+ confidence: advice.confidence,
630
+ source: 'ide-agent',
631
+ description: advice.reason,
632
+ evidence,
633
+ });
634
+ if (!proposal) {
635
+ return null;
636
+ }
637
+ return {
638
+ proposalId: proposal.id,
639
+ type: 'enhance',
640
+ targetRecipe: { id: target.id, title: target.title },
641
+ status: proposal.status,
642
+ expiresAt: proposal.expiresAt,
643
+ message: `候选独立价值不足,已创建增强提案建议补充到「${target.title}」。`,
644
+ };
645
+ }
646
+ return null;
647
+ }
@@ -44,6 +44,10 @@ const _taskRules = {
44
44
  * Unified entry point
45
45
  */
46
46
  export async function taskHandler(ctx, args) {
47
+ // Normalize taskId → id (schema accepts both for convenience)
48
+ if (!args.id && typeof args.taskId === 'string') {
49
+ args.id = args.taskId;
50
+ }
47
51
  let result;
48
52
  switch (args.operation) {
49
53
  case 'prime':
@@ -88,11 +92,20 @@ async function _prime(ctx, args) {
88
92
  if (pipeline && extracted.queries[0]?.trim()) {
89
93
  try {
90
94
  searchResult = await pipeline.search(extracted);
95
+ if (!searchResult) {
96
+ process.stderr.write('[MCP/Task] prime: pipeline.search returned null (all filtered)\n');
97
+ }
91
98
  }
92
- catch {
93
- // search failure is non-fatal
99
+ catch (err) {
100
+ process.stderr.write(`[MCP/Task] prime search error: ${err instanceof Error ? err.stack || err.message : String(err)}\n`);
94
101
  }
95
102
  }
103
+ else if (!pipeline) {
104
+ process.stderr.write('[MCP/Task] prime: pipeline is null, skipping search\n');
105
+ }
106
+ else {
107
+ process.stderr.write(`[MCP/Task] prime: queries empty, skipping search. queries=${JSON.stringify(extracted.queries)}\n`);
108
+ }
96
109
  // ─── Lifecycle: initialize IntentState ───
97
110
  const freshIntent = createIdleIntent();
98
111
  freshIntent.phase = 'active';
@@ -175,14 +188,16 @@ async function _create(ctx, args) {
175
188
  }
176
189
  // ═══ close ══════════════════════════════════════════════
177
190
  async function _close(ctx, args) {
178
- if (!args.id) {
191
+ const intent = ctx.session?.intent;
192
+ // Resolve id: explicit arg > session intent > fail
193
+ const id = args.id || (intent?.taskId ?? '');
194
+ if (!id) {
179
195
  return envelope({
180
196
  success: false,
181
- message: 'id is required',
197
+ message: 'id is required (pass id or ensure a task was created in this session)',
182
198
  meta: { tool: 'autosnippet_task' },
183
199
  });
184
200
  }
185
- const intent = ctx.session?.intent;
186
201
  const reason = args.reason || 'Completed';
187
202
  // Persist intent chain via SignalBus
188
203
  if (intent && intent.phase === 'active') {
@@ -192,13 +207,13 @@ async function _close(ctx, args) {
192
207
  if (ctx.session) {
193
208
  ctx.session.intent = createIdleIntent();
194
209
  }
195
- const lines = [`✅ Closed: ${args.id} — ${reason}`];
210
+ const lines = [`✅ Closed: ${id} — ${reason}`];
196
211
  lines.push('');
197
212
  lines.push('⚠️ REQUIRED: You MUST call autosnippet_guard (no args) NOW to review changed files for compliance violations.');
198
213
  return envelope({
199
214
  success: true,
200
215
  data: {
201
- closed: { id: args.id, reason, closedAt: Date.now() },
216
+ closed: { id, reason, closedAt: Date.now() },
202
217
  nextAction: {
203
218
  tool: 'autosnippet_guard',
204
219
  args: {},
@@ -212,14 +227,16 @@ async function _close(ctx, args) {
212
227
  }
213
228
  // ═══ fail ═══════════════════════════════════════════════
214
229
  async function _fail(ctx, args) {
215
- if (!args.id) {
230
+ const intent = ctx.session?.intent;
231
+ // Resolve id: explicit arg > session intent > fail
232
+ const id = args.id || (intent?.taskId ?? '');
233
+ if (!id) {
216
234
  return envelope({
217
235
  success: false,
218
- message: 'id is required',
236
+ message: 'id is required (pass id or ensure a task was created in this session)',
219
237
  meta: { tool: 'autosnippet_task' },
220
238
  });
221
239
  }
222
- const intent = ctx.session?.intent;
223
240
  const reason = args.reason || 'Agent execution failed';
224
241
  // Persist intent chain via SignalBus
225
242
  if (intent && intent.phase === 'active') {
@@ -232,9 +249,9 @@ async function _fail(ctx, args) {
232
249
  return envelope({
233
250
  success: true,
234
251
  data: {
235
- failed: { id: args.id, reason, failedAt: Date.now() },
252
+ failed: { id, reason, failedAt: Date.now() },
236
253
  },
237
- message: `❌ Failed: ${args.id} — ${reason}`,
254
+ message: `❌ Failed: ${id} — ${reason}`,
238
255
  meta: { tool: 'autosnippet_task' },
239
256
  });
240
257
  }
@@ -323,9 +340,14 @@ function _computeDriftScore(intent) {
323
340
  }
324
341
  function _getPipeline(container) {
325
342
  try {
326
- return container.get('primeSearchPipeline');
343
+ const p = container.get('primeSearchPipeline');
344
+ if (!p) {
345
+ process.stderr.write('[MCP/Task] _getPipeline: container returned null/undefined\n');
346
+ }
347
+ return p;
327
348
  }
328
- catch {
349
+ catch (err) {
350
+ process.stderr.write(`[MCP/Task] _getPipeline failed: ${err instanceof Error ? err.message : String(err)}\n`);
329
351
  return null;
330
352
  }
331
353
  }
@@ -147,8 +147,9 @@ export const TOOLS = [
147
147
  description: 'Submit knowledge entries (single/batch unified pipeline). Pass 1~N items via the items array.\n' +
148
148
  '• All entries undergo strict validation; all V3 fields must be provided at once\n' +
149
149
  '• Unified consolidation analysis: detects overlap with existing Recipes and batch candidates\n' +
150
- '• Handle CONSOLIDATION_MERGE / CONSOLIDATION_REORGANIZE / CONSOLIDATION_INSUFFICIENT responses\n' +
150
+ '• Overlap detected evolution proposal created automatically (merge/enhance/reorganize); system auto-executes after observation window\n' +
151
151
  '• Set skipConsolidation: true to skip consolidation check. content and reasoning must be objects.\n' +
152
+ '• Set supersedes: "old-recipe-id" to declare the new Recipe replaces an existing one (creates a supersede proposal with observation window).\n' +
152
153
  '⚠️ Batch rule: items in the array must NOT be cross-redundant — no highly overlapping doClause/coreCode/trigger within the same batch. ' +
153
154
  'If two entries share 80%+ content, merge into one or split into primary + extends supplementary entries.',
154
155
  inputSchema: zodToMcpSchema(SubmitKnowledgeInput),
@@ -65,7 +65,10 @@ export function register(c) {
65
65
  });
66
66
  c.singleton('knowledgeSyncService', (ct) => {
67
67
  const projectRoot = resolveProjectRoot(ct);
68
- return new KnowledgeSyncService(projectRoot);
68
+ const sourceRefReconciler = ct.singletons.sourceRefReconciler;
69
+ return new KnowledgeSyncService(projectRoot, {
70
+ sourceRefReconciler: sourceRefReconciler || undefined,
71
+ });
69
72
  });
70
73
  // ═══ ReportStore ═══
71
74
  c.singleton('reportStore', (ct) => {
@@ -13,18 +13,21 @@ import { getEnhancementRegistry } from '../../core/enhancement/index.js';
13
13
  import { HnswVectorAdapter } from '../../infrastructure/vector/HnswVectorAdapter.js';
14
14
  import { IndexingPipeline } from '../../infrastructure/vector/IndexingPipeline.js';
15
15
  import { JsonVectorAdapter } from '../../infrastructure/vector/JsonVectorAdapter.js';
16
+ import { ProposalRepository } from '../../repository/evolution/ProposalRepository.js';
16
17
  import { DimensionCopy } from '../../service/bootstrap/DimensionCopyRegistry.js';
17
18
  import { ConsolidationAdvisor } from '../../service/evolution/ConsolidationAdvisor.js';
18
19
  import { ContradictionDetector } from '../../service/evolution/ContradictionDetector.js';
19
20
  import { DecayDetector } from '../../service/evolution/DecayDetector.js';
20
21
  import { EnhancementSuggester } from '../../service/evolution/EnhancementSuggester.js';
21
22
  import { KnowledgeMetabolism } from '../../service/evolution/KnowledgeMetabolism.js';
23
+ import { ProposalExecutor } from '../../service/evolution/ProposalExecutor.js';
22
24
  import { RedundancyAnalyzer } from '../../service/evolution/RedundancyAnalyzer.js';
23
25
  import { StagingManager } from '../../service/evolution/StagingManager.js';
24
26
  import { CodeEntityGraph } from '../../service/knowledge/CodeEntityGraph.js';
25
27
  import { ConfidenceRouter } from '../../service/knowledge/ConfidenceRouter.js';
26
28
  import { KnowledgeGraphService } from '../../service/knowledge/KnowledgeGraphService.js';
27
29
  import { KnowledgeService } from '../../service/knowledge/KnowledgeService.js';
30
+ import { SourceRefReconciler } from '../../service/knowledge/SourceRefReconciler.js';
28
31
  import { HybridRetriever } from '../../service/search/HybridRetriever.js';
29
32
  import { SearchEngine } from '../../service/search/SearchEngine.js';
30
33
  import { LanguageService } from '../../shared/LanguageService.js';
@@ -129,6 +132,13 @@ export function register(c) {
129
132
  c.register('aiProvider', () => c.singletons.aiProvider || null);
130
133
  c.register('projectGraph', () => c.singletons.projectGraph || null);
131
134
  // ═══ Governance / Evolution ═══
135
+ c.singleton('sourceRefReconciler', (ct) => {
136
+ const db = ct.get('database');
137
+ const projectRoot = resolveProjectRoot();
138
+ return new SourceRefReconciler(projectRoot, db.getDb(), {
139
+ signalBus: ct.singletons.signalBus || undefined,
140
+ });
141
+ });
132
142
  c.singleton('stagingManager', (ct) => {
133
143
  const db = ct.get('database');
134
144
  return new StagingManager(db.getDb(), {
@@ -165,6 +175,19 @@ export function register(c) {
165
175
  redundancyAnalyzer: ct.get('redundancyAnalyzer'),
166
176
  decayDetector: ct.get('decayDetector'),
167
177
  signalBus: ct.singletons.signalBus || undefined,
178
+ proposalRepository: ct.services.proposalRepository
179
+ ? ct.get('proposalRepository')
180
+ : undefined,
181
+ });
182
+ });
183
+ c.singleton('proposalRepository', (ct) => {
184
+ const db = ct.get('database');
185
+ return new ProposalRepository(db.getDb());
186
+ });
187
+ c.singleton('proposalExecutor', (ct) => {
188
+ const db = ct.get('database');
189
+ return new ProposalExecutor(db.getDb(), ct.get('proposalRepository'), {
190
+ signalBus: ct.singletons.signalBus || undefined,
168
191
  });
169
192
  });
170
193
  c.singleton('consolidationAdvisor', (ct) => {
@@ -0,0 +1,99 @@
1
+ /**
2
+ * ProposalRepository — evolution_proposals 表 CRUD
3
+ *
4
+ * 操作 evolution_proposals 表,存储进化提案(merge/supersede/enhance/deprecate/
5
+ * reorganize/contradiction/correction)。
6
+ *
7
+ * 设计要求:
8
+ * - 去重:同 target + 同 type 不允许多个 observing 状态的 Proposal
9
+ * - Rate Limit:同一 target 不允许同时存在多个相同类型的 observing Proposal
10
+ * - JSON 字段(evidence/related_recipe_ids)序列化/反序列化
11
+ */
12
+ interface DatabaseLike {
13
+ prepare(sql: string): {
14
+ all(...params: unknown[]): Record<string, unknown>[];
15
+ get(...params: unknown[]): Record<string, unknown> | undefined;
16
+ run(...params: unknown[]): {
17
+ changes: number;
18
+ };
19
+ };
20
+ }
21
+ /** Proposal 类型 — 统一标准 */
22
+ export type ProposalType = 'merge' | 'supersede' | 'enhance' | 'deprecate' | 'reorganize' | 'contradiction' | 'correction';
23
+ /** Proposal 来源 */
24
+ export type ProposalSource = 'ide-agent' | 'metabolism' | 'decay-scan';
25
+ /** Proposal 状态 */
26
+ export type ProposalStatus = 'pending' | 'observing' | 'executed' | 'rejected' | 'expired';
27
+ /** evolution_proposals 行对象 */
28
+ export interface ProposalRecord {
29
+ id: string;
30
+ type: ProposalType;
31
+ targetRecipeId: string;
32
+ relatedRecipeIds: string[];
33
+ confidence: number;
34
+ source: ProposalSource;
35
+ description: string;
36
+ evidence: Record<string, unknown>[];
37
+ status: ProposalStatus;
38
+ proposedAt: number;
39
+ expiresAt: number;
40
+ resolvedAt: number | null;
41
+ resolvedBy: string | null;
42
+ resolution: string | null;
43
+ }
44
+ /** 创建 Proposal 输入 */
45
+ export interface CreateProposalInput {
46
+ type: ProposalType;
47
+ targetRecipeId: string;
48
+ relatedRecipeIds?: string[];
49
+ confidence: number;
50
+ source: ProposalSource;
51
+ description: string;
52
+ evidence?: Record<string, unknown>[];
53
+ status?: ProposalStatus;
54
+ expiresAt?: number;
55
+ }
56
+ /** 查询过滤器 */
57
+ export interface ProposalFilter {
58
+ status?: ProposalStatus | ProposalStatus[];
59
+ type?: ProposalType;
60
+ targetRecipeId?: string;
61
+ source?: ProposalSource;
62
+ expiredBefore?: number;
63
+ }
64
+ export declare class ProposalRepository {
65
+ #private;
66
+ constructor(db: DatabaseLike);
67
+ /**
68
+ * 创建 Proposal 并写入 DB。
69
+ *
70
+ * - 自动生成 ID(ep-{timestamp}-{random})
71
+ * - 自动设定 expiresAt(按 type 默认窗口)
72
+ * - 自动判断 status(低风险 + 高置信度 → observing,否则 pending)
73
+ * - 去重:同 target + 同 type 已有 pending/observing 时拒绝创建
74
+ */
75
+ create(input: CreateProposalInput): ProposalRecord | null;
76
+ /** 按 ID 查询 */
77
+ findById(id: string): ProposalRecord | null;
78
+ /** 按条件查询 */
79
+ find(filter?: ProposalFilter): ProposalRecord[];
80
+ /** 查询已到期的 observing 状态 Proposal */
81
+ findExpiredObserving(): ProposalRecord[];
82
+ /** 查询所有未完成的 Proposal(pending + observing) */
83
+ findActive(): ProposalRecord[];
84
+ /** 按 target Recipe ID 查询活跃 Proposal */
85
+ findByTarget(targetRecipeId: string): ProposalRecord[];
86
+ /** 将 Proposal 状态转为 observing */
87
+ startObserving(id: string): boolean;
88
+ /** 标记 Proposal 为已执行 */
89
+ markExecuted(id: string, resolution: string, resolvedBy?: string): boolean;
90
+ /** 标记 Proposal 为已拒绝 */
91
+ markRejected(id: string, resolution: string, resolvedBy?: string): boolean;
92
+ /** 标记 Proposal 为过期 */
93
+ markExpired(id: string): boolean;
94
+ /** 更新 evidence(用于追加观察期指标快照) */
95
+ updateEvidence(id: string, evidence: Record<string, unknown>[]): boolean;
96
+ /** 统计各状态的 Proposal 数量 */
97
+ stats(): Record<ProposalStatus, number>;
98
+ }
99
+ export {};