recallnest 2.2.0 → 2.3.0
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/.env +4 -0
- package/CLAUDE.md +1 -1
- package/README.md +30 -5
- package/README_CN.md +30 -5
- package/config.json +55 -0
- 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/logs/daily-recall.error.log +0 -0
- package/logs/daily-recall.log +60 -0
- package/logs/ingest-2026-04-11.log +110 -0
- package/logs/ingest-2026-04-12.log +90 -0
- package/logs/ingest-2026-04-13.log +87 -0
- package/logs/launchagent-stderr.log +0 -0
- package/logs/launchagent-stdout.log +0 -0
- package/package.json +1 -1
- package/src/cli.ts +30 -0
- package/src/connector-types.ts +57 -0
- package/src/conversation-importer.ts +25 -1
- package/src/data-checkup.ts +31 -1
- package/src/doctor.ts +29 -0
- package/src/ingest.ts +203 -0
- package/src/mcp-server.ts +2 -2
- package/src/obsidian-connector.ts +282 -0
- package/src/session-schema.ts +2 -2
- package/src/source-heartbeat.ts +108 -0
- package/.claude-plugin/CLAUDE.md +0 -29
- package/.claude-plugin/marketplace.json +0 -18
- package/.dockerignore +0 -11
- package/.github/workflows/ci.yml +0 -28
- package/.mcp.json +0 -9
- package/CHANGELOG.md +0 -51
- package/DEMO.md +0 -94
- package/Dockerfile +0 -20
- package/EVAL.md +0 -89
- package/FAILURES.md +0 -75
- package/OPERATIONS.md +0 -59
- package/ROADMAP.md +0 -245
- package/SKILL.md +0 -250
- package/assets/dashboard.png +0 -0
- package/assets/knowledge-graph.png +0 -0
- package/assets/recallnest-workbench.png +0 -0
- package/assets/screenshots/ui-full.png +0 -0
- package/assets/screenshots/ui-overview.png +0 -0
- package/assets/screenshots/ui-search.png +0 -0
- package/assets/ui/app.js +0 -1031
- package/assets/ui/index.html +0 -224
- package/assets/ui/styles.css +0 -781
- package/assets/wechat-qr.jpg +0 -0
- package/bun.lock +0 -337
- package/daily-reflections/2026-03-05.md +0 -15
- package/daily-reflections/2026-03-06.md +0 -15
- package/daily-reflections/2026-03-07.md +0 -7
- package/daily-reflections/2026-03-08.md +0 -34
- package/daily-reflections/2026-03-09.md +0 -35
- package/daily-reflections/2026-03-10.md +0 -36
- package/daily-reflections/2026-03-11.md +0 -35
- package/daily-reflections/2026-03-12.md +0 -36
- package/daily-reflections/2026-03-13.md +0 -35
- package/daily-reflections/2026-03-14.md +0 -35
- package/daily-reflections/2026-03-15.md +0 -35
- package/daily-reflections/2026-03-16.md +0 -41
- package/daily-reflections/2026-03-17.md +0 -10
- package/daily-reflections/2026-03-18.md +0 -10
- package/daily-reflections/2026-03-19.md +0 -10
- package/daily-reflections/2026-03-20.md +0 -10
- package/daily-reflections/2026-03-21.md +0 -10
- package/daily-reflections/2026-03-22.md +0 -10
- package/daily-reflections/2026-03-23.md +0 -10
- package/daily-reflections/2026-03-24.md +0 -10
- package/daily-reflections/2026-03-25.md +0 -32
- package/daily-reflections/2026-03-26.md +0 -37
- package/daily-reflections/2026-03-27.md +0 -35
- package/daily-reflections/2026-03-28.md +0 -35
- package/daily-reflections/2026-03-29.md +0 -35
- package/daily-reflections/2026-03-30.md +0 -36
- package/daily-reflections/2026-03-31.md +0 -35
- package/daily-reflections/2026-04-01.md +0 -35
- package/daily-reflections/2026-04-02.md +0 -35
- package/daily-reflections/2026-04-03.md +0 -37
- package/daily-reflections/2026-04-04.md +0 -37
- package/daily-reflections/2026-04-05.md +0 -35
- package/daily-reflections/2026-04-06.md +0 -36
- package/daily-reflections/2026-04-07.md +0 -35
- package/daily-reflections/2026-04-08.md +0 -35
- package/daily-reflections/2026-04-12.md +0 -35
- package/data/alias-map.example.json +0 -22
- package/docs/api-reference.md +0 -1291
- package/docs/architecture.md +0 -168
- package/docs/memory-boundary-contract.md +0 -226
- package/docs/memory-categories.md +0 -119
- package/docs/pipeline-audit-ultramemory.md +0 -177
- package/docs/plans/2026-03-08-query-expansion.md +0 -270
- package/docs/plans/2026-03-16-continuity-layer.md +0 -196
- package/docs/plans/2026-04-01-recall-boost.md +0 -100
- package/docs/plans/mcp-tiering-plan.md +0 -126
- package/docs/research/recall-quality-audit-2026-03-22.md +0 -153
- package/docs/self-evolution.md +0 -198
- package/docs/superpowers/plans/2026-03-19-ingest-pipeline-upgrade.md +0 -782
- package/docs/superpowers/plans/2026-04-11-constructive-retrieval-emotion.md +0 -1195
- package/docs/superpowers/specs/2026-03-19-ingest-pipeline-upgrade-design.md +0 -77
- package/docs/superpowers/specs/2026-03-23-pull-claude-desktop-design.md +0 -95
- package/docs/superpowers/specs/2026-04-11-constructive-retrieval-emotion-design.md +0 -398
- package/eval/continuity/results-2026-03-16.md +0 -117
- package/eval/continuity/results-2026-03-19.md +0 -453
- package/eval/continuity/runbook.md +0 -110
- package/eval/continuity/seed-cases.md +0 -54
- package/eval/reports/2026-03-06-abstract-query-baseline.md +0 -96
- package/eval/reports/2026-03-06-aws-ssh-baseline.md +0 -82
- package/eval/reports/2026-03-06-baseline.md +0 -82
- package/eval/reports/query-expansion.md +0 -138
- package/glama.json +0 -6
- package/lm +0 -19
- package/plan-philosophy-upgrades.md +0 -363
- package/plan-product-upgrades.md +0 -155
- package/plan-v1.2-distributable.md +0 -105
- package/plan.md +0 -140
- package/research-memory-evolution.md +0 -440
- package/research-rl-agent-memory.md +0 -330
- package/scripts/archive-distilled-junk.ts +0 -116
- package/scripts/backfill-anchors.ts +0 -130
- package/scripts/backfill-l0l1l2.ts +0 -98
- package/scripts/blind-scan.ts +0 -588
- package/scripts/cleanup-step1-exact-dups.ts +0 -117
- package/scripts/cleanup-step2-template-dups.ts +0 -113
- package/scripts/cleanup-step3-stale-content.ts +0 -149
- package/scripts/cleanup-step4-orphan-scopes.ts +0 -131
- package/scripts/delete-distilled-junk.ts +0 -88
- package/scripts/diagnose-vector-search.ts +0 -132
- package/scripts/distill-facts.ts +0 -305
- package/scripts/health-check.ts +0 -343
- package/scripts/incremental-ingest.sh +0 -71
- package/scripts/migrate-language.ts +0 -75
- package/scripts/mlp-health-check.ts +0 -382
- package/scripts/pull-claude-desktop.ts +0 -515
- package/scripts/re-ingest-cc.sh +0 -66
- package/scripts/rebalance-memory-health.ts +0 -181
- package/scripts/run-desktop-ingest.sh +0 -11
- package/scripts/start-server.sh +0 -12
- package/scripts/verify-emotion-decay.ts +0 -148
- package/scripts/viz-dashboard.html +0 -423
- package/scripts/viz-export.ts +0 -703
- package/src/__tests__/access-tracker-novelty.test.ts +0 -228
- package/src/__tests__/activity-counter.test.ts +0 -96
- package/src/__tests__/admission-control.test.ts +0 -206
- package/src/__tests__/anchor-generator.test.ts +0 -67
- package/src/__tests__/auto-consolidation.test.ts +0 -168
- package/src/__tests__/auto-recall.test.ts +0 -131
- package/src/__tests__/babel-integration.test.ts +0 -139
- package/src/__tests__/batch-dedup.test.ts +0 -65
- package/src/__tests__/capture-engine.test.ts +0 -1144
- package/src/__tests__/capture-heuristic.test.ts +0 -171
- package/src/__tests__/cascade-forget.test.ts +0 -210
- package/src/__tests__/category-min-scores.test.ts +0 -33
- package/src/__tests__/cc8-cc1-essential-context.test.ts +0 -298
- package/src/__tests__/circuit-breaker.test.ts +0 -99
- package/src/__tests__/cluster-consolidation.test.ts +0 -712
- package/src/__tests__/confidence-metadata.test.ts +0 -125
- package/src/__tests__/confidence-tracker.test.ts +0 -249
- package/src/__tests__/conflict-advisor.test.ts +0 -206
- package/src/__tests__/conflict-escalation.test.ts +0 -149
- package/src/__tests__/conflict-lifecycle.test.ts +0 -83
- package/src/__tests__/conflict-output.test.ts +0 -85
- package/src/__tests__/conflict-store.test.ts +0 -374
- package/src/__tests__/consolidation-engine.test.ts +0 -181
- package/src/__tests__/constructive-retrieval.test.ts +0 -509
- package/src/__tests__/context-collapse-renderer.test.ts +0 -214
- package/src/__tests__/context-composer-pins.test.ts +0 -64
- package/src/__tests__/context-composer-task-selection.test.ts +0 -24
- package/src/__tests__/context-reconstructor.test.ts +0 -65
- package/src/__tests__/context-renderer.test.ts +0 -91
- package/src/__tests__/continuity-eval.test.ts +0 -275
- package/src/__tests__/continuity-schema.test.ts +0 -97
- package/src/__tests__/conversation-importer.test.ts +0 -692
- package/src/__tests__/core-recall-eval.test.ts +0 -235
- package/src/__tests__/core-summary.test.ts +0 -109
- package/src/__tests__/data-checkup.test.ts +0 -225
- package/src/__tests__/decay-exemption.test.ts +0 -84
- package/src/__tests__/dedup-actions.test.ts +0 -58
- package/src/__tests__/dedup-guard.test.ts +0 -39
- package/src/__tests__/deterministic-id.test.ts +0 -39
- package/src/__tests__/distill-lock.test.ts +0 -176
- package/src/__tests__/doctor.test.ts +0 -303
- package/src/__tests__/dream-pipeline.test.ts +0 -232
- package/src/__tests__/drill-down.test.ts +0 -108
- package/src/__tests__/embedder.test.ts +0 -122
- package/src/__tests__/emotion-decay.test.ts +0 -46
- package/src/__tests__/emotion-detector.test.ts +0 -56
- package/src/__tests__/emotion-phase1-integration.test.ts +0 -153
- package/src/__tests__/emotion-retrieval.test.ts +0 -56
- package/src/__tests__/entity-resolver.test.ts +0 -58
- package/src/__tests__/ethics-layer.test.ts +0 -476
- package/src/__tests__/event-segmenter.test.ts +0 -153
- package/src/__tests__/feature-flags-integration.test.ts +0 -54
- package/src/__tests__/frequency-tracker.test.ts +0 -113
- package/src/__tests__/graph-export.test.ts +0 -489
- package/src/__tests__/hotness-blend.test.ts +0 -76
- package/src/__tests__/ingest.test.ts +0 -360
- package/src/__tests__/interference-detector.test.ts +0 -203
- package/src/__tests__/kg-extractor.test.ts +0 -184
- package/src/__tests__/kg-store.test.ts +0 -198
- package/src/__tests__/llm-consolidation.test.ts +0 -226
- package/src/__tests__/mcp-efficiency.test.ts +0 -288
- package/src/__tests__/memory-boundaries.test.ts +0 -237
- package/src/__tests__/memory-evolution-b2-b3-e1.test.ts +0 -243
- package/src/__tests__/memory-evolution.test.ts +0 -271
- package/src/__tests__/memory-health-rebalance.test.ts +0 -144
- package/src/__tests__/memory-lint.test.ts +0 -271
- package/src/__tests__/memory-output.test.ts +0 -215
- package/src/__tests__/multi-vector.test.ts +0 -242
- package/src/__tests__/narrative-architecture.test.ts +0 -456
- package/src/__tests__/pending-review.test.ts +0 -77
- package/src/__tests__/post-retrieval-filter.test.ts +0 -178
- package/src/__tests__/ppr-traversal.test.ts +0 -256
- package/src/__tests__/prediction-engine.test.ts +0 -349
- package/src/__tests__/predictive-prospective-memory.test.ts +0 -416
- package/src/__tests__/preference-guard-smoke.test.ts +0 -211
- package/src/__tests__/preference-matcher.test.ts +0 -230
- package/src/__tests__/preference-slots.test.ts +0 -205
- package/src/__tests__/prospective-memory.test.ts +0 -283
- package/src/__tests__/query-entity-detector.test.ts +0 -171
- package/src/__tests__/query-expander-alias.test.ts +0 -89
- package/src/__tests__/query-expander.test.ts +0 -34
- package/src/__tests__/recall-governor.test.ts +0 -227
- package/src/__tests__/recall-mode.test.ts +0 -64
- package/src/__tests__/result-synthesizer.test.ts +0 -198
- package/src/__tests__/resume-context.test.ts +0 -4898
- package/src/__tests__/retrieval-trace.test.ts +0 -213
- package/src/__tests__/retriever-boundary-weight.test.ts +0 -136
- package/src/__tests__/retroactive-boost.test.ts +0 -223
- package/src/__tests__/salience-scoring.test.ts +0 -106
- package/src/__tests__/scope-inventory.test.ts +0 -200
- package/src/__tests__/scope-policy.test.ts +0 -69
- package/src/__tests__/session-checkpoint.test.ts +0 -259
- package/src/__tests__/session-distiller.test.ts +0 -611
- package/src/__tests__/session-gc.test.ts +0 -215
- package/src/__tests__/skill-engine.test.ts +0 -546
- package/src/__tests__/skill-promotion.test.ts +0 -349
- package/src/__tests__/store-scope-audit.test.ts +0 -81
- package/src/__tests__/supersede-chain.test.ts +0 -154
- package/src/__tests__/temporal-parser.test.ts +0 -241
- package/src/__tests__/temporal-validity.test.ts +0 -160
- package/src/__tests__/term-registry.test.ts +0 -121
- package/src/__tests__/tool-output-compressor.test.ts +0 -133
- package/src/__tests__/topic-tag.test.ts +0 -113
- package/src/__tests__/trusted-memory.test.ts +0 -828
- package/src/__tests__/version-manager.test.ts +0 -213
- package/src/__tests__/workflow-observation.test.ts +0 -400
- package/src/__tests__/write-verifier.test.ts +0 -122
package/scripts/health-check.ts
DELETED
|
@@ -1,343 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* RecallNest 记忆体检脚本
|
|
3
|
-
* 只读分析 LanceDB 数据,不写入任何内容
|
|
4
|
-
*
|
|
5
|
-
* 检查项:
|
|
6
|
-
* 1. Category 分布(含百分比)
|
|
7
|
-
* 2. Tier 分布(peripheral/working/core)
|
|
8
|
-
* 3. 死记忆(accessCount = 0 或无 accessCount)
|
|
9
|
-
* 4. Importance 分布
|
|
10
|
-
* 5. 来源分布(CC/Codex/Gemini/manual)
|
|
11
|
-
* 6. 年龄分布
|
|
12
|
-
* 7. 近似重复检测(采样 cosine similarity)
|
|
13
|
-
*/
|
|
14
|
-
|
|
15
|
-
import lancedb from "@lancedb/lancedb";
|
|
16
|
-
import path from "path";
|
|
17
|
-
|
|
18
|
-
const DB_PATH = path.join(import.meta.dir, "..", "data", "lancedb");
|
|
19
|
-
const TABLE_NAME = "memories";
|
|
20
|
-
|
|
21
|
-
interface MemoryRow {
|
|
22
|
-
id: string;
|
|
23
|
-
text: string;
|
|
24
|
-
vector: number[];
|
|
25
|
-
category: string;
|
|
26
|
-
scope: string;
|
|
27
|
-
importance: number;
|
|
28
|
-
timestamp: number;
|
|
29
|
-
metadata?: string;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
function cosineSimilarity(a: number[], b: number[]): number {
|
|
33
|
-
let dot = 0, normA = 0, normB = 0;
|
|
34
|
-
for (let i = 0; i < a.length; i++) {
|
|
35
|
-
dot += a[i] * b[i];
|
|
36
|
-
normA += a[i] * a[i];
|
|
37
|
-
normB += b[i] * b[i];
|
|
38
|
-
}
|
|
39
|
-
return dot / (Math.sqrt(normA) * Math.sqrt(normB));
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
function formatDate(ts: number): string {
|
|
43
|
-
return new Date(ts).toISOString().slice(0, 10);
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
function percentBar(pct: number, width = 30): string {
|
|
47
|
-
const filled = Math.round(pct / 100 * width);
|
|
48
|
-
return "█".repeat(filled) + "░".repeat(width - filled);
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
async function main() {
|
|
52
|
-
console.log("\n🏥 RecallNest 记忆体检报告");
|
|
53
|
-
console.log("=" .repeat(60));
|
|
54
|
-
console.log(`📅 检查时间: ${new Date().toISOString().slice(0, 19)}`);
|
|
55
|
-
console.log(`📂 数据路径: ${DB_PATH}\n`);
|
|
56
|
-
|
|
57
|
-
const db = await lancedb.connect(DB_PATH);
|
|
58
|
-
const table = await db.openTable(TABLE_NAME);
|
|
59
|
-
|
|
60
|
-
// Fetch all rows (without vectors first for speed)
|
|
61
|
-
console.log("⏳ 正在读取全量数据...");
|
|
62
|
-
const allRows: MemoryRow[] = await table
|
|
63
|
-
.query()
|
|
64
|
-
.select(["id", "text", "category", "scope", "importance", "timestamp", "metadata"])
|
|
65
|
-
.toArray() as any;
|
|
66
|
-
|
|
67
|
-
const total = allRows.length;
|
|
68
|
-
console.log(`✅ 读取完成: ${total.toLocaleString()} 条记忆\n`);
|
|
69
|
-
|
|
70
|
-
// ============================================
|
|
71
|
-
// 1. Category 分布
|
|
72
|
-
// ============================================
|
|
73
|
-
console.log("📊 1. Category 分布");
|
|
74
|
-
console.log("-".repeat(60));
|
|
75
|
-
const catMap = new Map<string, number>();
|
|
76
|
-
for (const row of allRows) {
|
|
77
|
-
catMap.set(row.category, (catMap.get(row.category) || 0) + 1);
|
|
78
|
-
}
|
|
79
|
-
const catSorted = [...catMap.entries()].sort((a, b) => b[1] - a[1]);
|
|
80
|
-
for (const [cat, count] of catSorted) {
|
|
81
|
-
const pct = (count / total * 100).toFixed(1);
|
|
82
|
-
console.log(` ${cat.padEnd(14)} ${count.toString().padStart(6)} (${pct.padStart(5)}%) ${percentBar(parseFloat(pct))}`);
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
// ============================================
|
|
86
|
-
// 2. Tier 分布
|
|
87
|
-
// ============================================
|
|
88
|
-
console.log("\n📊 2. Tier 分布");
|
|
89
|
-
console.log("-".repeat(60));
|
|
90
|
-
const tierMap = new Map<string, number>();
|
|
91
|
-
let noTier = 0;
|
|
92
|
-
for (const row of allRows) {
|
|
93
|
-
const meta = row.metadata ? JSON.parse(row.metadata) : {};
|
|
94
|
-
const tier = meta.tier || "unknown";
|
|
95
|
-
if (tier === "unknown") noTier++;
|
|
96
|
-
tierMap.set(tier, (tierMap.get(tier) || 0) + 1);
|
|
97
|
-
}
|
|
98
|
-
const tierSorted = [...tierMap.entries()].sort((a, b) => b[1] - a[1]);
|
|
99
|
-
for (const [tier, count] of tierSorted) {
|
|
100
|
-
const pct = (count / total * 100).toFixed(1);
|
|
101
|
-
console.log(` ${tier.padEnd(14)} ${count.toString().padStart(6)} (${pct.padStart(5)}%) ${percentBar(parseFloat(pct))}`);
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
// ============================================
|
|
105
|
-
// 3. 死记忆(从未被 recall 命中)
|
|
106
|
-
// ============================================
|
|
107
|
-
console.log("\n📊 3. 死记忆分析(accessCount = 0 或无记录)");
|
|
108
|
-
console.log("-".repeat(60));
|
|
109
|
-
let deadCount = 0;
|
|
110
|
-
let aliveCount = 0;
|
|
111
|
-
let totalAccess = 0;
|
|
112
|
-
let maxAccess = 0;
|
|
113
|
-
let maxAccessId = "";
|
|
114
|
-
let maxAccessText = "";
|
|
115
|
-
const accessDistribution = new Map<string, number>();
|
|
116
|
-
|
|
117
|
-
for (const row of allRows) {
|
|
118
|
-
const meta = row.metadata ? JSON.parse(row.metadata) : {};
|
|
119
|
-
const ac = meta.accessCount || 0;
|
|
120
|
-
totalAccess += ac;
|
|
121
|
-
|
|
122
|
-
if (ac === 0) {
|
|
123
|
-
deadCount++;
|
|
124
|
-
} else {
|
|
125
|
-
aliveCount++;
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
if (ac > maxAccess) {
|
|
129
|
-
maxAccess = ac;
|
|
130
|
-
maxAccessId = row.id;
|
|
131
|
-
maxAccessText = row.text.slice(0, 80);
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
// Bucket access counts
|
|
135
|
-
let bucket: string;
|
|
136
|
-
if (ac === 0) bucket = "0 (死记忆)";
|
|
137
|
-
else if (ac <= 2) bucket = "1-2";
|
|
138
|
-
else if (ac <= 5) bucket = "3-5";
|
|
139
|
-
else if (ac <= 10) bucket = "6-10";
|
|
140
|
-
else if (ac <= 50) bucket = "11-50";
|
|
141
|
-
else bucket = "50+";
|
|
142
|
-
accessDistribution.set(bucket, (accessDistribution.get(bucket) || 0) + 1);
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
const deadPct = (deadCount / total * 100).toFixed(1);
|
|
146
|
-
const alivePct = (aliveCount / total * 100).toFixed(1);
|
|
147
|
-
console.log(` 死记忆: ${deadCount.toLocaleString()} / ${total.toLocaleString()} (${deadPct}%)`);
|
|
148
|
-
console.log(` 活记忆: ${aliveCount.toLocaleString()} / ${total.toLocaleString()} (${alivePct}%)`);
|
|
149
|
-
console.log(` 总访问次: ${totalAccess.toLocaleString()}`);
|
|
150
|
-
console.log(` 平均访问: ${(totalAccess / total).toFixed(2)} 次/条`);
|
|
151
|
-
console.log(` 最热记忆: [${maxAccess}次] ${maxAccessText}...`);
|
|
152
|
-
|
|
153
|
-
console.log("\n 访问次数分布:");
|
|
154
|
-
const bucketOrder = ["0 (死记忆)", "1-2", "3-5", "6-10", "11-50", "50+"];
|
|
155
|
-
for (const bucket of bucketOrder) {
|
|
156
|
-
const count = accessDistribution.get(bucket) || 0;
|
|
157
|
-
const pct = (count / total * 100).toFixed(1);
|
|
158
|
-
console.log(` ${bucket.padEnd(14)} ${count.toString().padStart(6)} (${pct.padStart(5)}%) ${percentBar(parseFloat(pct))}`);
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
// ============================================
|
|
162
|
-
// 4. Importance 分布
|
|
163
|
-
// ============================================
|
|
164
|
-
console.log("\n📊 4. Importance 分布");
|
|
165
|
-
console.log("-".repeat(60));
|
|
166
|
-
const impBuckets = new Map<string, number>();
|
|
167
|
-
let impSum = 0;
|
|
168
|
-
for (const row of allRows) {
|
|
169
|
-
const imp = row.importance || 0;
|
|
170
|
-
impSum += imp;
|
|
171
|
-
let bucket: string;
|
|
172
|
-
if (imp < 0.2) bucket = "0.0-0.2 (低)";
|
|
173
|
-
else if (imp < 0.4) bucket = "0.2-0.4";
|
|
174
|
-
else if (imp < 0.6) bucket = "0.4-0.6 (中)";
|
|
175
|
-
else if (imp < 0.8) bucket = "0.6-0.8";
|
|
176
|
-
else bucket = "0.8-1.0 (高)";
|
|
177
|
-
impBuckets.set(bucket, (impBuckets.get(bucket) || 0) + 1);
|
|
178
|
-
}
|
|
179
|
-
console.log(` 平均 importance: ${(impSum / total).toFixed(3)}`);
|
|
180
|
-
const impOrder = ["0.0-0.2 (低)", "0.2-0.4", "0.4-0.6 (中)", "0.6-0.8", "0.8-1.0 (高)"];
|
|
181
|
-
for (const bucket of impOrder) {
|
|
182
|
-
const count = impBuckets.get(bucket) || 0;
|
|
183
|
-
const pct = (count / total * 100).toFixed(1);
|
|
184
|
-
console.log(` ${bucket.padEnd(16)} ${count.toString().padStart(6)} (${pct.padStart(5)}%) ${percentBar(parseFloat(pct))}`);
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
// ============================================
|
|
188
|
-
// 5. 来源分类汇总
|
|
189
|
-
// ============================================
|
|
190
|
-
console.log("\n📊 5. 来源分类汇总");
|
|
191
|
-
console.log("-".repeat(60));
|
|
192
|
-
let ccCount = 0, codexCount = 0, geminiCount = 0, memoryCount = 0, otherCount = 0;
|
|
193
|
-
const scopeSet = new Set<string>();
|
|
194
|
-
for (const row of allRows) {
|
|
195
|
-
scopeSet.add(row.scope);
|
|
196
|
-
const meta = row.metadata ? JSON.parse(row.metadata) : {};
|
|
197
|
-
const source = meta.source || row.scope || "";
|
|
198
|
-
if (source.startsWith("cc:") || row.scope.startsWith("cc:")) ccCount++;
|
|
199
|
-
else if (source.startsWith("codex:") || row.scope.startsWith("codex:")) codexCount++;
|
|
200
|
-
else if (source.startsWith("gemini:") || row.scope.startsWith("gemini:")) geminiCount++;
|
|
201
|
-
else if (source === "memory" || row.scope === "memory" || source === "manual" || source === "agent") memoryCount++;
|
|
202
|
-
else otherCount++;
|
|
203
|
-
}
|
|
204
|
-
console.log(` Claude Code: ${ccCount.toLocaleString().padStart(6)} (${(ccCount/total*100).toFixed(1)}%)`);
|
|
205
|
-
console.log(` Codex: ${codexCount.toLocaleString().padStart(6)} (${(codexCount/total*100).toFixed(1)}%)`);
|
|
206
|
-
console.log(` Gemini: ${geminiCount.toLocaleString().padStart(6)} (${(geminiCount/total*100).toFixed(1)}%)`);
|
|
207
|
-
console.log(` 手动记忆: ${memoryCount.toLocaleString().padStart(6)} (${(memoryCount/total*100).toFixed(1)}%)`);
|
|
208
|
-
console.log(` 其他: ${otherCount.toLocaleString().padStart(6)} (${(otherCount/total*100).toFixed(1)}%)`);
|
|
209
|
-
console.log(` 独立 scope 数: ${scopeSet.size}`);
|
|
210
|
-
|
|
211
|
-
// ============================================
|
|
212
|
-
// 6. 年龄分布
|
|
213
|
-
// ============================================
|
|
214
|
-
console.log("\n📊 6. 年龄分布");
|
|
215
|
-
console.log("-".repeat(60));
|
|
216
|
-
const now = Date.now();
|
|
217
|
-
const ageBuckets = new Map<string, number>();
|
|
218
|
-
let oldest = Infinity, newest = 0;
|
|
219
|
-
for (const row of allRows) {
|
|
220
|
-
const ts = row.timestamp;
|
|
221
|
-
if (ts < oldest) oldest = ts;
|
|
222
|
-
if (ts > newest) newest = ts;
|
|
223
|
-
const ageHours = (now - ts) / (1000 * 60 * 60);
|
|
224
|
-
let bucket: string;
|
|
225
|
-
if (ageHours < 24) bucket = "< 1 天";
|
|
226
|
-
else if (ageHours < 24 * 7) bucket = "1-7 天";
|
|
227
|
-
else if (ageHours < 24 * 30) bucket = "7-30 天";
|
|
228
|
-
else if (ageHours < 24 * 90) bucket = "30-90 天";
|
|
229
|
-
else bucket = "> 90 天";
|
|
230
|
-
ageBuckets.set(bucket, (ageBuckets.get(bucket) || 0) + 1);
|
|
231
|
-
}
|
|
232
|
-
console.log(` 最早: ${formatDate(oldest)}`);
|
|
233
|
-
console.log(` 最新: ${formatDate(newest)}`);
|
|
234
|
-
const ageOrder = ["< 1 天", "1-7 天", "7-30 天", "30-90 天", "> 90 天"];
|
|
235
|
-
for (const bucket of ageOrder) {
|
|
236
|
-
const count = ageBuckets.get(bucket) || 0;
|
|
237
|
-
const pct = (count / total * 100).toFixed(1);
|
|
238
|
-
console.log(` ${bucket.padEnd(12)} ${count.toString().padStart(6)} (${pct.padStart(5)}%) ${percentBar(parseFloat(pct))}`);
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
// ============================================
|
|
242
|
-
// 7. 近似重复检测(采样)
|
|
243
|
-
// ============================================
|
|
244
|
-
console.log("\n📊 7. 近似重复检测(采样 500 条,cosine > 0.95)");
|
|
245
|
-
console.log("-".repeat(60));
|
|
246
|
-
console.log(" ⏳ 正在读取向量数据(采样)...");
|
|
247
|
-
|
|
248
|
-
// Sample 500 random rows with vectors
|
|
249
|
-
const sampleSize = 500;
|
|
250
|
-
const sampleRows: MemoryRow[] = await table
|
|
251
|
-
.query()
|
|
252
|
-
.select(["id", "text", "vector", "category"])
|
|
253
|
-
.limit(sampleSize)
|
|
254
|
-
.toArray() as any;
|
|
255
|
-
|
|
256
|
-
let dupPairs = 0;
|
|
257
|
-
const dupExamples: Array<{sim: number, textA: string, textB: string}> = [];
|
|
258
|
-
|
|
259
|
-
for (let i = 0; i < sampleRows.length; i++) {
|
|
260
|
-
for (let j = i + 1; j < sampleRows.length; j++) {
|
|
261
|
-
if (sampleRows[i].vector && sampleRows[j].vector) {
|
|
262
|
-
const sim = cosineSimilarity(sampleRows[i].vector, sampleRows[j].vector);
|
|
263
|
-
if (sim > 0.95) {
|
|
264
|
-
dupPairs++;
|
|
265
|
-
if (dupExamples.length < 3) {
|
|
266
|
-
dupExamples.push({
|
|
267
|
-
sim,
|
|
268
|
-
textA: sampleRows[i].text.slice(0, 60),
|
|
269
|
-
textB: sampleRows[j].text.slice(0, 60),
|
|
270
|
-
});
|
|
271
|
-
}
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
const totalPairs = sampleSize * (sampleSize - 1) / 2;
|
|
278
|
-
const dupRate = (dupPairs / totalPairs * 100).toFixed(3);
|
|
279
|
-
console.log(` 采样对数: ${totalPairs.toLocaleString()}`);
|
|
280
|
-
console.log(` 高相似对: ${dupPairs} (>${dupRate}%)`);
|
|
281
|
-
console.log(` 推算全库: ~${Math.round(dupPairs / totalPairs * total * (total-1) / 2).toLocaleString()} 对潜在重复`);
|
|
282
|
-
|
|
283
|
-
if (dupExamples.length > 0) {
|
|
284
|
-
console.log("\n 示例重复对:");
|
|
285
|
-
for (const ex of dupExamples) {
|
|
286
|
-
console.log(` [sim=${ex.sim.toFixed(3)}]`);
|
|
287
|
-
console.log(` A: ${ex.textA}...`);
|
|
288
|
-
console.log(` B: ${ex.textB}...`);
|
|
289
|
-
}
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
// ============================================
|
|
293
|
-
// 8. 综合诊断
|
|
294
|
-
// ============================================
|
|
295
|
-
console.log("\n" + "=".repeat(60));
|
|
296
|
-
console.log("🩺 综合诊断");
|
|
297
|
-
console.log("=".repeat(60));
|
|
298
|
-
|
|
299
|
-
const issues: string[] = [];
|
|
300
|
-
const healthy: string[] = [];
|
|
301
|
-
|
|
302
|
-
// Check dead memory ratio
|
|
303
|
-
const deadRatio = deadCount / total;
|
|
304
|
-
if (deadRatio > 0.9) {
|
|
305
|
-
issues.push(`🔴 ${deadPct}% 记忆从未被访问(死记忆率过高)`);
|
|
306
|
-
} else if (deadRatio > 0.7) {
|
|
307
|
-
issues.push(`🟡 ${deadPct}% 记忆从未被访问(死记忆率偏高)`);
|
|
308
|
-
} else {
|
|
309
|
-
healthy.push(`✅ 死记忆率 ${deadPct}% 在合理范围`);
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
// Check category balance
|
|
313
|
-
const factRatio = (catMap.get("fact") || 0) / total;
|
|
314
|
-
if (factRatio > 0.8) {
|
|
315
|
-
issues.push(`🟡 fact 类别占 ${(factRatio*100).toFixed(1)}%,结构化记忆(entities/patterns/cases)占比偏低`);
|
|
316
|
-
} else {
|
|
317
|
-
healthy.push(`✅ 类别分布均衡`);
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
// Check tier distribution
|
|
321
|
-
const coreCount = tierMap.get("core") || 0;
|
|
322
|
-
const workingCount = tierMap.get("working") || 0;
|
|
323
|
-
if (coreCount < 10) {
|
|
324
|
-
issues.push(`🟡 core tier 仅 ${coreCount} 条,高价值记忆太少`);
|
|
325
|
-
} else {
|
|
326
|
-
healthy.push(`✅ core tier 有 ${coreCount} 条高价值记忆`);
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
// Check dup rate
|
|
330
|
-
if (parseFloat(dupRate) > 1) {
|
|
331
|
-
issues.push(`🟡 采样重复率 ${dupRate}%,全库可能存在大量近似重复`);
|
|
332
|
-
} else {
|
|
333
|
-
healthy.push(`✅ 采样重复率 ${dupRate}%,重复可控`);
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
for (const h of healthy) console.log(` ${h}`);
|
|
337
|
-
for (const i of issues) console.log(` ${i}`);
|
|
338
|
-
|
|
339
|
-
console.log("\n" + "=".repeat(60));
|
|
340
|
-
console.log("📋 体检完成\n");
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
main().catch(console.error);
|
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
#!/bin/bash
|
|
2
|
-
# RecallNest 增量更新脚本 — LaunchAgent 调用
|
|
3
|
-
# 只处理新增/修改的文件,已处理的自动跳过
|
|
4
|
-
# 超时保护:最多运行 2 小时,超时自动 kill
|
|
5
|
-
|
|
6
|
-
SCRIPT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
|
|
7
|
-
|
|
8
|
-
# Load API key from .env (CLI also reads .env, this is for LaunchAgent)
|
|
9
|
-
if [ -f "$SCRIPT_DIR/.env" ]; then
|
|
10
|
-
set -a
|
|
11
|
-
source "$SCRIPT_DIR/.env"
|
|
12
|
-
set +a
|
|
13
|
-
fi
|
|
14
|
-
export PATH="$HOME/.bun/bin:/opt/homebrew/bin:/usr/local/bin:$PATH"
|
|
15
|
-
|
|
16
|
-
LOG_DIR="$SCRIPT_DIR/logs"
|
|
17
|
-
mkdir -p "$LOG_DIR"
|
|
18
|
-
LOG_FILE="$LOG_DIR/ingest-$(date +%Y-%m-%d).log"
|
|
19
|
-
|
|
20
|
-
# 超时时间(秒):2 小时 = 7200 秒
|
|
21
|
-
TIMEOUT=7200
|
|
22
|
-
|
|
23
|
-
echo "=== $(date '+%Y-%m-%d %H:%M:%S') 增量更新开始 ===" >> "$LOG_FILE"
|
|
24
|
-
|
|
25
|
-
cd "$SCRIPT_DIR" || exit 1
|
|
26
|
-
|
|
27
|
-
# 用 timeout 命令限制运行时间(macOS 需要 gtimeout 或用 perl 替代)
|
|
28
|
-
if command -v gtimeout &>/dev/null; then
|
|
29
|
-
gtimeout "$TIMEOUT" bun run src/cli.ts ingest --source all >> "$LOG_FILE" 2>&1
|
|
30
|
-
EXIT_CODE=$?
|
|
31
|
-
elif command -v timeout &>/dev/null; then
|
|
32
|
-
timeout "$TIMEOUT" bun run src/cli.ts ingest --source all >> "$LOG_FILE" 2>&1
|
|
33
|
-
EXIT_CODE=$?
|
|
34
|
-
else
|
|
35
|
-
# macOS 没有 timeout,用后台进程 + kill 实现
|
|
36
|
-
bun run src/cli.ts ingest --source all >> "$LOG_FILE" 2>&1 &
|
|
37
|
-
INGEST_PID=$!
|
|
38
|
-
|
|
39
|
-
# 监控进程
|
|
40
|
-
ELAPSED=0
|
|
41
|
-
while kill -0 "$INGEST_PID" 2>/dev/null; do
|
|
42
|
-
sleep 60
|
|
43
|
-
ELAPSED=$((ELAPSED + 60))
|
|
44
|
-
if [ "$ELAPSED" -ge "$TIMEOUT" ]; then
|
|
45
|
-
echo "⚠️ $(date '+%H:%M:%S') 超时 ${TIMEOUT}s,强制终止进程 $INGEST_PID" >> "$LOG_FILE"
|
|
46
|
-
kill "$INGEST_PID" 2>/dev/null
|
|
47
|
-
sleep 5
|
|
48
|
-
kill -9 "$INGEST_PID" 2>/dev/null
|
|
49
|
-
EXIT_CODE=124
|
|
50
|
-
break
|
|
51
|
-
fi
|
|
52
|
-
done
|
|
53
|
-
|
|
54
|
-
if [ -z "$EXIT_CODE" ]; then
|
|
55
|
-
wait "$INGEST_PID"
|
|
56
|
-
EXIT_CODE=$?
|
|
57
|
-
fi
|
|
58
|
-
fi
|
|
59
|
-
|
|
60
|
-
if [ "$EXIT_CODE" -eq 124 ]; then
|
|
61
|
-
echo "⚠️ $(date '+%Y-%m-%d %H:%M:%S') 增量更新超时(${TIMEOUT}s),已自动终止" >> "$LOG_FILE"
|
|
62
|
-
elif [ "$EXIT_CODE" -ne 0 ]; then
|
|
63
|
-
echo "❌ $(date '+%Y-%m-%d %H:%M:%S') 增量更新异常退出(exit code: $EXIT_CODE)" >> "$LOG_FILE"
|
|
64
|
-
else
|
|
65
|
-
echo "=== $(date '+%Y-%m-%d %H:%M:%S') 增量更新完成 ===" >> "$LOG_FILE"
|
|
66
|
-
fi
|
|
67
|
-
|
|
68
|
-
echo "" >> "$LOG_FILE"
|
|
69
|
-
|
|
70
|
-
# 只保留最近 7 天的日志
|
|
71
|
-
find "$LOG_DIR" -name "ingest-*.log" -mtime +7 -delete 2>/dev/null
|
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
import { detectLanguage, tokenizeForFts, initTokenizer } from "babel-memory";
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Backfill language + fts_text for existing memories.
|
|
5
|
-
* Idempotent: skips records that already have language set.
|
|
6
|
-
*
|
|
7
|
-
* Usage: bun scripts/migrate-language.ts <lance-db-path>
|
|
8
|
-
*/
|
|
9
|
-
async function migrate(dbPath: string) {
|
|
10
|
-
await initTokenizer();
|
|
11
|
-
|
|
12
|
-
const lancedb = await import("@lancedb/lancedb");
|
|
13
|
-
const db = await lancedb.connect(dbPath);
|
|
14
|
-
const tableNames = await db.tableNames();
|
|
15
|
-
|
|
16
|
-
for (const name of tableNames) {
|
|
17
|
-
if (!name.startsWith("memories_")) continue;
|
|
18
|
-
|
|
19
|
-
const table = await db.openTable(name);
|
|
20
|
-
const rows = await table.query()
|
|
21
|
-
.select(["id", "text", "language"])
|
|
22
|
-
.toArray();
|
|
23
|
-
|
|
24
|
-
const toUpdate: Array<{
|
|
25
|
-
id: string;
|
|
26
|
-
language: string;
|
|
27
|
-
fts_text: string;
|
|
28
|
-
}> = [];
|
|
29
|
-
|
|
30
|
-
for (const row of rows) {
|
|
31
|
-
if (row.language && row.language !== "") continue;
|
|
32
|
-
const text = row.text as string;
|
|
33
|
-
const language = detectLanguage(text);
|
|
34
|
-
const fts_text = tokenizeForFts(text, language);
|
|
35
|
-
toUpdate.push({ id: row.id as string, language, fts_text });
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
if (toUpdate.length === 0) {
|
|
39
|
-
console.log(`[${name}] No records to migrate`);
|
|
40
|
-
continue;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
for (let i = 0; i < toUpdate.length; i += 100) {
|
|
44
|
-
const batch = toUpdate.slice(i, i + 100);
|
|
45
|
-
for (const item of batch) {
|
|
46
|
-
await table.update({
|
|
47
|
-
where: `id = '${item.id}'`,
|
|
48
|
-
values: {
|
|
49
|
-
language: item.language,
|
|
50
|
-
fts_text: item.fts_text,
|
|
51
|
-
},
|
|
52
|
-
});
|
|
53
|
-
}
|
|
54
|
-
console.log(
|
|
55
|
-
`[${name}] Migrated ${Math.min(i + 100, toUpdate.length)}/${toUpdate.length}`,
|
|
56
|
-
);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
console.log(`[${name}] Rebuilding FTS index on fts_text...`);
|
|
60
|
-
const lancedbMod = await import("@lancedb/lancedb");
|
|
61
|
-
await table.createIndex("fts_text", {
|
|
62
|
-
config: (lancedbMod as any).Index.fts(),
|
|
63
|
-
replace: true,
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
console.log(`[${name}] Done`);
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
const dbPath = process.argv[2] || process.env.RECALLNEST_DB_PATH;
|
|
71
|
-
if (!dbPath) {
|
|
72
|
-
console.error("Usage: bun scripts/migrate-language.ts <lance-db-path>");
|
|
73
|
-
process.exit(1);
|
|
74
|
-
}
|
|
75
|
-
migrate(dbPath).catch(console.error);
|