openclaw-cortex-memory 0.1.0-Alpha.3 → 0.1.0-Alpha.30

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (82) hide show
  1. package/README.md +263 -204
  2. package/SKILL.md +77 -268
  3. package/dist/index.d.ts +92 -22
  4. package/dist/index.d.ts.map +1 -1
  5. package/dist/index.js +1062 -1207
  6. package/dist/index.js.map +1 -1
  7. package/dist/openclaw.plugin.json +384 -15
  8. package/dist/src/dedup/three_stage_deduplicator.d.ts +25 -0
  9. package/dist/src/dedup/three_stage_deduplicator.d.ts.map +1 -0
  10. package/dist/src/dedup/three_stage_deduplicator.js +224 -0
  11. package/dist/src/dedup/three_stage_deduplicator.js.map +1 -0
  12. package/dist/src/engine/memory_engine.d.ts +2 -1
  13. package/dist/src/engine/memory_engine.d.ts.map +1 -1
  14. package/dist/src/engine/ts_engine.d.ts +126 -0
  15. package/dist/src/engine/ts_engine.d.ts.map +1 -1
  16. package/dist/src/engine/ts_engine.js +1145 -44
  17. package/dist/src/engine/ts_engine.js.map +1 -1
  18. package/dist/src/engine/types.d.ts +12 -0
  19. package/dist/src/engine/types.d.ts.map +1 -1
  20. package/dist/src/graph/ontology.d.ts +103 -0
  21. package/dist/src/graph/ontology.d.ts.map +1 -0
  22. package/dist/src/graph/ontology.js +564 -0
  23. package/dist/src/graph/ontology.js.map +1 -0
  24. package/dist/src/net/http_post.d.ts +17 -0
  25. package/dist/src/net/http_post.d.ts.map +1 -0
  26. package/dist/src/net/http_post.js +56 -0
  27. package/dist/src/net/http_post.js.map +1 -0
  28. package/dist/src/quality/llm_output_validator.d.ts +48 -0
  29. package/dist/src/quality/llm_output_validator.d.ts.map +1 -0
  30. package/dist/src/quality/llm_output_validator.js +404 -0
  31. package/dist/src/quality/llm_output_validator.js.map +1 -0
  32. package/dist/src/reflect/reflector.d.ts +7 -0
  33. package/dist/src/reflect/reflector.d.ts.map +1 -1
  34. package/dist/src/reflect/reflector.js +352 -8
  35. package/dist/src/reflect/reflector.js.map +1 -1
  36. package/dist/src/rules/rule_store.d.ts.map +1 -1
  37. package/dist/src/rules/rule_store.js +75 -16
  38. package/dist/src/rules/rule_store.js.map +1 -1
  39. package/dist/src/session/session_end.d.ts +33 -0
  40. package/dist/src/session/session_end.d.ts.map +1 -1
  41. package/dist/src/session/session_end.js +67 -64
  42. package/dist/src/session/session_end.js.map +1 -1
  43. package/dist/src/store/archive_store.d.ts +128 -0
  44. package/dist/src/store/archive_store.d.ts.map +1 -0
  45. package/dist/src/store/archive_store.js +475 -0
  46. package/dist/src/store/archive_store.js.map +1 -0
  47. package/dist/src/store/embedding_utils.d.ts +32 -0
  48. package/dist/src/store/embedding_utils.d.ts.map +1 -0
  49. package/dist/src/store/embedding_utils.js +173 -0
  50. package/dist/src/store/embedding_utils.js.map +1 -0
  51. package/dist/src/store/graph_memory_store.d.ts +44 -0
  52. package/dist/src/store/graph_memory_store.d.ts.map +1 -0
  53. package/dist/src/store/graph_memory_store.js +168 -0
  54. package/dist/src/store/graph_memory_store.js.map +1 -0
  55. package/dist/src/store/read_store.d.ts +86 -0
  56. package/dist/src/store/read_store.d.ts.map +1 -1
  57. package/dist/src/store/read_store.js +1661 -25
  58. package/dist/src/store/read_store.js.map +1 -1
  59. package/dist/src/store/vector_store.d.ts +44 -0
  60. package/dist/src/store/vector_store.d.ts.map +1 -0
  61. package/dist/src/store/vector_store.js +201 -0
  62. package/dist/src/store/vector_store.js.map +1 -0
  63. package/dist/src/store/write_store.d.ts +52 -0
  64. package/dist/src/store/write_store.d.ts.map +1 -1
  65. package/dist/src/store/write_store.js +239 -3
  66. package/dist/src/store/write_store.js.map +1 -1
  67. package/dist/src/sync/session_sync.d.ts +100 -2
  68. package/dist/src/sync/session_sync.d.ts.map +1 -1
  69. package/dist/src/sync/session_sync.js +725 -28
  70. package/dist/src/sync/session_sync.js.map +1 -1
  71. package/dist/src/utils/runtime_env.d.ts +4 -0
  72. package/dist/src/utils/runtime_env.d.ts.map +1 -0
  73. package/dist/src/utils/runtime_env.js +51 -0
  74. package/dist/src/utils/runtime_env.js.map +1 -0
  75. package/openclaw.plugin.json +384 -15
  76. package/package.json +53 -7
  77. package/schema/graph.schema.yaml +175 -0
  78. package/scripts/cli.js +19 -14
  79. package/scripts/repair-memory.js +321 -0
  80. package/scripts/uninstall.js +22 -5
  81. package/index.ts +0 -2142
  82. package/scripts/install.js +0 -27
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openclaw-cortex-memory",
3
- "version": "0.1.0-Alpha.3",
3
+ "version": "0.1.0-Alpha.30",
4
4
  "description": "Long-term memory system for OpenClaw AI Agent",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -9,9 +9,19 @@
