recallnest 2.2.1 → 2.5.4
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/CHANGELOG.md +150 -0
- package/README.md +46 -35
- package/README_CN.md +41 -26
- package/assets/ui/app.js +1031 -0
- package/assets/ui/index.html +224 -0
- package/assets/ui/styles.css +781 -0
- package/config.json.example +4 -1
- package/connectors/examples/email-adapter.ts +73 -0
- package/connectors/examples/logs-adapter.ts +103 -0
- package/connectors/examples/rss-adapter.ts +95 -0
- package/docs/api-reference.md +1291 -0
- package/docs/architecture.md +168 -0
- package/docs/connector-spec.md +126 -0
- package/eval/cases-canary.json +58 -0
- package/eval/cases-scope-robustness.json +278 -0
- package/eval/cases.json +117 -20
- package/package.json +22 -1
- package/src/__tests__/access-tracker-novelty.test.ts +238 -0
- package/src/__tests__/activity-counter.test.ts +125 -0
- package/src/__tests__/adaptive-fidelity.test.ts +102 -0
- package/src/__tests__/admission-control.test.ts +206 -0
- package/src/__tests__/anchor-generator.test.ts +67 -0
- package/src/__tests__/auto-consolidation.test.ts +168 -0
- package/src/__tests__/auto-recall.test.ts +131 -0
- package/src/__tests__/babel-integration.test.ts +144 -0
- package/src/__tests__/batch-dedup.test.ts +65 -0
- package/src/__tests__/canary-eval.test.ts +114 -0
- package/src/__tests__/capture-engine.test.ts +1238 -0
- package/src/__tests__/capture-heuristic.test.ts +171 -0
- package/src/__tests__/cascade-forget.test.ts +210 -0
- package/src/__tests__/category-min-scores.test.ts +33 -0
- package/src/__tests__/cc8-cc1-essential-context.test.ts +298 -0
- package/src/__tests__/circuit-breaker.test.ts +99 -0
- package/src/__tests__/cluster-consolidation.test.ts +782 -0
- package/src/__tests__/confidence-metadata.test.ts +125 -0
- package/src/__tests__/confidence-tracker.test.ts +261 -0
- package/src/__tests__/conflict-advisor.test.ts +206 -0
- package/src/__tests__/conflict-escalation.test.ts +149 -0
- package/src/__tests__/conflict-lifecycle.test.ts +83 -0
- package/src/__tests__/conflict-output.test.ts +85 -0
- package/src/__tests__/conflict-store.test.ts +374 -0
- package/src/__tests__/connector-v1.test.ts +181 -0
- package/src/__tests__/consolidation-engine.test.ts +333 -0
- package/src/__tests__/constructive-retrieval.test.ts +509 -0
- package/src/__tests__/context-collapse-renderer.test.ts +214 -0
- package/src/__tests__/context-composer-pins.test.ts +64 -0
- package/src/__tests__/context-composer-task-selection.test.ts +24 -0
- package/src/__tests__/context-reconstructor.test.ts +65 -0
- package/src/__tests__/context-renderer.test.ts +91 -0
- package/src/__tests__/continuity-eval.test.ts +275 -0
- package/src/__tests__/continuity-schema.test.ts +97 -0
- package/src/__tests__/contracts.test.ts +216 -0
- package/src/__tests__/conversation-importer.test.ts +692 -0
- package/src/__tests__/core-recall-eval.test.ts +235 -0
- package/src/__tests__/core-summary.test.ts +109 -0
- package/src/__tests__/data-checkup.test.ts +276 -0
- package/src/__tests__/decay-exemption.test.ts +84 -0
- package/src/__tests__/dedup-actions.test.ts +58 -0
- package/src/__tests__/dedup-guard.test.ts +39 -0
- package/src/__tests__/deterministic-id.test.ts +39 -0
- package/src/__tests__/distill-lock.test.ts +305 -0
- package/src/__tests__/distill-session-argshape.test.ts +79 -0
- package/src/__tests__/doctor.test.ts +319 -0
- package/src/__tests__/dream-pipeline.test.ts +321 -0
- package/src/__tests__/drill-down.test.ts +108 -0
- package/src/__tests__/embedder.test.ts +122 -0
- package/src/__tests__/emotion-decay.test.ts +46 -0
- package/src/__tests__/emotion-detector.test.ts +56 -0
- package/src/__tests__/emotion-phase1-integration.test.ts +153 -0
- package/src/__tests__/emotion-retrieval.test.ts +56 -0
- package/src/__tests__/entity-resolver.test.ts +58 -0
- package/src/__tests__/env-config.test.ts +151 -0
- package/src/__tests__/error-signature-boost.test.ts +87 -0
- package/src/__tests__/error-signature.test.ts +95 -0
- package/src/__tests__/error-taxonomy.test.ts +129 -0
- package/src/__tests__/ethics-layer.test.ts +476 -0
- package/src/__tests__/event-segmenter.test.ts +153 -0
- package/src/__tests__/feature-flags-integration.test.ts +54 -0
- package/src/__tests__/frequency-tracker.test.ts +113 -0
- package/src/__tests__/graph-export.test.ts +489 -0
- package/src/__tests__/hotness-blend.test.ts +76 -0
- package/src/__tests__/ingest-chunking.test.ts +44 -0
- package/src/__tests__/ingest.test.ts +360 -0
- package/src/__tests__/interference-detector.test.ts +203 -0
- package/src/__tests__/kg-extractor.test.ts +192 -0
- package/src/__tests__/kg-store.test.ts +300 -0
- package/src/__tests__/llm-consolidation.test.ts +226 -0
- package/src/__tests__/maintenance-locks.test.ts +174 -0
- package/src/__tests__/mcp-efficiency.test.ts +288 -0
- package/src/__tests__/mcp-related-scopes.test.ts +151 -0
- package/src/__tests__/memory-boundaries.test.ts +250 -0
- package/src/__tests__/memory-evolution-b2-b3-e1.test.ts +345 -0
- package/src/__tests__/memory-evolution.test.ts +271 -0
- package/src/__tests__/memory-health-rebalance.test.ts +144 -0
- package/src/__tests__/memory-lint.test.ts +421 -0
- package/src/__tests__/memory-output.test.ts +215 -0
- package/src/__tests__/memory-promotion.test.ts +315 -0
- package/src/__tests__/migrate-dedup-ids.test.ts +65 -0
- package/src/__tests__/multi-vector.test.ts +242 -0
- package/src/__tests__/narrative-architecture.test.ts +456 -0
- package/src/__tests__/noise-filter.test.ts +205 -0
- package/src/__tests__/obsidian-connector.test.ts +363 -0
- package/src/__tests__/omitted-reporting.test.ts +98 -0
- package/src/__tests__/pending-review.test.ts +77 -0
- package/src/__tests__/pii-redact.test.ts +314 -0
- package/src/__tests__/post-retrieval-filter.test.ts +178 -0
- package/src/__tests__/ppr-traversal.test.ts +256 -0
- package/src/__tests__/prediction-engine.test.ts +349 -0
- package/src/__tests__/predictive-prospective-memory.test.ts +416 -0
- package/src/__tests__/preference-guard-smoke.test.ts +211 -0
- package/src/__tests__/preference-matcher.test.ts +230 -0
- package/src/__tests__/preference-slots.test.ts +205 -0
- package/src/__tests__/prospective-memory.test.ts +283 -0
- package/src/__tests__/query-entity-detector.test.ts +171 -0
- package/src/__tests__/query-expander-alias.test.ts +146 -0
- package/src/__tests__/query-expander.test.ts +34 -0
- package/src/__tests__/recall-governor.test.ts +227 -0
- package/src/__tests__/recall-mode.test.ts +64 -0
- package/src/__tests__/result-synthesizer.test.ts +198 -0
- package/src/__tests__/resume-context.test.ts +4898 -0
- package/src/__tests__/retrieval-trace.test.ts +213 -0
- package/src/__tests__/retriever-boundary-weight.test.ts +136 -0
- package/src/__tests__/retroactive-boost.test.ts +223 -0
- package/src/__tests__/salience-scoring.test.ts +106 -0
- package/src/__tests__/scope-inventory.test.ts +200 -0
- package/src/__tests__/scope-policy.test.ts +69 -0
- package/src/__tests__/scope-suggester.test.ts +80 -0
- package/src/__tests__/session-checkpoint.test.ts +291 -0
- package/src/__tests__/session-distiller.test.ts +640 -0
- package/src/__tests__/session-gc.test.ts +215 -0
- package/src/__tests__/skill-engine.test.ts +552 -0
- package/src/__tests__/skill-promotion.test.ts +601 -0
- package/src/__tests__/skill-verifier.test.ts +122 -0
- package/src/__tests__/source-heartbeat.test.ts +250 -0
- package/src/__tests__/store-patch-metadata.test.ts +205 -0
- package/src/__tests__/store-scope-audit.test.ts +81 -0
- package/src/__tests__/store-upsert.test.ts +184 -0
- package/src/__tests__/supersede-chain.test.ts +154 -0
- package/src/__tests__/temporal-parser.test.ts +241 -0
- package/src/__tests__/temporal-validity.test.ts +160 -0
- package/src/__tests__/term-registry.test.ts +121 -0
- package/src/__tests__/tool-error-surfacing.test.ts +98 -0
- package/src/__tests__/tool-output-compressor.test.ts +160 -0
- package/src/__tests__/topic-tag.test.ts +113 -0
- package/src/__tests__/trusted-memory.test.ts +839 -0
- package/src/__tests__/usage-tracker.test.ts +287 -0
- package/src/__tests__/version-manager.test.ts +213 -0
- package/src/__tests__/workflow-observation-skill-outcome.test.ts +352 -0
- package/src/__tests__/workflow-observation.test.ts +433 -0
- package/src/__tests__/write-verifier.test.ts +122 -0
- package/src/access-tracker.ts +38 -32
- package/src/activity-counter.ts +46 -18
- package/src/aliases.ts +133 -0
- package/src/api-server.ts +27 -7
- package/src/audit-log.ts +4 -2
- package/src/auto-consolidation.ts +8 -3
- package/src/auto-gc.ts +85 -21
- package/src/capture-engine.ts +54 -21
- package/src/cli.ts +199 -37
- package/src/confidence-tracker.ts +36 -18
- package/src/connector-types.ts +57 -0
- package/src/consolidation-engine.ts +109 -11
- package/src/context-composer.ts +8 -1
- package/src/conversation-importer.ts +25 -1
- package/src/data-checkup.ts +126 -7
- package/src/distill-lock.ts +194 -15
- package/src/doctor.ts +38 -2
- package/src/dream-pipeline.ts +118 -34
- package/src/env-config.ts +77 -0
- package/src/error-signature.ts +153 -0
- package/src/error-taxonomy.ts +115 -0
- package/src/eval.ts +251 -6
- package/src/idempotency.ts +8 -0
- package/src/ingest.ts +234 -10
- package/src/kg-extractor.ts +6 -2
- package/src/kg-store.ts +236 -44
- package/src/llm-consolidation.ts +2 -1
- package/src/mcp-server.ts +85 -1799
- package/src/mcp-tool-deps.ts +48 -0
- package/src/mcp-tools-advanced.ts +941 -0
- package/src/mcp-tools-core.ts +633 -0
- package/src/mcp-tools-governance.ts +475 -0
- package/src/memory-boundaries.ts +3 -1
- package/src/memory-evolution.ts +73 -1
- package/src/memory-lint.ts +183 -10
- package/src/memory-output.ts +114 -3
- package/src/memory-promotion.ts +246 -0
- package/src/memory-schema.ts +25 -2
- package/src/memory-store-port.ts +43 -0
- package/src/multi-vector.ts +2 -1
- package/src/narrative-schema.ts +2 -1
- package/src/noise-filter.ts +57 -6
- package/src/obsidian-connector.ts +282 -0
- package/src/pii-detector.ts +157 -5
- package/src/query-expander.ts +90 -9
- package/src/result-synthesizer.ts +2 -1
- package/src/retention-policy.ts +2 -1
- package/src/retriever.ts +259 -139
- package/src/runtime-config.ts +8 -1
- package/src/scope-suggester.ts +113 -0
- package/src/session-distiller.ts +23 -2
- package/src/session-engine.ts +2 -1
- package/src/session-schema.ts +3 -2
- package/src/session-store.ts +5 -0
- package/src/skill-engine.ts +78 -0
- package/src/skill-promotion.ts +247 -47
- package/src/skill-schema.ts +16 -3
- package/src/skill-verifier.ts +152 -0
- package/src/source-heartbeat.ts +108 -0
- package/src/store.ts +267 -38
- package/src/tool-output-compressor.ts +3 -1
- package/src/ui-server.ts +2 -1
- package/src/usage-tracker.ts +242 -0
- package/src/version-manager.ts +3 -2
- package/src/workflow-observation-engine.ts +2 -1
- package/src/workflow-observation-schema.ts +2 -1
- package/src/workflow-observation-store.ts +6 -2
- package/.env +0 -4
- package/CLAUDE.md +0 -46
- package/config.json +0 -55
- package/daily-recall.sh +0 -110
- package/logs/daily-recall.error.log +0 -0
- package/logs/daily-recall.log +0 -30
- package/logs/ingest-2026-04-11.log +0 -110
- package/logs/ingest-2026-04-12.log +0 -90
- package/logs/launchagent-stderr.log +0 -0
- package/logs/launchagent-stdout.log +0 -0
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Connector Standard Types (connector-v1)
|
|
3
|
+
*
|
|
4
|
+
* 定义外部数据源(Obsidian、Email、RSS、自定义脚本)统一产出的 JSON 格式。
|
|
5
|
+
* ingest.ts 的 ingestConnectorFile 消费这个格式,走已有的 dedup/extract/入库管线。
|
|
6
|
+
*
|
|
7
|
+
* 协议版本管理:version 字段用于未来兼容性。当前固定 "connector-v1"。
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
export interface ConnectorOutputV1 {
|
|
11
|
+
/** 格式版本,固定 "connector-v1" */
|
|
12
|
+
version: "connector-v1";
|
|
13
|
+
/** 数据源标识,如 "obsidian", "email", "rss", "x-bookmarks" */
|
|
14
|
+
source: string;
|
|
15
|
+
/** 目标 scope,如 "vault:my-notes", "project:research" */
|
|
16
|
+
scope: string;
|
|
17
|
+
/** 批次 ID(可选,用于幂等重试) */
|
|
18
|
+
batchId?: string;
|
|
19
|
+
/** 产出时间 ISO 8601 */
|
|
20
|
+
producedAt: string;
|
|
21
|
+
/** 记录列表 */
|
|
22
|
+
records: ConnectorRecord[];
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface ConnectorRecord {
|
|
26
|
+
/** 记录唯一 ID(source 内唯一,用于增量去重) */
|
|
27
|
+
id: string;
|
|
28
|
+
/** 正文 */
|
|
29
|
+
text: string;
|
|
30
|
+
/** 标题/heading(可选) */
|
|
31
|
+
title?: string;
|
|
32
|
+
/** 分类提示:preferences / events / knowledge / facts / decisions */
|
|
33
|
+
categoryHint?: string;
|
|
34
|
+
/** 重要性提示 0-1(可选,默认 0.7) */
|
|
35
|
+
importanceHint?: number;
|
|
36
|
+
/** 标签 */
|
|
37
|
+
tags?: string[];
|
|
38
|
+
/** 原始时间戳 ISO 8601 */
|
|
39
|
+
timestamp?: string;
|
|
40
|
+
/** 内容哈希(用于增量同步,调用方计算) */
|
|
41
|
+
contentHash?: string;
|
|
42
|
+
/** 来源元数据(自由字段) */
|
|
43
|
+
sourceMetadata?: Record<string, unknown>;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** 运行时格式验证:判定一个 parsed JSON 是否是合法 connector-v1 */
|
|
47
|
+
export function isConnectorOutputV1(value: unknown): value is ConnectorOutputV1 {
|
|
48
|
+
if (!value || typeof value !== "object") return false;
|
|
49
|
+
const obj = value as Record<string, unknown>;
|
|
50
|
+
return (
|
|
51
|
+
obj.version === "connector-v1"
|
|
52
|
+
&& typeof obj.source === "string"
|
|
53
|
+
&& typeof obj.scope === "string"
|
|
54
|
+
&& typeof obj.producedAt === "string"
|
|
55
|
+
&& Array.isArray(obj.records)
|
|
56
|
+
);
|
|
57
|
+
}
|
|
@@ -17,7 +17,9 @@
|
|
|
17
17
|
* LLM-free — deterministic clustering and merge only.
|
|
18
18
|
*/
|
|
19
19
|
|
|
20
|
-
import type { MemoryEntry, MemorySearchResult
|
|
20
|
+
import type { MemoryEntry, MemorySearchResult } from "./store.js";
|
|
21
|
+
import { deterministicId } from "./store.js";
|
|
22
|
+
import type { MemoryStorePort } from "./memory-store-port.js";
|
|
21
23
|
import { createVersionGroup } from "./version-manager.js";
|
|
22
24
|
import { isActiveMemory, parseEvolution, buildSupersedeMetadata, buildConsolidatedMetadata, patchEvolution } from "./memory-evolution.js";
|
|
23
25
|
import { cosineSimilarity } from "./multi-vector.js";
|
|
@@ -35,12 +37,22 @@ export interface ConsolidationConfig {
|
|
|
35
37
|
mergeThreshold: number;
|
|
36
38
|
/** Maximum entries to scan per consolidation run (default 500) */
|
|
37
39
|
maxEntriesPerRun: number;
|
|
40
|
+
/**
|
|
41
|
+
* KG evidence: triple-set Jaccard at/above this merges a grey-zone pair
|
|
42
|
+
* (vector sim between clusterThreshold and mergeThreshold). Default 0.5 —
|
|
43
|
+
* provisional until calibrated on production data (plan Q3).
|
|
44
|
+
*/
|
|
45
|
+
tripleJaccardThreshold?: number;
|
|
46
|
+
/** KG evidence: both sides need at least this many triples to qualify (default 2) */
|
|
47
|
+
minTriplesForEvidence?: number;
|
|
38
48
|
}
|
|
39
49
|
|
|
40
50
|
export const DEFAULT_CONSOLIDATION_CONFIG: ConsolidationConfig = {
|
|
41
51
|
clusterThreshold: 0.82,
|
|
42
52
|
mergeThreshold: 0.92,
|
|
43
53
|
maxEntriesPerRun: 500,
|
|
54
|
+
tripleJaccardThreshold: 0.5,
|
|
55
|
+
minTriplesForEvidence: 2,
|
|
44
56
|
};
|
|
45
57
|
|
|
46
58
|
export interface ConflictEvent {
|
|
@@ -54,10 +66,52 @@ export interface ConsolidationResult {
|
|
|
54
66
|
clustersFound: number;
|
|
55
67
|
mergedCount: number;
|
|
56
68
|
relationsAdded: number;
|
|
69
|
+
/** Of mergedCount, how many were below mergeThreshold and merged on KG triple evidence */
|
|
70
|
+
tripleEvidenceMerges: number;
|
|
57
71
|
conflictsDetected: ConflictEvent[];
|
|
58
72
|
scope: string;
|
|
59
73
|
}
|
|
60
74
|
|
|
75
|
+
// ---------------------------------------------------------------------------
|
|
76
|
+
// KG evidence source (duck-typed subset of KGStore)
|
|
77
|
+
// ---------------------------------------------------------------------------
|
|
78
|
+
|
|
79
|
+
/** The two triple fields consolidation needs — structurally satisfied by KGTriple. */
|
|
80
|
+
export interface ConsolidationTripleEvidence {
|
|
81
|
+
id: string;
|
|
82
|
+
mention_count: number;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export interface ConsolidationKGSource {
|
|
86
|
+
getTriplesBySourceMemories(memoryIds: string[]): Promise<Map<string, ConsolidationTripleEvidence[]>>;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Jaccard overlap of two triple-id sets. Below `minSize` on either side the
|
|
91
|
+
* evidence is considered too weak and 0 is returned (a single shared triple
|
|
92
|
+
* must not merge two memories on its own).
|
|
93
|
+
*/
|
|
94
|
+
export function tripleJaccard(
|
|
95
|
+
a: ReadonlySet<string> | undefined,
|
|
96
|
+
b: ReadonlySet<string> | undefined,
|
|
97
|
+
minSize = 2,
|
|
98
|
+
): number {
|
|
99
|
+
if (!a || !b || a.size < minSize || b.size < minSize) return 0;
|
|
100
|
+
let inter = 0;
|
|
101
|
+
for (const x of a) if (b.has(x)) inter++;
|
|
102
|
+
return inter / (a.size + b.size - inter);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Mention-frequency boost in canonical selection: tie-breaker ONLY, never an
|
|
107
|
+
* override of a clear canonicalScore gap (an entry with one hot triple must
|
|
108
|
+
* not demote a substantially more important memory to consolidated status).
|
|
109
|
+
* α=0.06 keeps the boost differentiating (m=1 → 1.042, m=9 → 1.138) while the
|
|
110
|
+
* 1.1 cap bounds the maximum flip at ~10% of the base score — effectively ties.
|
|
111
|
+
*/
|
|
112
|
+
const MENTION_ALPHA = 0.06;
|
|
113
|
+
const MENTION_BOOST_CAP = 1.1;
|
|
114
|
+
|
|
61
115
|
// ---------------------------------------------------------------------------
|
|
62
116
|
// Helpers
|
|
63
117
|
// ---------------------------------------------------------------------------
|
|
@@ -108,7 +162,7 @@ export function detectHeuristicContradiction(textA: string, textB: string): bool
|
|
|
108
162
|
// Store interface (duck-typed to avoid hard dependency)
|
|
109
163
|
// ---------------------------------------------------------------------------
|
|
110
164
|
|
|
111
|
-
type ConsolidationStore = Pick<
|
|
165
|
+
type ConsolidationStore = Pick<MemoryStorePort, "list" | "getById" | "vectorSearch" | "update">;
|
|
112
166
|
|
|
113
167
|
// ---------------------------------------------------------------------------
|
|
114
168
|
// Engine
|
|
@@ -118,17 +172,25 @@ export class ConsolidationEngine {
|
|
|
118
172
|
constructor(
|
|
119
173
|
private store: ConsolidationStore,
|
|
120
174
|
private config: ConsolidationConfig = DEFAULT_CONSOLIDATION_CONFIG,
|
|
175
|
+
/** Optional KG evidence source — absent = pure vector behavior, unchanged */
|
|
176
|
+
private kgSource: ConsolidationKGSource | null = null,
|
|
121
177
|
) {}
|
|
122
178
|
|
|
123
179
|
async run(scope: string): Promise<ConsolidationResult> {
|
|
124
|
-
const {
|
|
180
|
+
const {
|
|
181
|
+
clusterThreshold,
|
|
182
|
+
mergeThreshold,
|
|
183
|
+
maxEntriesPerRun,
|
|
184
|
+
tripleJaccardThreshold = 0.5,
|
|
185
|
+
minTriplesForEvidence = 2,
|
|
186
|
+
} = this.config;
|
|
125
187
|
|
|
126
188
|
// 1. Fetch entries in scope
|
|
127
189
|
const entries = await this.store.list([scope], undefined, maxEntriesPerRun, 0);
|
|
128
190
|
const active = entries.filter(isActive);
|
|
129
191
|
|
|
130
192
|
if (active.length === 0) {
|
|
131
|
-
return { originalCount: 0, clustersFound: 0, mergedCount: 0, relationsAdded: 0, conflictsDetected: [], scope };
|
|
193
|
+
return { originalCount: 0, clustersFound: 0, mergedCount: 0, relationsAdded: 0, tripleEvidenceMerges: 0, conflictsDetected: [], scope };
|
|
132
194
|
}
|
|
133
195
|
|
|
134
196
|
// 2. Group by category
|
|
@@ -142,6 +204,7 @@ export class ConsolidationEngine {
|
|
|
142
204
|
let clustersFound = 0;
|
|
143
205
|
let mergedCount = 0;
|
|
144
206
|
let relationsAdded = 0;
|
|
207
|
+
let tripleEvidenceMerges = 0;
|
|
145
208
|
const conflictsDetected: ConflictEvent[] = [];
|
|
146
209
|
|
|
147
210
|
// 3. Cluster within each category
|
|
@@ -183,8 +246,27 @@ export class ConsolidationEngine {
|
|
|
183
246
|
}
|
|
184
247
|
if (memberEntries.length < 2) continue;
|
|
185
248
|
|
|
186
|
-
//
|
|
187
|
-
|
|
249
|
+
// KG triple evidence for this cluster (single batch query; best-effort —
|
|
250
|
+
// on failure consolidation proceeds vector-only)
|
|
251
|
+
const tripleIdsByMember = new Map<string, Set<string>>();
|
|
252
|
+
const maxMentionByMember = new Map<string, number>();
|
|
253
|
+
if (this.kgSource) {
|
|
254
|
+
try {
|
|
255
|
+
const triplesByMember = await this.kgSource.getTriplesBySourceMemories(memberEntries.map(e => e.id));
|
|
256
|
+
for (const [mid, triples] of triplesByMember) {
|
|
257
|
+
tripleIdsByMember.set(mid, new Set(triples.map(t => t.id)));
|
|
258
|
+
maxMentionByMember.set(mid, triples.reduce((m, t) => Math.max(m, t.mention_count), 0));
|
|
259
|
+
}
|
|
260
|
+
} catch { /* KG evidence is best-effort */ }
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// Determine canonical: highest canonicalScore, frequency-boosted when
|
|
264
|
+
// KG evidence exists (memories carrying often-mentioned facts win ties)
|
|
265
|
+
const mentionBoost = (e: MemoryEntry) =>
|
|
266
|
+
Math.min(MENTION_BOOST_CAP, 1 + MENTION_ALPHA * Math.log((maxMentionByMember.get(e.id) ?? 0) + 1));
|
|
267
|
+
const sorted = [...memberEntries].sort(
|
|
268
|
+
(a, b) => canonicalScore(b) * mentionBoost(b) - canonicalScore(a) * mentionBoost(a),
|
|
269
|
+
);
|
|
188
270
|
const canonical = sorted[0];
|
|
189
271
|
|
|
190
272
|
if (!canonical.vector?.length) continue;
|
|
@@ -199,7 +281,16 @@ export class ConsolidationEngine {
|
|
|
199
281
|
for (const member of sorted.slice(1)) {
|
|
200
282
|
const sim = scoreMap.get(member.id) ?? clusterThreshold;
|
|
201
283
|
|
|
202
|
-
|
|
284
|
+
// Second evidence source: two memories whose extracted fact sets
|
|
285
|
+
// overlap heavily are duplicates even when vector sim sits in the
|
|
286
|
+
// grey zone (the "pairwise near-duplicates" the 0.92 bar misses).
|
|
287
|
+
const jaccard = tripleJaccard(
|
|
288
|
+
tripleIdsByMember.get(canonical.id),
|
|
289
|
+
tripleIdsByMember.get(member.id),
|
|
290
|
+
minTriplesForEvidence,
|
|
291
|
+
);
|
|
292
|
+
|
|
293
|
+
if (sim >= mergeThreshold || jaccard >= tripleJaccardThreshold) {
|
|
203
294
|
// Tier 3.3: Version coexistence — both stay but grouped.
|
|
204
295
|
await createVersionGroup(this.store, canonical, member, scope);
|
|
205
296
|
// C-1: Also mark the weaker entry as consolidated via evolution metadata
|
|
@@ -214,6 +305,7 @@ export class ConsolidationEngine {
|
|
|
214
305
|
await this.store.update(canonical.id, { metadata: updatedCanon }, [scope]);
|
|
215
306
|
}
|
|
216
307
|
mergedCount++;
|
|
308
|
+
if (sim < mergeThreshold) tripleEvidenceMerges++;
|
|
217
309
|
} else {
|
|
218
310
|
// Link: add clustered_with relation
|
|
219
311
|
const memberMeta = parseMetadata(member);
|
|
@@ -245,7 +337,7 @@ export class ConsolidationEngine {
|
|
|
245
337
|
}
|
|
246
338
|
}
|
|
247
339
|
|
|
248
|
-
return { originalCount: active.length, clustersFound, mergedCount, relationsAdded, conflictsDetected, scope };
|
|
340
|
+
return { originalCount: active.length, clustersFound, mergedCount, relationsAdded, tripleEvidenceMerges, conflictsDetected, scope };
|
|
249
341
|
}
|
|
250
342
|
}
|
|
251
343
|
|
|
@@ -278,7 +370,7 @@ export async function clusterAndConsolidate(params: {
|
|
|
278
370
|
entries: MemoryEntry[];
|
|
279
371
|
embedder: Pick<Embedder, "embedPassage">;
|
|
280
372
|
llm: LLMClient;
|
|
281
|
-
store: Pick<
|
|
373
|
+
store: Pick<MemoryStorePort, "store" | "update">;
|
|
282
374
|
scope: string;
|
|
283
375
|
/** Minimum cluster size to trigger consolidation (default: 3) */
|
|
284
376
|
minClusterSize?: number;
|
|
@@ -400,8 +492,13 @@ export async function clusterAndConsolidate(params: {
|
|
|
400
492
|
// Embed the insight text
|
|
401
493
|
const insightVector = await params.embedder.embedPassage(insight);
|
|
402
494
|
|
|
403
|
-
// Store the insight as a new memory
|
|
495
|
+
// Store the insight as a new memory. P0-2/P1-2: derive a deterministic id from the
|
|
496
|
+
// (sorted) source member ids so re-running dream/consolidate on the same cluster
|
|
497
|
+
// upserts the same insight row instead of appending a near-duplicate each run
|
|
498
|
+
// (the highest-frequency dup-id source). Cross-run stable because sourceIds are stable.
|
|
499
|
+
const sortedSourceIds = cluster.map(m => m.id).slice().sort();
|
|
404
500
|
const insightEntry = await store.store({
|
|
501
|
+
id: deterministicId(scope, `cluster-insight:${sortedSourceIds.join(",")}`),
|
|
405
502
|
text: insight,
|
|
406
503
|
vector: insightVector,
|
|
407
504
|
category: bestCat,
|
|
@@ -444,6 +541,7 @@ export async function clusterAndConsolidate(params: {
|
|
|
444
541
|
const patternImportance = Math.min(maxImportance + 0.1, 1.0);
|
|
445
542
|
const patternVector = await params.embedder.embedPassage(patternText);
|
|
446
543
|
const patternEntry = await store.store({
|
|
544
|
+
id: deterministicId(scope, `cluster-pattern:${sortedSourceIds.join(",")}`),
|
|
447
545
|
text: patternText,
|
|
448
546
|
vector: patternVector,
|
|
449
547
|
category: "patterns",
|
|
@@ -492,7 +590,7 @@ export function formatConsolidationResult(result: ConsolidationResult): string {
|
|
|
492
590
|
`Consolidation complete for scope: ${result.scope}`,
|
|
493
591
|
`Scanned: ${result.originalCount} active entries`,
|
|
494
592
|
`Clusters found: ${result.clustersFound}`,
|
|
495
|
-
`Merged (versioned): ${result.mergedCount}`,
|
|
593
|
+
`Merged (versioned): ${result.mergedCount}${result.tripleEvidenceMerges > 0 ? ` (${result.tripleEvidenceMerges} on triple evidence)` : ""}`,
|
|
496
594
|
`Relations added: ${result.relationsAdded}`,
|
|
497
595
|
`Conflicts detected: ${result.conflictsDetected.length}`,
|
|
498
596
|
];
|
package/src/context-composer.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { listPinAssets, type PinAsset } from "./memory-assets.js";
|
|
2
|
+
import * as envConfig from "./env-config.js";
|
|
2
3
|
import { buildStableContextSections } from "./context-composer-stable.js";
|
|
3
4
|
import {
|
|
4
5
|
buildAssociativeNestEntityFallbackQuery,
|
|
@@ -32,6 +33,8 @@ type ResumeCategory = "profile" | "preferences" | "entities" | "patterns" | "cas
|
|
|
32
33
|
|
|
33
34
|
interface ResumeRetriever {
|
|
34
35
|
retrieve(context: RetrievalContext): Promise<RetrievalResult[]>;
|
|
36
|
+
/** Optional: 影子记录 resume reconstruction 引用的记忆(injection-vs-use)。未实现则 no-op。 */
|
|
37
|
+
recordReconstructionUsage?(citedIds: string[]): Promise<void>;
|
|
35
38
|
}
|
|
36
39
|
|
|
37
40
|
interface CheckpointLookup {
|
|
@@ -467,7 +470,7 @@ export async function composeResumeContext(
|
|
|
467
470
|
let reconstructedContext: string | undefined;
|
|
468
471
|
let reconstructionConfidence: number | undefined;
|
|
469
472
|
let reconstructionContradictions: Array<{ memoryIds: [string, string]; description: string }> | undefined;
|
|
470
|
-
const constructiveFlag =
|
|
473
|
+
const constructiveFlag = envConfig.constructiveRetrieval();
|
|
471
474
|
if (constructiveFlag && deps.llm?.isAvailable?.()) {
|
|
472
475
|
const allReconResults = [...profileResults, ...preferenceResults, ...entityResults, ...filteredPatterns, ...filteredCases];
|
|
473
476
|
if (allReconResults.length >= 3) {
|
|
@@ -489,6 +492,10 @@ export async function composeResumeContext(
|
|
|
489
492
|
if (recon.contradictions.length > 0) {
|
|
490
493
|
reconstructionContradictions = recon.contradictions;
|
|
491
494
|
}
|
|
495
|
+
// Shadow usage:resume reconstruction 真正引用的记忆记 use 计数(不动 ranking/forget)。
|
|
496
|
+
if (recon.sources.length > 0) {
|
|
497
|
+
await deps.retriever.recordReconstructionUsage?.(recon.sources.map(s => s.id));
|
|
498
|
+
}
|
|
492
499
|
} catch { /* silent fallback */ }
|
|
493
500
|
}
|
|
494
501
|
}
|
|
@@ -2,6 +2,7 @@ import { readFileSync } from "node:fs";
|
|
|
2
2
|
import type { PersistMemoryDeps } from "./capture-engine.js";
|
|
3
3
|
import { persistMemory } from "./capture-engine.js";
|
|
4
4
|
import type { StoredMemoryRecord } from "./memory-schema.js";
|
|
5
|
+
import { isConnectorOutputV1, type ConnectorOutputV1 } from "./connector-types.js";
|
|
5
6
|
|
|
6
7
|
export interface NormalizedMessage {
|
|
7
8
|
role: "user" | "assistant" | "system";
|
|
@@ -14,7 +15,8 @@ export type ConversationFormat =
|
|
|
14
15
|
| "claude-ai"
|
|
15
16
|
| "chatgpt"
|
|
16
17
|
| "slack"
|
|
17
|
-
| "plaintext"
|
|
18
|
+
| "plaintext"
|
|
19
|
+
| "connector-v1";
|
|
18
20
|
|
|
19
21
|
// ---------------------------------------------------------------------------
|
|
20
22
|
// Format detection
|
|
@@ -40,6 +42,10 @@ export function detectFormat(content: string): ConversationFormat {
|
|
|
40
42
|
}
|
|
41
43
|
|
|
42
44
|
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
45
|
+
// connector-v1: highest priority — explicit version tag
|
|
46
|
+
if (isConnectorOutputV1(parsed)) {
|
|
47
|
+
return "connector-v1";
|
|
48
|
+
}
|
|
43
49
|
if (parsed.chat_conversations !== undefined) {
|
|
44
50
|
return "claude-ai";
|
|
45
51
|
}
|
|
@@ -366,6 +372,22 @@ export function normalizePlaintext(content: string): NormalizedMessage[] {
|
|
|
366
372
|
return messages;
|
|
367
373
|
}
|
|
368
374
|
|
|
375
|
+
// ---------------------------------------------------------------------------
|
|
376
|
+
// Connector-v1 normalizer
|
|
377
|
+
// ---------------------------------------------------------------------------
|
|
378
|
+
|
|
379
|
+
export function normalizeConnectorV1(content: string): NormalizedMessage[] {
|
|
380
|
+
const parsed: ConnectorOutputV1 = JSON.parse(content);
|
|
381
|
+
if (!isConnectorOutputV1(parsed)) {
|
|
382
|
+
throw new Error("Invalid connector-v1 format: missing version/source/scope/records");
|
|
383
|
+
}
|
|
384
|
+
return parsed.records.map((r) => ({
|
|
385
|
+
role: "user" as const,
|
|
386
|
+
content: r.title ? `[${r.title}] ${r.text}` : r.text,
|
|
387
|
+
...(r.timestamp ? { timestamp: r.timestamp } : {}),
|
|
388
|
+
}));
|
|
389
|
+
}
|
|
390
|
+
|
|
369
391
|
// ---------------------------------------------------------------------------
|
|
370
392
|
// Unified normalize dispatcher
|
|
371
393
|
// ---------------------------------------------------------------------------
|
|
@@ -382,6 +404,8 @@ export function normalizeConversation(content: string, format: ConversationForma
|
|
|
382
404
|
return normalizeSlack(content);
|
|
383
405
|
case "plaintext":
|
|
384
406
|
return normalizePlaintext(content);
|
|
407
|
+
case "connector-v1":
|
|
408
|
+
return normalizeConnectorV1(content);
|
|
385
409
|
}
|
|
386
410
|
}
|
|
387
411
|
|
package/src/data-checkup.ts
CHANGED
|
@@ -15,6 +15,8 @@
|
|
|
15
15
|
import type { MemoryEntry, MemoryStore } from "./store.js";
|
|
16
16
|
import { resolveTier, type MemoryTier } from "./decay-engine.js";
|
|
17
17
|
import { measureInterferenceDensity } from "./interference-detector.js";
|
|
18
|
+
import { readHeartbeats, checkSourceStaleness, formatAge } from "./source-heartbeat.js";
|
|
19
|
+
import { deriveUsageStatus, isUsageSignalActive, type UsageStatus } from "./usage-tracker.js";
|
|
18
20
|
|
|
19
21
|
// ---------------------------------------------------------------------------
|
|
20
22
|
// Types
|
|
@@ -29,6 +31,8 @@ export interface CheckResult {
|
|
|
29
31
|
export interface CheckupReport {
|
|
30
32
|
checks: CheckResult[];
|
|
31
33
|
totalEntries: number;
|
|
34
|
+
/** Total entries in store; when > totalEntries, only the most recent were scanned. */
|
|
35
|
+
totalAvailable: number;
|
|
32
36
|
timestamp: string;
|
|
33
37
|
}
|
|
34
38
|
|
|
@@ -38,15 +42,33 @@ export interface CheckupReport {
|
|
|
38
42
|
|
|
39
43
|
/** Check 1: All vectors should have the same dimension. */
|
|
40
44
|
function checkVectorDimensions(entries: MemoryEntry[]): CheckResult {
|
|
45
|
+
// 只对真正取到向量的条目判维度一致性;取不到向量的(dim=0)单独计数后跳过——
|
|
46
|
+
// 否则会把"未补到向量"误报成"维度不一致",或把"全 0 维空向量"假报成健康。
|
|
41
47
|
const dims = new Map<number, number>(); // dim → count
|
|
48
|
+
let missingVectors = 0;
|
|
42
49
|
for (const e of entries) {
|
|
43
50
|
const d = e.vector?.length ?? 0;
|
|
51
|
+
if (d === 0) { missingVectors++; continue; }
|
|
44
52
|
dims.set(d, (dims.get(d) ?? 0) + 1);
|
|
45
53
|
}
|
|
46
54
|
|
|
47
|
-
|
|
55
|
+
const withVectors = entries.length - missingVectors;
|
|
56
|
+
if (entries.length === 0) {
|
|
57
|
+
return { name: "vector_dimensions", status: "ok", detail: "No entries to check" };
|
|
58
|
+
}
|
|
59
|
+
if (withVectors === 0) {
|
|
60
|
+
return {
|
|
61
|
+
name: "vector_dimensions",
|
|
62
|
+
status: "warning",
|
|
63
|
+
detail: `No retrievable vectors among ${entries.length} entries — dimension consistency not verified`,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const suffix = missingVectors > 0 ? ` (${missingVectors} without retrievable vector, skipped)` : "";
|
|
68
|
+
|
|
69
|
+
if (dims.size === 1) {
|
|
48
70
|
const dim = dims.keys().next().value ?? 0;
|
|
49
|
-
return { name: "vector_dimensions", status: "ok", detail: `All ${
|
|
71
|
+
return { name: "vector_dimensions", status: "ok", detail: `All ${withVectors} vectors have dimension ${dim}${suffix}` };
|
|
50
72
|
}
|
|
51
73
|
|
|
52
74
|
const sorted = [...dims.entries()].sort((a, b) => b[1] - a[1]);
|
|
@@ -55,7 +77,7 @@ function checkVectorDimensions(entries: MemoryEntry[]): CheckResult {
|
|
|
55
77
|
return {
|
|
56
78
|
name: "vector_dimensions",
|
|
57
79
|
status: "error",
|
|
58
|
-
detail: `${mismatched} entries have wrong dimension (expected ${expected}): ${sorted.map(([d, c]) => `dim=${d}: ${c}`).join(", ")}`,
|
|
80
|
+
detail: `${mismatched} entries have wrong dimension (expected ${expected}): ${sorted.map(([d, c]) => `dim=${d}: ${c}`).join(", ")}${suffix}`,
|
|
59
81
|
};
|
|
60
82
|
}
|
|
61
83
|
|
|
@@ -151,7 +173,33 @@ function checkVersionGroups(entries: MemoryEntry[]): CheckResult {
|
|
|
151
173
|
};
|
|
152
174
|
}
|
|
153
175
|
|
|
154
|
-
/** Check 6 (
|
|
176
|
+
/** Check 6 (GB-3): Source health — detect stale connector data sources. */
|
|
177
|
+
function checkSourceHealth(heartbeatPath?: string): CheckResult {
|
|
178
|
+
const heartbeats = readHeartbeats(heartbeatPath);
|
|
179
|
+
const sources = Object.values(heartbeats);
|
|
180
|
+
if (sources.length === 0) {
|
|
181
|
+
return { name: "source_health", status: "ok", detail: "No connector sources tracked yet" };
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const stale30 = checkSourceStaleness(30, heartbeatPath);
|
|
185
|
+
const stale7 = checkSourceStaleness(7, heartbeatPath);
|
|
186
|
+
|
|
187
|
+
const summary = sources
|
|
188
|
+
.map((s) => `${s.source}: ${formatAge(s.lastIngest)}`)
|
|
189
|
+
.join(", ");
|
|
190
|
+
|
|
191
|
+
if (stale30.length > 0) {
|
|
192
|
+
const names = stale30.map((s) => `${s.source} (${s.daysSince}d)`).join(", ");
|
|
193
|
+
return { name: "source_health", status: "error", detail: `${summary} — critically stale: ${names}` };
|
|
194
|
+
}
|
|
195
|
+
if (stale7.length > 0) {
|
|
196
|
+
const names = stale7.map((s) => `${s.source} (${s.daysSince}d)`).join(", ");
|
|
197
|
+
return { name: "source_health", status: "warning", detail: `${summary} — stale: ${names}` };
|
|
198
|
+
}
|
|
199
|
+
return { name: "source_health", status: "ok", detail: `All sources fresh: ${summary}` };
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/** Check 7 (F2): Interference density — detect semantic clustering pressure. */
|
|
155
203
|
function checkInterferenceDensity(entries: MemoryEntry[]): CheckResult {
|
|
156
204
|
// Only check entries with vectors (skip schema/empty entries)
|
|
157
205
|
const withVectors = entries.filter(e => e.vector && e.vector.length > 0);
|
|
@@ -189,13 +237,78 @@ function checkInterferenceDensity(entries: MemoryEntry[]): CheckResult {
|
|
|
189
237
|
// Main
|
|
190
238
|
// ---------------------------------------------------------------------------
|
|
191
239
|
|
|
240
|
+
/** Check 8 (P0 B-1 观察): usage 四档分布 — injection-vs-use 影子统计的离线观察口。
|
|
241
|
+
* 纯报告,不接任何决策;cold = 被反复 surface(accessCount≥6)却从未被
|
|
242
|
+
* reconstruction 引用,是第二阶段 decay 联动前要先观察校准的核心对象。 */
|
|
243
|
+
function checkUsageDistribution(entries: MemoryEntry[]): CheckResult {
|
|
244
|
+
// use 信号未采集时(reconstruction flag 关),useCount 恒零 → "cold" 全是假阳性。
|
|
245
|
+
// 如实报告信号缺失,不输出不可解释的判定。
|
|
246
|
+
if (!isUsageSignalActive()) {
|
|
247
|
+
return {
|
|
248
|
+
name: "usage_distribution",
|
|
249
|
+
status: "ok",
|
|
250
|
+
detail: "use signal inactive (RECALLNEST_CONSTRUCTIVE_RETRIEVAL off) — useCount 无写入路径,cold/warm/hot 不可判,统计跳过。该 flag 因合成召回缺失被 Alice 主动关闭(2026-06-11),usage 线搁置中。",
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
const counts: Record<UsageStatus, number> = { unused: 0, cold: 0, warm: 0, hot: 0 };
|
|
255
|
+
const coldSamples: Array<{ id: string; injection: number; ageDays: number; scope: string }> = [];
|
|
256
|
+
const now = Date.now();
|
|
257
|
+
|
|
258
|
+
for (const e of entries) {
|
|
259
|
+
const status = deriveUsageStatus(e);
|
|
260
|
+
counts[status]++;
|
|
261
|
+
if (status === "cold") {
|
|
262
|
+
let injection = 0;
|
|
263
|
+
try {
|
|
264
|
+
const meta = JSON.parse(e.metadata || "{}") as Record<string, unknown>;
|
|
265
|
+
injection = typeof meta.accessCount === "number" ? meta.accessCount : 0;
|
|
266
|
+
} catch { /* keep 0 */ }
|
|
267
|
+
coldSamples.push({
|
|
268
|
+
id: e.id.slice(0, 8),
|
|
269
|
+
injection,
|
|
270
|
+
ageDays: Math.floor((now - e.timestamp) / 86_400_000),
|
|
271
|
+
scope: e.scope || "(none)",
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
const total = entries.length;
|
|
277
|
+
const coldPct = total > 0 ? (counts.cold / total) * 100 : 0;
|
|
278
|
+
const top = coldSamples
|
|
279
|
+
.sort((a, b) => b.injection - a.injection)
|
|
280
|
+
.slice(0, 20)
|
|
281
|
+
.map(s => `${s.id}(inj=${s.injection}, ${s.ageDays}d, ${s.scope})`)
|
|
282
|
+
.join(", ");
|
|
283
|
+
|
|
284
|
+
return {
|
|
285
|
+
name: "usage_distribution",
|
|
286
|
+
status: coldPct > 10 ? "warning" : "ok",
|
|
287
|
+
detail:
|
|
288
|
+
`unused=${counts.unused} cold=${counts.cold} (${coldPct.toFixed(1)}%) ` +
|
|
289
|
+
`warm=${counts.warm} hot=${counts.hot}` +
|
|
290
|
+
(coldSamples.length > 0 ? `; cold top-${Math.min(20, coldSamples.length)}: ${top}` : ""),
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
|
|
192
294
|
export interface CheckupDeps {
|
|
193
|
-
store: Pick<MemoryStore, "list" | "stats">;
|
|
295
|
+
store: Pick<MemoryStore, "list" | "stats" | "getVectors">;
|
|
194
296
|
openConflictCount: number;
|
|
297
|
+
/** Optional override path for source-heartbeat.json (used in tests). */
|
|
298
|
+
heartbeatPath?: string;
|
|
195
299
|
}
|
|
196
300
|
|
|
197
301
|
export async function runDataCheckup(deps: CheckupDeps): Promise<CheckupReport> {
|
|
198
|
-
const
|
|
302
|
+
const SCAN_LIMIT = 10000;
|
|
303
|
+
const listed = await deps.store.list(undefined, undefined, SCAN_LIMIT, 0);
|
|
304
|
+
|
|
305
|
+
// list() 为性能不返回向量(vector: []);维度/干扰检查靠向量,否则把"全 0 维"
|
|
306
|
+
// 误判成健康、干扰分析永远"样本不足"。补回真实向量再检查。
|
|
307
|
+
const vectorMap = await deps.store.getVectors(listed.map(e => e.id));
|
|
308
|
+
const entries = listed.map(e => ({ ...e, vector: vectorMap.get(e.id) ?? [] }));
|
|
309
|
+
|
|
310
|
+
// 截断披露:库总量 > 已扫,说明只体检了最近 SCAN_LIMIT 条。
|
|
311
|
+
const totalAvailable = (await deps.store.stats()).totalCount;
|
|
199
312
|
|
|
200
313
|
return {
|
|
201
314
|
checks: [
|
|
@@ -204,17 +317,23 @@ export async function runDataCheckup(deps: CheckupDeps): Promise<CheckupReport>
|
|
|
204
317
|
checkTierDistribution(entries),
|
|
205
318
|
checkConflictBacklog(deps.openConflictCount),
|
|
206
319
|
checkVersionGroups(entries),
|
|
320
|
+
checkSourceHealth(deps.heartbeatPath),
|
|
207
321
|
checkInterferenceDensity(entries),
|
|
322
|
+
checkUsageDistribution(entries),
|
|
208
323
|
],
|
|
209
324
|
totalEntries: entries.length,
|
|
325
|
+
totalAvailable,
|
|
210
326
|
timestamp: new Date().toISOString(),
|
|
211
327
|
};
|
|
212
328
|
}
|
|
213
329
|
|
|
214
330
|
export function formatCheckupReport(report: CheckupReport): string {
|
|
331
|
+
const truncationNote = report.totalAvailable > report.totalEntries
|
|
332
|
+
? ` (of ${report.totalAvailable} total — only the most recent ${report.totalEntries} analyzed)`
|
|
333
|
+
: "";
|
|
215
334
|
const lines = [
|
|
216
335
|
`Data Checkup Report (${report.timestamp})`,
|
|
217
|
-
`Total entries scanned: ${report.totalEntries}`,
|
|
336
|
+
`Total entries scanned: ${report.totalEntries}${truncationNote}`,
|
|
218
337
|
"",
|
|
219
338
|
];
|
|
220
339
|
|