ccjk 4.0.0-beta.1 → 4.0.0-beta.3
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/LICENSE +50 -1
- package/dist/chunks/claude-wrapper.mjs +3587 -394
- package/dist/chunks/codex.mjs +3 -3
- package/dist/chunks/context.mjs +737 -214
- package/dist/chunks/index3.mjs +19 -19
- package/dist/chunks/index4.mjs +8 -237
- package/dist/chunks/init.mjs +25 -9
- package/dist/chunks/menu.mjs +1 -1
- package/dist/chunks/notification.mjs +20 -20
- package/dist/chunks/package.mjs +1 -1
- package/dist/chunks/platform.mjs +21 -70
- package/dist/chunks/version-checker.mjs +31 -31
- package/dist/cli.mjs +8 -7
- package/dist/i18n/locales/en/errors.json +14 -1
- package/dist/i18n/locales/zh-CN/errors.json +14 -1
- package/dist/index.d.mts +271 -196
- package/dist/index.d.ts +271 -196
- package/dist/index.mjs +2 -2
- package/package.json +67 -63
- package/templates/claude-code/en/workflow/essential/commands/feat.md +6 -0
- package/templates/claude-code/zh-CN/workflow/essential/commands/feat.md +6 -0
- package/dist/chunks/context-manager.mjs +0 -641
package/dist/chunks/context.mjs
CHANGED
|
@@ -1,248 +1,771 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
1
|
+
import * as nodeFs from 'node:fs';
|
|
2
|
+
import * as os from 'node:os';
|
|
3
|
+
import * as path from 'node:path';
|
|
4
|
+
import ansis from 'ansis';
|
|
5
|
+
import * as crypto from 'node:crypto';
|
|
6
|
+
import process__default from 'node:process';
|
|
3
7
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
8
|
+
class ContextAnalyzer {
|
|
9
|
+
/**
|
|
10
|
+
* 分析消息重要性
|
|
11
|
+
*/
|
|
12
|
+
static analyzeImportance(message) {
|
|
13
|
+
let score = 0;
|
|
14
|
+
const content = this.extractTextContent(message);
|
|
15
|
+
if (this.containsDecision(content))
|
|
16
|
+
score += 30;
|
|
17
|
+
if (this.containsCodeChange(message))
|
|
18
|
+
score += 25;
|
|
19
|
+
if (this.containsErrorResolution(content))
|
|
20
|
+
score += 20;
|
|
21
|
+
if (message.type === "user" && content.length > 50)
|
|
22
|
+
score += 15;
|
|
23
|
+
if (message.toolUseResult)
|
|
24
|
+
score += 10;
|
|
25
|
+
const age = Date.now() - new Date(message.timestamp).getTime();
|
|
26
|
+
const hourAge = age / (1e3 * 60 * 60);
|
|
27
|
+
score += Math.max(0, 20 - hourAge * 2);
|
|
28
|
+
return Math.min(100, score);
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* 提取文本内容
|
|
32
|
+
*/
|
|
33
|
+
static extractTextContent(message) {
|
|
34
|
+
const content = message.message.content;
|
|
35
|
+
if (typeof content === "string")
|
|
36
|
+
return content;
|
|
37
|
+
return content.filter(
|
|
38
|
+
(block) => block.type === "text" && typeof block.text === "string"
|
|
39
|
+
).map((block) => block.text).join("\n");
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* 检测是否包含决策
|
|
43
|
+
*/
|
|
44
|
+
static containsDecision(content) {
|
|
45
|
+
const decisionPatterns = [
|
|
46
|
+
/决定|决策|选择|采用|使用|方案/,
|
|
47
|
+
/decide|decision|choose|adopt|use|approach/i,
|
|
48
|
+
/我们应该|建议|推荐/,
|
|
49
|
+
/should|recommend|suggest/i,
|
|
50
|
+
/✅|✔|确定|confirmed/i
|
|
51
|
+
];
|
|
52
|
+
return decisionPatterns.some((p) => p.test(content));
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* 检测是否包含代码变更
|
|
56
|
+
*/
|
|
57
|
+
static containsCodeChange(message) {
|
|
58
|
+
const content = message.message.content;
|
|
59
|
+
if (typeof content === "string")
|
|
60
|
+
return false;
|
|
61
|
+
return content.some(
|
|
62
|
+
(block) => block.type === "tool_use" && ["Write", "Edit", "Bash"].includes(block.name || "")
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* 检测是否包含错误解决
|
|
67
|
+
*/
|
|
68
|
+
static containsErrorResolution(content) {
|
|
69
|
+
const patterns = [
|
|
70
|
+
/修复|解决|fix|resolve|solved/i,
|
|
71
|
+
/错误|error|bug|issue/i,
|
|
72
|
+
/成功|success|works|working/i
|
|
73
|
+
];
|
|
74
|
+
return patterns.filter((p) => p.test(content)).length >= 2;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* 提取关键决策
|
|
78
|
+
*/
|
|
79
|
+
static extractDecisions(messages) {
|
|
80
|
+
const decisions = [];
|
|
81
|
+
for (const msg of messages) {
|
|
82
|
+
const content = this.extractTextContent(msg);
|
|
83
|
+
if (this.containsDecision(content)) {
|
|
84
|
+
const sentences = content.split(/[。.!!?\n]/).filter((s) => s.trim());
|
|
85
|
+
for (const sentence of sentences) {
|
|
86
|
+
if (this.containsDecision(sentence) && sentence.length < 200) {
|
|
87
|
+
decisions.push(sentence.trim());
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return [...new Set(decisions)].slice(0, 10);
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* 提取代码变更
|
|
96
|
+
*/
|
|
97
|
+
static extractCodeChanges(messages) {
|
|
98
|
+
const changes = [];
|
|
99
|
+
for (const msg of messages) {
|
|
100
|
+
const content = msg.message.content;
|
|
101
|
+
if (typeof content === "string")
|
|
102
|
+
continue;
|
|
103
|
+
for (const block of content) {
|
|
104
|
+
if (block.type === "tool_use") {
|
|
105
|
+
const input = block.input;
|
|
106
|
+
if (block.name === "Write" && input?.file_path) {
|
|
107
|
+
changes.push({
|
|
108
|
+
file: String(input.file_path),
|
|
109
|
+
action: "create",
|
|
110
|
+
description: `Created file`
|
|
111
|
+
});
|
|
112
|
+
} else if (block.name === "Edit" && input?.file_path) {
|
|
113
|
+
changes.push({
|
|
114
|
+
file: String(input.file_path),
|
|
115
|
+
action: "modify",
|
|
116
|
+
description: `Modified file`
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
const merged = /* @__PURE__ */ new Map();
|
|
123
|
+
for (const change of changes) {
|
|
124
|
+
const existing = merged.get(change.file);
|
|
125
|
+
if (existing) {
|
|
126
|
+
existing.action = "modify";
|
|
127
|
+
} else {
|
|
128
|
+
merged.set(change.file, change);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
return Array.from(merged.values());
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* 提取主题
|
|
135
|
+
*/
|
|
136
|
+
static extractTopics(messages) {
|
|
137
|
+
const topicKeywords = /* @__PURE__ */ new Map();
|
|
138
|
+
for (const msg of messages) {
|
|
139
|
+
const content = this.extractTextContent(msg).toLowerCase();
|
|
140
|
+
const techPatterns = [
|
|
141
|
+
/\b(mcp|api|cli|sdk|npm|git|docker)\b/gi,
|
|
142
|
+
/\b(typescript|javascript|python|rust|go)\b/gi,
|
|
143
|
+
/\b(react|vue|angular|node|express)\b/gi,
|
|
144
|
+
/\b(database|cache|redis|mongodb|postgres)\b/gi,
|
|
145
|
+
/\b(performance|optimization|refactor|debug)\b/gi
|
|
146
|
+
];
|
|
147
|
+
for (const pattern of techPatterns) {
|
|
148
|
+
const matches = content.match(pattern) || [];
|
|
149
|
+
for (const match of matches) {
|
|
150
|
+
const key = match.toLowerCase();
|
|
151
|
+
topicKeywords.set(key, (topicKeywords.get(key) || 0) + 1);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
return Array.from(topicKeywords.entries()).sort((a, b) => b[1] - a[1]).slice(0, 5).map(([topic]) => topic);
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* 估算 token 数量
|
|
159
|
+
*/
|
|
160
|
+
static estimateTokens(messages) {
|
|
161
|
+
let totalChars = 0;
|
|
162
|
+
for (const msg of messages) {
|
|
163
|
+
const content = this.extractTextContent(msg);
|
|
164
|
+
totalChars += content.length;
|
|
165
|
+
if (msg.toolUseResult?.stdout) {
|
|
166
|
+
totalChars += msg.toolUseResult.stdout.length;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
return Math.ceil(totalChars / 4);
|
|
36
170
|
}
|
|
37
171
|
}
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
console.log(`${yellow("Current Session:")}`);
|
|
56
|
-
console.log(` ${gray("ID:")} ${status.sessionId || "None"}`);
|
|
57
|
-
console.log(` ${gray("Started:")} ${status.startTime ? new Date(status.startTime).toLocaleString() : "N/A"}`);
|
|
58
|
-
console.log(` ${gray("Duration:")} ${status.duration || "N/A"}`);
|
|
59
|
-
console.log(`
|
|
60
|
-
${yellow("Context Statistics:")}`);
|
|
61
|
-
console.log(` ${gray("Total Tokens:")} ${status.totalTokens?.toLocaleString() || 0}`);
|
|
62
|
-
console.log(` ${gray("Compressed Tokens:")} ${status.compressedTokens?.toLocaleString() || 0}`);
|
|
63
|
-
console.log(` ${gray("Compression Ratio:")} ${status.compressionRatio ? `${(status.compressionRatio * 100).toFixed(1)}%` : "N/A"}`);
|
|
64
|
-
console.log(` ${gray("Savings:")} ${status.tokensSaved?.toLocaleString() || 0} tokens`);
|
|
65
|
-
console.log(`
|
|
66
|
-
${yellow("Compression Status:")}`);
|
|
67
|
-
console.log(` ${gray("Auto-compress:")} ${status.autoCompress ? green("Enabled") : gray("Disabled")}`);
|
|
68
|
-
console.log(` ${gray("Threshold:")} ${status.threshold?.toLocaleString() || "N/A"} tokens`);
|
|
69
|
-
console.log(` ${gray("Last Compressed:")} ${status.lastCompressed ? new Date(status.lastCompressed).toLocaleString() : "Never"}`);
|
|
70
|
-
console.log(` ${gray("Compressions Today:")} ${status.compressionsToday || 0}`);
|
|
71
|
-
if (options.verbose) {
|
|
72
|
-
console.log(`
|
|
73
|
-
${yellow("Detailed Info:")}`);
|
|
74
|
-
console.log(` ${gray("Storage Path:")} ${status.storagePath || "N/A"}`);
|
|
75
|
-
console.log(` ${gray("Cache Size:")} ${status.cacheSize || "N/A"}`);
|
|
76
|
-
console.log(` ${gray("Model:")} ${status.model || "haiku"}`);
|
|
172
|
+
class ContextManager {
|
|
173
|
+
claudeDir;
|
|
174
|
+
projectsDir;
|
|
175
|
+
archiveDir;
|
|
176
|
+
summaryDir;
|
|
177
|
+
constructor() {
|
|
178
|
+
this.claudeDir = path.join(os.homedir(), ".claude");
|
|
179
|
+
this.projectsDir = path.join(this.claudeDir, "projects");
|
|
180
|
+
this.archiveDir = path.join(this.claudeDir, "archive");
|
|
181
|
+
this.summaryDir = path.join(this.claudeDir, "summaries");
|
|
182
|
+
this.ensureDirectories();
|
|
183
|
+
}
|
|
184
|
+
ensureDirectories() {
|
|
185
|
+
for (const dir of [this.archiveDir, this.summaryDir]) {
|
|
186
|
+
if (!nodeFs.existsSync(dir)) {
|
|
187
|
+
nodeFs.mkdirSync(dir, { recursive: true });
|
|
188
|
+
}
|
|
77
189
|
}
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* 获取当前项目的会话文件
|
|
193
|
+
*/
|
|
194
|
+
getProjectSessions(projectPath) {
|
|
195
|
+
const projectKey = projectPath ? projectPath.replace(/\//g, "-").replace(/^-/, "") : this.detectCurrentProject();
|
|
196
|
+
const projectDir = path.join(this.projectsDir, projectKey);
|
|
197
|
+
if (!nodeFs.existsSync(projectDir)) {
|
|
198
|
+
return [];
|
|
85
199
|
}
|
|
200
|
+
return nodeFs.readdirSync(projectDir).filter((f) => f.endsWith(".jsonl")).map((f) => path.join(projectDir, f));
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* 检测当前项目
|
|
204
|
+
*/
|
|
205
|
+
detectCurrentProject() {
|
|
206
|
+
const cwd = process__default.cwd();
|
|
207
|
+
return cwd.replace(/\//g, "-").replace(/^-/, "");
|
|
86
208
|
}
|
|
209
|
+
/**
|
|
210
|
+
* 读取会话消息
|
|
211
|
+
*/
|
|
212
|
+
readSessionMessages(sessionFile) {
|
|
213
|
+
if (!nodeFs.existsSync(sessionFile)) {
|
|
214
|
+
return [];
|
|
215
|
+
}
|
|
216
|
+
const content = nodeFs.readFileSync(sessionFile, "utf-8");
|
|
217
|
+
const lines = content.trim().split("\n").filter((l) => l.trim());
|
|
218
|
+
return lines.map((line) => {
|
|
219
|
+
try {
|
|
220
|
+
return JSON.parse(line);
|
|
221
|
+
} catch {
|
|
222
|
+
return null;
|
|
223
|
+
}
|
|
224
|
+
}).filter((msg) => msg !== null);
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* 压缩会话 - 核心功能
|
|
228
|
+
*/
|
|
229
|
+
async compact(sessionFile, options = {}) {
|
|
230
|
+
const {
|
|
231
|
+
keepLastN = 20,
|
|
232
|
+
archiveThreshold = 200,
|
|
233
|
+
preserveDecisions = true,
|
|
234
|
+
preserveCodeChanges = true
|
|
235
|
+
} = options;
|
|
236
|
+
const messages = this.readSessionMessages(sessionFile);
|
|
237
|
+
const originalCount = messages.length;
|
|
238
|
+
const originalTokens = ContextAnalyzer.estimateTokens(messages);
|
|
239
|
+
if (messages.length <= keepLastN) {
|
|
240
|
+
return {
|
|
241
|
+
originalMessages: originalCount,
|
|
242
|
+
compactedMessages: originalCount,
|
|
243
|
+
summaryGenerated: false,
|
|
244
|
+
archived: false,
|
|
245
|
+
tokensSaved: 0
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
const scoredMessages = messages.map((msg) => ({
|
|
249
|
+
message: msg,
|
|
250
|
+
score: ContextAnalyzer.analyzeImportance(msg)
|
|
251
|
+
}));
|
|
252
|
+
const recentMessages = messages.slice(-keepLastN);
|
|
253
|
+
const olderMessages = messages.slice(0, -keepLastN);
|
|
254
|
+
const importantOlder = scoredMessages.slice(0, -keepLastN).filter((m) => m.score >= 50).map((m) => m.message);
|
|
255
|
+
const summary = this.generateSummary(olderMessages);
|
|
256
|
+
this.saveSummary(sessionFile, summary);
|
|
257
|
+
let archived = false;
|
|
258
|
+
if (originalCount >= archiveThreshold) {
|
|
259
|
+
this.archiveSession(sessionFile, olderMessages);
|
|
260
|
+
archived = true;
|
|
261
|
+
}
|
|
262
|
+
const compactedMessages = [];
|
|
263
|
+
compactedMessages.push(this.createSummaryMessage(summary));
|
|
264
|
+
if (preserveDecisions || preserveCodeChanges) {
|
|
265
|
+
compactedMessages.push(...importantOlder.slice(0, 10));
|
|
266
|
+
}
|
|
267
|
+
compactedMessages.push(...recentMessages);
|
|
268
|
+
this.writeSession(sessionFile, compactedMessages);
|
|
269
|
+
const compactedTokens = ContextAnalyzer.estimateTokens(compactedMessages);
|
|
270
|
+
return {
|
|
271
|
+
originalMessages: originalCount,
|
|
272
|
+
compactedMessages: compactedMessages.length,
|
|
273
|
+
summaryGenerated: true,
|
|
274
|
+
archived,
|
|
275
|
+
tokensSaved: originalTokens - compactedTokens,
|
|
276
|
+
summary
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
/**
|
|
280
|
+
* 生成会话摘要
|
|
281
|
+
*/
|
|
282
|
+
generateSummary(messages) {
|
|
283
|
+
const id = crypto.randomUUID();
|
|
284
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
285
|
+
const keyDecisions = ContextAnalyzer.extractDecisions(messages);
|
|
286
|
+
const codeChanges = ContextAnalyzer.extractCodeChanges(messages);
|
|
287
|
+
const topics = ContextAnalyzer.extractTopics(messages);
|
|
288
|
+
const tokenEstimate = ContextAnalyzer.estimateTokens(messages);
|
|
289
|
+
const summaryParts = [];
|
|
290
|
+
if (topics.length > 0) {
|
|
291
|
+
summaryParts.push(`\u4E3B\u8981\u8BDD\u9898: ${topics.join(", ")}`);
|
|
292
|
+
}
|
|
293
|
+
if (keyDecisions.length > 0) {
|
|
294
|
+
summaryParts.push(`\u5173\u952E\u51B3\u7B56:
|
|
295
|
+
${keyDecisions.map((d) => ` - ${d}`).join("\n")}`);
|
|
296
|
+
}
|
|
297
|
+
if (codeChanges.length > 0) {
|
|
298
|
+
summaryParts.push(`\u4EE3\u7801\u53D8\u66F4:
|
|
299
|
+
${codeChanges.map((c) => ` - ${c.action}: ${c.file}`).join("\n")}`);
|
|
300
|
+
}
|
|
301
|
+
return {
|
|
302
|
+
id,
|
|
303
|
+
createdAt: now,
|
|
304
|
+
updatedAt: now,
|
|
305
|
+
messageCount: messages.length,
|
|
306
|
+
tokenEstimate,
|
|
307
|
+
keyDecisions,
|
|
308
|
+
codeChanges,
|
|
309
|
+
topics,
|
|
310
|
+
summary: summaryParts.join("\n\n")
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
/**
|
|
314
|
+
* 保存摘要
|
|
315
|
+
*/
|
|
316
|
+
saveSummary(sessionFile, summary) {
|
|
317
|
+
const sessionId = path.basename(sessionFile, ".jsonl");
|
|
318
|
+
const summaryFile = path.join(this.summaryDir, `${sessionId}-${summary.id}.json`);
|
|
319
|
+
nodeFs.writeFileSync(summaryFile, JSON.stringify(summary, null, 2));
|
|
320
|
+
return summaryFile;
|
|
321
|
+
}
|
|
322
|
+
/**
|
|
323
|
+
* 归档会话
|
|
324
|
+
*/
|
|
325
|
+
archiveSession(sessionFile, messages) {
|
|
326
|
+
const sessionId = path.basename(sessionFile, ".jsonl");
|
|
327
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
328
|
+
const archiveFile = path.join(this.archiveDir, `${sessionId}-${timestamp}.jsonl`);
|
|
329
|
+
const content = messages.map((m) => JSON.stringify(m)).join("\n");
|
|
330
|
+
nodeFs.writeFileSync(archiveFile, content);
|
|
331
|
+
return archiveFile;
|
|
332
|
+
}
|
|
333
|
+
/**
|
|
334
|
+
* 创建摘要消息
|
|
335
|
+
*/
|
|
336
|
+
createSummaryMessage(summary) {
|
|
337
|
+
return {
|
|
338
|
+
uuid: crypto.randomUUID(),
|
|
339
|
+
type: "assistant",
|
|
340
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
341
|
+
message: {
|
|
342
|
+
role: "assistant",
|
|
343
|
+
content: `[\u4F1A\u8BDD\u6458\u8981 - ${summary.messageCount} \u6761\u6D88\u606F\u5DF2\u538B\u7F29]
|
|
344
|
+
|
|
345
|
+
${summary.summary}`
|
|
346
|
+
},
|
|
347
|
+
metadata: {
|
|
348
|
+
isSummary: true,
|
|
349
|
+
summaryId: summary.id,
|
|
350
|
+
originalMessageCount: summary.messageCount,
|
|
351
|
+
originalTokenEstimate: summary.tokenEstimate
|
|
352
|
+
}
|
|
353
|
+
};
|
|
354
|
+
}
|
|
355
|
+
/**
|
|
356
|
+
* 写入会话
|
|
357
|
+
*/
|
|
358
|
+
writeSession(sessionFile, messages) {
|
|
359
|
+
const content = messages.map((m) => JSON.stringify(m)).join("\n");
|
|
360
|
+
nodeFs.writeFileSync(sessionFile, content);
|
|
361
|
+
}
|
|
362
|
+
/**
|
|
363
|
+
* 获取会话状态
|
|
364
|
+
*/
|
|
365
|
+
getSessionStatus(sessionFile) {
|
|
366
|
+
const messages = this.readSessionMessages(sessionFile);
|
|
367
|
+
const tokenEstimate = ContextAnalyzer.estimateTokens(messages);
|
|
368
|
+
return {
|
|
369
|
+
messageCount: messages.length,
|
|
370
|
+
tokenEstimate,
|
|
371
|
+
oldestMessage: messages[0]?.timestamp || "N/A",
|
|
372
|
+
newestMessage: messages[messages.length - 1]?.timestamp || "N/A",
|
|
373
|
+
needsCompact: messages.length > 50 || tokenEstimate > 5e4
|
|
374
|
+
};
|
|
375
|
+
}
|
|
376
|
+
/**
|
|
377
|
+
* 列出所有摘要
|
|
378
|
+
*/
|
|
379
|
+
listSummaries() {
|
|
380
|
+
if (!nodeFs.existsSync(this.summaryDir)) {
|
|
381
|
+
return [];
|
|
382
|
+
}
|
|
383
|
+
return nodeFs.readdirSync(this.summaryDir).filter((f) => f.endsWith(".json")).map((f) => {
|
|
384
|
+
try {
|
|
385
|
+
const content = nodeFs.readFileSync(path.join(this.summaryDir, f), "utf-8");
|
|
386
|
+
return JSON.parse(content);
|
|
387
|
+
} catch {
|
|
388
|
+
return null;
|
|
389
|
+
}
|
|
390
|
+
}).filter((s) => s !== null);
|
|
391
|
+
}
|
|
392
|
+
/**
|
|
393
|
+
* 恢复归档的会话
|
|
394
|
+
*/
|
|
395
|
+
restoreArchive(archiveFile, targetSession) {
|
|
396
|
+
if (!nodeFs.existsSync(archiveFile)) {
|
|
397
|
+
return false;
|
|
398
|
+
}
|
|
399
|
+
const archiveContent = nodeFs.readFileSync(archiveFile, "utf-8");
|
|
400
|
+
const currentContent = nodeFs.existsSync(targetSession) ? nodeFs.readFileSync(targetSession, "utf-8") : "";
|
|
401
|
+
const merged = `${archiveContent}
|
|
402
|
+
${currentContent}`;
|
|
403
|
+
nodeFs.writeFileSync(targetSession, merged.trim());
|
|
404
|
+
return true;
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
const contextManager = new ContextManager();
|
|
408
|
+
|
|
409
|
+
const chalk = ansis;
|
|
410
|
+
function formatBytes(bytes) {
|
|
411
|
+
if (bytes < 1024)
|
|
412
|
+
return `${bytes} B`;
|
|
413
|
+
if (bytes < 1024 * 1024)
|
|
414
|
+
return `${(bytes / 1024).toFixed(2)} KB`;
|
|
415
|
+
return `${(bytes / (1024 * 1024)).toFixed(2)} MB`;
|
|
416
|
+
}
|
|
417
|
+
function formatNumber(num) {
|
|
418
|
+
return num.toLocaleString();
|
|
87
419
|
}
|
|
88
|
-
|
|
89
|
-
const spinner = ora("Compressing context...").start();
|
|
420
|
+
function formatDate(dateStr) {
|
|
90
421
|
try {
|
|
91
|
-
const
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
422
|
+
const date = new Date(dateStr);
|
|
423
|
+
return date.toLocaleString("zh-CN", {
|
|
424
|
+
year: "numeric",
|
|
425
|
+
month: "2-digit",
|
|
426
|
+
day: "2-digit",
|
|
427
|
+
hour: "2-digit",
|
|
428
|
+
minute: "2-digit"
|
|
429
|
+
});
|
|
430
|
+
} catch {
|
|
431
|
+
return dateStr;
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
function printHeader(title) {
|
|
435
|
+
console.log();
|
|
436
|
+
console.log(chalk.cyan.bold(`\u{1F4E6} ${title}`));
|
|
437
|
+
console.log(chalk.gray("\u2500".repeat(50)));
|
|
438
|
+
}
|
|
439
|
+
function printSuccess(message) {
|
|
440
|
+
console.log(chalk.green(`\u2714 ${message}`));
|
|
441
|
+
}
|
|
442
|
+
function printWarning(message) {
|
|
443
|
+
console.log(chalk.yellow(`\u26A0 ${message}`));
|
|
444
|
+
}
|
|
445
|
+
function printError(message) {
|
|
446
|
+
console.log(chalk.red(`\u2716 ${message}`));
|
|
447
|
+
}
|
|
448
|
+
function printInfo(label, value) {
|
|
449
|
+
console.log(` ${chalk.gray(label.padEnd(20))} ${chalk.white(value)}`);
|
|
450
|
+
}
|
|
451
|
+
async function contextStatus(_options = {}) {
|
|
452
|
+
printHeader("Context Status");
|
|
453
|
+
const sessions = contextManager.getProjectSessions();
|
|
454
|
+
if (sessions.length === 0) {
|
|
455
|
+
printWarning("No sessions found for current project");
|
|
456
|
+
return;
|
|
457
|
+
}
|
|
458
|
+
let totalMessages = 0;
|
|
459
|
+
let totalTokens = 0;
|
|
460
|
+
for (const sessionFile of sessions) {
|
|
461
|
+
const status = contextManager.getSessionStatus(sessionFile);
|
|
462
|
+
const sessionId = path.basename(sessionFile, ".jsonl");
|
|
102
463
|
console.log();
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
464
|
+
console.log(chalk.white.bold(`Session: ${sessionId.slice(0, 8)}...`));
|
|
465
|
+
printInfo("Messages", formatNumber(status.messageCount));
|
|
466
|
+
printInfo("Est. Tokens", formatNumber(status.tokenEstimate));
|
|
467
|
+
printInfo("Oldest", formatDate(status.oldestMessage));
|
|
468
|
+
printInfo("Newest", formatDate(status.newestMessage));
|
|
469
|
+
if (status.needsCompact) {
|
|
470
|
+
console.log(chalk.yellow(` \u26A0 Needs compact (${status.messageCount > 50 ? "too many messages" : "high token count"})`));
|
|
471
|
+
} else {
|
|
472
|
+
console.log(chalk.green(` \u2714 Healthy`));
|
|
107
473
|
}
|
|
474
|
+
totalMessages += status.messageCount;
|
|
475
|
+
totalTokens += status.tokenEstimate;
|
|
476
|
+
}
|
|
477
|
+
console.log();
|
|
478
|
+
console.log(chalk.gray("\u2500".repeat(50)));
|
|
479
|
+
console.log(chalk.cyan.bold("Total:"));
|
|
480
|
+
printInfo("Sessions", sessions.length.toString());
|
|
481
|
+
printInfo("Messages", formatNumber(totalMessages));
|
|
482
|
+
printInfo("Est. Tokens", formatNumber(totalTokens));
|
|
483
|
+
if (totalTokens > 1e5) {
|
|
484
|
+
console.log();
|
|
485
|
+
printWarning(`High token usage detected. Run ${chalk.cyan("ccjk context compact")} to optimize.`);
|
|
108
486
|
}
|
|
109
487
|
}
|
|
110
|
-
async function
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
488
|
+
async function contextCompact(options = {}) {
|
|
489
|
+
printHeader("Context Compact");
|
|
490
|
+
const sessions = contextManager.getProjectSessions();
|
|
491
|
+
if (sessions.length === 0) {
|
|
492
|
+
printWarning("No sessions found for current project");
|
|
493
|
+
return;
|
|
494
|
+
}
|
|
495
|
+
const compactOptions = {
|
|
496
|
+
keepLastN: Number.parseInt(options.keep || "20", 10),
|
|
497
|
+
summarizeThreshold: Number.parseInt(options.threshold || "50", 10),
|
|
498
|
+
preserveDecisions: options.decisions !== false,
|
|
499
|
+
preserveCodeChanges: options.codeChanges !== false
|
|
500
|
+
};
|
|
501
|
+
console.log(chalk.gray("Compact options:"));
|
|
502
|
+
printInfo("Keep last N", compactOptions.keepLastN.toString());
|
|
503
|
+
printInfo("Threshold", compactOptions.summarizeThreshold.toString());
|
|
504
|
+
printInfo("Preserve decisions", compactOptions.preserveDecisions ? "Yes" : "No");
|
|
505
|
+
printInfo("Preserve code changes", compactOptions.preserveCodeChanges ? "Yes" : "No");
|
|
506
|
+
console.log();
|
|
507
|
+
let totalSaved = 0;
|
|
508
|
+
let compactedCount = 0;
|
|
509
|
+
for (const sessionFile of sessions) {
|
|
510
|
+
const sessionId = path.basename(sessionFile, ".jsonl").slice(0, 8);
|
|
511
|
+
const status = contextManager.getSessionStatus(sessionFile);
|
|
512
|
+
if (!options.force && !status.needsCompact) {
|
|
513
|
+
console.log(chalk.gray(` \u23ED ${sessionId}... (no compact needed)`));
|
|
514
|
+
continue;
|
|
127
515
|
}
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
const ratio = ((1 - entry.compressedTokens / entry.originalTokens) * 100).toFixed(1);
|
|
133
|
-
console.log(
|
|
134
|
-
`${entry.id.slice(0, 10).padEnd(12)} ${date.padEnd(20)} ${entry.originalTokens.toString().padEnd(12)} ${entry.compressedTokens.toString().padEnd(12)} ${green(`-${ratio}%`)}`
|
|
135
|
-
);
|
|
516
|
+
if (options.dryRun) {
|
|
517
|
+
console.log(chalk.yellow(` \u{1F50D} ${sessionId}... (dry run)`));
|
|
518
|
+
console.log(chalk.gray(` Would compact ${status.messageCount} messages`));
|
|
519
|
+
continue;
|
|
136
520
|
}
|
|
137
|
-
|
|
138
|
-
console.log(
|
|
139
|
-
|
|
521
|
+
try {
|
|
522
|
+
console.log(chalk.cyan(` \u23F3 Compacting ${sessionId}...`));
|
|
523
|
+
const result = await contextManager.compact(sessionFile, compactOptions);
|
|
524
|
+
if (result.summaryGenerated) {
|
|
525
|
+
printSuccess(`${sessionId}: ${result.originalMessages} \u2192 ${result.compactedMessages} messages`);
|
|
526
|
+
console.log(chalk.gray(` Tokens saved: ~${formatNumber(result.tokensSaved)}`));
|
|
527
|
+
if (result.archived) {
|
|
528
|
+
console.log(chalk.gray(` Archived to cold storage`));
|
|
529
|
+
}
|
|
530
|
+
if (result.summary) {
|
|
531
|
+
console.log(chalk.gray(` Topics: ${result.summary.topics.join(", ") || "N/A"}`));
|
|
532
|
+
}
|
|
533
|
+
totalSaved += result.tokensSaved;
|
|
534
|
+
compactedCount++;
|
|
535
|
+
} else {
|
|
536
|
+
console.log(chalk.gray(` \u23ED ${sessionId}... (already compact)`));
|
|
537
|
+
}
|
|
538
|
+
} catch (error) {
|
|
539
|
+
printError(`Failed to compact ${sessionId}: ${error}`);
|
|
140
540
|
}
|
|
541
|
+
}
|
|
542
|
+
console.log();
|
|
543
|
+
console.log(chalk.gray("\u2500".repeat(50)));
|
|
544
|
+
if (compactedCount > 0) {
|
|
545
|
+
printSuccess(`Compacted ${compactedCount} session(s)`);
|
|
546
|
+
console.log(chalk.green(` Total tokens saved: ~${formatNumber(totalSaved)}`));
|
|
547
|
+
} else if (options.dryRun) {
|
|
548
|
+
printInfo("Dry run", "No changes made");
|
|
549
|
+
} else {
|
|
550
|
+
printInfo("Result", "All sessions already optimized");
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
async function contextSummaries(options = {}) {
|
|
554
|
+
printHeader("Session Summaries");
|
|
555
|
+
const summaries = contextManager.listSummaries();
|
|
556
|
+
if (summaries.length === 0) {
|
|
557
|
+
printWarning("No summaries found. Run `ccjk context compact` to generate summaries.");
|
|
558
|
+
return;
|
|
559
|
+
}
|
|
560
|
+
const sorted = summaries.sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()).slice(0, Number.parseInt(options.limit || "10", 10));
|
|
561
|
+
for (const summary of sorted) {
|
|
141
562
|
console.log();
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
563
|
+
console.log(chalk.white.bold(`Summary: ${summary.id.slice(0, 8)}...`));
|
|
564
|
+
printInfo("Created", formatDate(summary.createdAt));
|
|
565
|
+
printInfo("Messages", formatNumber(summary.messageCount));
|
|
566
|
+
printInfo("Est. Tokens", formatNumber(summary.tokenEstimate));
|
|
567
|
+
if (summary.topics.length > 0) {
|
|
568
|
+
printInfo("Topics", summary.topics.join(", "));
|
|
569
|
+
}
|
|
570
|
+
if (summary.keyDecisions.length > 0) {
|
|
571
|
+
console.log(chalk.gray(" Key Decisions:"));
|
|
572
|
+
for (const decision of summary.keyDecisions.slice(0, 3)) {
|
|
573
|
+
console.log(chalk.gray(` \u2022 ${decision.slice(0, 80)}${decision.length > 80 ? "..." : ""}`));
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
if (summary.codeChanges.length > 0) {
|
|
577
|
+
console.log(chalk.gray(" Code Changes:"));
|
|
578
|
+
for (const change of summary.codeChanges.slice(0, 5)) {
|
|
579
|
+
const fileName = path.basename(change.file);
|
|
580
|
+
console.log(chalk.gray(` \u2022 ${change.action}: ${fileName}`));
|
|
581
|
+
}
|
|
146
582
|
}
|
|
147
583
|
}
|
|
584
|
+
console.log();
|
|
585
|
+
console.log(chalk.gray(`Showing ${sorted.length} of ${summaries.length} summaries`));
|
|
148
586
|
}
|
|
149
|
-
async function
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
587
|
+
async function contextArchives() {
|
|
588
|
+
printHeader("Archived Sessions");
|
|
589
|
+
const archiveDir = path.join(os.homedir(), ".claude", "archive");
|
|
590
|
+
if (!nodeFs.existsSync(archiveDir)) {
|
|
591
|
+
printWarning("No archives found.");
|
|
153
592
|
return;
|
|
154
593
|
}
|
|
155
|
-
const
|
|
156
|
-
|
|
157
|
-
const
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
594
|
+
const archives = nodeFs.readdirSync(archiveDir).filter((f) => f.endsWith(".jsonl")).map((f) => {
|
|
595
|
+
const filePath = path.join(archiveDir, f);
|
|
596
|
+
const stats = nodeFs.statSync(filePath);
|
|
597
|
+
return {
|
|
598
|
+
name: f,
|
|
599
|
+
path: filePath,
|
|
600
|
+
size: stats.size,
|
|
601
|
+
mtime: stats.mtime
|
|
602
|
+
};
|
|
603
|
+
}).sort((a, b) => b.mtime.getTime() - a.mtime.getTime());
|
|
604
|
+
if (archives.length === 0) {
|
|
605
|
+
printWarning("No archives found.");
|
|
606
|
+
return;
|
|
607
|
+
}
|
|
608
|
+
for (const archive of archives) {
|
|
161
609
|
console.log();
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
610
|
+
console.log(chalk.white.bold(archive.name));
|
|
611
|
+
printInfo("Size", formatBytes(archive.size));
|
|
612
|
+
printInfo("Archived", formatDate(archive.mtime.toISOString()));
|
|
613
|
+
}
|
|
614
|
+
console.log();
|
|
615
|
+
console.log(chalk.gray(`Total: ${archives.length} archive(s)`));
|
|
616
|
+
console.log();
|
|
617
|
+
console.log(chalk.gray(`To restore: ${chalk.cyan("ccjk context restore <archive-file>")}`));
|
|
618
|
+
}
|
|
619
|
+
async function contextRestore(archive, options = {}) {
|
|
620
|
+
printHeader("Restore Archive");
|
|
621
|
+
const archiveDir = path.join(os.homedir(), ".claude", "archive");
|
|
622
|
+
let archivePath = archive;
|
|
623
|
+
if (!path.isAbsolute(archive)) {
|
|
624
|
+
archivePath = path.join(archiveDir, archive);
|
|
625
|
+
}
|
|
626
|
+
if (!nodeFs.existsSync(archivePath)) {
|
|
627
|
+
printError(`Archive not found: ${archivePath}`);
|
|
628
|
+
return;
|
|
629
|
+
}
|
|
630
|
+
let targetSession = options.target;
|
|
631
|
+
if (!targetSession) {
|
|
632
|
+
const sessions = contextManager.getProjectSessions();
|
|
633
|
+
if (sessions.length === 0) {
|
|
634
|
+
printError("No active sessions found. Please specify target with --target");
|
|
635
|
+
return;
|
|
166
636
|
}
|
|
637
|
+
targetSession = sessions[sessions.length - 1];
|
|
638
|
+
}
|
|
639
|
+
console.log(chalk.gray(`Archive: ${archivePath}`));
|
|
640
|
+
console.log(chalk.gray(`Target: ${targetSession}`));
|
|
641
|
+
console.log();
|
|
642
|
+
const success = contextManager.restoreArchive(archivePath, targetSession);
|
|
643
|
+
if (success) {
|
|
644
|
+
printSuccess("Archive restored successfully!");
|
|
645
|
+
console.log(chalk.gray("The archived messages have been prepended to the target session."));
|
|
646
|
+
} else {
|
|
647
|
+
printError("Failed to restore archive");
|
|
167
648
|
}
|
|
168
649
|
}
|
|
169
|
-
async function
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
650
|
+
async function contextAnalyze() {
|
|
651
|
+
printHeader("Context Analysis");
|
|
652
|
+
const sessions = contextManager.getProjectSessions();
|
|
653
|
+
if (sessions.length === 0) {
|
|
654
|
+
printWarning("No sessions found for current project");
|
|
655
|
+
return;
|
|
656
|
+
}
|
|
657
|
+
const latestSession = sessions[sessions.length - 1];
|
|
658
|
+
const messages = contextManager.readSessionMessages(latestSession);
|
|
659
|
+
if (messages.length === 0) {
|
|
660
|
+
printWarning("No messages in current session");
|
|
661
|
+
return;
|
|
662
|
+
}
|
|
663
|
+
console.log(chalk.gray(`Analyzing ${messages.length} messages...`));
|
|
664
|
+
console.log();
|
|
665
|
+
const topics = ContextAnalyzer.extractTopics(messages);
|
|
666
|
+
const decisions = ContextAnalyzer.extractDecisions(messages);
|
|
667
|
+
const codeChanges = ContextAnalyzer.extractCodeChanges(messages);
|
|
668
|
+
const tokenEstimate = ContextAnalyzer.estimateTokens(messages);
|
|
669
|
+
const importanceScores = messages.map((m) => ContextAnalyzer.analyzeImportance(m));
|
|
670
|
+
const avgImportance = importanceScores.reduce((a, b) => a + b, 0) / importanceScores.length;
|
|
671
|
+
const highImportance = importanceScores.filter((s) => s >= 50).length;
|
|
672
|
+
console.log(chalk.white.bold("\u{1F4CA} Statistics"));
|
|
673
|
+
printInfo("Total Messages", formatNumber(messages.length));
|
|
674
|
+
printInfo("Est. Tokens", formatNumber(tokenEstimate));
|
|
675
|
+
printInfo("Avg Importance", avgImportance.toFixed(1));
|
|
676
|
+
printInfo("High Importance", `${highImportance} (${(highImportance / messages.length * 100).toFixed(1)}%)`);
|
|
677
|
+
if (topics.length > 0) {
|
|
187
678
|
console.log();
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
console.error(error);
|
|
679
|
+
console.log(chalk.white.bold("\u{1F3F7}\uFE0F Topics"));
|
|
680
|
+
for (const topic of topics) {
|
|
681
|
+
console.log(chalk.gray(` \u2022 ${topic}`));
|
|
192
682
|
}
|
|
193
683
|
}
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
684
|
+
if (decisions.length > 0) {
|
|
685
|
+
console.log();
|
|
686
|
+
console.log(chalk.white.bold("\u{1F3AF} Key Decisions"));
|
|
687
|
+
for (const decision of decisions.slice(0, 5)) {
|
|
688
|
+
console.log(chalk.gray(` \u2022 ${decision.slice(0, 100)}${decision.length > 100 ? "..." : ""}`));
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
if (codeChanges.length > 0) {
|
|
202
692
|
console.log();
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
693
|
+
console.log(chalk.white.bold("\u{1F4DD} Code Changes"));
|
|
694
|
+
for (const change of codeChanges.slice(0, 10)) {
|
|
695
|
+
const fileName = path.basename(change.file);
|
|
696
|
+
const icon = change.action === "create" ? "\u2795" : change.action === "modify" ? "\u270F\uFE0F" : "\u{1F5D1}\uFE0F";
|
|
697
|
+
console.log(chalk.gray(` ${icon} ${fileName}`));
|
|
207
698
|
}
|
|
208
699
|
}
|
|
700
|
+
console.log();
|
|
701
|
+
console.log(chalk.white.bold("\u{1F4A1} Recommendations"));
|
|
702
|
+
if (tokenEstimate > 5e4) {
|
|
703
|
+
console.log(chalk.yellow(` \u2022 High token usage. Consider running ${chalk.cyan("ccjk context compact")}`));
|
|
704
|
+
}
|
|
705
|
+
if (messages.length > 100) {
|
|
706
|
+
console.log(chalk.yellow(` \u2022 Many messages. Consider archiving old conversations.`));
|
|
707
|
+
}
|
|
708
|
+
if (highImportance < messages.length * 0.1) {
|
|
709
|
+
console.log(chalk.gray(` \u2022 Most messages are low importance. Safe to compact.`));
|
|
710
|
+
}
|
|
711
|
+
if (tokenEstimate < 2e4 && messages.length < 50) {
|
|
712
|
+
console.log(chalk.green(` \u2022 Session is healthy. No action needed.`));
|
|
713
|
+
}
|
|
209
714
|
}
|
|
210
|
-
function
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
715
|
+
async function context(action, args = [], options = {}) {
|
|
716
|
+
switch (action) {
|
|
717
|
+
case "status":
|
|
718
|
+
await contextStatus({ all: options.all });
|
|
719
|
+
break;
|
|
720
|
+
case "compact":
|
|
721
|
+
await contextCompact({
|
|
722
|
+
keep: options.keep,
|
|
723
|
+
threshold: options.threshold,
|
|
724
|
+
decisions: options.decisions,
|
|
725
|
+
codeChanges: options.codeChanges,
|
|
726
|
+
force: options.force,
|
|
727
|
+
dryRun: options.dryRun
|
|
728
|
+
});
|
|
729
|
+
break;
|
|
730
|
+
case "summaries":
|
|
731
|
+
case "sum":
|
|
732
|
+
await contextSummaries({ limit: options.limit });
|
|
733
|
+
break;
|
|
734
|
+
case "archives":
|
|
735
|
+
case "arch":
|
|
736
|
+
await contextArchives();
|
|
737
|
+
break;
|
|
738
|
+
case "restore":
|
|
739
|
+
if (args.length === 0) {
|
|
740
|
+
printError("Please specify an archive file to restore");
|
|
741
|
+
console.log(chalk.gray(`Usage: ccjk context restore <archive-file>`));
|
|
742
|
+
return;
|
|
743
|
+
}
|
|
744
|
+
await contextRestore(args[0], { target: options.target });
|
|
745
|
+
break;
|
|
746
|
+
case "analyze":
|
|
747
|
+
await contextAnalyze();
|
|
748
|
+
break;
|
|
749
|
+
default:
|
|
750
|
+
console.log();
|
|
751
|
+
console.log(chalk.cyan.bold("\u{1F4E6} Context Management Commands"));
|
|
752
|
+
console.log();
|
|
753
|
+
console.log(chalk.white("Usage: ccjk context <action> [options]"));
|
|
754
|
+
console.log();
|
|
755
|
+
console.log(chalk.white("Actions:"));
|
|
756
|
+
console.log(`${chalk.gray(" status ")}View current session status`);
|
|
757
|
+
console.log(`${chalk.gray(" compact ")}Smart compress sessions (preserve key info)`);
|
|
758
|
+
console.log(`${chalk.gray(" summaries ")}View all session summaries`);
|
|
759
|
+
console.log(`${chalk.gray(" archives ")}View archived sessions`);
|
|
760
|
+
console.log(`${chalk.gray(" restore ")}Restore an archived session`);
|
|
761
|
+
console.log(`${chalk.gray(" analyze ")}Analyze current session content`);
|
|
762
|
+
console.log();
|
|
763
|
+
console.log(chalk.white("Examples:"));
|
|
764
|
+
console.log(chalk.gray(" ccjk context status"));
|
|
765
|
+
console.log(chalk.gray(" ccjk context compact --keep 20"));
|
|
766
|
+
console.log(chalk.gray(" ccjk context restore session-123.jsonl"));
|
|
767
|
+
break;
|
|
768
|
+
}
|
|
246
769
|
}
|
|
247
770
|
|
|
248
|
-
export {
|
|
771
|
+
export { context, contextAnalyze, contextArchives, contextCompact, contextRestore, contextStatus, contextSummaries };
|