9
9
  },
10
10
  "scripts": {
11
11
  "build": "tsc && node -e \"require('fs').copyFileSync('openclaw.plugin.json','dist/openclaw.plugin.json')\"",
12
+ "typecheck": "tsc --noEmit",
13
+ "check:version": "node scripts/check-version.js",
14
+ "test:compat": "node scripts/compat-regression.js",
15
+ "test:model": "node scripts/model-regression.js",
16
+ "test:graph": "node scripts/graph-eval.js",
17
+ "test:graph-quality": "node scripts/graph-quality-eval.js",
18
+ "test:graph-quality-zh": "node scripts/graph-quality-eval.js --gold eval/graph_quality_gold.zh.jsonl --pred eval/graph_quality_predictions.zh.sample.jsonl --thresholds eval/graph_quality_thresholds.json",
19
+ "test:lengthnorm": "node scripts/lengthnorm-regression.js",
20
+ "test:all": "npm run test:compat && npm run test:model && npm run test:graph && npm run test:graph-quality && npm run test:graph-quality-zh && npm run test:lengthnorm",
21
+ "release:check": "node scripts/release-pipeline.js",
12
22
  "dev": "tsc --watch",
13
- "prepublishOnly": "npm run build",
14
- "postinstall": "node scripts/install.js"
23
+ "prepack": "npm run build",
24
+ "prepublishOnly": "npm run check:version && npm run build && npm run test:all"
15
25
  },
16
26
  "keywords": [
17
27
  "openclaw",
@@ -20,27 +30,30 @@
20
30
  "agent",
21
31
  "rag"
22
32
  ],
23
- "author": "",
33
+ "author": "deki18",
24
34
  "license": "MIT",
25
35
  "dependencies": {
26
36
  "undici": "^6.21.0"
27
37
  },
38
+ "optionalDependencies": {
39
+ "@lancedb/lancedb": "^0.22.0"
40
+ },
28
41
  "devDependencies": {
29
42
  "@types/node": "^22.19.15",
30
43
  "typescript": "^5.7.2"
31
44
  },
32
45
  "peerDependencies": {
33
- "openclaw": ">=2026.3.8"
46
+ "openclaw": ">=2026.4.5"
34
47
  },
35
48
  "engines": {
36
49
  "node": ">=22.0.0"
37
50
  },
38
51
  "files": [
39
52
  "dist/",
40
- "index.ts",
41
53
  "scripts/cli.js",
42
- "scripts/install.js",
43
54
  "scripts/uninstall.js",
55
+ "scripts/repair-memory.js",
56
+ "schema/",
44
57
  "openclaw.plugin.json",
45
58
  "SKILL.md"
46
59
  ],
