ccjk 13.5.2 → 13.5.4
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 +5 -5
- package/dist/chunks/boost.mjs +8 -1
- package/dist/chunks/ccjk-config.mjs +17 -0
- package/dist/chunks/ccr.mjs +2 -2
- package/dist/chunks/check-updates.mjs +1 -1
- package/dist/chunks/codex-config-switch.mjs +1 -1
- package/dist/chunks/codex-provider-manager.mjs +1 -1
- package/dist/chunks/codex.mjs +2 -2
- package/dist/chunks/completion.mjs +1 -1
- package/dist/chunks/config-switch.mjs +1 -1
- package/dist/chunks/context.mjs +316 -1
- package/dist/chunks/features.mjs +43 -14
- package/dist/chunks/index4.mjs +8 -1
- package/dist/chunks/init.mjs +2 -2
- package/dist/chunks/mcp-cli.mjs +3 -3
- package/dist/chunks/mcp.mjs +177 -35
- package/dist/{shared/ccjk.Crd_nEfj.mjs → chunks/memory-check.mjs} +18 -424
- package/dist/chunks/memory-paths.mjs +259 -0
- package/dist/chunks/memory-sync.mjs +209 -0
- package/dist/chunks/memory.mjs +140 -17
- package/dist/chunks/menu-hierarchical.mjs +2 -2
- package/dist/chunks/menu.mjs +1 -1
- package/dist/chunks/onboarding-wizard.mjs +8 -1
- package/dist/chunks/package.mjs +1 -1
- package/dist/chunks/quick-actions.mjs +8 -1
- package/dist/chunks/quick-setup.mjs +1 -1
- package/dist/chunks/smart-defaults.mjs +1 -20
- package/dist/chunks/status.mjs +8 -1
- package/dist/chunks/update.mjs +2 -2
- package/dist/cli.mjs +16 -4
- package/dist/shared/{ccjk.LTONy3IS.mjs → ccjk.BOfPON0N.mjs} +1 -1
- package/dist/shared/{ccjk.0aJQmVxS.mjs → ccjk.miT0g_vA.mjs} +3 -160
- package/dist/shared/ccjk.xkKNsC02.mjs +421 -0
- package/package.json +1 -1
- package/dist/chunks/auto-memory-bridge.mjs +0 -221
|
@@ -0,0 +1,421 @@
|
|
|
1
|
+
import { existsSync, readdirSync, statSync, readFileSync } from 'node:fs';
|
|
2
|
+
import { CLAUDE_AGENTS_DIR, SETTINGS_FILE, CCJK_SKILLS_DIR } from '../chunks/constants.mjs';
|
|
3
|
+
import { j as join } from './ccjk.bQ7Dh1g4.mjs';
|
|
4
|
+
import { memoryCheck } from '../chunks/memory-check.mjs';
|
|
5
|
+
import process__default from 'node:process';
|
|
6
|
+
|
|
7
|
+
const agentsCheck = {
|
|
8
|
+
name: "Agents",
|
|
9
|
+
weight: 4,
|
|
10
|
+
async check() {
|
|
11
|
+
try {
|
|
12
|
+
if (!existsSync(CLAUDE_AGENTS_DIR)) {
|
|
13
|
+
return {
|
|
14
|
+
name: this.name,
|
|
15
|
+
status: "warn",
|
|
16
|
+
score: 30,
|
|
17
|
+
weight: this.weight,
|
|
18
|
+
message: "No agents directory",
|
|
19
|
+
fix: "Create agents for specialized tasks",
|
|
20
|
+
command: "ccjk agents"
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
const files = readdirSync(CLAUDE_AGENTS_DIR).filter((f) => f.endsWith(".md"));
|
|
24
|
+
const agentCount = files.length;
|
|
25
|
+
if (agentCount === 0) {
|
|
26
|
+
return {
|
|
27
|
+
name: this.name,
|
|
28
|
+
status: "warn",
|
|
29
|
+
score: 30,
|
|
30
|
+
weight: this.weight,
|
|
31
|
+
message: "No agents configured",
|
|
32
|
+
fix: "Create agents for your project",
|
|
33
|
+
command: "ccjk agents"
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
const score = Math.min(100, 40 + agentCount * 15);
|
|
37
|
+
return {
|
|
38
|
+
name: this.name,
|
|
39
|
+
status: score >= 60 ? "pass" : "warn",
|
|
40
|
+
score,
|
|
41
|
+
weight: this.weight,
|
|
42
|
+
message: `${agentCount} agent${agentCount > 1 ? "s" : ""} configured`,
|
|
43
|
+
details: files.slice(0, 5).map((f) => ` ${f.replace(".md", "")}`)
|
|
44
|
+
};
|
|
45
|
+
} catch {
|
|
46
|
+
return { name: this.name, status: "fail", score: 0, weight: this.weight, message: "Failed to read agents" };
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const claudeMdCheck = {
|
|
52
|
+
name: "CLAUDE.md",
|
|
53
|
+
weight: 7,
|
|
54
|
+
async check() {
|
|
55
|
+
const cwd = process.cwd();
|
|
56
|
+
const claudeMdPath = join(cwd, "CLAUDE.md");
|
|
57
|
+
try {
|
|
58
|
+
if (!existsSync(claudeMdPath)) {
|
|
59
|
+
return {
|
|
60
|
+
name: this.name,
|
|
61
|
+
status: "fail",
|
|
62
|
+
score: 0,
|
|
63
|
+
weight: this.weight,
|
|
64
|
+
message: "No CLAUDE.md in project root",
|
|
65
|
+
fix: "Create CLAUDE.md for project-specific AI instructions",
|
|
66
|
+
command: "ccjk init --smart"
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
const stat = statSync(claudeMdPath);
|
|
70
|
+
const content = readFileSync(claudeMdPath, "utf-8");
|
|
71
|
+
const lines = content.split("\n").length;
|
|
72
|
+
const sizeKb = Math.round(stat.size / 1024);
|
|
73
|
+
const hasHeaders = (content.match(/^#{1,3}\s/gm) || []).length;
|
|
74
|
+
const hasCodeBlocks = (content.match(/```/g) || []).length / 2;
|
|
75
|
+
const hasBuildCommands = /build|test|lint|dev/i.test(content);
|
|
76
|
+
let score = 40;
|
|
77
|
+
if (lines > 20)
|
|
78
|
+
score += 15;
|
|
79
|
+
if (hasHeaders >= 3)
|
|
80
|
+
score += 15;
|
|
81
|
+
if (hasCodeBlocks >= 1)
|
|
82
|
+
score += 10;
|
|
83
|
+
if (hasBuildCommands)
|
|
84
|
+
score += 20;
|
|
85
|
+
score = Math.min(100, score);
|
|
86
|
+
return {
|
|
87
|
+
name: this.name,
|
|
88
|
+
status: score >= 60 ? "pass" : "warn",
|
|
89
|
+
score,
|
|
90
|
+
weight: this.weight,
|
|
91
|
+
message: `${lines} lines, ${sizeKb}KB`,
|
|
92
|
+
details: [
|
|
93
|
+
` Sections: ${hasHeaders}`,
|
|
94
|
+
` Code blocks: ${Math.floor(hasCodeBlocks)}`,
|
|
95
|
+
` Build info: ${hasBuildCommands ? "yes" : "no"}`
|
|
96
|
+
],
|
|
97
|
+
...score < 80 && { fix: "Enrich CLAUDE.md with more project context", command: "ccjk init --smart" }
|
|
98
|
+
};
|
|
99
|
+
} catch {
|
|
100
|
+
return { name: this.name, status: "fail", score: 0, weight: this.weight, message: "Failed to read CLAUDE.md" };
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
const mcpCheck = {
|
|
106
|
+
name: "MCP Services",
|
|
107
|
+
weight: 8,
|
|
108
|
+
async check() {
|
|
109
|
+
try {
|
|
110
|
+
if (!existsSync(SETTINGS_FILE)) {
|
|
111
|
+
return {
|
|
112
|
+
name: this.name,
|
|
113
|
+
status: "fail",
|
|
114
|
+
score: 0,
|
|
115
|
+
weight: this.weight,
|
|
116
|
+
message: "No settings.json found",
|
|
117
|
+
fix: "Run ccjk init to create settings",
|
|
118
|
+
command: "ccjk init"
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
const settings = JSON.parse(readFileSync(SETTINGS_FILE, "utf-8"));
|
|
122
|
+
const mcpServers = settings.mcpServers || {};
|
|
123
|
+
const serverCount = Object.keys(mcpServers).length;
|
|
124
|
+
if (serverCount === 0) {
|
|
125
|
+
return {
|
|
126
|
+
name: this.name,
|
|
127
|
+
status: "fail",
|
|
128
|
+
score: 0,
|
|
129
|
+
weight: this.weight,
|
|
130
|
+
message: "No MCP services configured",
|
|
131
|
+
fix: "Install MCP services for enhanced capabilities",
|
|
132
|
+
command: "ccjk mcp"
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
const essentialServices = ["context7"];
|
|
136
|
+
const hasEssentials = essentialServices.filter(
|
|
137
|
+
(s) => Object.keys(mcpServers).some((k) => k.toLowerCase().includes(s))
|
|
138
|
+
);
|
|
139
|
+
const score = Math.min(100, serverCount * 20 + hasEssentials.length * 20);
|
|
140
|
+
const status = score >= 60 ? "pass" : "warn";
|
|
141
|
+
const details = Object.keys(mcpServers).map((name) => ` ${name}`);
|
|
142
|
+
return {
|
|
143
|
+
name: this.name,
|
|
144
|
+
status,
|
|
145
|
+
score,
|
|
146
|
+
weight: this.weight,
|
|
147
|
+
message: `${serverCount} service${serverCount > 1 ? "s" : ""} active`,
|
|
148
|
+
details,
|
|
149
|
+
...score < 80 && { fix: "Add more MCP services", command: "ccjk mcp" }
|
|
150
|
+
};
|
|
151
|
+
} catch {
|
|
152
|
+
return {
|
|
153
|
+
name: this.name,
|
|
154
|
+
status: "fail",
|
|
155
|
+
score: 0,
|
|
156
|
+
weight: this.weight,
|
|
157
|
+
message: "Failed to read MCP configuration",
|
|
158
|
+
command: "ccjk doctor"
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
const modelCheck = {
|
|
165
|
+
name: "Default Model",
|
|
166
|
+
weight: 5,
|
|
167
|
+
async check() {
|
|
168
|
+
try {
|
|
169
|
+
if (!existsSync(SETTINGS_FILE)) {
|
|
170
|
+
return {
|
|
171
|
+
name: this.name,
|
|
172
|
+
status: "fail",
|
|
173
|
+
score: 0,
|
|
174
|
+
weight: this.weight,
|
|
175
|
+
message: "No settings file",
|
|
176
|
+
command: "ccjk init"
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
const settings = JSON.parse(readFileSync(SETTINGS_FILE, "utf-8"));
|
|
180
|
+
const hasModel = settings.model || settings.defaultModel || settings.preferredModel;
|
|
181
|
+
const hasApiKey = settings.apiKey || settings.env?.ANTHROPIC_API_KEY || process__default.env.ANTHROPIC_API_KEY;
|
|
182
|
+
if (!hasApiKey) {
|
|
183
|
+
return {
|
|
184
|
+
name: this.name,
|
|
185
|
+
status: "warn",
|
|
186
|
+
score: 40,
|
|
187
|
+
weight: this.weight,
|
|
188
|
+
message: "No API key configured (using default)",
|
|
189
|
+
fix: "Configure API for direct access",
|
|
190
|
+
command: "ccjk menu",
|
|
191
|
+
details: [" Using Claude Code default API"]
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
return {
|
|
195
|
+
name: this.name,
|
|
196
|
+
status: "pass",
|
|
197
|
+
score: hasModel ? 100 : 70,
|
|
198
|
+
weight: this.weight,
|
|
199
|
+
message: hasModel ? `Model: ${hasModel}` : "API configured (default model)"
|
|
200
|
+
};
|
|
201
|
+
} catch {
|
|
202
|
+
return { name: this.name, status: "fail", score: 0, weight: this.weight, message: "Failed to read model config" };
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
const INVALID_PERMISSION_NAMES = /* @__PURE__ */ new Set([
|
|
208
|
+
"AllowEdit",
|
|
209
|
+
"AllowWrite",
|
|
210
|
+
"AllowRead",
|
|
211
|
+
"AllowExec",
|
|
212
|
+
"AllowCreateProcess",
|
|
213
|
+
"AllowKillProcess",
|
|
214
|
+
"AllowNetworkAccess",
|
|
215
|
+
"AllowFileSystemAccess",
|
|
216
|
+
"AllowShellAccess",
|
|
217
|
+
"AllowHttpAccess"
|
|
218
|
+
]);
|
|
219
|
+
function isValidPermission(perm) {
|
|
220
|
+
if (INVALID_PERMISSION_NAMES.has(perm))
|
|
221
|
+
return false;
|
|
222
|
+
if (["mcp__.*", "mcp__*", "mcp__(*)"].includes(perm))
|
|
223
|
+
return false;
|
|
224
|
+
return true;
|
|
225
|
+
}
|
|
226
|
+
const permissionsCheck = {
|
|
227
|
+
name: "Permissions",
|
|
228
|
+
weight: 3,
|
|
229
|
+
async check() {
|
|
230
|
+
try {
|
|
231
|
+
if (!existsSync(SETTINGS_FILE)) {
|
|
232
|
+
return {
|
|
233
|
+
name: this.name,
|
|
234
|
+
status: "warn",
|
|
235
|
+
score: 30,
|
|
236
|
+
weight: this.weight,
|
|
237
|
+
message: "No settings file",
|
|
238
|
+
command: "ccjk init"
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
const settings = JSON.parse(readFileSync(SETTINGS_FILE, "utf-8"));
|
|
242
|
+
const allowedTools = settings.permissions?.allow || [];
|
|
243
|
+
if (allowedTools.length === 0) {
|
|
244
|
+
return {
|
|
245
|
+
name: this.name,
|
|
246
|
+
status: "warn",
|
|
247
|
+
score: 40,
|
|
248
|
+
weight: this.weight,
|
|
249
|
+
message: "No tool permissions configured",
|
|
250
|
+
fix: "Configure permissions to reduce prompts",
|
|
251
|
+
command: "ccjk menu"
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
const invalidPerms = allowedTools.filter((p) => !isValidPermission(p));
|
|
255
|
+
const validPerms = allowedTools.filter((p) => isValidPermission(p));
|
|
256
|
+
if (invalidPerms.length > 0) {
|
|
257
|
+
return {
|
|
258
|
+
name: this.name,
|
|
259
|
+
status: "warn",
|
|
260
|
+
score: 50,
|
|
261
|
+
weight: this.weight,
|
|
262
|
+
message: `${invalidPerms.length} invalid permission(s) found (${validPerms.length} valid)`,
|
|
263
|
+
fix: "Run ccjk init to repair permissions",
|
|
264
|
+
command: "ccjk init"
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
const score = Math.min(100, 60 + validPerms.length * 2);
|
|
268
|
+
return {
|
|
269
|
+
name: this.name,
|
|
270
|
+
status: "pass",
|
|
271
|
+
score,
|
|
272
|
+
weight: this.weight,
|
|
273
|
+
message: `${validPerms.length} valid permission${validPerms.length > 1 ? "s" : ""} configured`
|
|
274
|
+
};
|
|
275
|
+
} catch {
|
|
276
|
+
return { name: this.name, status: "fail", score: 0, weight: this.weight, message: "Failed to read permissions" };
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
};
|
|
280
|
+
|
|
281
|
+
const skillsCheck = {
|
|
282
|
+
name: "Skills",
|
|
283
|
+
weight: 6,
|
|
284
|
+
async check() {
|
|
285
|
+
try {
|
|
286
|
+
if (!existsSync(CCJK_SKILLS_DIR)) {
|
|
287
|
+
return {
|
|
288
|
+
name: this.name,
|
|
289
|
+
status: "warn",
|
|
290
|
+
score: 20,
|
|
291
|
+
weight: this.weight,
|
|
292
|
+
message: "No skills directory found",
|
|
293
|
+
fix: "Install skills to enhance Claude Code",
|
|
294
|
+
command: "ccjk skills"
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
const files = readdirSync(CCJK_SKILLS_DIR).filter((f) => f.endsWith(".md"));
|
|
298
|
+
const skillCount = files.length;
|
|
299
|
+
if (skillCount === 0) {
|
|
300
|
+
return {
|
|
301
|
+
name: this.name,
|
|
302
|
+
status: "warn",
|
|
303
|
+
score: 20,
|
|
304
|
+
weight: this.weight,
|
|
305
|
+
message: "No skills installed",
|
|
306
|
+
fix: "Install skills based on your project",
|
|
307
|
+
command: "ccjk skills"
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
const score = Math.min(100, 30 + skillCount * 10);
|
|
311
|
+
return {
|
|
312
|
+
name: this.name,
|
|
313
|
+
status: score >= 60 ? "pass" : "warn",
|
|
314
|
+
score,
|
|
315
|
+
weight: this.weight,
|
|
316
|
+
message: `${skillCount} skill${skillCount > 1 ? "s" : ""} installed`,
|
|
317
|
+
details: files.slice(0, 8).map((f) => ` ${f.replace(".md", "")}`),
|
|
318
|
+
...skillCount < 5 && { fix: "Install more project-specific skills", command: "ccjk skills" }
|
|
319
|
+
};
|
|
320
|
+
} catch {
|
|
321
|
+
return { name: this.name, status: "fail", score: 0, weight: this.weight, message: "Failed to read skills" };
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
};
|
|
325
|
+
|
|
326
|
+
const DEFAULT_CHECKS = [
|
|
327
|
+
mcpCheck,
|
|
328
|
+
skillsCheck,
|
|
329
|
+
claudeMdCheck,
|
|
330
|
+
modelCheck,
|
|
331
|
+
agentsCheck,
|
|
332
|
+
permissionsCheck,
|
|
333
|
+
memoryCheck
|
|
334
|
+
];
|
|
335
|
+
function calculateGrade(score) {
|
|
336
|
+
if (score >= 95)
|
|
337
|
+
return "S";
|
|
338
|
+
if (score >= 80)
|
|
339
|
+
return "A";
|
|
340
|
+
if (score >= 65)
|
|
341
|
+
return "B";
|
|
342
|
+
if (score >= 50)
|
|
343
|
+
return "C";
|
|
344
|
+
if (score >= 30)
|
|
345
|
+
return "D";
|
|
346
|
+
return "F";
|
|
347
|
+
}
|
|
348
|
+
function generateRecommendations(results) {
|
|
349
|
+
const recs = [];
|
|
350
|
+
for (const r of results) {
|
|
351
|
+
if (r.status === "fail" && r.command) {
|
|
352
|
+
recs.push({
|
|
353
|
+
priority: "high",
|
|
354
|
+
title: `Fix: ${r.name}`,
|
|
355
|
+
description: r.fix || r.message,
|
|
356
|
+
command: r.command,
|
|
357
|
+
category: mapCategory(r.name)
|
|
358
|
+
});
|
|
359
|
+
} else if (r.status === "warn" && r.command) {
|
|
360
|
+
recs.push({
|
|
361
|
+
priority: "medium",
|
|
362
|
+
title: `Improve: ${r.name}`,
|
|
363
|
+
description: r.fix || r.message,
|
|
364
|
+
command: r.command,
|
|
365
|
+
category: mapCategory(r.name)
|
|
366
|
+
});
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
const priorityOrder = { high: 0, medium: 1, low: 2 };
|
|
370
|
+
recs.sort((a, b) => priorityOrder[a.priority] - priorityOrder[b.priority]);
|
|
371
|
+
return recs;
|
|
372
|
+
}
|
|
373
|
+
function mapCategory(name) {
|
|
374
|
+
const lower = name.toLowerCase();
|
|
375
|
+
if (lower.includes("mcp"))
|
|
376
|
+
return "mcp";
|
|
377
|
+
if (lower.includes("skill"))
|
|
378
|
+
return "skills";
|
|
379
|
+
if (lower.includes("agent"))
|
|
380
|
+
return "agents";
|
|
381
|
+
if (lower.includes("model") || lower.includes("api"))
|
|
382
|
+
return "model";
|
|
383
|
+
if (lower.includes("memory"))
|
|
384
|
+
return "sync";
|
|
385
|
+
if (lower.includes("sync"))
|
|
386
|
+
return "sync";
|
|
387
|
+
if (lower.includes("perm"))
|
|
388
|
+
return "permissions";
|
|
389
|
+
return "general";
|
|
390
|
+
}
|
|
391
|
+
async function runHealthCheck(checks) {
|
|
392
|
+
const activeChecks = checks || DEFAULT_CHECKS;
|
|
393
|
+
const results = [];
|
|
394
|
+
const promises = activeChecks.map(async (check) => {
|
|
395
|
+
try {
|
|
396
|
+
return await check.check();
|
|
397
|
+
} catch {
|
|
398
|
+
return {
|
|
399
|
+
name: check.name,
|
|
400
|
+
status: "fail",
|
|
401
|
+
score: 0,
|
|
402
|
+
weight: check.weight,
|
|
403
|
+
message: "Check failed unexpectedly"
|
|
404
|
+
};
|
|
405
|
+
}
|
|
406
|
+
});
|
|
407
|
+
const settled = await Promise.all(promises);
|
|
408
|
+
results.push(...settled);
|
|
409
|
+
const totalWeight = results.reduce((sum, r) => sum + r.weight, 0);
|
|
410
|
+
const weightedSum = results.reduce((sum, r) => sum + r.score * r.weight, 0);
|
|
411
|
+
const totalScore = totalWeight > 0 ? Math.round(weightedSum / totalWeight) : 0;
|
|
412
|
+
return {
|
|
413
|
+
totalScore,
|
|
414
|
+
grade: calculateGrade(totalScore),
|
|
415
|
+
results,
|
|
416
|
+
recommendations: generateRecommendations(results),
|
|
417
|
+
timestamp: Date.now()
|
|
418
|
+
};
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
export { runHealthCheck as r };
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ccjk",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "13.5.
|
|
4
|
+
"version": "13.5.4",
|
|
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": {
|
|
@@ -1,221 +0,0 @@
|
|
|
1
|
-
import { stat, writeFile, readdir, readFile } from 'node:fs/promises';
|
|
2
|
-
import { homedir } from 'node:os';
|
|
3
|
-
import { j as join } from '../shared/ccjk.bQ7Dh1g4.mjs';
|
|
4
|
-
|
|
5
|
-
function parseAutoMemory(content) {
|
|
6
|
-
const lines = content.split("\n");
|
|
7
|
-
const entries = [];
|
|
8
|
-
let currentEntry = null;
|
|
9
|
-
for (const line of lines) {
|
|
10
|
-
const headerMatch = line.match(/^(#{1,6})\s+(.+)$/);
|
|
11
|
-
if (headerMatch) {
|
|
12
|
-
if (currentEntry && currentEntry.content.length > 0) {
|
|
13
|
-
entries.push(currentEntry);
|
|
14
|
-
}
|
|
15
|
-
currentEntry = {
|
|
16
|
-
title: headerMatch[2].trim(),
|
|
17
|
-
content: [],
|
|
18
|
-
level: headerMatch[1].length
|
|
19
|
-
};
|
|
20
|
-
} else if (currentEntry && line.trim()) {
|
|
21
|
-
currentEntry.content.push(line);
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
if (currentEntry && currentEntry.content.length > 0) {
|
|
25
|
-
entries.push(currentEntry);
|
|
26
|
-
}
|
|
27
|
-
return entries;
|
|
28
|
-
}
|
|
29
|
-
function autoMemoryToBrainContext(entries, projectPath) {
|
|
30
|
-
const context = {
|
|
31
|
-
facts: [],
|
|
32
|
-
patterns: [],
|
|
33
|
-
decisions: [],
|
|
34
|
-
metadata: {
|
|
35
|
-
source: "auto-memory",
|
|
36
|
-
projectPath,
|
|
37
|
-
syncedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
38
|
-
}
|
|
39
|
-
};
|
|
40
|
-
for (const entry of entries) {
|
|
41
|
-
const contentText = entry.content.join("\n").trim();
|
|
42
|
-
const titleLower = entry.title.toLowerCase();
|
|
43
|
-
if (titleLower.includes("architecture") || titleLower.includes("pattern") || titleLower.includes("design")) {
|
|
44
|
-
context.patterns.push({
|
|
45
|
-
name: entry.title,
|
|
46
|
-
description: contentText,
|
|
47
|
-
category: "architecture"
|
|
48
|
-
});
|
|
49
|
-
} else if (titleLower.includes("decision") || titleLower.includes("choice") || titleLower.includes("why")) {
|
|
50
|
-
context.decisions.push({
|
|
51
|
-
decision: entry.title,
|
|
52
|
-
rationale: contentText,
|
|
53
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
54
|
-
});
|
|
55
|
-
} else {
|
|
56
|
-
context.facts.push({
|
|
57
|
-
key: entry.title,
|
|
58
|
-
value: contentText,
|
|
59
|
-
confidence: 0.9
|
|
60
|
-
});
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
return context;
|
|
64
|
-
}
|
|
65
|
-
function brainContextToAutoMemory(context) {
|
|
66
|
-
const lines = [];
|
|
67
|
-
const timestamp = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
68
|
-
lines.push("# CCJK Brain Memory");
|
|
69
|
-
lines.push("");
|
|
70
|
-
lines.push(`Last synced: ${timestamp}`);
|
|
71
|
-
lines.push("");
|
|
72
|
-
if (context.facts.length > 0) {
|
|
73
|
-
lines.push("## Key Facts");
|
|
74
|
-
lines.push("");
|
|
75
|
-
for (const fact of context.facts) {
|
|
76
|
-
lines.push(`### ${fact.key}`);
|
|
77
|
-
lines.push(fact.value);
|
|
78
|
-
lines.push("");
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
if (context.patterns.length > 0) {
|
|
82
|
-
lines.push("## Patterns & Architecture");
|
|
83
|
-
lines.push("");
|
|
84
|
-
for (const pattern of context.patterns) {
|
|
85
|
-
lines.push(`### ${pattern.name}`);
|
|
86
|
-
lines.push(pattern.description);
|
|
87
|
-
if (pattern.category) {
|
|
88
|
-
lines.push(`Category: ${pattern.category}`);
|
|
89
|
-
}
|
|
90
|
-
lines.push("");
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
if (context.decisions.length > 0) {
|
|
94
|
-
lines.push("## Decisions");
|
|
95
|
-
lines.push("");
|
|
96
|
-
for (const decision of context.decisions) {
|
|
97
|
-
lines.push(`### ${decision.decision}`);
|
|
98
|
-
lines.push(decision.rationale);
|
|
99
|
-
if (decision.timestamp) {
|
|
100
|
-
lines.push(`Decided: ${decision.timestamp}`);
|
|
101
|
-
}
|
|
102
|
-
lines.push("");
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
return lines.join("\n");
|
|
106
|
-
}
|
|
107
|
-
async function findAutoMemoryFiles(claudeProjectsDir) {
|
|
108
|
-
const baseDir = claudeProjectsDir || join(homedir(), ".claude", "projects");
|
|
109
|
-
const documents = [];
|
|
110
|
-
try {
|
|
111
|
-
const projectDirs = await readdir(baseDir);
|
|
112
|
-
for (const projectDir of projectDirs) {
|
|
113
|
-
const memoryPath = join(baseDir, projectDir, "memory", "MEMORY.md");
|
|
114
|
-
try {
|
|
115
|
-
const content = await readFile(memoryPath, "utf-8");
|
|
116
|
-
const entries = parseAutoMemory(content);
|
|
117
|
-
const projectPath = projectDir.startsWith("-") ? projectDir.slice(1).replace(/-/g, "/") : projectDir;
|
|
118
|
-
documents.push({
|
|
119
|
-
projectPath,
|
|
120
|
-
entries,
|
|
121
|
-
rawContent: content
|
|
122
|
-
});
|
|
123
|
-
} catch {
|
|
124
|
-
continue;
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
} catch {
|
|
128
|
-
return [];
|
|
129
|
-
}
|
|
130
|
-
return documents;
|
|
131
|
-
}
|
|
132
|
-
async function syncAutoMemoryToBrain(session, config) {
|
|
133
|
-
const documents = await findAutoMemoryFiles(config?.claudeProjectsDir);
|
|
134
|
-
for (const doc of documents) {
|
|
135
|
-
if (session.metadata?.projectPath === doc.projectPath) {
|
|
136
|
-
const brainContext = autoMemoryToBrainContext(doc.entries, doc.projectPath);
|
|
137
|
-
session.context.facts.push(...brainContext.facts);
|
|
138
|
-
session.context.patterns.push(...brainContext.patterns);
|
|
139
|
-
session.context.decisions.push(...brainContext.decisions);
|
|
140
|
-
session.metadata = {
|
|
141
|
-
...session.metadata,
|
|
142
|
-
autoMemorySyncedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
143
|
-
};
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
async function syncBrainToAutoMemory(session, config) {
|
|
148
|
-
if (!config?.bidirectional) {
|
|
149
|
-
return;
|
|
150
|
-
}
|
|
151
|
-
const projectPath = session.metadata?.projectPath;
|
|
152
|
-
if (!projectPath) {
|
|
153
|
-
return;
|
|
154
|
-
}
|
|
155
|
-
const baseDir = config.claudeProjectsDir || join(homedir(), ".claude", "projects");
|
|
156
|
-
const projectDirName = `-${projectPath.replace(/\//g, "-")}`;
|
|
157
|
-
const memoryDir = join(baseDir, projectDirName, "memory");
|
|
158
|
-
const memoryPath = join(memoryDir, "MEMORY.md");
|
|
159
|
-
const content = brainContextToAutoMemory(session.context);
|
|
160
|
-
try {
|
|
161
|
-
await stat(memoryDir);
|
|
162
|
-
} catch {
|
|
163
|
-
return;
|
|
164
|
-
}
|
|
165
|
-
await writeFile(memoryPath, content, "utf-8");
|
|
166
|
-
}
|
|
167
|
-
class AutoMemoryBridge {
|
|
168
|
-
config;
|
|
169
|
-
syncTimer;
|
|
170
|
-
constructor(config) {
|
|
171
|
-
this.config = {
|
|
172
|
-
claudeProjectsDir: config?.claudeProjectsDir || join(homedir(), ".claude", "projects"),
|
|
173
|
-
syncInterval: config?.syncInterval ?? 0,
|
|
174
|
-
bidirectional: config?.bidirectional ?? false
|
|
175
|
-
};
|
|
176
|
-
}
|
|
177
|
-
/**
|
|
178
|
-
* Start periodic sync
|
|
179
|
-
*/
|
|
180
|
-
startSync(session) {
|
|
181
|
-
if (this.config.syncInterval <= 0) {
|
|
182
|
-
return;
|
|
183
|
-
}
|
|
184
|
-
this.syncTimer = setInterval(async () => {
|
|
185
|
-
try {
|
|
186
|
-
await syncAutoMemoryToBrain(session, this.config);
|
|
187
|
-
if (this.config.bidirectional) {
|
|
188
|
-
await syncBrainToAutoMemory(session, this.config);
|
|
189
|
-
}
|
|
190
|
-
} catch (err) {
|
|
191
|
-
console.error("Auto-memory sync failed:", err);
|
|
192
|
-
}
|
|
193
|
-
}, this.config.syncInterval);
|
|
194
|
-
}
|
|
195
|
-
/**
|
|
196
|
-
* Stop periodic sync
|
|
197
|
-
*/
|
|
198
|
-
stopSync() {
|
|
199
|
-
if (this.syncTimer) {
|
|
200
|
-
clearInterval(this.syncTimer);
|
|
201
|
-
this.syncTimer = void 0;
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
/**
|
|
205
|
-
* Perform one-time sync
|
|
206
|
-
*/
|
|
207
|
-
async sync(session) {
|
|
208
|
-
await syncAutoMemoryToBrain(session, this.config);
|
|
209
|
-
if (this.config.bidirectional) {
|
|
210
|
-
await syncBrainToAutoMemory(session, this.config);
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
async syncToClaude(session) {
|
|
214
|
-
if (!session) {
|
|
215
|
-
return;
|
|
216
|
-
}
|
|
217
|
-
await syncBrainToAutoMemory(session, this.config);
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
export { AutoMemoryBridge, autoMemoryToBrainContext, brainContextToAutoMemory, findAutoMemoryFiles, parseAutoMemory, syncAutoMemoryToBrain, syncBrainToAutoMemory };
|