openclaw-cortex-memory 0.1.0-Alpha.27 → 0.1.0-Alpha.28

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -109,16 +109,16 @@ openclaw plugins enable openclaw-cortex-memory
109
109
  如果 `clawhub:` 安装出现 `fetch failed`,可改用 npm 包本地安装(推荐兜底):
110
110
 
111
111
  ```bash
112
- bash -lc 'set -e; TMP="$(mktemp -d)"; cd "$TMP"; npm pack openclaw-cortex-memory@0.1.0-Alpha.27 >/dev/null; PKG="$(ls openclaw-cortex-memory-*.tgz | head -n1)"; openclaw plugins install "$TMP/$PKG"; openclaw plugins enable openclaw-cortex-memory'
112
+ bash -lc 'set -e; TMP="$(mktemp -d)"; cd "$TMP"; npm pack openclaw-cortex-memory@0.1.0-Alpha.28 >/dev/null; PKG="$(ls openclaw-cortex-memory-*.tgz | head -n1)"; openclaw plugins install "$TMP/$PKG"; openclaw plugins enable openclaw-cortex-memory'
113
113
  ```
114
114
 
115
115
  也可分步执行(便于排错):
116
116
 
117
117
  ```bash
118
- npm pack openclaw-cortex-memory@0.1.0-Alpha.27
119
- openclaw plugins install ./openclaw-cortex-memory-0.1.0-Alpha.27.tgz
118
+ npm pack openclaw-cortex-memory@0.1.0-Alpha.28
119
+ openclaw plugins install ./openclaw-cortex-memory-0.1.0-Alpha.28.tgz
120
120
  openclaw plugins enable openclaw-cortex-memory
121
- rm ./openclaw-cortex-memory-0.1.0-Alpha.27.tgz
121
+ rm ./openclaw-cortex-memory-0.1.0-Alpha.28.tgz
122
122
  ```
123
123
 
124
124
  完成安装后,请先按下方“最小配置”示例配置 `openclaw.json`,确认配置无误后再启动 gateway。
@@ -127,10 +127,10 @@ rm ./openclaw-cortex-memory-0.1.0-Alpha.27.tgz
127
127
 
128
128
  ```bash
129
129
  rm -r ~/.openclaw/extensions/openclaw-cortex-memory
130
- npm pack openclaw-cortex-memory@0.1.0-Alpha.27
131
- openclaw plugins install ./openclaw-cortex-memory-0.1.0-Alpha.27.tgz
130
+ npm pack openclaw-cortex-memory@0.1.0-Alpha.28
131
+ openclaw plugins install ./openclaw-cortex-memory-0.1.0-Alpha.28.tgz
132
132
  openclaw plugins enable openclaw-cortex-memory
133
- rm ./openclaw-cortex-memory-0.1.0-Alpha.27.tgz
133
+ rm ./openclaw-cortex-memory-0.1.0-Alpha.28.tgz
134
134
  openclaw plugins list
135
135
  openclaw gateway restart
136
136
  ```
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "id": "openclaw-cortex-memory",
3
3
  "name": "Cortex Memory",
4
- "version": "0.1.0-Alpha.27",
4
+ "version": "0.1.0-Alpha.28",
5
5
  "description": "Long-term memory system with semantic, episodic, and procedural memory for AI Agents",
6
6
  "main": "dist/index.js",
7
7
  "author": "deki18",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "id": "openclaw-cortex-memory",
3
3
  "name": "Cortex Memory",
4
- "version": "0.1.0-Alpha.27",
4
+ "version": "0.1.0-Alpha.28",
5
5
  "description": "Long-term memory system with semantic, episodic, and procedural memory for AI Agents",
6
6
  "main": "dist/index.js",
7
7
  "author": "deki18",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openclaw-cortex-memory",
3
- "version": "0.1.0-Alpha.27",
3
+ "version": "0.1.0-Alpha.28",
4
4
  "description": "Long-term memory system for OpenClaw AI Agent",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -52,6 +52,8 @@
52
52
  "dist/",
53
53
  "scripts/cli.js",
54
54
  "scripts/uninstall.js",
55
+ "scripts/repair-memory.js",
56
+ "schema/",
55
57
  "openclaw.plugin.json",
56
58
  "SKILL.md"
57
59
  ],
@@ -85,16 +87,16 @@
85
87
  "reranker API endpoint (/rerank)"
86
88
  ]
87
89
  },
88
- "build": {
89
- "openclawVersion": "2026.4.5",
90
- "pluginSdkVersion": "2026.4.5"
91
- },
92
- "install": {
93
- "minHostVersion": ">=2026.4.5"
94
- },
95
- "compat": {
96
- "pluginApi": ">=2026.4.5",
97
- "minGatewayVersion": "2026.4.5"
90
+ "build": {
91
+ "openclawVersion": "2026.4.5",
92
+ "pluginSdkVersion": "2026.4.5"
93
+ },
94
+ "install": {
95
+ "minHostVersion": ">=2026.4.5"
96
+ },
97
+ "compat": {
98
+ "pluginApi": ">=2026.4.5",
99
+ "minGatewayVersion": "2026.4.5"
98
100
  },
99
101
  "extensions": [
100
102
  "./dist/index.js"
@@ -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
+ }
@@ -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);