@@ -48,6 +61,39 @@
48
61
  "plugin": true,
49
62
  "requiresPython": false,
50
63
  "cli": "cortex-memory",
64
+ "requires": {
65
+ "credentials": true,
66
+ "network": true,
67
+ "env": [
68
+ "EMBEDDING_API_KEY",
69
+ "LLM_API_KEY",
70
+ "RERANKER_API_KEY"
71
+ ],
72
+ "config": [
73
+ "embedding.apiKey",
74
+ "llm.apiKey",
75
+ "reranker.apiKey",
76
+ "embedding.baseURL",
77
+ "llm.baseURL",
78
+ "reranker.baseURL"
79
+ ],
80
+ "externalEndpoints": [
81
+ "embedding API endpoint (OpenAI-compatible /embeddings)",
82
+ "LLM API endpoint (OpenAI-compatible /chat/completions)",
83
+ "reranker API endpoint (/rerank)"
84
+ ]
85
+ },
86
+ "build": {
87
+ "openclawVersion": "2026.4.5",
88
+ "pluginSdkVersion": "2026.4.5"
89
+ },
90
+ "install": {
91
+ "minHostVersion": ">=2026.4.5"
92
+ },
93
+ "compat": {
94
+ "pluginApi": ">=2026.4.5",
95
+ "minGatewayVersion": "2026.4.5"
96
+ },
51
97
  "extensions": [
52
98
  "./dist/index.js"
53
99
  ]
