ccjk 13.3.14 → 13.3.16

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.
@@ -0,0 +1,414 @@
1
+ import { existsSync, mkdirSync } from 'node:fs';
2
+ import { appendFile, readFile } from 'node:fs/promises';
3
+ import { homedir } from 'node:os';
4
+ import { j as join } from './ccjk.bQ7Dh1g4.mjs';
5
+
6
+ class BrainTelemetry {
7
+ logPath;
8
+ constructor(logPath) {
9
+ const ccjkDir = join(homedir(), ".ccjk");
10
+ if (!existsSync(ccjkDir)) {
11
+ mkdirSync(ccjkDir, { recursive: true });
12
+ }
13
+ this.logPath = logPath ?? join(ccjkDir, "task-decision-log.jsonl");
14
+ }
15
+ /**
16
+ * 记录任务执行
17
+ */
18
+ async logTask(log) {
19
+ const line = JSON.stringify({
20
+ ...log,
21
+ timestamp: log.timestamp || (/* @__PURE__ */ new Date()).toISOString()
22
+ });
23
+ await appendFile(this.logPath, `${line}
24
+ `, "utf-8");
25
+ const stats = await this.getStats();
26
+ if (stats.totalTasks % 10 === 0) {
27
+ await this.analyzeAndUpdate();
28
+ }
29
+ }
30
+ /**
31
+ * 读取所有日志
32
+ */
33
+ async readLogs() {
34
+ if (!existsSync(this.logPath)) {
35
+ return [];
36
+ }
37
+ const content = await readFile(this.logPath, "utf-8");
38
+ const lines = content.trim().split("\n").filter(Boolean);
39
+ return lines.map((line) => JSON.parse(line));
40
+ }
41
+ /**
42
+ * 获取统计数据
43
+ */
44
+ async getStats() {
45
+ const logs = await this.readLogs();
46
+ if (logs.length === 0) {
47
+ return {
48
+ totalTasks: 0,
49
+ byLevel: {},
50
+ recentTrend: [],
51
+ recommendations: []
52
+ };
53
+ }
54
+ const byLevel = {};
55
+ for (const log of logs) {
56
+ if (!byLevel[log.level]) {
57
+ byLevel[log.level] = [];
58
+ }
59
+ byLevel[log.level].push(log);
60
+ }
61
+ const levelStats = {};
62
+ for (const [level, levelLogs] of Object.entries(byLevel)) {
63
+ const successCount = levelLogs.filter((l) => l.success).length;
64
+ const totalDuration = levelLogs.reduce((sum, l) => sum + l.duration, 0);
65
+ const totalScore = levelLogs.reduce((sum, l) => sum + l.effectScore, 0);
66
+ levelStats[Number(level)] = {
67
+ count: levelLogs.length,
68
+ successRate: successCount / levelLogs.length,
69
+ avgDuration: totalDuration / levelLogs.length,
70
+ avgEffectScore: totalScore / levelLogs.length
71
+ };
72
+ }
73
+ const thirtyDaysAgo = /* @__PURE__ */ new Date();
74
+ thirtyDaysAgo.setDate(thirtyDaysAgo.getDate() - 30);
75
+ const recentLogs = logs.filter(
76
+ (log) => new Date(log.timestamp) >= thirtyDaysAgo
77
+ );
78
+ const dailyStats = /* @__PURE__ */ new Map();
79
+ for (const log of recentLogs) {
80
+ const date = log.timestamp.split("T")[0];
81
+ const stats = dailyStats.get(date) ?? { count: 0, success: 0 };
82
+ stats.count++;
83
+ if (log.success) stats.success++;
84
+ dailyStats.set(date, stats);
85
+ }
86
+ const recentTrend = Array.from(dailyStats.entries()).map(([date, stats]) => ({
87
+ date,
88
+ count: stats.count,
89
+ successRate: stats.success / stats.count
90
+ })).sort((a, b) => a.date.localeCompare(b.date));
91
+ const recommendations = this.generateRecommendations(levelStats);
92
+ return {
93
+ totalTasks: logs.length,
94
+ byLevel: levelStats,
95
+ recentTrend,
96
+ recommendations
97
+ };
98
+ }
99
+ /**
100
+ * 生成优化建议
101
+ */
102
+ generateRecommendations(levelStats) {
103
+ const recommendations = [];
104
+ for (const [level, stats] of Object.entries(levelStats)) {
105
+ const levelNum = Number(level);
106
+ if (stats.successRate < 0.8) {
107
+ recommendations.push(
108
+ `Level ${levelNum} \u6210\u529F\u7387\u504F\u4F4E (${(stats.successRate * 100).toFixed(1)}%)\uFF0C\u8003\u8651\u964D\u7EA7\u5230\u66F4\u7B80\u5355\u7684\u5C42\u7EA7`
109
+ );
110
+ }
111
+ if (stats.avgEffectScore < 7) {
112
+ recommendations.push(
113
+ `Level ${levelNum} \u6548\u679C\u8BC4\u5206\u504F\u4F4E (${stats.avgEffectScore.toFixed(1)}/10)\uFF0C\u9700\u8981\u4F18\u5316\u6267\u884C\u7B56\u7565`
114
+ );
115
+ }
116
+ if (levelNum <= 2 && stats.avgDuration > 30) {
117
+ recommendations.push(
118
+ `Level ${levelNum} \u5E73\u5747\u8017\u65F6\u8FC7\u957F (${stats.avgDuration.toFixed(1)}s)\uFF0C\u8003\u8651\u4F18\u5316\u6216\u5347\u7EA7`
119
+ );
120
+ }
121
+ }
122
+ return recommendations;
123
+ }
124
+ /**
125
+ * 分析并更新方法论
126
+ */
127
+ async analyzeAndUpdate() {
128
+ const stats = await this.getStats();
129
+ if (stats.recommendations.length > 0) {
130
+ console.log("\n\u{1F4CA} Brain Telemetry Analysis:");
131
+ for (const rec of stats.recommendations) {
132
+ console.log(` \u{1F4A1} ${rec}`);
133
+ }
134
+ console.log();
135
+ }
136
+ }
137
+ /**
138
+ * 清空日志
139
+ */
140
+ async clear() {
141
+ if (existsSync(this.logPath)) {
142
+ await appendFile(this.logPath, "", "utf-8");
143
+ }
144
+ }
145
+ }
146
+ let globalTelemetry = null;
147
+ function getTelemetry() {
148
+ if (!globalTelemetry) {
149
+ globalTelemetry = new BrainTelemetry();
150
+ }
151
+ return globalTelemetry;
152
+ }
153
+
154
+ function decideCapability(context) {
155
+ const { input, conversationTurns, hasUncommittedChanges, recentFailures } = context;
156
+ const lower = input.toLowerCase();
157
+ if (lower.includes("\u89E3\u91CA") || lower.includes("\u4EC0\u4E48\u662F") || lower.includes("\u4E3A\u4EC0\u4E48") || lower.includes("\u5982\u4F55\u7406\u89E3")) {
158
+ return {
159
+ level: 0,
160
+ reasoning: "\u7EAF\u6587\u672C\u63A8\u7406\u4EFB\u52A1",
161
+ expectedSteps: 1,
162
+ expectedDuration: 5,
163
+ complexity: 1
164
+ };
165
+ }
166
+ if (lower.startsWith("/") || lower.includes("commit") || lower.includes("review")) {
167
+ return {
168
+ level: 1,
169
+ reasoning: "Skill\u5FEB\u6377\u6307\u4EE4",
170
+ expectedSteps: 1,
171
+ expectedDuration: 10,
172
+ complexity: 2
173
+ };
174
+ }
175
+ if ((lower.includes("\u4FEE\u6539") || lower.includes("fix") || lower.includes("\u66F4\u65B0")) && !lower.includes("\u6240\u6709") && !lower.includes("\u6279\u91CF")) {
176
+ return {
177
+ level: 2,
178
+ reasoning: "\u5355\u6587\u4EF6\u4FEE\u6539",
179
+ expectedSteps: 3,
180
+ expectedDuration: 20,
181
+ complexity: 3
182
+ };
183
+ }
184
+ if (lower.includes("\u91CD\u6784") || lower.includes("\u6279\u91CF") || lower.includes("\u6240\u6709") || lower.includes("\u6DFB\u52A0") && lower.includes("\u529F\u80FD")) {
185
+ return {
186
+ level: 3,
187
+ reasoning: "\u591A\u6587\u4EF6\u534F\u8C03\u64CD\u4F5C",
188
+ expectedSteps: 8,
189
+ expectedDuration: 60,
190
+ complexity: 6
191
+ };
192
+ }
193
+ if (lower.includes("\u5206\u6790\u6574\u4E2A") || lower.includes("\u5168\u5C40") || lower.includes("\u67B6\u6784") || lower.includes("\u8FC1\u79FB")) {
194
+ return {
195
+ level: 4,
196
+ reasoning: "\u590D\u6742\u4EFB\u52A1\uFF0C\u5EFA\u8BAEsubagent",
197
+ expectedSteps: 15,
198
+ expectedDuration: 180,
199
+ complexity: 8
200
+ };
201
+ }
202
+ if (lower.includes("\u5B8C\u5168\u91CD\u5199") || lower.includes("\u4ECE\u96F6\u5F00\u59CB") || lower.includes("\u5927\u89C4\u6A21")) {
203
+ return {
204
+ level: 5,
205
+ reasoning: "\u6781\u590D\u6742\u4EFB\u52A1\uFF0C\u5FC5\u987Bsubagent",
206
+ expectedSteps: 30,
207
+ expectedDuration: 600,
208
+ complexity: 10
209
+ };
210
+ }
211
+ const complexity = Math.min(
212
+ 10,
213
+ 3 + conversationTurns * 0.5 + recentFailures * 2 + (hasUncommittedChanges ? 1 : 0)
214
+ );
215
+ if (complexity >= 8) {
216
+ return {
217
+ level: 4,
218
+ reasoning: "\u4E0A\u4E0B\u6587\u590D\u6742\u5EA6\u9AD8",
219
+ expectedSteps: 12,
220
+ expectedDuration: 120,
221
+ complexity: Math.round(complexity)
222
+ };
223
+ }
224
+ if (complexity >= 5) {
225
+ return {
226
+ level: 3,
227
+ reasoning: "\u4E2D\u7B49\u590D\u6742\u5EA6\u4EFB\u52A1",
228
+ expectedSteps: 6,
229
+ expectedDuration: 45,
230
+ complexity: Math.round(complexity)
231
+ };
232
+ }
233
+ return {
234
+ level: 2,
235
+ reasoning: "\u6807\u51C6\u64CD\u4F5C",
236
+ expectedSteps: 3,
237
+ expectedDuration: 20,
238
+ complexity: Math.round(complexity)
239
+ };
240
+ }
241
+ function getCapabilityName(level) {
242
+ const names = {
243
+ 0: "\u7EAF\u6587\u672C\u63A8\u7406",
244
+ 1: "Skill\u6267\u884C",
245
+ 2: "\u5355\u6587\u4EF6\u64CD\u4F5C",
246
+ 3: "\u591A\u6587\u4EF6\u534F\u8C03",
247
+ 4: "\u590D\u6742\u4EFB\u52A1",
248
+ 5: "\u6781\u590D\u6742\u4EFB\u52A1"
249
+ };
250
+ return names[level];
251
+ }
252
+
253
+ class SmartRouter {
254
+ config;
255
+ recentFailures = 0;
256
+ constructor(config = {}) {
257
+ this.config = {
258
+ capabilityPreference: config.capabilityPreference ?? 2,
259
+ autoSubagentThreshold: config.autoSubagentThreshold ?? 7,
260
+ maxParallelAgents: config.maxParallelAgents ?? 3,
261
+ enableTelemetry: config.enableTelemetry ?? true,
262
+ showReasoning: config.showReasoning ?? true
263
+ };
264
+ }
265
+ /**
266
+ * 路由任务到合适的能力层级
267
+ */
268
+ async route(input, context = {}) {
269
+ const fullContext = {
270
+ input,
271
+ conversationTurns: context.conversationTurns ?? 0,
272
+ cwd: context.cwd ?? process.cwd(),
273
+ hasUncommittedChanges: context.hasUncommittedChanges ?? false,
274
+ recentFailures: this.recentFailures
275
+ };
276
+ const decision = decideCapability(fullContext);
277
+ const adjustedDecision = this.applyPreference(decision);
278
+ const executionAdvice = this.generateAdvice(adjustedDecision);
279
+ if (this.config.showReasoning) {
280
+ console.log(`
281
+ [Brain Router] \u51B3\u7B56: ${getCapabilityName(adjustedDecision.level)}`);
282
+ console.log(` \u7406\u7531: ${adjustedDecision.reasoning}`);
283
+ console.log(` \u9884\u671F: ${adjustedDecision.expectedSteps}\u6B65 / ${adjustedDecision.expectedDuration}s`);
284
+ console.log(` \u590D\u6742\u5EA6: ${adjustedDecision.complexity}/10
285
+ `);
286
+ }
287
+ return {
288
+ decision: adjustedDecision,
289
+ shouldExecute: this.shouldExecute(adjustedDecision),
290
+ executionAdvice
291
+ };
292
+ }
293
+ /**
294
+ * 应用用户偏好调整决策
295
+ */
296
+ applyPreference(decision) {
297
+ const { capabilityPreference, autoSubagentThreshold } = this.config;
298
+ if (capabilityPreference <= 2 && decision.level >= 3) {
299
+ if (decision.complexity < autoSubagentThreshold) {
300
+ return {
301
+ ...decision,
302
+ level: 2,
303
+ // 降级到主Agent
304
+ reasoning: `${decision.reasoning} (\u7528\u6237\u504F\u597D\u7B80\u5355\uFF0C\u964D\u7EA7\u5230\u4E3BAgent)`
305
+ };
306
+ }
307
+ }
308
+ if (capabilityPreference >= 4 && decision.level === 2) {
309
+ if (decision.complexity >= 5) {
310
+ return {
311
+ ...decision,
312
+ level: 3,
313
+ // 升级到Subagent
314
+ reasoning: `${decision.reasoning} (\u7528\u6237\u504F\u597D\u590D\u6742\uFF0C\u5347\u7EA7\u5230Subagent)`
315
+ };
316
+ }
317
+ }
318
+ return decision;
319
+ }
320
+ /**
321
+ * 判断是否应该执行
322
+ */
323
+ shouldExecute(decision) {
324
+ if (decision.level <= 1) return true;
325
+ if (decision.level === 2) return true;
326
+ if (decision.level === 3) {
327
+ return decision.complexity >= this.config.autoSubagentThreshold;
328
+ }
329
+ return false;
330
+ }
331
+ /**
332
+ * 生成执行建议
333
+ */
334
+ generateAdvice(decision) {
335
+ switch (decision.level) {
336
+ case 0:
337
+ return "\u76F4\u63A5\u56DE\u7B54\uFF0C\u65E0\u9700\u5DE5\u5177\u8C03\u7528";
338
+ case 1:
339
+ return "\u4F7F\u7528Skill\u6267\u884C\uFF0C\u5355\u6B65\u5B8C\u6210";
340
+ case 2:
341
+ return "\u4E3BAgent\u5904\u7406\uFF0C\u9884\u8BA13-5\u6B65\u5B8C\u6210";
342
+ case 3:
343
+ return `\u542F\u52A8Subagent\uFF0C\u9884\u8BA1${decision.expectedSteps}\u6B65\u5B8C\u6210`;
344
+ case 4:
345
+ return "\u9700\u8981\u591AAgent\u534F\u4F5C\uFF0C\u5EFA\u8BAE\u7528\u6237\u786E\u8BA4";
346
+ case 5:
347
+ return "\u9700\u8981\u957F\u5BFFSession\uFF0C\u5EFA\u8BAE\u7528\u6237\u786E\u8BA4";
348
+ default:
349
+ return "\u672A\u77E5\u5C42\u7EA7";
350
+ }
351
+ }
352
+ /**
353
+ * 记录任务执行结果
354
+ */
355
+ async recordExecution(decision, result) {
356
+ if (!this.config.enableTelemetry) return;
357
+ const telemetry = getTelemetry();
358
+ const log = {
359
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
360
+ task: decision.reasoning,
361
+ level: decision.level,
362
+ actualSteps: result.actualSteps,
363
+ duration: result.duration,
364
+ success: result.success,
365
+ effectScore: result.effectScore,
366
+ recommendation: this.generateRecommendation(decision, result)
367
+ };
368
+ await telemetry.logTask(log);
369
+ if (!result.success) {
370
+ this.recentFailures++;
371
+ } else {
372
+ this.recentFailures = 0;
373
+ }
374
+ }
375
+ /**
376
+ * 生成下次建议
377
+ */
378
+ generateRecommendation(decision, result) {
379
+ if (!result.success) {
380
+ return `\u5931\u8D25\uFF0C\u4E0B\u6B21\u8003\u8651\u5347\u7EA7\u5230Level ${decision.level + 1}`;
381
+ }
382
+ if (result.effectScore < 7) {
383
+ return `\u6548\u679C\u4E00\u822C\uFF0C\u4E0B\u6B21\u8003\u8651\u8C03\u6574\u7B56\u7565`;
384
+ }
385
+ return `\u6548\u679C\u826F\u597D\uFF0C\u7EE7\u7EED\u4F7F\u7528Level ${decision.level}`;
386
+ }
387
+ /**
388
+ * 获取配置
389
+ */
390
+ getConfig() {
391
+ return { ...this.config };
392
+ }
393
+ /**
394
+ * 更新配置
395
+ */
396
+ updateConfig(config) {
397
+ this.config = { ...this.config, ...config };
398
+ }
399
+ /**
400
+ * 重置失败计数
401
+ */
402
+ resetFailures() {
403
+ this.recentFailures = 0;
404
+ }
405
+ }
406
+ let globalRouter = null;
407
+ function getSmartRouter() {
408
+ if (!globalRouter) {
409
+ globalRouter = new SmartRouter();
410
+ }
411
+ return globalRouter;
412
+ }
413
+
414
+ export { getSmartRouter as a, getCapabilityName as b, getTelemetry as g };
@@ -0,0 +1,225 @@
1
+ import { existsSync, mkdirSync, readFileSync, writeFileSync, readdirSync } from 'node:fs';
2
+ import { homedir } from 'node:os';
3
+ import { j as join } from './ccjk.bQ7Dh1g4.mjs';
4
+
5
+ class StatsStorage {
6
+ baseDir;
7
+ recordsDir;
8
+ dailyDir;
9
+ constructor(baseDir) {
10
+ this.baseDir = baseDir || join(homedir(), ".ccjk", "stats");
11
+ this.recordsDir = join(this.baseDir, "records");
12
+ this.dailyDir = join(this.baseDir, "daily");
13
+ this.ensureDirectories();
14
+ }
15
+ /**
16
+ * Ensure storage directories exist
17
+ */
18
+ ensureDirectories() {
19
+ for (const dir of [this.baseDir, this.recordsDir, this.dailyDir]) {
20
+ if (!existsSync(dir)) {
21
+ mkdirSync(dir, { recursive: true });
22
+ }
23
+ }
24
+ }
25
+ /**
26
+ * Get file path for a specific date
27
+ */
28
+ getRecordFilePath(date) {
29
+ return join(this.recordsDir, `${date}.json`);
30
+ }
31
+ /**
32
+ * Get daily stats file path for a specific date
33
+ */
34
+ getDailyStatsFilePath(date) {
35
+ return join(this.dailyDir, `${date}.json`);
36
+ }
37
+ /**
38
+ * Format date as YYYY-MM-DD
39
+ */
40
+ formatDate(timestamp) {
41
+ const date = new Date(timestamp);
42
+ const year = date.getFullYear();
43
+ const month = String(date.getMonth() + 1).padStart(2, "0");
44
+ const day = String(date.getDate()).padStart(2, "0");
45
+ return `${year}-${month}-${day}`;
46
+ }
47
+ /**
48
+ * Save a request record
49
+ */
50
+ saveRecord(record) {
51
+ const date = this.formatDate(record.timestamp);
52
+ const filePath = this.getRecordFilePath(date);
53
+ let records = [];
54
+ if (existsSync(filePath)) {
55
+ try {
56
+ const content = readFileSync(filePath, "utf-8");
57
+ records = JSON.parse(content);
58
+ } catch {
59
+ records = [];
60
+ }
61
+ }
62
+ records.push(record);
63
+ writeFileSync(filePath, JSON.stringify(records, null, 2), "utf-8");
64
+ }
65
+ /**
66
+ * Get records for a specific date
67
+ */
68
+ getRecordsByDate(date) {
69
+ const filePath = this.getRecordFilePath(date);
70
+ if (!existsSync(filePath)) {
71
+ return [];
72
+ }
73
+ try {
74
+ const content = readFileSync(filePath, "utf-8");
75
+ return JSON.parse(content);
76
+ } catch {
77
+ return [];
78
+ }
79
+ }
80
+ /**
81
+ * Get records for a date range
82
+ */
83
+ getRecordsByDateRange(startDate, endDate) {
84
+ const records = [];
85
+ const start = new Date(startDate);
86
+ const end = new Date(endDate);
87
+ const d = new Date(start);
88
+ while (d <= end) {
89
+ const dateStr = this.formatDate(d.getTime());
90
+ records.push(...this.getRecordsByDate(dateStr));
91
+ d.setDate(d.getDate() + 1);
92
+ }
93
+ return records;
94
+ }
95
+ /**
96
+ * Get all available record dates
97
+ */
98
+ getAvailableDates() {
99
+ if (!existsSync(this.recordsDir)) {
100
+ return [];
101
+ }
102
+ const files = readdirSync(this.recordsDir);
103
+ return files.filter((f) => f.endsWith(".json")).map((f) => f.replace(".json", "")).sort();
104
+ }
105
+ /**
106
+ * Save daily statistics
107
+ */
108
+ saveDailyStats(stats) {
109
+ const filePath = this.getDailyStatsFilePath(stats.date);
110
+ writeFileSync(filePath, JSON.stringify(stats, null, 2), "utf-8");
111
+ }
112
+ /**
113
+ * Get daily statistics for a specific date
114
+ */
115
+ getDailyStats(date) {
116
+ const filePath = this.getDailyStatsFilePath(date);
117
+ if (!existsSync(filePath)) {
118
+ return null;
119
+ }
120
+ try {
121
+ const content = readFileSync(filePath, "utf-8");
122
+ return JSON.parse(content);
123
+ } catch {
124
+ return null;
125
+ }
126
+ }
127
+ /**
128
+ * Get daily statistics for a date range
129
+ */
130
+ getDailyStatsByDateRange(startDate, endDate) {
131
+ const stats = [];
132
+ const start = new Date(startDate);
133
+ const end = new Date(endDate);
134
+ const d = new Date(start);
135
+ while (d <= end) {
136
+ const dateStr = this.formatDate(d.getTime());
137
+ const dailyStats = this.getDailyStats(dateStr);
138
+ if (dailyStats) {
139
+ stats.push(dailyStats);
140
+ }
141
+ d.setDate(d.getDate() + 1);
142
+ }
143
+ return stats;
144
+ }
145
+ /**
146
+ * Get all available daily stats dates
147
+ */
148
+ getAvailableDailyStatsDates() {
149
+ if (!existsSync(this.dailyDir)) {
150
+ return [];
151
+ }
152
+ const files = readdirSync(this.dailyDir);
153
+ return files.filter((f) => f.endsWith(".json")).map((f) => f.replace(".json", "")).sort();
154
+ }
155
+ /**
156
+ * Calculate date range for a period
157
+ */
158
+ getDateRangeForPeriod(period) {
159
+ const now = /* @__PURE__ */ new Date();
160
+ const endDate = this.formatDate(now.getTime());
161
+ if (period === "all") {
162
+ const dates = this.getAvailableDates();
163
+ const startDate2 = dates.length > 0 ? dates[0] : endDate;
164
+ return { startDate: startDate2, endDate };
165
+ }
166
+ const days = period === "1d" ? 1 : period === "7d" ? 7 : period === "30d" ? 30 : 90;
167
+ const start = new Date(now);
168
+ start.setDate(start.getDate() - days + 1);
169
+ const startDate = this.formatDate(start.getTime());
170
+ return { startDate, endDate };
171
+ }
172
+ /**
173
+ * Clean up old records (older than specified days)
174
+ */
175
+ cleanupOldRecords(daysToKeep) {
176
+ const cutoffDate = /* @__PURE__ */ new Date();
177
+ cutoffDate.setDate(cutoffDate.getDate() - daysToKeep);
178
+ const cutoffStr = this.formatDate(cutoffDate.getTime());
179
+ const dates = this.getAvailableDates();
180
+ let deletedCount = 0;
181
+ for (const date of dates) {
182
+ if (date < cutoffStr) {
183
+ try {
184
+ const recordFile = this.getRecordFilePath(date);
185
+ const dailyFile = this.getDailyStatsFilePath(date);
186
+ if (existsSync(recordFile)) {
187
+ deletedCount++;
188
+ }
189
+ if (existsSync(dailyFile)) {
190
+ }
191
+ } catch {
192
+ }
193
+ }
194
+ }
195
+ return deletedCount;
196
+ }
197
+ /**
198
+ * Get storage statistics
199
+ */
200
+ getStorageStats() {
201
+ const recordDates = this.getAvailableDates();
202
+ const dailyDates = this.getAvailableDailyStatsDates();
203
+ let totalRecords = 0;
204
+ for (const date of recordDates) {
205
+ const records = this.getRecordsByDate(date);
206
+ totalRecords += records.length;
207
+ }
208
+ return {
209
+ totalRecordFiles: recordDates.length,
210
+ totalDailyFiles: dailyDates.length,
211
+ oldestDate: recordDates.length > 0 ? recordDates[0] : null,
212
+ newestDate: recordDates.length > 0 ? recordDates[recordDates.length - 1] : null,
213
+ totalRecords
214
+ };
215
+ }
216
+ }
217
+ let storageInstance = null;
218
+ function getStatsStorage() {
219
+ if (!storageInstance) {
220
+ storageInstance = new StatsStorage();
221
+ }
222
+ return storageInstance;
223
+ }
224
+
225
+ export { getStatsStorage as g };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "ccjk",
3
3
  "type": "module",
4
- "version": "13.3.14",
4
+ "version": "13.3.16",
5
5
  "packageManager": "pnpm@10.17.1",
6
6
  "description": "Turn Claude Code into a production-ready AI dev environment with one-command setup, persistent memory, MCP automation, cloud sync, and zero-config browser workflows.",
7
7
  "author": {