autosnippet 3.2.8 → 3.2.9
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/bin/cli.js +6 -5
- package/dashboard/dist/assets/index-BTAsOZv2.js +128 -0
- package/dashboard/dist/assets/index-C_72Ct98.css +1 -0
- package/dashboard/dist/index.html +2 -2
- package/lib/cli/AiScanService.js +23 -26
- package/lib/cli/SetupService.js +1 -1
- package/lib/core/AstAnalyzer.js +1 -1
- package/lib/core/discovery/index.js +2 -2
- package/lib/external/ai/AiProvider.js +66 -172
- package/lib/external/ai/providers/GoogleGeminiProvider.js +23 -1
- package/lib/external/mcp/handlers/bootstrap/BootstrapSession.js +1 -1
- package/lib/external/mcp/handlers/bootstrap/ExternalSubmissionTracker.js +3 -3
- package/lib/external/mcp/handlers/bootstrap/MissionBriefingBuilder.js +1 -1
- package/lib/external/mcp/handlers/bootstrap/pipeline/IncrementalBootstrap.js +1 -1
- package/lib/external/mcp/handlers/bootstrap/pipeline/dimension-context.js +8 -8
- package/lib/external/mcp/handlers/bootstrap/pipeline/noAiFallback.js +1 -1
- package/lib/external/mcp/handlers/bootstrap/pipeline/orchestrator.js +287 -204
- package/lib/external/mcp/handlers/bootstrap/shared/bootstrap-phases.js +7 -6
- package/lib/external/mcp/handlers/bootstrap/shared/dimension-sop.js +1 -1
- package/lib/external/mcp/handlers/bootstrap-internal.js +2 -2
- package/lib/external/mcp/handlers/dimension-complete-external.js +6 -6
- package/lib/http/HttpServer.js +1 -1
- package/lib/http/middleware/requestLogger.js +1 -0
- package/lib/http/routes/ai.js +240 -35
- package/lib/http/routes/candidates.js +2 -3
- package/lib/http/routes/extract.js +13 -11
- package/lib/http/routes/modules.js +2 -2
- package/lib/http/routes/recipes.js +9 -5
- package/lib/http/routes/remote.js +134 -255
- package/lib/http/routes/violations.js +0 -54
- package/lib/http/utils/sse-sessions.js +1 -1
- package/lib/infrastructure/logging/Logger.js +5 -4
- package/lib/infrastructure/monitoring/PerformanceMonitor.js +3 -2
- package/lib/injection/ServiceContainer.js +64 -17
- package/lib/platform/ScreenCaptureService.js +177 -0
- package/lib/platform/ios/routes/spm.js +2 -2
- package/lib/service/agent/AgentEventBus.js +207 -0
- package/lib/service/agent/AgentFactory.js +490 -0
- package/lib/service/agent/AgentMessage.js +240 -0
- package/lib/service/agent/AgentRouter.js +228 -0
- package/lib/service/agent/AgentRuntime.js +1016 -0
- package/lib/service/agent/AgentState.js +217 -0
- package/lib/service/agent/IntentClassifier.js +331 -0
- package/lib/service/agent/LarkTransport.js +389 -0
- package/lib/service/agent/capabilities.js +408 -0
- package/lib/service/{chat → agent/context}/ContextWindow.js +37 -12
- package/lib/service/{chat → agent/context}/ExplorationTracker.js +25 -14
- package/lib/service/{chat → agent/core}/ChatAgentPrompts.js +1 -1
- package/lib/service/agent/core/LoopContext.js +170 -0
- package/lib/service/agent/core/MessageAdapter.js +223 -0
- package/lib/service/agent/core/ToolExecutionPipeline.js +376 -0
- package/lib/service/{chat → agent/domain}/ChatAgentTasks.js +19 -98
- package/lib/service/{chat → agent/domain}/EpisodicConsolidator.js +7 -7
- package/lib/service/{chat → agent/domain}/EvidenceCollector.js +4 -2
- package/lib/service/{chat/AnalystAgent.js → agent/domain/insight-analyst.js} +37 -172
- package/lib/service/{chat/HandoffProtocol.js → agent/domain/insight-gate.js} +85 -135
- package/lib/service/agent/domain/insight-producer.js +267 -0
- package/lib/service/agent/domain/scan-prompts.js +105 -0
- package/lib/service/agent/forced-summary.js +266 -0
- package/lib/service/agent/index.js +91 -0
- package/lib/service/{chat → agent}/memory/MemoryCoordinator.js +7 -7
- package/lib/service/{chat/ProjectSemanticMemory.js → agent/memory/PersistentMemory.js} +359 -89
- package/lib/service/{chat → agent}/memory/SessionStore.js +1 -1
- package/lib/service/{chat → agent}/memory/index.js +1 -1
- package/lib/service/agent/policies.js +442 -0
- package/lib/service/agent/presets.js +303 -0
- package/lib/service/agent/strategies.js +717 -0
- package/lib/service/{chat → agent/tools}/ToolRegistry.js +3 -3
- package/lib/service/agent/tools/ai-analysis.js +75 -0
- package/lib/service/{chat → agent}/tools/composite.js +2 -1
- package/lib/service/{chat → agent}/tools/guard.js +1 -121
- package/lib/service/{chat → agent}/tools/index.js +27 -21
- package/lib/service/{chat → agent}/tools/infrastructure.js +1 -1
- package/lib/service/agent/tools/knowledge-graph.js +112 -0
- package/lib/service/agent/tools/scan-recipe.js +189 -0
- package/lib/service/agent/tools/system-interaction.js +476 -0
- package/lib/service/automation/DirectiveDetector.js +0 -1
- package/lib/service/automation/FileWatcher.js +0 -8
- package/lib/service/automation/handlers/CreateHandler.js +7 -3
- package/lib/service/automation/handlers/DraftHandler.js +7 -6
- package/lib/service/module/ModuleService.js +40 -73
- package/lib/service/skills/SignalCollector.js +26 -19
- package/lib/service/snippet/codecs/VSCodeCodec.js +1 -1
- package/lib/shared/FieldSpec.js +1 -1
- package/lib/shared/StyleGuide.js +1 -1
- package/package.json +4 -1
- package/resources/native-ui/screenshot.swift +228 -0
- package/dashboard/dist/assets/index-D5jiDBQG.css +0 -1
- package/dashboard/dist/assets/index-e5OKj-Ni.js +0 -128
- package/lib/core/discovery/SpmDiscoverer.js +0 -5
- package/lib/external/mcp/handlers/bootstrap/pipeline/EpisodicMemory.js +0 -750
- package/lib/external/mcp/handlers/bootstrap/pipeline/ToolResultCache.js +0 -277
- package/lib/http/routes/spm.js +0 -5
- package/lib/infrastructure/external/XcodeAutomation.js +0 -15
- package/lib/service/chat/ChatAgent.js +0 -1602
- package/lib/service/chat/Memory.js +0 -161
- package/lib/service/chat/ProducerAgent.js +0 -431
- package/lib/service/chat/ReasoningTrace.js +0 -523
- package/lib/service/chat/TaskPipeline.js +0 -357
- package/lib/service/chat/WorkingMemory.js +0 -359
- package/lib/service/chat/memory/PersistentMemory.js +0 -450
- package/lib/service/chat/tools/ai-analysis.js +0 -267
- package/lib/service/chat/tools/knowledge-graph.js +0 -234
- package/lib/service/chat/tools.js +0 -18
- package/lib/service/snippet/PlaceholderConverter.js +0 -5
- package/lib/service/snippet/codecs/XcodeCodec.js +0 -5
- /package/lib/service/{chat → agent}/ConversationStore.js +0 -0
- /package/lib/service/{chat → agent}/memory/ActiveContext.js +0 -0
- /package/lib/service/{chat → agent}/tools/_shared.js +0 -0
- /package/lib/service/{chat → agent}/tools/ast-graph.js +0 -0
- /package/lib/service/{chat → agent}/tools/lifecycle.js +0 -0
- /package/lib/service/{chat → agent}/tools/project-access.js +0 -0
- /package/lib/service/{chat → agent}/tools/query.js +0 -0
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
* 新代码请使用 `import { PersistentMemory } from './memory/PersistentMemory.js'`
|
|
4
|
-
* 本文件保留作为 PersistentMemory 的父类,暂不删除。
|
|
2
|
+
* PersistentMemory — 持久化语义记忆 (Tier 3)
|
|
5
3
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* 替代/增强 Memory.js 的 JSONL 存储,使用 SQLite 提供:
|
|
4
|
+
* 统一的项目级永久记忆存储,使用 SQLite 提供:
|
|
9
5
|
* - 重要性评分 (importance 1.0-10.0)
|
|
10
6
|
* - 综合检索 (recency × importance × relevance)
|
|
11
7
|
* - Extract-Update 模式固化 (ADD / UPDATE / MERGE / NOOP)
|
|
8
|
+
* - Mem0 风格冲突解决 (矛盾检测 + 自动替换)
|
|
12
9
|
* - TTL 自动过期 + 访问计数
|
|
13
10
|
* - 关联实体 + 关联记忆
|
|
14
11
|
* - 来源追溯 (sourceDimension + sourceEvidence)
|
|
12
|
+
* - Legacy JSONL 迁移
|
|
13
|
+
* - 向量嵌入预留接口
|
|
14
|
+
* - 预算感知 toPromptSection
|
|
15
15
|
*
|
|
16
16
|
* 记忆类型:
|
|
17
17
|
* - fact: 项目事实 ("使用 BD 前缀命名", "有 200 个 ObjC 文件")
|
|
@@ -23,13 +23,21 @@
|
|
|
23
23
|
* - user: 用户对话中产生
|
|
24
24
|
* - system: SignalCollector 等后台
|
|
25
25
|
*
|
|
26
|
-
*
|
|
26
|
+
* 算法参数 (Generative Agents 三维打分):
|
|
27
|
+
* - 检索权重: RECENCY=0.2, IMPORTANCE=0.3, RELEVANCE=0.5
|
|
28
|
+
* - 半衰期: RECENCY_HALF_LIFE_DAYS=7
|
|
29
|
+
* - 固化阈值: SIMILARITY_UPDATE=0.85 (同义→UPDATE), SIMILARITY_MERGE=0.6 (相关→MERGE)
|
|
30
|
+
* - 遗忘策略: ARCHIVE_DAYS=30 (降级), FORGET_DAYS=90 (删除), MAX_MEMORIES=500
|
|
31
|
+
*
|
|
32
|
+
* 兼容: 保留 toPromptSection() / load() / append() / size() 接口,可无缝替代 Memory.js
|
|
27
33
|
*
|
|
28
|
-
* @module
|
|
34
|
+
* @module PersistentMemory
|
|
29
35
|
*/
|
|
30
36
|
|
|
37
|
+
import fs from 'node:fs';
|
|
38
|
+
import path from 'node:path';
|
|
31
39
|
import { randomUUID } from 'node:crypto';
|
|
32
|
-
import { jaccardSimilarity, tokenizeForSimilarity } from '
|
|
40
|
+
import { jaccardSimilarity, tokenizeForSimilarity } from '../../../shared/similarity.js';
|
|
33
41
|
|
|
34
42
|
// ──────────────────────────────────────────────────────────────
|
|
35
43
|
// 常量
|
|
@@ -55,37 +63,71 @@ const SIMILARITY_UPDATE = 0.85; // ≥85% 同义 → UPDATE
|
|
|
55
63
|
const SIMILARITY_MERGE = 0.6; // ≥60% 相关 → MERGE
|
|
56
64
|
|
|
57
65
|
// ──────────────────────────────────────────────────────────────
|
|
58
|
-
//
|
|
66
|
+
// 矛盾检测模式 (Mem0 风格冲突解决)
|
|
67
|
+
// ──────────────────────────────────────────────────────────────
|
|
68
|
+
|
|
69
|
+
/** 中文否定/禁止模式 */
|
|
70
|
+
const NEGATION_PATTERNS_ZH =
|
|
71
|
+
/不(再)?使用|不(再)?用|禁止|废弃|移除|取消|停止|不要|不采用|弃用|淘汰/;
|
|
72
|
+
|
|
73
|
+
/** 英文否定/禁止模式 */
|
|
74
|
+
const NEGATION_PATTERNS_EN =
|
|
75
|
+
/\b(don'?t|do\s+not|never|no\s+longer|removed?|deprecated?|stop|avoid|disable|abandon|drop)\b/i;
|
|
76
|
+
|
|
77
|
+
/** 共享词语最少匹配数 — 用于判断两条记忆是否讨论同一主题 */
|
|
78
|
+
const MIN_TOPIC_OVERLAP_WORDS = 2;
|
|
79
|
+
|
|
80
|
+
/** 共享词语比例阈值 — 低于此值视为不同主题 */
|
|
81
|
+
const MIN_TOPIC_OVERLAP_RATIO = 0.3;
|
|
82
|
+
|
|
83
|
+
// ──────────────────────────────────────────────────────────────
|
|
84
|
+
// PersistentMemory
|
|
59
85
|
// ──────────────────────────────────────────────────────────────
|
|
60
86
|
|
|
61
|
-
export class
|
|
87
|
+
export class PersistentMemory {
|
|
62
88
|
/** @type {import('better-sqlite3').Database} */
|
|
63
89
|
#db;
|
|
64
90
|
|
|
65
|
-
/** @type {
|
|
91
|
+
/** @type {object|null} */
|
|
66
92
|
#logger;
|
|
67
93
|
|
|
68
|
-
|
|
94
|
+
/** 预编译 SQL Statements */
|
|
69
95
|
#stmts = null;
|
|
70
96
|
|
|
97
|
+
/**
|
|
98
|
+
* 向量嵌入函数 (预留接口)
|
|
99
|
+
*
|
|
100
|
+
* 签名: (queryText: string, contentText: string) => number (0.0-1.0)
|
|
101
|
+
* 当前: null → 使用 Jaccard + 子串匹配
|
|
102
|
+
* 未来: ADR-3 — 当嵌入模型可用时,通过 setEmbeddingFunction() 注入
|
|
103
|
+
*
|
|
104
|
+
* @type {Function|null}
|
|
105
|
+
*/
|
|
106
|
+
#embeddingFn;
|
|
107
|
+
|
|
71
108
|
/**
|
|
72
109
|
* @param {import('better-sqlite3').Database} db — better-sqlite3 实例
|
|
73
110
|
* @param {object} [opts]
|
|
74
|
-
* @param {object}
|
|
111
|
+
* @param {object} [opts.logger] — Logger 实例
|
|
112
|
+
* @param {Function} [opts.embeddingFn] — 向量嵌入函数 (预留)
|
|
75
113
|
*/
|
|
76
|
-
constructor(db,
|
|
114
|
+
constructor(db, opts = {}) {
|
|
115
|
+
const { logger, embeddingFn } = typeof opts === 'object' && opts !== null ? opts : {};
|
|
77
116
|
if (!db) {
|
|
78
|
-
throw new Error('
|
|
117
|
+
throw new Error('PersistentMemory requires a database instance');
|
|
79
118
|
}
|
|
80
119
|
this.#db = typeof db?.getDb === 'function' ? db.getDb() : db;
|
|
81
120
|
this.#logger = logger || null;
|
|
121
|
+
this.#embeddingFn = typeof embeddingFn === 'function' ? embeddingFn : null;
|
|
82
122
|
|
|
83
123
|
// 确保表存在 (如果 migration 未运行)
|
|
84
124
|
this.#ensureTable();
|
|
85
125
|
this.#prepareStatements();
|
|
86
126
|
}
|
|
87
127
|
|
|
88
|
-
//
|
|
128
|
+
// ═══════════════════════════════════════════════════════════
|
|
129
|
+
// 基本 CRUD
|
|
130
|
+
// ═══════════════════════════════════════════════════════════
|
|
89
131
|
|
|
90
132
|
/**
|
|
91
133
|
* 添加一条记忆
|
|
@@ -208,25 +250,32 @@ export class ProjectSemanticMemory {
|
|
|
208
250
|
return row ? this.#deserialize(row) : null;
|
|
209
251
|
}
|
|
210
252
|
|
|
211
|
-
//
|
|
253
|
+
// ═══════════════════════════════════════════════════════════
|
|
254
|
+
// 智能存储: Extract-Update Consolidation + 冲突预解决
|
|
255
|
+
// ═══════════════════════════════════════════════════════════
|
|
212
256
|
|
|
213
257
|
/**
|
|
214
|
-
* 智能固化:
|
|
258
|
+
* 智能固化: 先执行冲突检测 (Mem0 风格),再执行 ADD / UPDATE / MERGE / NOOP
|
|
215
259
|
*
|
|
216
|
-
*
|
|
217
|
-
* 1.
|
|
218
|
-
* 2.
|
|
260
|
+
* 冲突解决逻辑:
|
|
261
|
+
* 1. 对每条候选记忆,在现有库中搜索相似记忆
|
|
262
|
+
* 2. 如果发现同类型 + 矛盾内容 → 直接 REPLACE (新信息更可信)
|
|
263
|
+
* 3. 未冲突的候选走正常 ADD/UPDATE/MERGE 流程
|
|
219
264
|
*
|
|
220
265
|
* @param {Array<object>} candidateMemories — 候选记忆列表
|
|
221
266
|
* @param {object} [opts]
|
|
222
267
|
* @param {string} [opts.bootstrapSession] — Bootstrap session ID
|
|
223
|
-
* @returns {{ added: number, updated: number, merged: number, skipped: number }}
|
|
268
|
+
* @returns {{ added: number, updated: number, merged: number, skipped: number, replaced?: number }}
|
|
224
269
|
*/
|
|
225
270
|
consolidate(candidateMemories, { bootstrapSession } = {}) {
|
|
271
|
+
// Phase 1: 冲突预解决
|
|
272
|
+
const { processed, replaced } = this.#preResolveConflicts(candidateMemories);
|
|
273
|
+
|
|
274
|
+
// Phase 2: 正常 consolidate 流程
|
|
226
275
|
const stats = { added: 0, updated: 0, merged: 0, skipped: 0 };
|
|
227
276
|
|
|
228
277
|
const runConsolidate = this.#db.transaction(() => {
|
|
229
|
-
for (const candidate of
|
|
278
|
+
for (const candidate of processed) {
|
|
230
279
|
const content = (candidate.content || '').trim();
|
|
231
280
|
if (!content || content.length < 5) {
|
|
232
281
|
stats.skipped++;
|
|
@@ -285,10 +334,16 @@ export class ProjectSemanticMemory {
|
|
|
285
334
|
// 容量控制
|
|
286
335
|
this.#enforceCapacity();
|
|
287
336
|
|
|
337
|
+
if (replaced > 0) {
|
|
338
|
+
stats.replaced = replaced;
|
|
339
|
+
}
|
|
340
|
+
|
|
288
341
|
return stats;
|
|
289
342
|
}
|
|
290
343
|
|
|
291
|
-
//
|
|
344
|
+
// ═══════════════════════════════════════════════════════════
|
|
345
|
+
// 综合检索
|
|
346
|
+
// ═══════════════════════════════════════════════════════════
|
|
292
347
|
|
|
293
348
|
/**
|
|
294
349
|
* 综合检索: recency × importance × relevance
|
|
@@ -363,23 +418,29 @@ export class ProjectSemanticMemory {
|
|
|
363
418
|
return results.map((r) => this.#deserialize(r));
|
|
364
419
|
}
|
|
365
420
|
|
|
366
|
-
//
|
|
421
|
+
// ═══════════════════════════════════════════════════════════
|
|
422
|
+
// Prompt 生成 (兼容 Memory.js) — 预算感知
|
|
423
|
+
// ═══════════════════════════════════════════════════════════
|
|
367
424
|
|
|
368
425
|
/**
|
|
369
|
-
* 生成供系统提示词的记忆摘要
|
|
370
|
-
*
|
|
371
|
-
* 兼容 Memory.toPromptSection() 接口:
|
|
372
|
-
* memory.toPromptSection({ source: 'user' })
|
|
373
|
-
*
|
|
374
|
-
* 增强: 使用综合检索 (recency + importance + relevance)
|
|
426
|
+
* 生成供系统提示词的记忆摘要 (预算感知)
|
|
375
427
|
*
|
|
376
428
|
* @param {object} [opts]
|
|
377
429
|
* @param {string} [opts.source] — 过滤 source (user/system/bootstrap)
|
|
378
430
|
* @param {string} [opts.query] — 查询上下文 (用于 relevance 打分)
|
|
379
431
|
* @param {number} [opts.limit=15]
|
|
432
|
+
* @param {number} [opts.tokenBudget] — token 预算 (由 MemoryCoordinator 分配)
|
|
380
433
|
* @returns {string} Markdown 格式
|
|
381
434
|
*/
|
|
382
|
-
toPromptSection({ source, query, limit = 15 } = {}) {
|
|
435
|
+
toPromptSection({ source, query, limit = 15, tokenBudget } = {}) {
|
|
436
|
+
// 预算感知: 根据 tokenBudget 限制条数
|
|
437
|
+
if (tokenBudget && tokenBudget > 0) {
|
|
438
|
+
const EST_TOKENS_PER_MEMORY = 30;
|
|
439
|
+
const HEADER_TOKENS = 15;
|
|
440
|
+
const maxByBudget = Math.max(3, Math.floor((tokenBudget - HEADER_TOKENS) / EST_TOKENS_PER_MEMORY));
|
|
441
|
+
limit = Math.min(limit, maxByBudget);
|
|
442
|
+
}
|
|
443
|
+
|
|
383
444
|
let memories;
|
|
384
445
|
|
|
385
446
|
if (query) {
|
|
@@ -409,7 +470,9 @@ export class ProjectSemanticMemory {
|
|
|
409
470
|
return `\n## 项目记忆 (${memories.length} 条最相关)\n${lines.join('\n')}\n`;
|
|
410
471
|
}
|
|
411
472
|
|
|
412
|
-
//
|
|
473
|
+
// ═══════════════════════════════════════════════════════════
|
|
474
|
+
// Memory.js 兼容层
|
|
475
|
+
// ═══════════════════════════════════════════════════════════
|
|
413
476
|
|
|
414
477
|
/**
|
|
415
478
|
* 兼容 Memory.load() — 返回最近 N 条记忆
|
|
@@ -485,7 +548,9 @@ export class ProjectSemanticMemory {
|
|
|
485
548
|
return this.#db.prepare('SELECT COUNT(*) as cnt FROM semantic_memories').get()?.cnt || 0;
|
|
486
549
|
}
|
|
487
550
|
|
|
488
|
-
//
|
|
551
|
+
// ═══════════════════════════════════════════════════════════
|
|
552
|
+
// 维护: 过期清理 + 容量控制
|
|
553
|
+
// ═══════════════════════════════════════════════════════════
|
|
489
554
|
|
|
490
555
|
/**
|
|
491
556
|
* 执行维护: 清理过期记忆 + 容量控制
|
|
@@ -534,7 +599,9 @@ export class ProjectSemanticMemory {
|
|
|
534
599
|
return stats;
|
|
535
600
|
}
|
|
536
601
|
|
|
537
|
-
//
|
|
602
|
+
// ═══════════════════════════════════════════════════════════
|
|
603
|
+
// 统计
|
|
604
|
+
// ═══════════════════════════════════════════════════════════
|
|
538
605
|
|
|
539
606
|
/**
|
|
540
607
|
* 获取统计信息
|
|
@@ -571,7 +638,256 @@ export class ProjectSemanticMemory {
|
|
|
571
638
|
return result.changes;
|
|
572
639
|
}
|
|
573
640
|
|
|
574
|
-
//
|
|
641
|
+
// ═══════════════════════════════════════════════════════════
|
|
642
|
+
// Legacy Migration (Memory.js JSONL → SQLite)
|
|
643
|
+
// ═══════════════════════════════════════════════════════════
|
|
644
|
+
|
|
645
|
+
/**
|
|
646
|
+
* 从旧版 Memory.js JSONL 文件迁移数据到 SQLite
|
|
647
|
+
*
|
|
648
|
+
* 流程:
|
|
649
|
+
* 1. 读取 .autosnippet/memory.jsonl (逐行 JSON)
|
|
650
|
+
* 2. 映射 type: preference→preference, decision→fact, 其他→fact
|
|
651
|
+
* 3. 通过 consolidate() 智能去重合并
|
|
652
|
+
* 4. 成功后将旧文件重命名为 .migrated
|
|
653
|
+
*
|
|
654
|
+
* @param {string} projectRoot — 用户项目根目录
|
|
655
|
+
* @returns {Promise<{ migrated: number, skipped: number, error?: string }>}
|
|
656
|
+
*/
|
|
657
|
+
async migrateFromLegacy(projectRoot) {
|
|
658
|
+
const legacyPath = path.join(projectRoot, '.autosnippet', 'memory.jsonl');
|
|
659
|
+
|
|
660
|
+
if (!fs.existsSync(legacyPath)) {
|
|
661
|
+
return { migrated: 0, skipped: 0 };
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
try {
|
|
665
|
+
const raw = fs.readFileSync(legacyPath, 'utf-8').trim();
|
|
666
|
+
if (!raw) {
|
|
667
|
+
return { migrated: 0, skipped: 0 };
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
const lines = raw.split('\n').filter(Boolean);
|
|
671
|
+
const candidates = lines
|
|
672
|
+
.map((line) => {
|
|
673
|
+
try {
|
|
674
|
+
return JSON.parse(line);
|
|
675
|
+
} catch {
|
|
676
|
+
return null;
|
|
677
|
+
}
|
|
678
|
+
})
|
|
679
|
+
.filter(Boolean)
|
|
680
|
+
.map((m) => ({
|
|
681
|
+
type: PersistentMemory.#mapLegacyType(m.type),
|
|
682
|
+
content: (m.content || '').trim(),
|
|
683
|
+
source: m.source || 'user',
|
|
684
|
+
importance: m.type === 'decision' ? 7 : 5,
|
|
685
|
+
}))
|
|
686
|
+
.filter((m) => m.content.length >= 5);
|
|
687
|
+
|
|
688
|
+
if (candidates.length === 0) {
|
|
689
|
+
return { migrated: 0, skipped: lines.length };
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
const result = this.consolidate(candidates, {
|
|
693
|
+
bootstrapSession: 'legacy-migration',
|
|
694
|
+
});
|
|
695
|
+
|
|
696
|
+
// 迁移成功 → 重命名旧文件 (保留备份)
|
|
697
|
+
try {
|
|
698
|
+
fs.renameSync(legacyPath, `${legacyPath}.migrated`);
|
|
699
|
+
} catch {
|
|
700
|
+
// 重命名失败不影响迁移结果
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
const migrated = result.added + result.merged;
|
|
704
|
+
this.#log(
|
|
705
|
+
`Legacy migration: ${migrated} migrated (${result.added} added, ${result.merged} merged), ${result.skipped} skipped from ${legacyPath}`
|
|
706
|
+
);
|
|
707
|
+
|
|
708
|
+
return { migrated, skipped: result.skipped };
|
|
709
|
+
} catch (err) {
|
|
710
|
+
this.#log(`Legacy migration failed: ${err.message}`);
|
|
711
|
+
return { migrated: 0, skipped: 0, error: err.message };
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
// ═══════════════════════════════════════════════════════════
|
|
716
|
+
// 向量嵌入接口 (ADR-3 预留)
|
|
717
|
+
// ═══════════════════════════════════════════════════════════
|
|
718
|
+
|
|
719
|
+
/**
|
|
720
|
+
* 设置向量嵌入函数
|
|
721
|
+
* @param {Function|null} fn — (query: string, content: string) => number (0.0-1.0)
|
|
722
|
+
*/
|
|
723
|
+
setEmbeddingFunction(fn) {
|
|
724
|
+
this.#embeddingFn = typeof fn === 'function' ? fn : null;
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
/**
|
|
728
|
+
* 获取当前嵌入函数 (用于检测是否已配置)
|
|
729
|
+
* @returns {Function|null}
|
|
730
|
+
*/
|
|
731
|
+
getEmbeddingFunction() {
|
|
732
|
+
return this.#embeddingFn;
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
/**
|
|
736
|
+
* 使用嵌入函数计算语义相关性 (如已设置)
|
|
737
|
+
*
|
|
738
|
+
* @param {string} query
|
|
739
|
+
* @param {string} content
|
|
740
|
+
* @returns {number|null} — 0.0-1.0 或 null (无嵌入函数)
|
|
741
|
+
*/
|
|
742
|
+
computeEmbeddingRelevance(query, content) {
|
|
743
|
+
if (!this.#embeddingFn) return null;
|
|
744
|
+
try {
|
|
745
|
+
return this.#embeddingFn(query, content);
|
|
746
|
+
} catch {
|
|
747
|
+
return null;
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
// ═══════════════════════════════════════════════════════════
|
|
752
|
+
// Private: 冲突预解决 (Mem0 风格)
|
|
753
|
+
// ═══════════════════════════════════════════════════════════
|
|
754
|
+
|
|
755
|
+
/**
|
|
756
|
+
* 在 consolidate 主流程前检测并解决矛盾
|
|
757
|
+
*
|
|
758
|
+
* @param {Array<object>} candidates
|
|
759
|
+
* @returns {{ processed: Array<object>, replaced: number }}
|
|
760
|
+
*/
|
|
761
|
+
#preResolveConflicts(candidates) {
|
|
762
|
+
if (!candidates || candidates.length === 0) {
|
|
763
|
+
return { processed: [], replaced: 0 };
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
const processed = [];
|
|
767
|
+
let replaced = 0;
|
|
768
|
+
|
|
769
|
+
for (const candidate of candidates) {
|
|
770
|
+
const content = (candidate.content || '').trim();
|
|
771
|
+
if (!content || content.length < 5) {
|
|
772
|
+
processed.push(candidate);
|
|
773
|
+
continue;
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
try {
|
|
777
|
+
const similar = this.search(content, { limit: 3 });
|
|
778
|
+
let conflictResolved = false;
|
|
779
|
+
|
|
780
|
+
for (const existing of similar) {
|
|
781
|
+
if (existing.type === (candidate.type || 'fact')) {
|
|
782
|
+
const isContradiction = PersistentMemory.#detectContradiction(
|
|
783
|
+
existing.content,
|
|
784
|
+
content
|
|
785
|
+
);
|
|
786
|
+
|
|
787
|
+
if (isContradiction) {
|
|
788
|
+
this.update(existing.id, {
|
|
789
|
+
content: content.substring(0, 500),
|
|
790
|
+
importance: Math.max(existing.importance || 5, candidate.importance || 5),
|
|
791
|
+
});
|
|
792
|
+
conflictResolved = true;
|
|
793
|
+
replaced++;
|
|
794
|
+
this.#log(
|
|
795
|
+
`Conflict resolved: replaced "${existing.content.substring(0, 50)}..." with "${content.substring(0, 50)}..."`
|
|
796
|
+
);
|
|
797
|
+
break;
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
if (!conflictResolved) {
|
|
803
|
+
processed.push(candidate);
|
|
804
|
+
}
|
|
805
|
+
} catch {
|
|
806
|
+
processed.push(candidate);
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
return { processed, replaced };
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
/**
|
|
814
|
+
* 检测两段记忆内容是否矛盾
|
|
815
|
+
*
|
|
816
|
+
* @param {string} contentA — 现有记忆内容
|
|
817
|
+
* @param {string} contentB — 候选记忆内容
|
|
818
|
+
* @returns {boolean}
|
|
819
|
+
*/
|
|
820
|
+
static #detectContradiction(contentA, contentB) {
|
|
821
|
+
if (!contentA || !contentB) return false;
|
|
822
|
+
|
|
823
|
+
const aNeg =
|
|
824
|
+
NEGATION_PATTERNS_ZH.test(contentA) || NEGATION_PATTERNS_EN.test(contentA);
|
|
825
|
+
const bNeg =
|
|
826
|
+
NEGATION_PATTERNS_ZH.test(contentB) || NEGATION_PATTERNS_EN.test(contentB);
|
|
827
|
+
|
|
828
|
+
if (aNeg === bNeg) return false;
|
|
829
|
+
|
|
830
|
+
const wordsA = PersistentMemory.#extractTopicWords(contentA);
|
|
831
|
+
const wordsB = PersistentMemory.#extractTopicWords(contentB);
|
|
832
|
+
|
|
833
|
+
let overlap = 0;
|
|
834
|
+
for (const w of wordsA) {
|
|
835
|
+
if (wordsB.has(w)) overlap++;
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
const minSize = Math.min(wordsA.size, wordsB.size);
|
|
839
|
+
if (minSize === 0) return false;
|
|
840
|
+
|
|
841
|
+
return overlap >= MIN_TOPIC_OVERLAP_WORDS || overlap / minSize >= MIN_TOPIC_OVERLAP_RATIO;
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
/**
|
|
845
|
+
* 提取主题词 (去停用词 + 短词)
|
|
846
|
+
* @param {string} text
|
|
847
|
+
* @returns {Set<string>}
|
|
848
|
+
*/
|
|
849
|
+
static #extractTopicWords(text) {
|
|
850
|
+
if (!text) return new Set();
|
|
851
|
+
|
|
852
|
+
const tokens = text
|
|
853
|
+
.toLowerCase()
|
|
854
|
+
.split(/[\s,;:!?。,;:!?\-_/\\|()[\]{}'"<>·、]+/)
|
|
855
|
+
.filter((t) => t.length >= 2);
|
|
856
|
+
|
|
857
|
+
const stopWords = new Set([
|
|
858
|
+
'我们', '使用', '项目', '需要', '可以', '应该', '建议', '目前',
|
|
859
|
+
'已经', '这个', '那个', '一个', '进行', '通过', '对于',
|
|
860
|
+
'the', 'is', 'are', 'was', 'were', 'be', 'been', 'being',
|
|
861
|
+
'have', 'has', 'had', 'do', 'does', 'did', 'will', 'would',
|
|
862
|
+
'could', 'should', 'may', 'might', 'shall', 'can', 'this',
|
|
863
|
+
'that', 'these', 'those', 'with', 'from', 'for', 'and',
|
|
864
|
+
'but', 'not', 'all', 'any', 'each', 'every', 'some',
|
|
865
|
+
]);
|
|
866
|
+
|
|
867
|
+
return new Set(tokens.filter((t) => !stopWords.has(t)));
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
/**
|
|
871
|
+
* 从 Memory.js 的 type 映射到 PersistentMemory 的 type
|
|
872
|
+
* @param {string} legacyType
|
|
873
|
+
* @returns {string}
|
|
874
|
+
*/
|
|
875
|
+
static #mapLegacyType(legacyType) {
|
|
876
|
+
switch (legacyType) {
|
|
877
|
+
case 'preference':
|
|
878
|
+
return 'preference';
|
|
879
|
+
case 'decision':
|
|
880
|
+
return 'fact';
|
|
881
|
+
case 'context':
|
|
882
|
+
return 'fact';
|
|
883
|
+
default:
|
|
884
|
+
return 'fact';
|
|
885
|
+
}
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
// ═══════════════════════════════════════════════════════════
|
|
889
|
+
// Private: DB / SQL 基础设施
|
|
890
|
+
// ═══════════════════════════════════════════════════════════
|
|
575
891
|
|
|
576
892
|
/**
|
|
577
893
|
* 确保表存在 (用于 migration 未运行的情况)
|
|
@@ -679,9 +995,6 @@ export class ProjectSemanticMemory {
|
|
|
679
995
|
};
|
|
680
996
|
}
|
|
681
997
|
|
|
682
|
-
/**
|
|
683
|
-
* 获取所有活跃记忆 (未过期)
|
|
684
|
-
*/
|
|
685
998
|
#getAllActive({ source, type } = {}) {
|
|
686
999
|
const now = new Date().toISOString();
|
|
687
1000
|
if (source && type) {
|
|
@@ -696,9 +1009,6 @@ export class ProjectSemanticMemory {
|
|
|
696
1009
|
return this.#stmts.getAllActive.all({ now });
|
|
697
1010
|
}
|
|
698
1011
|
|
|
699
|
-
/**
|
|
700
|
-
* 更新访问计数和最后访问时间
|
|
701
|
-
*/
|
|
702
1012
|
#touchAccess(id) {
|
|
703
1013
|
try {
|
|
704
1014
|
this.#stmts.touchAccess.run({ id, now: new Date().toISOString() });
|
|
@@ -709,11 +1019,6 @@ export class ProjectSemanticMemory {
|
|
|
709
1019
|
|
|
710
1020
|
/**
|
|
711
1021
|
* 查找相似记忆 (基于 token overlap)
|
|
712
|
-
*
|
|
713
|
-
* @param {string} content
|
|
714
|
-
* @param {string|null} type
|
|
715
|
-
* @param {number} limit
|
|
716
|
-
* @returns {Array<object & { similarity: number }>}
|
|
717
1022
|
*/
|
|
718
1023
|
#findSimilar(content, type, limit) {
|
|
719
1024
|
const now = new Date().toISOString();
|
|
@@ -735,14 +1040,6 @@ export class ProjectSemanticMemory {
|
|
|
735
1040
|
return scored.slice(0, limit);
|
|
736
1041
|
}
|
|
737
1042
|
|
|
738
|
-
/**
|
|
739
|
-
* 计算两段文本的相似度 (Jaccard + 子串) — 委托共享 similarity 模块
|
|
740
|
-
*
|
|
741
|
-
* @param {Set<string>} tokensA — 预分词的 token 集合
|
|
742
|
-
* @param {string} lowerA — 小写原文
|
|
743
|
-
* @param {string} contentB — 原始文本 B
|
|
744
|
-
* @returns {number} 0.0-1.0
|
|
745
|
-
*/
|
|
746
1043
|
#computeSimilarity(tokensA, lowerA, contentB) {
|
|
747
1044
|
const lowerB = (contentB || '').toLowerCase();
|
|
748
1045
|
const tokensB = tokenizeForSimilarity(lowerB);
|
|
@@ -755,21 +1052,11 @@ export class ProjectSemanticMemory {
|
|
|
755
1052
|
}
|
|
756
1053
|
|
|
757
1054
|
const jaccard = jaccardSimilarity(tokensA, tokensB);
|
|
758
|
-
|
|
759
|
-
// 子串包含加分
|
|
760
1055
|
const containsBonus = lowerA.includes(lowerB) || lowerB.includes(lowerA) ? 0.3 : 0;
|
|
761
1056
|
|
|
762
1057
|
return Math.min(1.0, jaccard + containsBonus);
|
|
763
1058
|
}
|
|
764
1059
|
|
|
765
|
-
/**
|
|
766
|
-
* 计算查询与记忆内容的相关性 (用于检索打分)
|
|
767
|
-
*
|
|
768
|
-
* @param {string} lowerQuery
|
|
769
|
-
* @param {Set<string>} queryTokens
|
|
770
|
-
* @param {string} content
|
|
771
|
-
* @returns {number} 0.0-1.0
|
|
772
|
-
*/
|
|
773
1060
|
#computeRelevance(lowerQuery, queryTokens, content) {
|
|
774
1061
|
if (!lowerQuery || !content) {
|
|
775
1062
|
return 0;
|
|
@@ -782,7 +1069,6 @@ export class ProjectSemanticMemory {
|
|
|
782
1069
|
return 0;
|
|
783
1070
|
}
|
|
784
1071
|
|
|
785
|
-
// Token overlap
|
|
786
1072
|
let matchCount = 0;
|
|
787
1073
|
for (const t of queryTokens) {
|
|
788
1074
|
if (contentTokens.has(t)) {
|
|
@@ -791,10 +1077,8 @@ export class ProjectSemanticMemory {
|
|
|
791
1077
|
}
|
|
792
1078
|
const tokenOverlap = matchCount / queryTokens.size;
|
|
793
1079
|
|
|
794
|
-
// 子串匹配
|
|
795
1080
|
const substringMatch = lowerContent.includes(lowerQuery) ? 0.4 : 0;
|
|
796
1081
|
|
|
797
|
-
// 关键词部分匹配
|
|
798
1082
|
let partialMatch = 0;
|
|
799
1083
|
for (const qt of queryTokens) {
|
|
800
1084
|
if (qt.length >= 3 && lowerContent.includes(qt)) {
|
|
@@ -806,11 +1090,6 @@ export class ProjectSemanticMemory {
|
|
|
806
1090
|
return Math.min(1.0, tokenOverlap * 0.5 + substringMatch + partialMatch);
|
|
807
1091
|
}
|
|
808
1092
|
|
|
809
|
-
/**
|
|
810
|
-
* 分词 (按空格/标点分割, 去短词) — 用于 relevance 计算
|
|
811
|
-
* @param {string} text
|
|
812
|
-
* @returns {Set<string>}
|
|
813
|
-
*/
|
|
814
1093
|
#tokenizeWords(text) {
|
|
815
1094
|
if (!text) {
|
|
816
1095
|
return new Set();
|
|
@@ -823,9 +1102,6 @@ export class ProjectSemanticMemory {
|
|
|
823
1102
|
);
|
|
824
1103
|
}
|
|
825
1104
|
|
|
826
|
-
/**
|
|
827
|
-
* 反序列化 DB 行为 JS 对象
|
|
828
|
-
*/
|
|
829
1105
|
#deserialize(row) {
|
|
830
1106
|
return {
|
|
831
1107
|
id: row.id,
|
|
@@ -847,9 +1123,6 @@ export class ProjectSemanticMemory {
|
|
|
847
1123
|
};
|
|
848
1124
|
}
|
|
849
1125
|
|
|
850
|
-
/**
|
|
851
|
-
* 安全 JSON 解析
|
|
852
|
-
*/
|
|
853
1126
|
#safeParseJSON(str, fallback) {
|
|
854
1127
|
try {
|
|
855
1128
|
return str ? JSON.parse(str) : fallback;
|
|
@@ -858,9 +1131,6 @@ export class ProjectSemanticMemory {
|
|
|
858
1131
|
}
|
|
859
1132
|
}
|
|
860
1133
|
|
|
861
|
-
/**
|
|
862
|
-
* 容量控制: 超过 MAX_MEMORIES 时删除最不重要的
|
|
863
|
-
*/
|
|
864
1134
|
#enforceCapacity() {
|
|
865
1135
|
const count = this.#db.prepare('SELECT COUNT(*) as cnt FROM semantic_memories').get()?.cnt || 0;
|
|
866
1136
|
if (count <= MAX_MEMORIES) {
|
|
@@ -881,15 +1151,15 @@ export class ProjectSemanticMemory {
|
|
|
881
1151
|
this.#log(`Capacity enforced: removed ${excess} lowest-priority memories`);
|
|
882
1152
|
}
|
|
883
1153
|
|
|
884
|
-
/**
|
|
885
|
-
* 日志输出
|
|
886
|
-
*/
|
|
887
1154
|
#log(msg) {
|
|
888
|
-
const formatted = `[
|
|
889
|
-
if (this.#logger) {
|
|
1155
|
+
const formatted = `[PersistentMemory] ${msg}`;
|
|
1156
|
+
if (this.#logger?.info) {
|
|
890
1157
|
this.#logger.info(formatted);
|
|
891
1158
|
}
|
|
892
1159
|
}
|
|
893
1160
|
}
|
|
894
1161
|
|
|
895
|
-
|
|
1162
|
+
// ── 向后兼容: 使用旧名称导入时仍可用 ──
|
|
1163
|
+
export { PersistentMemory as ProjectSemanticMemory };
|
|
1164
|
+
|
|
1165
|
+
export default PersistentMemory;
|
|
@@ -519,7 +519,7 @@ export class SessionStore {
|
|
|
519
519
|
}
|
|
520
520
|
|
|
521
521
|
// ═══════════════════════════════════════════════════════
|
|
522
|
-
// §7: 蒸馏上下文 (
|
|
522
|
+
// §7: 蒸馏上下文 (for PipelineStrategy produce 阶段)
|
|
523
523
|
// ═══════════════════════════════════════════════════════
|
|
524
524
|
|
|
525
525
|
/**
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Phase 2: MemoryCoordinator + legacy module re-exports
|
|
5
5
|
* Phase 3: ActiveContext (合并 WorkingMemory + ReasoningTrace)
|
|
6
6
|
* Phase 4: SessionStore (合并 EpisodicMemory + ToolResultCache)
|
|
7
|
-
* Phase 5: PersistentMemory (
|
|
7
|
+
* Phase 5: PersistentMemory (统一的持久化语义记忆)
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
export { MemoryCoordinator } from './MemoryCoordinator.js';
|