@@ -0,0 +1,175 @@
1
+ {
2
+ "eventTypes": [
3
+ "decision",
4
+ "issue",
5
+ "fix",
6
+ "preference",
7
+ "plan",
8
+ "risk",
9
+ "insight",
10
+ "action_item",
11
+ "conversation_summary",
12
+ "constraint",
13
+ "requirement",
14
+ "milestone",
15
+ "blocker",
16
+ "dependency",
17
+ "assumption",
18
+ "retrospective",
19
+ "follow_up"
20
+ ],
21
+ "eventTypeAliases": {
22
+ "problem": "issue",
23
+ "error": "issue",
24
+ "bug": "issue",
25
+ "solution": "fix",
26
+ "workaround": "fix",
27
+ "todo": "action_item",
28
+ "next_step": "action_item",
29
+ "limitation": "constraint",
30
+ "guardrail": "constraint",
31
+ "spec": "requirement",
32
+ "acceptance_criteria": "requirement",
33
+ "deadline": "milestone",
34
+ "target": "milestone",
35
+ "roadblock": "blocker",
36
+ "stuck": "blocker",
37
+ "depends_on": "dependency",
38
+ "upstream": "dependency",
39
+ "hypothesis": "assumption",
40
+ "lesson": "retrospective",
41
+ "postmortem": "retrospective",
42
+ "followup": "follow_up",
43
+ "next_action": "follow_up"
44
+ },
45
+ "entityTypes": [
46
+ "Person",
47
+ "FamilyMember",
48
+ "Friend",
49
+ "Team",
50
+ "Project",
51
+ "Task",
52
+ "Plan",
53
+ "Milestone",
54
+ "Location",
55
+ "Event",
56
+ "Schedule",
57
+ "Habit",
58
+ "HealthItem",
59
+ "FinanceItem",
60
+ "Issue",
61
+ "Fix",
62
+ "Decision",
63
+ "Action",
64
+ "Risk",
65
+ "Blocker",
66
+ "Assumption",
67
+ "Concept",
68
+ "Resource",
69
+ "Document"
70
+ ],
71
+ "entityAliases": {
72
+ "OpenClaw": ["openclaw", "插件", "该项目", "本项目"],
73
+ "FamilyMember": ["家人", "家庭成员", "亲人"],
74
+ "Friend": ["朋友", "好友"],
75
+ "Location": ["地点", "位置", "住址", "地址"],
76
+ "Event": ["活动", "事情", "事项"],
77
+ "Schedule": ["日程", "安排", "计划表"],
78
+ "Habit": ["习惯", "作息"],
79
+ "HealthItem": ["健康", "体检", "药物", "锻炼"],
80
+ "FinanceItem": ["账单", "支出", "收入", "预算"],
81
+ "Person": ["我", "自己", "本人", "同事", "客户", "用户"],
82
+ "Project": ["项目", "工程"],
83
+ "Task": ["任务", "待办", "todo"],
84
+ "Milestone": ["里程碑", "节点"],
85
+ "Issue": ["问题", "故障", "报错"],
86
+ "Fix": ["修复", "解决方案"]
87
+ },
88
+ "relationTypes": [
89
+ "depends_on",
90
+ "blocks",
91
+ "related_to",
92
+ "causes",
93
+ "resolves",
94
+ "plans_to",
95
+ "scheduled_for",
96
+ "lives_in",
97
+ "cares_for",
98
+ "pays_for",
99
+ "supports",
100
+ "conflicts_with",
101
+ "belongs_to",
102
+ "owned_by",
103
+ "references",
104
+ "prefers",
105
+ "implements",
106
+ "requires"
107
+ ],
108
+ "relationTypeAliases": {
109
+ "dependency": "depends_on",
110
+ "blocked_by": "blocks",
111
+ "linked_to": "related_to",
112
+ "plan_to": "plans_to",
113
+ "schedule_for": "scheduled_for",
114
+ "located_in": "lives_in",
115
+ "care_for": "cares_for",
116
+ "pay_for": "pays_for",
117
+ "support": "supports",
118
+ "conflict_with": "conflicts_with",
119
+ "依赖于": "depends_on",
120
+ "依赖": "depends_on",
121
+ "取决于": "depends_on",
122
+ "阻塞": "blocks",
123
+ "卡住": "blocks",
124
+ "导致": "causes",
125
+ "引起": "causes",
126
+ "解决": "resolves",
127
+ "修复": "resolves",
128
+ "属于": "belongs_to",
129
+ "归属": "belongs_to",
130
+ "负责": "owned_by",
131
+ "由": "owned_by",
132
+ "参考": "references",
133
+ "引用": "references",
134
+ "偏好": "prefers",
135
+ "更喜欢": "prefers",
136
+ "实现": "implements",
137
+ "需要": "requires",
138
+ "计划做": "plans_to",
139
+ "打算": "plans_to",
140
+ "安排在": "scheduled_for",
141
+ "约在": "scheduled_for",
142
+ "住在": "lives_in",
143
+ "居住在": "lives_in",
144
+ "照顾": "cares_for",
145
+ "看护": "cares_for",
146
+ "支付": "pays_for",
147
+ "付款": "pays_for",
148
+ "支持": "supports",
149
+ "冲突": "conflicts_with",
150
+ "矛盾": "conflicts_with",
151
+ "相关": "related_to",
152
+ "有关": "related_to",
153
+ "belongs": "belongs_to",
154
+ "owner_of": "owned_by",
155
+ "refer_to": "references",
156
+ "preference_for": "prefers",
157
+ "implement": "implements",
158
+ "need": "requires"
159
+ },
160
+ "relationRules": [
161
+ { "type": "depends_on", "fromTypes": ["Task", "Plan", "Milestone"], "toTypes": ["Task", "Plan", "Milestone"], "allowSelfLoop": false },
162
+ { "type": "blocks", "fromTypes": ["Issue", "Task", "Risk"], "toTypes": ["Task", "Plan"], "allowSelfLoop": false },
163
+ { "type": "causes", "fromTypes": ["Issue", "Risk", "Assumption"], "toTypes": ["Issue", "Risk"], "allowSelfLoop": false },
164
+ { "type": "resolves", "fromTypes": ["Fix", "Decision", "Action"], "toTypes": ["Issue", "Blocker"], "allowSelfLoop": false },
165
+ { "type": "belongs_to", "fromTypes": ["Task", "Issue", "Fix", "Decision"], "toTypes": ["Project", "Plan", "Milestone"], "allowSelfLoop": false },
166
+ { "type": "owned_by", "fromTypes": ["Task", "Plan", "Project", "Issue"], "toTypes": ["Person", "Team"], "allowSelfLoop": false }
167
+ ],
168
+ "highValueRelationTypes": ["depends_on", "blocks", "resolves", "owned_by"],
169
+ "relatedToMaxRatio": 0.35,
170
+ "relatedToMaxAbsolute": 2,
171
+ "minRelationConfidence": 0.35,
172
+ "evidenceSpanRequired": true,
173
+ "endpointMentionRequired": true,
174
+ "defaultEntityType": "Concept"
175
+ }
package/scripts/cli.js CHANGED
@@ -2,16 +2,17 @@
2
2
 
