autosnippet 3.3.4 → 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 (29) hide show
  1. package/README.md +174 -83
  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/tools.js +2 -1
  9. package/dist/lib/injection/modules/InfraModule.js +4 -1
  10. package/dist/lib/injection/modules/KnowledgeModule.js +23 -0
  11. package/dist/lib/repository/evolution/ProposalRepository.d.ts +99 -0
  12. package/dist/lib/repository/evolution/ProposalRepository.js +255 -0
  13. package/dist/lib/service/bootstrap/UiStartupTasks.d.ts +17 -4
  14. package/dist/lib/service/bootstrap/UiStartupTasks.js +53 -5
  15. package/dist/lib/service/evolution/DecayDetector.d.ts +4 -3
  16. package/dist/lib/service/evolution/DecayDetector.js +97 -22
  17. package/dist/lib/service/evolution/KnowledgeMetabolism.d.ts +4 -2
  18. package/dist/lib/service/evolution/KnowledgeMetabolism.js +29 -2
  19. package/dist/lib/service/evolution/ProposalExecutor.d.ts +62 -0
  20. package/dist/lib/service/evolution/ProposalExecutor.js +360 -0
  21. package/dist/lib/service/evolution/StagingManager.js +5 -3
  22. package/dist/lib/service/guard/GuardCrossFileChecks.js +2 -0
  23. package/dist/lib/service/guard/ReverseGuard.d.ts +1 -1
  24. package/dist/lib/service/guard/ReverseGuard.js +32 -2
  25. package/dist/lib/service/knowledge/SourceRefReconciler.d.ts +2 -0
  26. package/dist/lib/service/knowledge/SourceRefReconciler.js +48 -0
  27. package/dist/lib/shared/schemas/mcp-tools.d.ts +1 -0
  28. package/dist/lib/shared/schemas/mcp-tools.js +4 -0
  29. package/package.json +1 -1
@@ -2,10 +2,12 @@
2
2
  * UiStartupTasks — asd ui 启动后异步后台刷新任务
3
3
  *
4
4
  * 在 Dashboard 启动后异步执行,不阻塞 UI:
5
- * 1. syncAll: .md → DB 全量同步 + sourceRefs 对账
6
- * 2. staging promote: 到期 staging → active 晋升
7
- * 3. vector reconcile: 向量对账(best-effort)
8
- * 4. refreshIndex: BM25 增量刷新
5
+ * 1. syncAll: .md → DB 全量同步 + sourceRefs 对账
6
+ * 2. staging promote: 到期 staging → active 晋升
7
+ * 3. vector reconcile: 向量对账(best-effort)
8
+ * 4. refreshIndex: BM25 增量刷新
9
+ * 5. proposalCheck: 到期 Proposal 检查 + 自动执行/拒绝
10
+ * 6. metabolismCycle: 知识新陈代谢(矛盾/冗余/衰退扫描 → 新 Proposal)
9
11
  */
10
12
  interface UiStartupContext {
11
13
  projectRoot: string;
@@ -34,6 +36,17 @@ export interface UiStartupReport {
34
36
  missing: number;
35
37
  };
36
38
  indexRefresh?: boolean;
39
+ proposalCheck?: {
40
+ executed: number;
41
+ rejected: number;
42
+ expired: number;
43
+ };
44
+ metabolismCycle?: {
45
+ proposalCount: number;
46
+ contradictions: number;
47
+ redundancies: number;
48
+ decaying: number;
49
+ };
37
50
  durationMs: number;
38
51
  errors: string[];
39
52
  }
@@ -2,10 +2,12 @@
2
2
  * UiStartupTasks — asd ui 启动后异步后台刷新任务
3
3
  *
4
4
  * 在 Dashboard 启动后异步执行,不阻塞 UI:
