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.
- package/README.md +176 -81
- package/config/constitution.yaml +2 -0
- package/dist/lib/cli/KnowledgeSyncService.d.ts +5 -1
- package/dist/lib/cli/KnowledgeSyncService.js +5 -2
- package/dist/lib/domain/knowledge/values/Stats.d.ts +1 -1
- package/dist/lib/domain/knowledge/values/Stats.js +2 -2
- package/dist/lib/external/mcp/handlers/consolidated.js +178 -0
- package/dist/lib/external/mcp/handlers/task.js +36 -14
- package/dist/lib/external/mcp/tools.js +2 -1
- package/dist/lib/injection/modules/InfraModule.js +4 -1
- package/dist/lib/injection/modules/KnowledgeModule.js +23 -0
- package/dist/lib/repository/evolution/ProposalRepository.d.ts +99 -0
- package/dist/lib/repository/evolution/ProposalRepository.js +255 -0
- package/dist/lib/service/bootstrap/UiStartupTasks.d.ts +17 -4
- package/dist/lib/service/bootstrap/UiStartupTasks.js +53 -5
- package/dist/lib/service/evolution/DecayDetector.d.ts +4 -3
- package/dist/lib/service/evolution/DecayDetector.js +97 -22
- package/dist/lib/service/evolution/KnowledgeMetabolism.d.ts +4 -2
- package/dist/lib/service/evolution/KnowledgeMetabolism.js +29 -2
- package/dist/lib/service/evolution/ProposalExecutor.d.ts +62 -0
- package/dist/lib/service/evolution/ProposalExecutor.js +360 -0
- package/dist/lib/service/evolution/StagingManager.js +5 -3
- package/dist/lib/service/guard/GuardCrossFileChecks.js +2 -0
- package/dist/lib/service/guard/ReverseGuard.d.ts +1 -1
- package/dist/lib/service/guard/ReverseGuard.js +32 -2
- package/dist/lib/service/knowledge/SourceRefReconciler.d.ts +2 -0
- package/dist/lib/service/knowledge/SourceRefReconciler.js +48 -0
- package/dist/lib/service/task/IntentExtractor.d.ts +3 -1
- package/dist/lib/service/task/IntentExtractor.js +30 -10
- package/dist/lib/service/task/PrimeSearchPipeline.js +67 -12
- package/dist/lib/shared/schemas/mcp-tools.d.ts +2 -0
- package/dist/lib/shared/schemas/mcp-tools.js +9 -1
- package/package.json +1 -1
- package/templates/instructions/conventions.md +4 -2
|
@@ -0,0 +1,255 @@
|
|
|
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
|
+
import { randomBytes } from 'node:crypto';
|
|
13
|
+
/* ────────────────────── Constants ────────────────────── */
|
|
14
|
+
/** 默认观察窗口:7 天 */
|
|
15
|
+
const DEFAULT_OBSERVATION_WINDOW = 7 * 24 * 60 * 60 * 1000;
|
|
16
|
+
/** 各 Proposal 类型的默认观察窗口(ms) */
|
|
17
|
+
const OBSERVATION_WINDOWS = {
|
|
18
|
+
enhance: 48 * 60 * 60 * 1000, // 48h
|
|
19
|
+
correction: 24 * 60 * 60 * 1000, // 24h
|
|
20
|
+
merge: 72 * 60 * 60 * 1000, // 72h
|
|
21
|
+
supersede: 72 * 60 * 60 * 1000, // 72h
|
|
22
|
+
deprecate: 7 * 24 * 60 * 60 * 1000, // 7d
|
|
23
|
+
contradiction: 7 * 24 * 60 * 60 * 1000, // 7d
|
|
24
|
+
reorganize: 7 * 24 * 60 * 60 * 1000, // 7d
|
|
25
|
+
};
|
|
26
|
+
/** 自动进入观察状态的置信度阈值 */
|
|
27
|
+
const AUTO_OBSERVE_THRESHOLDS = {
|
|
28
|
+
enhance: 0.7,
|
|
29
|
+
correction: 0.7,
|
|
30
|
+
merge: 0.75,
|
|
31
|
+
supersede: 0.8,
|
|
32
|
+
deprecate: 0.0, // decayScore ≤ 40 即可
|
|
33
|
+
contradiction: Infinity, // 需开发者确认
|
|
34
|
+
reorganize: Infinity, // 需开发者确认
|
|
35
|
+
};
|
|
36
|
+
/* ────────────────────── Class ────────────────────── */
|
|
37
|
+
export class ProposalRepository {
|
|
38
|
+
#db;
|
|
39
|
+
constructor(db) {
|
|
40
|
+
this.#db = db;
|
|
41
|
+
}
|
|
42
|
+
/* ═══════════════════ Create ═══════════════════ */
|
|
43
|
+
/**
|
|
44
|
+
* 创建 Proposal 并写入 DB。
|
|
45
|
+
*
|
|
46
|
+
* - 自动生成 ID(ep-{timestamp}-{random})
|
|
47
|
+
* - 自动设定 expiresAt(按 type 默认窗口)
|
|
48
|
+
* - 自动判断 status(低风险 + 高置信度 → observing,否则 pending)
|
|
49
|
+
* - 去重:同 target + 同 type 已有 pending/observing 时拒绝创建
|
|
50
|
+
*/
|
|
51
|
+
create(input) {
|
|
52
|
+
const now = Date.now();
|
|
53
|
+
// 去重检查
|
|
54
|
+
if (this.#hasDuplicate(input.targetRecipeId, input.type)) {
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
const id = ProposalRepository.#generateId(now);
|
|
58
|
+
const expiresAt = input.expiresAt ?? now + (OBSERVATION_WINDOWS[input.type] ?? DEFAULT_OBSERVATION_WINDOW);
|
|
59
|
+
const status = input.status ?? this.#resolveInitialStatus(input.type, input.confidence);
|
|
60
|
+
const record = {
|
|
61
|
+
id,
|
|
62
|
+
type: input.type,
|
|
63
|
+
targetRecipeId: input.targetRecipeId,
|
|
64
|
+
relatedRecipeIds: input.relatedRecipeIds ?? [],
|
|
65
|
+
confidence: input.confidence,
|
|
66
|
+
source: input.source,
|
|
67
|
+
description: input.description,
|
|
68
|
+
evidence: input.evidence ?? [],
|
|
69
|
+
status,
|
|
70
|
+
proposedAt: now,
|
|
71
|
+
expiresAt,
|
|
72
|
+
resolvedAt: null,
|
|
73
|
+
resolvedBy: null,
|
|
74
|
+
resolution: null,
|
|
75
|
+
};
|
|
76
|
+
this.#db
|
|
77
|
+
.prepare(`INSERT INTO evolution_proposals
|
|
78
|
+
(id, type, target_recipe_id, related_recipe_ids, confidence, source, description, evidence, status, proposed_at, expires_at)
|
|
79
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
|
|
80
|
+
.run(record.id, record.type, record.targetRecipeId, JSON.stringify(record.relatedRecipeIds), record.confidence, record.source, record.description, JSON.stringify(record.evidence), record.status, record.proposedAt, record.expiresAt);
|
|
81
|
+
return record;
|
|
82
|
+
}
|
|
83
|
+
/* ═══════════════════ Read ═══════════════════ */
|
|
84
|
+
/** 按 ID 查询 */
|
|
85
|
+
findById(id) {
|
|
86
|
+
const row = this.#db.prepare(`SELECT * FROM evolution_proposals WHERE id = ?`).get(id);
|
|
87
|
+
return row ? ProposalRepository.#mapRow(row) : null;
|
|
88
|
+
}
|
|
89
|
+
/** 按条件查询 */
|
|
90
|
+
find(filter = {}) {
|
|
91
|
+
const conditions = [];
|
|
92
|
+
const params = [];
|
|
93
|
+
if (filter.status) {
|
|
94
|
+
if (Array.isArray(filter.status)) {
|
|
95
|
+
const placeholders = filter.status.map(() => '?').join(', ');
|
|
96
|
+
conditions.push(`status IN (${placeholders})`);
|
|
97
|
+
params.push(...filter.status);
|
|
98
|
+
}
|
|
99
|
+
else {
|
|
100
|
+
conditions.push('status = ?');
|
|
101
|
+
params.push(filter.status);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
if (filter.type) {
|
|
105
|
+
conditions.push('type = ?');
|
|
106
|
+
params.push(filter.type);
|
|
107
|
+
}
|
|
108
|
+
if (filter.targetRecipeId) {
|
|
109
|
+
conditions.push('target_recipe_id = ?');
|
|
110
|
+
params.push(filter.targetRecipeId);
|
|
111
|
+
}
|
|
112
|
+
if (filter.source) {
|
|
113
|
+
conditions.push('source = ?');
|
|
114
|
+
params.push(filter.source);
|
|
115
|
+
}
|
|
116
|
+
if (filter.expiredBefore) {
|
|
117
|
+
conditions.push('expires_at <= ?');
|
|
118
|
+
params.push(filter.expiredBefore);
|
|
119
|
+
}
|
|
120
|
+
const where = conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : '';
|
|
121
|
+
const rows = this.#db
|
|
122
|
+
.prepare(`SELECT * FROM evolution_proposals ${where} ORDER BY proposed_at DESC`)
|
|
123
|
+
.all(...params);
|
|
124
|
+
return rows.map(ProposalRepository.#mapRow);
|
|
125
|
+
}
|
|
126
|
+
/** 查询已到期的 observing 状态 Proposal */
|
|
127
|
+
findExpiredObserving() {
|
|
128
|
+
return this.find({
|
|
129
|
+
status: 'observing',
|
|
130
|
+
expiredBefore: Date.now(),
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
/** 查询所有未完成的 Proposal(pending + observing) */
|
|
134
|
+
findActive() {
|
|
135
|
+
return this.find({
|
|
136
|
+
status: ['pending', 'observing'],
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
/** 按 target Recipe ID 查询活跃 Proposal */
|
|
140
|
+
findByTarget(targetRecipeId) {
|
|
141
|
+
return this.find({
|
|
142
|
+
targetRecipeId,
|
|
143
|
+
status: ['pending', 'observing'],
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
/* ═══════════════════ Update ═══════════════════ */
|
|
147
|
+
/** 将 Proposal 状态转为 observing */
|
|
148
|
+
startObserving(id) {
|
|
149
|
+
const now = Date.now();
|
|
150
|
+
const proposal = this.findById(id);
|
|
151
|
+
if (!proposal || proposal.status !== 'pending') {
|
|
152
|
+
return false;
|
|
153
|
+
}
|
|
154
|
+
const expiresAt = now + (OBSERVATION_WINDOWS[proposal.type] ?? DEFAULT_OBSERVATION_WINDOW);
|
|
155
|
+
const result = this.#db
|
|
156
|
+
.prepare(`UPDATE evolution_proposals SET status = 'observing', expires_at = ? WHERE id = ? AND status = 'pending'`)
|
|
157
|
+
.run(expiresAt, id);
|
|
158
|
+
return result.changes > 0;
|
|
159
|
+
}
|
|
160
|
+
/** 标记 Proposal 为已执行 */
|
|
161
|
+
markExecuted(id, resolution, resolvedBy = 'auto') {
|
|
162
|
+
const result = this.#db
|
|
163
|
+
.prepare(`UPDATE evolution_proposals SET status = 'executed', resolved_at = ?, resolved_by = ?, resolution = ? WHERE id = ? AND status = 'observing'`)
|
|
164
|
+
.run(Date.now(), resolvedBy, resolution, id);
|
|
165
|
+
return result.changes > 0;
|
|
166
|
+
}
|
|
167
|
+
/** 标记 Proposal 为已拒绝 */
|
|
168
|
+
markRejected(id, resolution, resolvedBy = 'auto') {
|
|
169
|
+
const result = this.#db
|
|
170
|
+
.prepare(`UPDATE evolution_proposals SET status = 'rejected', resolved_at = ?, resolved_by = ?, resolution = ? WHERE id = ? AND status IN ('pending', 'observing')`)
|
|
171
|
+
.run(Date.now(), resolvedBy, resolution, id);
|
|
172
|
+
return result.changes > 0;
|
|
173
|
+
}
|
|
174
|
+
/** 标记 Proposal 为过期 */
|
|
175
|
+
markExpired(id) {
|
|
176
|
+
const result = this.#db
|
|
177
|
+
.prepare(`UPDATE evolution_proposals SET status = 'expired', resolved_at = ? WHERE id = ? AND status IN ('pending', 'observing')`)
|
|
178
|
+
.run(Date.now(), id);
|
|
179
|
+
return result.changes > 0;
|
|
180
|
+
}
|
|
181
|
+
/** 更新 evidence(用于追加观察期指标快照) */
|
|
182
|
+
updateEvidence(id, evidence) {
|
|
183
|
+
const result = this.#db
|
|
184
|
+
.prepare(`UPDATE evolution_proposals SET evidence = ? WHERE id = ?`)
|
|
185
|
+
.run(JSON.stringify(evidence), id);
|
|
186
|
+
return result.changes > 0;
|
|
187
|
+
}
|
|
188
|
+
/* ═══════════════════ Stats ═══════════════════ */
|
|
189
|
+
/** 统计各状态的 Proposal 数量 */
|
|
190
|
+
stats() {
|
|
191
|
+
const rows = this.#db
|
|
192
|
+
.prepare(`SELECT status, COUNT(*) as count FROM evolution_proposals GROUP BY status`)
|
|
193
|
+
.all();
|
|
194
|
+
const result = {
|
|
195
|
+
pending: 0,
|
|
196
|
+
observing: 0,
|
|
197
|
+
executed: 0,
|
|
198
|
+
rejected: 0,
|
|
199
|
+
expired: 0,
|
|
200
|
+
};
|
|
201
|
+
for (const row of rows) {
|
|
202
|
+
result[row.status] = row.count;
|
|
203
|
+
}
|
|
204
|
+
return result;
|
|
205
|
+
}
|
|
206
|
+
/* ═══════════════════ Private ═══════════════════ */
|
|
207
|
+
/** 去重检查:同 target + 同 type 是否已有 pending/observing Proposal */
|
|
208
|
+
#hasDuplicate(targetRecipeId, type) {
|
|
209
|
+
const row = this.#db
|
|
210
|
+
.prepare(`SELECT 1 FROM evolution_proposals WHERE target_recipe_id = ? AND type = ? AND status IN ('pending', 'observing') LIMIT 1`)
|
|
211
|
+
.get(targetRecipeId, type);
|
|
212
|
+
return row !== undefined;
|
|
213
|
+
}
|
|
214
|
+
/** 根据 type + confidence 判断初始状态 */
|
|
215
|
+
#resolveInitialStatus(type, confidence) {
|
|
216
|
+
const threshold = AUTO_OBSERVE_THRESHOLDS[type];
|
|
217
|
+
return confidence >= threshold ? 'observing' : 'pending';
|
|
218
|
+
}
|
|
219
|
+
/** 生成 Proposal ID */
|
|
220
|
+
static #generateId(timestamp) {
|
|
221
|
+
const rand = randomBytes(4).toString('hex');
|
|
222
|
+
return `ep-${timestamp}-${rand}`;
|
|
223
|
+
}
|
|
224
|
+
/** DB 行 → ProposalRecord */
|
|
225
|
+
static #mapRow(row) {
|
|
226
|
+
return {
|
|
227
|
+
id: row.id,
|
|
228
|
+
type: row.type,
|
|
229
|
+
targetRecipeId: row.target_recipe_id,
|
|
230
|
+
relatedRecipeIds: safeJsonParse(row.related_recipe_ids, []),
|
|
231
|
+
confidence: row.confidence,
|
|
232
|
+
source: row.source,
|
|
233
|
+
description: row.description,
|
|
234
|
+
evidence: safeJsonParse(row.evidence, []),
|
|
235
|
+
status: row.status,
|
|
236
|
+
proposedAt: row.proposed_at,
|
|
237
|
+
expiresAt: row.expires_at,
|
|
238
|
+
resolvedAt: row.resolved_at ?? null,
|
|
239
|
+
resolvedBy: row.resolved_by ?? null,
|
|
240
|
+
resolution: row.resolution ?? null,
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
/* ────────────────────── Util ────────────────────── */
|
|
245
|
+
function safeJsonParse(json, fallback) {
|
|
246
|
+
if (!json) {
|
|
247
|
+
return fallback;
|
|
248
|
+
}
|
|
249
|
+
try {
|
|
250
|
+
return JSON.parse(json);
|
|
251
|
+
}
|
|
252
|
+
catch {
|
|
253
|
+
return fallback;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
@@ -2,10 +2,12 @@
|
|
|
2
2
|
* UiStartupTasks — asd ui 启动后异步后台刷新任务
|
|
3
3
|
*
|
|
4
4
|
* 在 Dashboard 启动后异步执行,不阻塞 UI:
|
|
5
|
-
* 1. syncAll:
|
|
6
|
-
* 2. staging promote:
|
|
7
|
-
* 3. vector reconcile:
|
|
8
|
-
* 4. refreshIndex:
|
|
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:
|
|
6
|
-
* 2. staging promote:
|
|
7
|
-
* 3. vector reconcile:
|
|
8
|
-
* 4. refreshIndex:
|
|
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
|
|
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
|
-
*
|
|
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:
|
|
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
|
-
*
|
|
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 =
|
|
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 =
|
|
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
|
|
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 =
|
|
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,
|
|
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
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
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
|
-
#
|
|
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
|
-
|
|
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' | '
|
|
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
|
-
/** 过期时间
|
|
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
|
* 执行完整治理周期
|