@adversity/coding-tool-x 2.3.0 → 2.4.1
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 +41 -0
- package/README.md +8 -0
- package/dist/web/assets/icons-Dom8a0SN.js +1 -0
- package/dist/web/assets/index-CQeUIH7E.css +41 -0
- package/dist/web/assets/index-YrjlFzC4.js +14 -0
- package/dist/web/assets/naive-ui-BjMHakwv.js +1 -0
- package/dist/web/assets/vendors-DtJKdpSj.js +7 -0
- package/dist/web/assets/vue-vendor-VFuFB5f4.js +44 -0
- package/dist/web/index.html +6 -2
- package/package.json +2 -2
- package/src/commands/export-config.js +205 -0
- package/src/config/default.js +1 -1
- package/src/server/api/config-export.js +122 -0
- package/src/server/api/config-sync.js +155 -0
- package/src/server/api/config-templates.js +12 -6
- package/src/server/api/health-check.js +1 -89
- package/src/server/api/permissions.js +92 -69
- package/src/server/api/projects.js +2 -2
- package/src/server/api/sessions.js +70 -70
- package/src/server/api/skills.js +206 -0
- package/src/server/api/terminal.js +26 -0
- package/src/server/index.js +7 -11
- package/src/server/services/config-export-service.js +415 -0
- package/src/server/services/config-sync-service.js +515 -0
- package/src/server/services/config-templates-service.js +61 -38
- package/src/server/services/enhanced-cache.js +196 -0
- package/src/server/services/health-check.js +1 -315
- package/src/server/services/permission-templates-service.js +339 -0
- package/src/server/services/pty-manager.js +35 -1
- package/src/server/services/sessions.js +122 -44
- package/src/server/services/skill-service.js +252 -2
- package/src/server/services/workspace-service.js +44 -84
- package/src/server/websocket-server.js +4 -1
- package/dist/web/assets/index-dhun1bYQ.js +0 -3555
- package/dist/web/assets/index-hHb7DAda.css +0 -41
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Enhanced Cache Manager
|
|
3
|
+
*
|
|
4
|
+
* 提供全局缓存管理,支持TTL、自动清理、LRU策略
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
class CacheManager {
|
|
8
|
+
constructor(options = {}) {
|
|
9
|
+
this.caches = new Map();
|
|
10
|
+
this.ttls = new Map();
|
|
11
|
+
this.accessTimes = new Map();
|
|
12
|
+
this.maxSize = options.maxSize || 1000; // 最大缓存条目数
|
|
13
|
+
this.defaultTTL = options.defaultTTL || 300000; // 默认5分钟
|
|
14
|
+
this.cleanupInterval = options.cleanupInterval || 60000; // 1分钟清理一次
|
|
15
|
+
|
|
16
|
+
// 启动自动清理
|
|
17
|
+
this.startCleanup();
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* 设置缓存
|
|
22
|
+
* @param {string} key - 缓存键
|
|
23
|
+
* @param {any} value - 缓存值
|
|
24
|
+
* @param {number} ttl - 过期时间(毫秒),默认使用defaultTTL
|
|
25
|
+
*/
|
|
26
|
+
set(key, value, ttl) {
|
|
27
|
+
const expiryTime = Date.now() + (ttl || this.defaultTTL);
|
|
28
|
+
|
|
29
|
+
// 如果缓存已满,清理最少使用的条目
|
|
30
|
+
if (this.caches.size >= this.maxSize) {
|
|
31
|
+
this.evictLRU();
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
this.caches.set(key, value);
|
|
35
|
+
this.ttls.set(key, expiryTime);
|
|
36
|
+
this.accessTimes.set(key, Date.now());
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* 获取缓存
|
|
41
|
+
* @param {string} key - 缓存键
|
|
42
|
+
* @returns {any|null} 缓存值,不存在或过期返回null
|
|
43
|
+
*/
|
|
44
|
+
get(key) {
|
|
45
|
+
if (!this.caches.has(key)) {
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// 检查是否过期
|
|
50
|
+
if (Date.now() > this.ttls.get(key)) {
|
|
51
|
+
this.delete(key);
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// 更新访问时间(LRU)
|
|
56
|
+
this.accessTimes.set(key, Date.now());
|
|
57
|
+
return this.caches.get(key);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* 删除缓存
|
|
62
|
+
* @param {string} key - 缓存键
|
|
63
|
+
*/
|
|
64
|
+
delete(key) {
|
|
65
|
+
this.caches.delete(key);
|
|
66
|
+
this.ttls.delete(key);
|
|
67
|
+
this.accessTimes.delete(key);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* 清空所有缓存
|
|
72
|
+
*/
|
|
73
|
+
clear() {
|
|
74
|
+
this.caches.clear();
|
|
75
|
+
this.ttls.clear();
|
|
76
|
+
this.accessTimes.clear();
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* 检查缓存是否存在且有效
|
|
81
|
+
* @param {string} key - 缓存键
|
|
82
|
+
* @returns {boolean}
|
|
83
|
+
*/
|
|
84
|
+
has(key) {
|
|
85
|
+
return this.get(key) !== null;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* 获取或设置缓存(如果不存在则调用工厂函数)
|
|
90
|
+
* @param {string} key - 缓存键
|
|
91
|
+
* @param {Function} factory - 工厂函数,返回值或Promise
|
|
92
|
+
* @param {number} ttl - 过期时间
|
|
93
|
+
* @returns {any|Promise<any>}
|
|
94
|
+
*/
|
|
95
|
+
async getOrSet(key, factory, ttl) {
|
|
96
|
+
const cached = this.get(key);
|
|
97
|
+
if (cached !== null) {
|
|
98
|
+
return cached;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const value = await factory();
|
|
102
|
+
this.set(key, value, ttl);
|
|
103
|
+
return value;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* LRU驱逐策略:移除最久未使用的条目
|
|
108
|
+
*/
|
|
109
|
+
evictLRU() {
|
|
110
|
+
let oldestKey = null;
|
|
111
|
+
let oldestTime = Infinity;
|
|
112
|
+
|
|
113
|
+
for (const [key, accessTime] of this.accessTimes) {
|
|
114
|
+
if (accessTime < oldestTime) {
|
|
115
|
+
oldestTime = accessTime;
|
|
116
|
+
oldestKey = key;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (oldestKey) {
|
|
121
|
+
this.delete(oldestKey);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* 启动自动清理过期缓存
|
|
127
|
+
*/
|
|
128
|
+
startCleanup() {
|
|
129
|
+
this.cleanupTimer = setInterval(() => {
|
|
130
|
+
const now = Date.now();
|
|
131
|
+
const expiredKeys = [];
|
|
132
|
+
|
|
133
|
+
for (const [key, expiryTime] of this.ttls) {
|
|
134
|
+
if (now > expiryTime) {
|
|
135
|
+
expiredKeys.push(key);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
expiredKeys.forEach(key => this.delete(key));
|
|
140
|
+
|
|
141
|
+
if (expiredKeys.length > 0) {
|
|
142
|
+
console.log(`[CacheManager] Cleaned up ${expiredKeys.length} expired entries`);
|
|
143
|
+
}
|
|
144
|
+
}, this.cleanupInterval);
|
|
145
|
+
|
|
146
|
+
// 确保Node.js进程退出时清理定时器
|
|
147
|
+
if (this.cleanupTimer.unref) {
|
|
148
|
+
this.cleanupTimer.unref();
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* 停止自动清理
|
|
154
|
+
*/
|
|
155
|
+
stopCleanup() {
|
|
156
|
+
if (this.cleanupTimer) {
|
|
157
|
+
clearInterval(this.cleanupTimer);
|
|
158
|
+
this.cleanupTimer = null;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* 获取缓存统计信息
|
|
164
|
+
* @returns {object}
|
|
165
|
+
*/
|
|
166
|
+
getStats() {
|
|
167
|
+
return {
|
|
168
|
+
size: this.caches.size,
|
|
169
|
+
maxSize: this.maxSize,
|
|
170
|
+
keys: Array.from(this.caches.keys())
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// 创建全局缓存实例
|
|
176
|
+
const globalCache = new CacheManager({
|
|
177
|
+
maxSize: 1000,
|
|
178
|
+
defaultTTL: 300000, // 5分钟
|
|
179
|
+
cleanupInterval: 60000 // 1分钟
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
// 预定义的缓存键前缀
|
|
183
|
+
const CacheKeys = {
|
|
184
|
+
PROJECTS: 'projects:',
|
|
185
|
+
SESSIONS: 'sessions:',
|
|
186
|
+
SKILLS: 'skills:',
|
|
187
|
+
CONFIG_TEMPLATES: 'config-templates:',
|
|
188
|
+
REPOS: 'repos:',
|
|
189
|
+
HAS_MESSAGES: 'has-messages:'
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
module.exports = {
|
|
193
|
+
CacheManager,
|
|
194
|
+
globalCache,
|
|
195
|
+
CacheKeys
|
|
196
|
+
};
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
const fs = require('fs');
|
|
2
2
|
const path = require('path');
|
|
3
|
-
const os = require('os');
|
|
4
|
-
const readline = require('readline');
|
|
5
3
|
|
|
6
4
|
/**
|
|
7
5
|
* 健康检查:确保项目的 .claude/sessions 目录存在
|
|
@@ -81,319 +79,7 @@ function healthCheckAllProjects(projects) {
|
|
|
81
79
|
};
|
|
82
80
|
}
|
|
83
81
|
|
|
84
|
-
/**
|
|
85
|
-
* 从会话文件中提取 cwd
|
|
86
|
-
* @param {string} sessionFilePath - 会话文件路径
|
|
87
|
-
* @returns {string|null} cwd 或 null
|
|
88
|
-
*/
|
|
89
|
-
function extractCwdFromSession(sessionFilePath) {
|
|
90
|
-
try {
|
|
91
|
-
const content = fs.readFileSync(sessionFilePath, 'utf8');
|
|
92
|
-
const firstLine = content.split('\n')[0];
|
|
93
|
-
if (firstLine) {
|
|
94
|
-
const json = JSON.parse(firstLine);
|
|
95
|
-
return json.cwd || null;
|
|
96
|
-
}
|
|
97
|
-
} catch (err) {
|
|
98
|
-
// Ignore parse errors
|
|
99
|
-
}
|
|
100
|
-
return null;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
/**
|
|
104
|
-
* 扫描旧的全局目录中的会话文件
|
|
105
|
-
* @returns {Object} 扫描结果
|
|
106
|
-
*/
|
|
107
|
-
function scanLegacySessionFiles() {
|
|
108
|
-
try {
|
|
109
|
-
const legacyProjectsDir = path.join(os.homedir(), '.claude', 'projects');
|
|
110
|
-
|
|
111
|
-
if (!fs.existsSync(legacyProjectsDir)) {
|
|
112
|
-
return {
|
|
113
|
-
found: false,
|
|
114
|
-
message: 'Legacy directory not found (nothing to clean)',
|
|
115
|
-
legacyDir: legacyProjectsDir
|
|
116
|
-
};
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
const projects = [];
|
|
120
|
-
const entries = fs.readdirSync(legacyProjectsDir, { withFileTypes: true });
|
|
121
|
-
|
|
122
|
-
for (const entry of entries) {
|
|
123
|
-
if (!entry.isDirectory()) continue;
|
|
124
|
-
|
|
125
|
-
const projectName = entry.name;
|
|
126
|
-
const projectDir = path.join(legacyProjectsDir, projectName);
|
|
127
|
-
const files = fs.readdirSync(projectDir)
|
|
128
|
-
.filter(f => f.endsWith('.jsonl') || f.endsWith('.json'));
|
|
129
|
-
|
|
130
|
-
if (files.length > 0) {
|
|
131
|
-
let totalSize = 0;
|
|
132
|
-
for (const file of files) {
|
|
133
|
-
const filePath = path.join(projectDir, file);
|
|
134
|
-
const stats = fs.statSync(filePath);
|
|
135
|
-
totalSize += stats.size;
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
projects.push({
|
|
139
|
-
projectName,
|
|
140
|
-
projectDir,
|
|
141
|
-
fileCount: files.length,
|
|
142
|
-
totalSize,
|
|
143
|
-
files: files.slice(0, 5) // 只显示前5个文件名
|
|
144
|
-
});
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
return {
|
|
149
|
-
found: true,
|
|
150
|
-
legacyDir: legacyProjectsDir,
|
|
151
|
-
projectCount: projects.length,
|
|
152
|
-
projects
|
|
153
|
-
};
|
|
154
|
-
} catch (err) {
|
|
155
|
-
return {
|
|
156
|
-
found: false,
|
|
157
|
-
error: err.message
|
|
158
|
-
};
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
/**
|
|
163
|
-
* 迁移会话文件到正确的位置
|
|
164
|
-
* @param {Object} options - 迁移选项
|
|
165
|
-
* @returns {Object} 迁移结果
|
|
166
|
-
*/
|
|
167
|
-
function migrateSessionFiles(options = {}) {
|
|
168
|
-
const {
|
|
169
|
-
dryRun = false, // 是否只是预演
|
|
170
|
-
projectNames = null // 指定要迁移的项目
|
|
171
|
-
} = options;
|
|
172
|
-
|
|
173
|
-
try {
|
|
174
|
-
const legacyProjectsDir = path.join(os.homedir(), '.claude', 'projects');
|
|
175
|
-
|
|
176
|
-
if (!fs.existsSync(legacyProjectsDir)) {
|
|
177
|
-
return {
|
|
178
|
-
success: true,
|
|
179
|
-
message: 'No legacy directory found',
|
|
180
|
-
migrated: 0
|
|
181
|
-
};
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
const results = {
|
|
185
|
-
dryRun,
|
|
186
|
-
migrated: [],
|
|
187
|
-
skipped: [],
|
|
188
|
-
errors: []
|
|
189
|
-
};
|
|
190
|
-
|
|
191
|
-
const entries = fs.readdirSync(legacyProjectsDir, { withFileTypes: true });
|
|
192
|
-
|
|
193
|
-
for (const entry of entries) {
|
|
194
|
-
if (!entry.isDirectory()) continue;
|
|
195
|
-
|
|
196
|
-
const projectName = entry.name;
|
|
197
|
-
|
|
198
|
-
// 如果指定了项目列表,只迁移列表中的项目
|
|
199
|
-
if (projectNames && !projectNames.includes(projectName)) {
|
|
200
|
-
continue;
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
const projectDir = path.join(legacyProjectsDir, projectName);
|
|
204
|
-
const files = fs.readdirSync(projectDir)
|
|
205
|
-
.filter(f => f.endsWith('.jsonl'));
|
|
206
|
-
|
|
207
|
-
for (const file of files) {
|
|
208
|
-
const oldPath = path.join(projectDir, file);
|
|
209
|
-
|
|
210
|
-
try {
|
|
211
|
-
// 从会话文件提取 cwd
|
|
212
|
-
const cwd = extractCwdFromSession(oldPath);
|
|
213
|
-
|
|
214
|
-
if (!cwd || !fs.existsSync(cwd)) {
|
|
215
|
-
results.skipped.push({
|
|
216
|
-
file,
|
|
217
|
-
reason: 'Invalid or missing cwd',
|
|
218
|
-
oldPath
|
|
219
|
-
});
|
|
220
|
-
continue;
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
// 目标路径: {cwd}/.claude/sessions/{file}
|
|
224
|
-
const targetDir = path.join(cwd, '.claude', 'sessions');
|
|
225
|
-
const newPath = path.join(targetDir, file);
|
|
226
|
-
|
|
227
|
-
// 如果文件已存在,跳过
|
|
228
|
-
if (fs.existsSync(newPath)) {
|
|
229
|
-
results.skipped.push({
|
|
230
|
-
file,
|
|
231
|
-
reason: 'Already exists at target',
|
|
232
|
-
oldPath,
|
|
233
|
-
newPath
|
|
234
|
-
});
|
|
235
|
-
continue;
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
if (!dryRun) {
|
|
239
|
-
// 确保目标目录存在
|
|
240
|
-
if (!fs.existsSync(targetDir)) {
|
|
241
|
-
fs.mkdirSync(targetDir, { recursive: true });
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
// 复制文件(保留原文件)
|
|
245
|
-
fs.copyFileSync(oldPath, newPath);
|
|
246
|
-
console.log(`[Migration] Migrated: ${file} -> ${newPath}`);
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
results.migrated.push({
|
|
250
|
-
file,
|
|
251
|
-
oldPath,
|
|
252
|
-
newPath,
|
|
253
|
-
cwd
|
|
254
|
-
});
|
|
255
|
-
} catch (err) {
|
|
256
|
-
results.errors.push({
|
|
257
|
-
file,
|
|
258
|
-
oldPath,
|
|
259
|
-
error: err.message
|
|
260
|
-
});
|
|
261
|
-
}
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
return {
|
|
266
|
-
success: true,
|
|
267
|
-
dryRun,
|
|
268
|
-
migratedCount: results.migrated.length,
|
|
269
|
-
skippedCount: results.skipped.length,
|
|
270
|
-
errorCount: results.errors.length,
|
|
271
|
-
results
|
|
272
|
-
};
|
|
273
|
-
} catch (err) {
|
|
274
|
-
return {
|
|
275
|
-
success: false,
|
|
276
|
-
error: err.message
|
|
277
|
-
};
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
/**
|
|
282
|
-
* 清理旧的全局目录中的会话文件
|
|
283
|
-
* @param {Object} options - 清理选项
|
|
284
|
-
* @returns {Object} 清理结果
|
|
285
|
-
*/
|
|
286
|
-
function cleanLegacySessionFiles(options = {}) {
|
|
287
|
-
const {
|
|
288
|
-
dryRun = false, // 是否只是预演,不实际删除
|
|
289
|
-
projectNames = null // 指定要清理的项目名称列表,null 表示全部
|
|
290
|
-
} = options;
|
|
291
|
-
|
|
292
|
-
try {
|
|
293
|
-
const legacyProjectsDir = path.join(os.homedir(), '.claude', 'projects');
|
|
294
|
-
|
|
295
|
-
if (!fs.existsSync(legacyProjectsDir)) {
|
|
296
|
-
return {
|
|
297
|
-
success: true,
|
|
298
|
-
message: 'No legacy directory found',
|
|
299
|
-
deleted: 0
|
|
300
|
-
};
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
const results = {
|
|
304
|
-
dryRun,
|
|
305
|
-
deleted: [],
|
|
306
|
-
errors: [],
|
|
307
|
-
totalSize: 0
|
|
308
|
-
};
|
|
309
|
-
|
|
310
|
-
const entries = fs.readdirSync(legacyProjectsDir, { withFileTypes: true });
|
|
311
|
-
|
|
312
|
-
for (const entry of entries) {
|
|
313
|
-
if (!entry.isDirectory()) continue;
|
|
314
|
-
|
|
315
|
-
const projectName = entry.name;
|
|
316
|
-
|
|
317
|
-
// 如果指定了项目列表,只清理列表中的项目
|
|
318
|
-
if (projectNames && !projectNames.includes(projectName)) {
|
|
319
|
-
continue;
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
const projectDir = path.join(legacyProjectsDir, projectName);
|
|
323
|
-
const files = fs.readdirSync(projectDir);
|
|
324
|
-
|
|
325
|
-
for (const file of files) {
|
|
326
|
-
const filePath = path.join(projectDir, file);
|
|
327
|
-
|
|
328
|
-
try {
|
|
329
|
-
const stats = fs.statSync(filePath);
|
|
330
|
-
results.totalSize += stats.size;
|
|
331
|
-
|
|
332
|
-
if (!dryRun) {
|
|
333
|
-
fs.unlinkSync(filePath);
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
results.deleted.push({
|
|
337
|
-
projectName,
|
|
338
|
-
file,
|
|
339
|
-
size: stats.size
|
|
340
|
-
});
|
|
341
|
-
} catch (err) {
|
|
342
|
-
results.errors.push({
|
|
343
|
-
projectName,
|
|
344
|
-
file,
|
|
345
|
-
error: err.message
|
|
346
|
-
});
|
|
347
|
-
}
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
// 如果项目目录为空,删除目录
|
|
351
|
-
if (!dryRun) {
|
|
352
|
-
try {
|
|
353
|
-
const remainingFiles = fs.readdirSync(projectDir);
|
|
354
|
-
if (remainingFiles.length === 0) {
|
|
355
|
-
fs.rmdirSync(projectDir);
|
|
356
|
-
console.log(`[Cleanup] Removed empty directory: ${projectDir}`);
|
|
357
|
-
}
|
|
358
|
-
} catch (err) {
|
|
359
|
-
console.warn(`[Cleanup] Failed to remove directory ${projectDir}:`, err.message);
|
|
360
|
-
}
|
|
361
|
-
}
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
// 如果 projects 目录为空,删除它
|
|
365
|
-
if (!dryRun) {
|
|
366
|
-
try {
|
|
367
|
-
const remainingProjects = fs.readdirSync(legacyProjectsDir);
|
|
368
|
-
if (remainingProjects.length === 0) {
|
|
369
|
-
fs.rmdirSync(legacyProjectsDir);
|
|
370
|
-
console.log(`[Cleanup] Removed empty legacy directory: ${legacyProjectsDir}`);
|
|
371
|
-
}
|
|
372
|
-
} catch (err) {
|
|
373
|
-
console.warn(`[Cleanup] Failed to remove legacy directory:`, err.message);
|
|
374
|
-
}
|
|
375
|
-
}
|
|
376
|
-
|
|
377
|
-
return {
|
|
378
|
-
success: true,
|
|
379
|
-
dryRun,
|
|
380
|
-
deletedCount: results.deleted.length,
|
|
381
|
-
errorCount: results.errors.length,
|
|
382
|
-
totalSize: results.totalSize,
|
|
383
|
-
results
|
|
384
|
-
};
|
|
385
|
-
} catch (err) {
|
|
386
|
-
return {
|
|
387
|
-
success: false,
|
|
388
|
-
error: err.message
|
|
389
|
-
};
|
|
390
|
-
}
|
|
391
|
-
}
|
|
392
|
-
|
|
393
82
|
module.exports = {
|
|
394
83
|
ensureProjectClaudeDir,
|
|
395
|
-
healthCheckAllProjects
|
|
396
|
-
scanLegacySessionFiles,
|
|
397
|
-
migrateSessionFiles,
|
|
398
|
-
cleanLegacySessionFiles
|
|
84
|
+
healthCheckAllProjects
|
|
399
85
|
};
|