3
3
  const fs = require('fs');
4
4
  const path = require('path');
5
- const { spawn } = require('child_process');
6
5
 
7
6
  const PLUGIN_NAME = 'openclaw-cortex-memory';
8
7
 
9
8
  function findOpenClawConfig() {
10
9
  const explicitConfigPath = process.env.OPENCLAW_CONFIG_PATH || '';
10
+ const stateDir = process.env.OPENCLAW_STATE_DIR || '';
11
11
  const basePath = process.env.OPENCLAW_BASE_PATH || '';
12
12
  const homePath = process.env.USERPROFILE || process.env.HOME || '';
13
13
  const possiblePaths = [
14
14
  explicitConfigPath,
15
+ stateDir ? path.join(stateDir, 'openclaw.json') : '',
15
16
  basePath ? path.join(basePath, 'openclaw.json') : '',
16
17
  path.join(process.cwd(), 'openclaw.json'),
17
18
  homePath ? path.join(homePath, '.openclaw', 'openclaw.json') : '',
@@ -53,7 +54,10 @@ function enablePlugin() {
53
54
  const configPath = findOpenClawConfig();
54
55
 
55
56
  if (!configPath) {
56
- const defaultPath = path.join(process.env.USERPROFILE || process.env.HOME || '', '.openclaw', 'openclaw.json');
57
+ const defaultPath = process.env.OPENCLAW_CONFIG_PATH
58
+ || (process.env.OPENCLAW_STATE_DIR ? path.join(process.env.OPENCLAW_STATE_DIR, 'openclaw.json') : '')
59
+ || (process.env.OPENCLAW_BASE_PATH ? path.join(process.env.OPENCLAW_BASE_PATH, 'openclaw.json') : '')
60
+ || path.join(process.env.USERPROFILE || process.env.HOME || '', '.openclaw', 'openclaw.json');
57
61
  console.log(`No config file found. Creating: ${defaultPath}`);
58
62
 
59
63
  const config = {
@@ -170,21 +174,22 @@ function getStatus() {
170
174
  }
171
175
 
172
176
  function runUninstall(args) {
173
- const uninstallScript = path.join(__dirname, 'uninstall.js');
174
-
175
- if (!fs.existsSync(uninstallScript)) {
177
+ const uninstallScriptPath = path.join(__dirname, 'uninstall.js');
178
+ if (!fs.existsSync(uninstallScriptPath)) {
176
179
  console.error('Uninstall script not found.');
177
180
  process.exit(1);
178
181
  }
179
-
180
- const child = spawn(process.execPath, [uninstallScript, 'uninstall', ...args], {
181
- stdio: 'inherit',
182
- cwd: process.cwd()
183
- });
184
-
185
- child.on('exit', (code) => {
186
- process.exit(code || 0);
187
- });
182
+ try {
183
+ const uninstallModule = require(uninstallScriptPath);
184
+ if (!uninstallModule || typeof uninstallModule.uninstall !== 'function') {
185
+ console.error('Invalid uninstall script export.');
186
+ process.exit(1);
187
+ }
188
+ uninstallModule.uninstall(args);
189
+ } catch (error) {
190
+ console.error(`Failed to run uninstall: ${error instanceof Error ? error.message : String(error)}`);
191
+ process.exit(1);
192
+ }
188
193
  }
189
194
 
190
195
  function showHelp() {
@@ -0,0 +1,321 @@
1
+ #!/usr/bin/env node
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+
6
+ const args = process.argv.slice(2);
7
+ const isDryRun = args.includes('--dry-run');
8
+ const isFix = args.includes('--fix');
9
+ const targetArg = args.find(a => a.startsWith('--target='));
10
+ const target = targetArg ? targetArg.split('=')[1] : 'all';
11
+
12
+ const PROJECT_ROOT = process.cwd();
13
+ const MEMORY_ROOT = path.join(PROJECT_ROOT, 'data', 'memory');
14
+
15
+ const VALID_TARGETS = ['all', 'archive', 'active', 'vector'];
16
+
17
+ function printUsage() {
18
+ console.log(`
19
+ Memory Data Repair Tool
20
+
21
+ Usage: node scripts/repair-memory.js [options]
22
+
23
+ Options:
24
+ --dry-run Scan and report issues without making changes
25
+ --fix Remove invalid records and create quarantine file
26
+ --target=<target> Specify target: all, archive, active, vector (default: all)
27
+
28
+ Examples:
29
+ node scripts/repair-memory.js --dry-run
30
+ node scripts/repair-memory.js --fix --target=archive
31
+ `);
32
+ }
33
+
34
+ if (!VALID_TARGETS.includes(target)) {
35
+ console.error(`Invalid target: ${target}. Valid targets: ${VALID_TARGETS.join(', ')}`);
36
+ printUsage();
37
+ process.exit(1);
38
+ }
39
+
40
+ if (!isDryRun && !isFix) {
41
+ printUsage();
42
+ process.exit(0);
43
+ }
44
+
45
+ function validateJsonlLine(line, lineNumber) {
46
+ const errors = [];
47
+ if (!line || !line.trim()) {
48
+ return { valid: true, errors: [], record: null };
49
+ }
50
+ let record;
51
+ try {
52
+ record = JSON.parse(line);
53
+ } catch (e) {
54
+ errors.push(`JSON parse error: ${e.message}`);
55
+ return { valid: false, errors, record: null };
56
+ }
57
+ if (!record || typeof record !== 'object') {
58
+ errors.push('Record is not an object');
59
+ return { valid: false, errors, record: null };
60
+ }
61
+ if (typeof record.id !== 'string' || !record.id.trim()) {
62
+ errors.push('Missing or invalid id field');
63
+ }
64
+ if (typeof record.timestamp !== 'string' || !record.timestamp.trim()) {
65
+ errors.push('Missing or invalid timestamp field');
66
+ }
67
+ if (record.layer !== 'active' && record.layer !== 'archive') {
68
+ errors.push('Missing or invalid layer field');
69
+ }
70
+ const anomalyPatterns = [
71
+ /\d+\.\d+,\s*"[^"]+"/,
72
+ /"[^"]+"\s+\d+\.\d+/,
73
+ /,\s*\d+\.\d+,/,
74
+ /"\w+\.\w+\.\w+"/,
75
+ /\d+\.\w+\.\d+/,
76
+ ];
77
+ const lineStr = JSON.stringify(record);
78
+ for (const pattern of anomalyPatterns) {
79
+ if (pattern.test(lineStr)) {
80
+ errors.push('Anomaly pattern detected in record');
81
+ break;
82
+ }
83
+ }
84
+ return { valid: errors.length === 0, errors, record };
85
+ }
86
+
87
+ function scanJsonlFile(filePath) {
88
+ const results = {
89
+ path: filePath,
90
+ exists: false,
91
+ totalLines: 0,
92
+ validLines: 0,
93
+ invalidLines: 0,
94
+ emptyLines: 0,
95
+ issues: [],
96
+ };
97
+ if (!fs.existsSync(filePath)) {
98
+ return results;
99
+ }
100
+ results.exists = true;
101
+ const content = fs.readFileSync(filePath, 'utf-8');
102
+ const lines = content.split(/\r?\n/);
103
+ for (let i = 0; i < lines.length; i++) {
104
+ const line = lines[i];
105
+ if (!line.trim()) {
106
+ results.emptyLines++;
107
+ continue;
108
+ }
109
+ results.totalLines++;
110
+ const validation = validateJsonlLine(line, i + 1);
111
+ if (validation.valid) {
112
+ results.validLines++;
113
+ } else {
114
+ results.invalidLines++;
115
+ results.issues.push({
116
+ lineNumber: i + 1,
117
+ errors: validation.errors,
118
+ preview: line.slice(0, 100) + (line.length > 100 ? '...' : ''),
119
+ });
120
+ }
121
+ }
122
+ return results;
123
+ }
124
+
125
+ function repairJsonlFile(filePath, dryRun) {
126
+ const results = {
127
+ path: filePath,
128
+ exists: false,
129
+ totalLines: 0,
130
+ validLines: 0,
131
+ removedLines: 0,
132
+ quarantineLines: [],
133
+ };
134
+ if (!fs.existsSync(filePath)) {
135
+ return results;
136
+ }
137
+ results.exists = true;
138
+ const content = fs.readFileSync(filePath, 'utf-8');
139
+ const lines = content.split(/\r?\n/);
140
+ const validRecords = [];
141
+ for (let i = 0; i < lines.length; i++) {
142
+ const line = lines[i];
143
+ if (!line.trim()) {
144
+ continue;
145
+ }
146
+ results.totalLines++;
147
+ const validation = validateJsonlLine(line, i + 1);
148
+ if (validation.valid) {
149
+ results.validLines++;
150
+ validRecords.push(line);
151
+ } else {
152
+ results.removedLines++;
153
+ results.quarantineLines.push({
154
+ lineNumber: i + 1,
155
+ content: line,
156
+ errors: validation.errors,
157
+ });
158
+ }
159
+ }
160
+ if (!dryRun && results.removedLines > 0) {
161
+ const newContent = validRecords.join('\n') + (validRecords.length > 0 ? '\n' : '');
162
+ fs.writeFileSync(filePath, newContent, 'utf-8');
163
+ const quarantinePath = filePath + '.quarantine.jsonl';
164
+ const quarantineContent = results.quarantineLines.map(q =>
165
+ JSON.stringify({ lineNumber: q.lineNumber, errors: q.errors, content: q.content })
166
+ ).join('\n');
167
+ fs.writeFileSync(quarantinePath, quarantineContent + '\n', 'utf-8');
168
+ }
169
+ return results;
170
+ }
171
+
172
+ function scanVectorFallback(filePath) {
173
+ const results = {
174
+ path: filePath,
175
+ exists: false,
176
+ totalRecords: 0,
177
+ validRecords: 0,
178
+ orphanRecords: 0,
179
+ issues: [],
180
+ };
181
+ if (!fs.existsSync(filePath)) {
182
+ return results;
183
+ }
184
+ results.exists = true;
185
+ const content = fs.readFileSync(filePath, 'utf-8');
186
+ const lines = content.split(/\r?\n/);
187
+ for (let i = 0; i < lines.length; i++) {
188
+ const line = lines[i];
189
+ if (!line.trim()) continue;
190
+ results.totalRecords++;
191
+ try {
192
+ const record = JSON.parse(line);
193
+ if (!record.id || !record.embedding) {
194
+ results.issues.push({
195
+ lineNumber: i + 1,
196
+ error: 'Missing id or embedding',
197
+ });
198
+ } else {
199
+ results.validRecords++;
200
+ }
201
+ } catch (e) {
202
+ results.issues.push({
203
+ lineNumber: i + 1,
204
+ error: `JSON parse error: ${e.message}`,
205
+ });
206
+ }
207
+ }
208
+ return results;
209
+ }
210
+
211
+ console.log('='.repeat(60));
212
+ console.log('Memory Data Repair Tool');
213
+ console.log('='.repeat(60));
214
+ console.log(`Mode: ${isDryRun ? 'DRY RUN (no changes)' : 'FIX (will modify files)'}`);
215
+ console.log(`Target: ${target}`);
216
+ console.log(`Memory Root: ${MEMORY_ROOT}`);
217
+ console.log('='.repeat(60));
218
+
219
+ const archivePath = path.join(MEMORY_ROOT, 'sessions', 'archive', 'archive.jsonl');
220
+ const activePath = path.join(MEMORY_ROOT, 'sessions', 'active', 'sessions.jsonl');
221
+ const vectorFallbackPath = path.join(MEMORY_ROOT, 'vector', 'lancedb_events.jsonl');
222
+
223
+ let totalIssues = 0;
224
+ const report = {
225
+ archive: null,
226
+ active: null,
227
+ vector: null,
228
+ };
229
+
230
+ if (target === 'all' || target === 'archive') {
231
+ console.log('\n[Archive Layer]');
232
+ if (isDryRun) {
233
+ report.archive = scanJsonlFile(archivePath);
234
+ } else {
235
+ report.archive = repairJsonlFile(archivePath, false);
236
+ }
237
+ if (!report.archive.exists) {
238
+ console.log(' File does not exist');
239
+ } else {
240
+ console.log(` Total lines: ${report.archive.totalLines}`);
241
+ console.log(` Valid lines: ${report.archive.validLines}`);
242
+ console.log(` Invalid lines: ${report.archive.invalidLines || report.archive.removedLines}`);
243
+ if (report.archive.issues && report.archive.issues.length > 0) {
244
+ console.log(' Issues found:');
245
+ report.archive.issues.slice(0, 5).forEach(issue => {
246
+ console.log(` Line ${issue.lineNumber}: ${issue.errors.join(', ')}`);
247
+ });
248
+ if (report.archive.issues.length > 5) {
249
+ console.log(` ... and ${report.archive.issues.length - 5} more`);
250
+ }
251
+ }
252
+ totalIssues += report.archive.invalidLines || report.archive.removedLines || 0;
253
+ }
254
+ }
255
+
256
+ if (target === 'all' || target === 'active') {
257
+ console.log('\n[Active Layer]');
258
+ if (isDryRun) {
259
+ report.active = scanJsonlFile(activePath);
260
+ } else {
261
+ report.active = repairJsonlFile(activePath, false);
262
+ }
263
+ if (!report.active.exists) {
264
+ console.log(' File does not exist');
265
+ } else {
266
+ console.log(` Total lines: ${report.active.totalLines}`);
267
+ console.log(` Valid lines: ${report.active.validLines}`);
268
+ console.log(` Invalid lines: ${report.active.invalidLines || report.active.removedLines}`);
269
+ if (report.active.issues && report.active.issues.length > 0) {
270
+ console.log(' Issues found:');
271
+ report.active.issues.slice(0, 5).forEach(issue => {
272
+ console.log(` Line ${issue.lineNumber}: ${issue.errors.join(', ')}`);
273
+ });
274
+ if (report.active.issues.length > 5) {
275
+ console.log(` ... and ${report.active.issues.length - 5} more`);
276
+ }
277
+ }
278
+ totalIssues += report.active.invalidLines || report.active.removedLines || 0;
279
+ }
280
+ }
281
+
282
+ if (target === 'all' || target === 'vector') {
283
+ console.log('\n[Vector Fallback]');
284
+ report.vector = scanVectorFallback(vectorFallbackPath);
285
+ if (!report.vector.exists) {
286
+ console.log(' File does not exist');
287
+ } else {
288
+ console.log(` Total records: ${report.vector.totalRecords}`);
289
+ console.log(` Valid records: ${report.vector.validRecords}`);
290
+ console.log(` Issues: ${report.vector.issues.length}`);
291
+ if (report.vector.issues.length > 0) {
292
+ console.log(' Issues found:');
293
+ report.vector.issues.slice(0, 5).forEach(issue => {
294
+ console.log(` Line ${issue.lineNumber}: ${issue.error}`);
295
+ });
296
+ }
297
+ totalIssues += report.vector.issues.length;
298
+ }
299
+ }
300
+
301
+ console.log('\n' + '='.repeat(60));
302
+ console.log('Summary');
303
+ console.log('='.repeat(60));
304
+ console.log(`Total issues found: ${totalIssues}`);
305
+
306
+ if (isDryRun) {
307
+ if (totalIssues > 0) {
308
+ console.log('\nRun with --fix to repair these issues.');
309
+ } else {
310
+ console.log('\nNo issues found. Memory data is healthy.');
311
+ }
312
+ } else {
313
+ if (totalIssues > 0) {
314
+ console.log('\nRepair completed. Invalid records have been quarantined.');
315
+ console.log('Check .quarantine.jsonl files for removed records.');
316
+ } else {
317
+ console.log('\nNo repairs needed. Memory data is healthy.');
318
+ }
319
+ }
320
+
321
+ process.exit(totalIssues > 0 && isDryRun ? 1 : 0);