5
- * 1. syncAll: .md → DB 全量同步 + sourceRefs 对账
6
- * 2. staging promote: 到期 staging → active 晋升
7
- * 3. vector reconcile: 向量对账(best-effort)
8
- * 4. refreshIndex: BM25 增量刷新
5
+ * 1. syncAll: .md → DB 全量同步 + sourceRefs 对账
6
+ * 2. staging promote: 到期 staging → active 晋升
7
+ * 3. vector reconcile: 向量对账(best-effort)
8
+ * 4. refreshIndex: BM25 增量刷新
9
+ * 5. proposalCheck: 到期 Proposal 检查 + 自动执行/拒绝
10
+ * 6. metabolismCycle: 知识新陈代谢(矛盾/冗余/衰退扫描 → 新 Proposal)
9
11
  */
10
12
  import Logger from '../../infrastructure/logging/Logger.js';
11
13
  const logger = Logger.getInstance();
@@ -20,7 +22,11 @@ export async function runUiStartupTasks(ctx) {
20
22
  // ── Stage 1: syncAll (.md → DB + sourceRefs reconcile) ──
21
23
  try {
22
24
  const { KnowledgeSyncService } = await import('../../cli/KnowledgeSyncService.js');
23
- const syncService = new KnowledgeSyncService(ctx.projectRoot);
25
+ const { SourceRefReconciler } = await import('../../service/knowledge/SourceRefReconciler.js');
26
+ const sourceRefReconciler = ctx.container.singletons.sourceRefReconciler;
27
+ const syncService = new KnowledgeSyncService(ctx.projectRoot, {
28
+ sourceRefReconciler: sourceRefReconciler || undefined,
29
+ });
24
30
  const db = ctx.container.get('database');
25
31
  const rawDb = db.getDb();
26
32
  const syncReport = await syncService.syncAll(rawDb, { skipViolations: true });
@@ -93,6 +99,48 @@ export async function runUiStartupTasks(ctx) {
93
99
  report.errors.push(msg);
94
100
  logger.warn(`[UiStartupTasks] ${msg}`);
95
101
  }
102
+ // ── Stage 5: ProposalExecutor — 到期 Proposal 检查 + 自动执行 ──
103
+ try {
104
+ if (ctx.container.services.proposalExecutor) {
105
+ const executor = ctx.container.get('proposalExecutor');
106
+ const result = executor.checkAndExecute();
107
+ report.proposalCheck = {
108
+ executed: result.executed.length,
109
+ rejected: result.rejected.length,
110
+ expired: result.expired.length,
111
+ };
112
+ const total = result.executed.length + result.rejected.length + result.expired.length;
113
+ if (total > 0) {
114
+ logger.info(`[UiStartupTasks] Stage 5: proposal check — executed=${result.executed.length}, rejected=${result.rejected.length}, expired=${result.expired.length}`);
115
+ }
116
+ }
117
+ }
118
+ catch (err) {
119
+ const msg = `proposal check failed: ${err.message}`;
120
+ report.errors.push(msg);
121
+ logger.warn(`[UiStartupTasks] ${msg}`);
122
+ }
123
+ // ── Stage 6: KnowledgeMetabolism — 知识新陈代谢扫描 ──
124
+ try {
125
+ if (ctx.container.services.knowledgeMetabolism) {
126
+ const metabolism = ctx.container.get('knowledgeMetabolism');
127
+ const result = metabolism.runFullCycle();
128
+ report.metabolismCycle = {
129
+ proposalCount: result.summary.proposalCount,
130
+ contradictions: result.summary.contradictionCount,
131
+ redundancies: result.summary.redundancyCount,
132
+ decaying: result.summary.decayingCount,
133
+ };
134
+ if (result.summary.proposalCount > 0) {
135
+ logger.info(`[UiStartupTasks] Stage 6: metabolism cycle — ${result.summary.proposalCount} proposals generated`);
136
+ }
137
+ }
138
+ }
139
+ catch (err) {
140
+ const msg = `metabolism cycle failed: ${err.message}`;
141
+ report.errors.push(msg);
142
+ logger.warn(`[UiStartupTasks] ${msg}`);
143
+ }
96
144
  report.durationMs = Date.now() - start;
97
145
  logger.info(`[UiStartupTasks] All tasks completed in ${report.durationMs}ms`, {
98
146
  errors: report.errors.length,
@@ -1,10 +1,11 @@
1
1
  /**
2
2
  * DecayDetector — 知识衰退检测 + 评分
3
3
  *
4
- * 5 种衰退检测策略(任一满足即触发 decaying 转换):
4
+ * 6 种衰退检测策略(任一满足即触发 decaying 转换):
5
5
  * 1. daysSinceLastHit > 90 — 90 天无使用
6
6
  * 2. ruleFalsePositiveRate > 0.4 && triggers > 10 — 规则已不准
7
7
  * 3. ReverseGuard: coreCode 引用的 API 符号已删除
8
+ * 3b. SourceRefReconciler: 来源文件路径失效(recipe_source_refs.status = stale)
8
9
  * 4. 同域新 Recipe 发布且 deprecated_by 关系指向它
9
10
  * 5. ContradictionDetector: 与更新的 Recipe 硬矛盾
10
11
  *
@@ -29,7 +30,7 @@ export interface DecaySignal {
29
30
  strategy: DecayStrategy;
30
31
  detail: string;
31
32
  }
32
- export type DecayStrategy = 'no_recent_usage' | 'high_false_positive' | 'symbol_drift' | 'superseded' | 'contradiction';
33
+ export type DecayStrategy = 'no_recent_usage' | 'high_false_positive' | 'symbol_drift' | 'source_ref_stale' | 'superseded' | 'contradiction';
33
34
  export interface DecayScoreResult {
34
35
  recipeId: string;
35
36
  title: string;
@@ -52,7 +53,7 @@ interface RecipeForDecay {
52
53
  stats: string | null;
53
54
  quality_grade: string | null;
54
55
  quality_score: number | null;
55
- created_at: string | null;
56
+ created_at: number | null;
56
57
  }
57
58
  export declare class DecayDetector {
58
59
  #private;
@@ -1,10 +1,11 @@
1
1
  /**
2
2
  * DecayDetector — 知识衰退检测 + 评分
3
3
  *
4
- * 5 种衰退检测策略(任一满足即触发 decaying 转换):
4
+ * 6 种衰退检测策略(任一满足即触发 decaying 转换):
5
5
  * 1. daysSinceLastHit > 90 — 90 天无使用
6
6
  * 2. ruleFalsePositiveRate > 0.4 && triggers > 10 — 规则已不准
7
7
  * 3. ReverseGuard: coreCode 引用的 API 符号已删除
8
+ * 3b. SourceRefReconciler: 来源文件路径失效(recipe_source_refs.status = stale)
8
9
  * 4. 同域新 Recipe 发布且 deprecated_by 关系指向它
9
10
  * 5. ContradictionDetector: 与更新的 Recipe 硬矛盾
10
11
  *
@@ -17,7 +18,17 @@
17
18
  * 20-39: 严重 → Grace Period 缩短到 15d
18
19
  * 0-19: 死亡 → 跳过确认直接 deprecated
19
20
  */
21
+ var _a;
20
22
  import Logger from '../../infrastructure/logging/Logger.js';
23
+ /* ────────────────────── Helpers ────────────────────── */
24
+ /**
25
+ * Normalize a timestamp to **milliseconds**.
26
+ * If the value looks like Unix seconds (< 1e12 ≈ year 2001 in ms), multiply by 1000.
27
+ * Otherwise assume it's already in ms and return as-is.
28
+ */
29
+ function toMs(ts) {
30
+ return ts < 1e12 ? ts * 1000 : ts;
31
+ }
21
32
  /* ────────────────────── Constants ────────────────────── */
22
33
  const DAY_MS = 24 * 60 * 60 * 1000;
23
34
  const GRACE_PERIOD_STANDARD = 30 * DAY_MS;
@@ -77,13 +88,13 @@ export class DecayDetector {
77
88
  * 评估单条 Recipe 的衰退状态
78
89
  */
79
90
  evaluate(recipe) {
80
- const stats = DecayDetector.#parseStats(recipe.stats);
91
+ const stats = _a.#parseStats(recipe.stats);
81
92
  const signals = [];
82
93
  const now = Date.now();
83
94
  // 策略 1: 90 天无使用
84
95
  const lastHitAt = stats.lastHitAt ?? null;
85
96
  if (lastHitAt) {
86
- const daysSince = (now - lastHitAt) / DAY_MS;
97
+ const daysSince = (now - toMs(lastHitAt)) / DAY_MS;
87
98
  if (daysSince > DECAY_THRESHOLDS.NO_USAGE_DAYS) {
88
99
  signals.push({
89
100
  recipeId: recipe.id,
@@ -93,8 +104,8 @@ export class DecayDetector {
93
104
  }
94
105
  }
95
106
  else {
96
- // 无 lastHitAt,检查创建时间
97
- const createdAt = recipe.created_at ? new Date(recipe.created_at).getTime() : now;
107
+ // 无 lastHitAt,检查创建时间(DB 可能存为秒或毫秒)
108
+ const createdAt = toMs(recipe.created_at ?? now);
98
109
  const daysSinceCreation = (now - createdAt) / DAY_MS;
99
110
  if (daysSinceCreation > DECAY_THRESHOLDS.NO_USAGE_DAYS) {
100
111
  signals.push({
@@ -123,6 +134,15 @@ export class DecayDetector {
123
134
  detail: 'ReverseGuard detected symbol drift in coreCode',
124
135
  });
125
136
  }
137
+ // 策略 3b: 来源引用失效(由 SourceRefReconciler 填充 recipe_source_refs)
138
+ const staleRefCount = this.#getStaleSourceRefCount(recipe.id);
139
+ if (staleRefCount > 0) {
140
+ signals.push({
141
+ recipeId: recipe.id,
142
+ strategy: 'source_ref_stale',
143
+ detail: `${staleRefCount} source reference(s) no longer exist on disk`,
144
+ });
145
+ }
126
146
  // 策略 4: 被取代(有 deprecated_by 关系指向更新版本)
127
147
  if (this.#isSuperseded(recipe.id)) {
128
148
  signals.push({
@@ -131,13 +151,14 @@ export class DecayDetector {
131
151
  detail: 'Newer version exists via deprecated_by relation',
132
152
  });
133
153
  }
134
- // 计算 decayScore
135
- const dimensions = this.#computeScoreDimensions(stats, recipe);
154
+ // 计算 decayScore(staleRatio 影响 quality 维度)
155
+ const staleRatio = this.#getSourceRefStaleRatio(recipe.id);
156
+ const dimensions = this.#computeScoreDimensions(stats, recipe, { staleRatio });
136
157
  const decayScore = Math.round(dimensions.freshness * SCORE_WEIGHTS.freshness * 100 +
137
158
  dimensions.usage * SCORE_WEIGHTS.usage * 100 +
138
159
  dimensions.quality * SCORE_WEIGHTS.quality * 100 +
139
160
  dimensions.authority * SCORE_WEIGHTS.authority * 100);
140
- const level = DecayDetector.#scoreToLevel(decayScore);
161
+ const level = _a.#scoreToLevel(decayScore);
141
162
  const suggestedGracePeriod = level === 'dead' ? 0 : level === 'severe' ? GRACE_PERIOD_SEVERE : GRACE_PERIOD_STANDARD;
142
163
  return {
143
164
  recipeId: recipe.id,
@@ -153,19 +174,22 @@ export class DecayDetector {
153
174
  #loadActiveRecipes() {
154
175
  try {
155
176
  const rows = this.#db
156
- .prepare(`SELECT id, title, lifecycle, stats, quality_grade, quality_score, created_at
177
+ .prepare(`SELECT id, title, lifecycle, stats, quality, createdAt
157
178
  FROM knowledge_entries
158
179
  WHERE lifecycle = 'active'`)
159
180
  .all();
160
- return rows.map((r) => ({
161
- id: r.id,
162
- title: r.title,
163
- lifecycle: r.lifecycle,
164
- stats: r.stats ?? null,
165
- quality_grade: r.quality_grade ?? null,
166
- quality_score: r.quality_score !== undefined ? Number(r.quality_score) : null,
167
- created_at: r.created_at ?? null,
168
- }));
181
+ return rows.map((r) => {
182
+ const qualityObj = _a.#parseQuality(r.quality);
183
+ return {
184
+ id: r.id,
185
+ title: r.title,
186
+ lifecycle: r.lifecycle,
187
+ stats: r.stats ?? null,
188
+ quality_grade: qualityObj.grade,
189
+ quality_score: qualityObj.score,
190
+ created_at: r.createdAt !== undefined ? Number(r.createdAt) : null,
191
+ };
192
+ });
169
193
  }
170
194
  catch {
171
195
  return [];
@@ -182,17 +206,35 @@ export class DecayDetector {
182
206
  return {};
183
207
  }
184
208
  }
185
- #computeScoreDimensions(stats, recipe) {
209
+ static #parseQuality(qualityJson) {
210
+ if (!qualityJson) {
211
+ return { grade: null, score: null };
212
+ }
213
+ try {
214
+ const obj = JSON.parse(qualityJson);
215
+ return {
216
+ grade: typeof obj.grade === 'string' ? obj.grade : null,
217
+ score: typeof obj.overall === 'number' ? obj.overall : null,
218
+ };
219
+ }
220
+ catch {
221
+ return { grade: null, score: null };
222
+ }
223
+ }
224
+ #computeScoreDimensions(stats, recipe, context = {}) {
186
225
  const now = Date.now();
187
226
  // freshness: days since last hit → 0-1 (0 = 365+ days, 1 = today)
188
227
  const lastHit = stats.lastHitAt ?? 0;
189
- const daysSinceHit = lastHit > 0 ? (now - lastHit) / DAY_MS : 365;
228
+ const daysSinceHit = lastHit > 0 ? (now - toMs(lastHit)) / DAY_MS : 365;
190
229
  const freshness = Math.max(0, 1 - daysSinceHit / 365);
191
230
  // usage: hitsLast90d 归一化 (0 = 0 hits, 1 = 50+ hits)
192
231
  const hitsLast90d = stats.hitsLast90d ?? 0;
193
232
  const usage = Math.min(1, hitsLast90d / 50);
194
- // quality: qualityScore 直接使用
195
- const quality = recipe.quality_score ?? 0.5;
233
+ // quality: qualityScore × sourceRef 健康度
234
+ // staleRatio 对 quality 打折,最多压低 30%(全部 stale → ×0.7)
235
+ const baseQuality = recipe.quality_score ?? 0.5;
236
+ const staleRatio = context.staleRatio ?? 0;
237
+ const quality = baseQuality * (1 - staleRatio * 0.3);
196
238
  // authority: from stats.authority 归一化 (0-100 → 0-1)
197
239
  const authorityRaw = stats.authority ?? 50;
198
240
  const authority = Math.min(1, authorityRaw / 100);
@@ -213,6 +255,38 @@ export class DecayDetector {
213
255
  return false;
214
256
  }
215
257
  }
258
+ #getStaleSourceRefCount(recipeId) {
259
+ try {
260
+ const row = this.#db
261
+ .prepare(`SELECT COUNT(*) AS cnt FROM recipe_source_refs
262
+ WHERE recipe_id = ? AND status = 'stale'`)
263
+ .get(recipeId);
264
+ return row?.cnt ?? 0;
265
+ }
266
+ catch {
267
+ // recipe_source_refs 表可能不存在
268
+ return 0;
269
+ }
270
+ }
271
+ /** stale / total 比率(0-1),无 ref 时返回 0(无惩罚) */
272
+ #getSourceRefStaleRatio(recipeId) {
273
+ try {
274
+ const row = this.#db
275
+ .prepare(`SELECT
276
+ SUM(CASE WHEN status = 'stale' THEN 1 ELSE 0 END) AS stale,
277
+ COUNT(*) AS total
278
+ FROM recipe_source_refs
279
+ WHERE recipe_id = ?`)
280
+ .get(recipeId);
281
+ if (!row || row.total === 0) {
282
+ return 0;
283
+ }
284
+ return row.stale / row.total;
285
+ }
286
+ catch {
287
+ return 0;
288
+ }
289
+ }
216
290
  #isSuperseded(recipeId) {
217
291
  try {
218
292
  const row = this.#db
@@ -242,3 +316,4 @@ export class DecayDetector {
242
316
  return 'dead';
243
317
  }
244
318
  }
319
+ _a = DecayDetector;
@@ -12,10 +12,11 @@
12
12
  */
13
13
  import type { ReportStore } from '../../infrastructure/report/ReportStore.js';
14
14
  import type { SignalBus } from '../../infrastructure/signal/SignalBus.js';
15
+ import type { ProposalRepository } from '../../repository/evolution/ProposalRepository.js';
15
16
  import type { ContradictionDetector, ContradictionResult } from './ContradictionDetector.js';
16
17
  import type { DecayDetector, DecayScoreResult } from './DecayDetector.js';
17
18
  import type { RedundancyAnalyzer, RedundancyResult } from './RedundancyAnalyzer.js';
18
- export type ProposalType = 'merge' | 'enhance' | 'refine_guard' | 'split' | 'deprecate' | 'review';
19
+ export type ProposalType = 'merge' | 'enhance' | 'deprecate' | 'contradiction' | 'correction';
19
20
  export interface EvolutionProposal {
20
21
  /** 进化提案类型 */
21
22
  type: ProposalType;
@@ -33,7 +34,7 @@ export interface EvolutionProposal {
33
34
  evidence: string[];
34
35
  /** 创建时间 */
35
36
  proposedAt: number;
36
- /** 过期时间 (proposedAt + 7d) */
37
+ /** 过期时间 */
37
38
  expiresAt: number;
38
39
  }
39
40
  export interface MetabolismReport {
@@ -62,6 +63,7 @@ export declare class KnowledgeMetabolism {
62
63
  decayDetector: DecayDetector;
63
64
  signalBus?: SignalBus;
64
65
  reportStore?: ReportStore;
66
+ proposalRepository?: ProposalRepository;
65
67
  });
66
68
  /**
67
69
  * 执行完整治理周期
@@ -20,6 +20,7 @@ export class KnowledgeMetabolism {
20
20
  #decayDetector;
21
21
  #signalBus;
22
22
  #reportStore;
23
+ #proposalRepo;
23
24
  #logger = Logger.getInstance();
24
25
  #pendingTriggers = [];
25
26
  #debounceTimer = null;
@@ -29,6 +30,7 @@ export class KnowledgeMetabolism {
29
30
  this.#decayDetector = options.decayDetector;
30
31
  this.#signalBus = options.signalBus ?? null;
31
32
  this.#reportStore = options.reportStore ?? null;
33
+ this.#proposalRepo = options.proposalRepository ?? null;
32
34
  // Phase 2: 订阅告警型信号,触发代谢周期
33
35
  if (this.#signalBus) {
34
36
  this.#signalBus.subscribe('decay|quality|anomaly', (signal) => {
@@ -66,7 +68,31 @@ export class KnowledgeMetabolism {
66
68
  ...this.#proposalsFromRedundancies(redundancies),
67
69
  ...this.#proposalsFromDecay(decayResults),
68
70
  ];
69
- // 5. 写入治理报告(降级:不再发射 Signal,改为 Report)
71
+ // 5. 持久化提案到 evolution_proposals
72
+ let persistedCount = 0;
73
+ if (this.#proposalRepo && proposals.length > 0) {
74
+ for (const p of proposals) {
75
+ const sourceMap = {
76
+ contradiction: 'metabolism',
77
+ redundancy: 'metabolism',
78
+ decay: 'decay-scan',
79
+ enhancement: 'metabolism',
80
+ };
81
+ const record = this.#proposalRepo.create({
82
+ type: p.type,
83
+ targetRecipeId: p.targetRecipeId,
84
+ relatedRecipeIds: p.relatedRecipeIds,
85
+ confidence: p.confidence,
86
+ source: sourceMap[p.source] ?? 'metabolism',
87
+ description: p.description,
88
+ evidence: p.evidence.map((e) => ({ detail: e })),
89
+ });
90
+ if (record) {
91
+ persistedCount++;
92
+ }
93
+ }
94
+ }
95
+ // 6. 写入治理报告(降级:同时写 ReportStore)
70
96
  if (this.#reportStore && proposals.length > 0) {
71
97
  void this.#reportStore.write({
72
98
  category: 'governance',
@@ -74,6 +100,7 @@ export class KnowledgeMetabolism {
74
100
  producer: 'KnowledgeMetabolism',
75
101
  data: {
76
102
  proposalCount: proposals.length,
103
+ persistedCount,
77
104
  contradictionCount: contradictions.length,
78
105
  redundancyCount: redundancies.length,
79
106
  decayingCount: decayResults.filter((d) => d.level !== 'healthy' && d.level !== 'watch')
@@ -121,7 +148,7 @@ export class KnowledgeMetabolism {
121
148
  #proposalsFromContradictions(results) {
122
149
  const now = Date.now();
123
150
  return results.map((r) => ({
124
- type: r.type === 'hard' ? 'merge' : 'review',
151
+ type: r.type === 'hard' ? 'contradiction' : 'correction',
125
152
  targetRecipeId: r.recipeA,
126
153
  relatedRecipeIds: [r.recipeB],
127
154
  confidence: r.confidence,
@@ -0,0 +1,62 @@
1
+ /**
2
+ * ProposalExecutor — 到期自动执行引擎
3
+ *
4
+ * 核心职责:
5
+ * 1. 扫描所有 observing 状态的 Proposal,检查是否到期
6
+ * 2. 到期 → 收集观察期表现数据 → 评估执行判据
7
+ * 3. 通过 → 执行操作(merge/deprecate/enhance/...)
8
+ * 4. 不通过 → 拒绝 Proposal,Recipe 恢复原状态
9
+ *
10
+ * 触发时机:UiStartupTasks Stage 5
11
+ *
12
+ * 安全边界:
13
+ * - Agent 只做分析,ProposalExecutor 做执行
14
+ * - merge/enhance 执行后 Recipe → staging(走正常路径)
15
+ * - contradiction/reorganize 始终等开发者确认(不自动执行)
16
+ * - 到期无判据 → expired
17
+ */
18
+ import type { SignalBus } from '../../infrastructure/signal/SignalBus.js';
19
+ import type { ProposalRepository, ProposalType } from '../../repository/evolution/ProposalRepository.js';
20
+ interface DatabaseLike {
21
+ prepare(sql: string): {
22
+ all(...params: unknown[]): Record<string, unknown>[];
23
+ get(...params: unknown[]): Record<string, unknown> | undefined;
24
+ run(...params: unknown[]): {
25
+ changes: number;
26
+ };
27
+ };
28
+ }
29
+ export interface ProposalExecutionResult {
30
+ executed: {
31
+ id: string;
32
+ type: ProposalType;
33
+ targetRecipeId: string;
34
+ }[];
35
+ rejected: {
36
+ id: string;
37
+ type: ProposalType;
38
+ reason: string;
39
+ }[];
40
+ expired: {
41
+ id: string;
42
+ type: ProposalType;
43
+ }[];
44
+ skipped: {
45
+ id: string;
46
+ type: ProposalType;
47
+ reason: string;
48
+ }[];
49
+ }
50
+ export declare class ProposalExecutor {
51
+ #private;
52
+ constructor(db: DatabaseLike, repo: ProposalRepository, options?: {
53
+ signalBus?: SignalBus;
54
+ });
55
+ /**
56
+ * 定期调用(UiStartupTasks Stage 5)
57
+ *
58
+ * 扫描所有到期 Proposal → 评估 → 执行/拒绝/过期
59
+ */
60
+ checkAndExecute(): ProposalExecutionResult;
61
+ }
62
+ export {};