cckb 0.1.2 → 0.1.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 +40 -6
- package/dist/bin/cckb.js +34 -12
- package/dist/bin/cckb.js.map +1 -1
- package/dist/{chunk-XAY6TTXB.js → chunk-4IQV2TQE.js} +2 -2
- package/dist/chunk-EPZCT4Y2.js +276 -0
- package/dist/chunk-EPZCT4Y2.js.map +1 -0
- package/dist/{chunk-TFFLX3YY.js → chunk-FRETJBP5.js} +25 -2
- package/dist/chunk-FRETJBP5.js.map +1 -0
- package/dist/{chunk-K4W3KOBL.js → chunk-HP5YVMZ6.js} +2 -2
- package/dist/chunk-P7O44DI3.js +944 -0
- package/dist/chunk-P7O44DI3.js.map +1 -0
- package/dist/{chunk-Z3CJQKTH.js → chunk-ZZDG5UWP.js} +58 -255
- package/dist/chunk-ZZDG5UWP.js.map +1 -0
- package/dist/hooks/notification.js +2 -2
- package/dist/hooks/post-tool-use.js +2 -2
- package/dist/hooks/session-start.js +3 -3
- package/dist/hooks/stop.js +7 -5
- package/dist/hooks/stop.js.map +1 -1
- package/dist/hooks/user-prompt.js +2 -2
- package/dist/index.d.ts +109 -1
- package/dist/index.js +16 -6
- package/package.json +1 -1
- package/dist/chunk-G6QWIQ6P.js +0 -207
- package/dist/chunk-G6QWIQ6P.js.map +0 -1
- package/dist/chunk-TFFLX3YY.js.map +0 -1
- package/dist/chunk-Z3CJQKTH.js.map +0 -1
- /package/dist/{chunk-XAY6TTXB.js.map → chunk-4IQV2TQE.js.map} +0 -0
- /package/dist/{chunk-K4W3KOBL.js.map → chunk-HP5YVMZ6.js.map} +0 -0
|
@@ -0,0 +1,944 @@
|
|
|
1
|
+
import {
|
|
2
|
+
VaultIntegrator,
|
|
3
|
+
isClaudeAvailable,
|
|
4
|
+
spawnClaudeAgent
|
|
5
|
+
} from "./chunk-ZZDG5UWP.js";
|
|
6
|
+
import {
|
|
7
|
+
DEFAULT_CONFIG,
|
|
8
|
+
ensureDir,
|
|
9
|
+
fileExists,
|
|
10
|
+
loadConfig,
|
|
11
|
+
readJSON,
|
|
12
|
+
readTextFile,
|
|
13
|
+
writeJSON,
|
|
14
|
+
writeTextFile
|
|
15
|
+
} from "./chunk-FRETJBP5.js";
|
|
16
|
+
|
|
17
|
+
// src/cli/install.ts
|
|
18
|
+
import * as fs from "fs/promises";
|
|
19
|
+
import * as fsSync from "fs";
|
|
20
|
+
import * as path from "path";
|
|
21
|
+
import { fileURLToPath } from "url";
|
|
22
|
+
var __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
23
|
+
function findPackageRoot() {
|
|
24
|
+
let dir = __dirname;
|
|
25
|
+
for (let i = 0; i < 5; i++) {
|
|
26
|
+
const packageJson = path.join(dir, "package.json");
|
|
27
|
+
if (fsSync.existsSync(packageJson)) {
|
|
28
|
+
return dir;
|
|
29
|
+
}
|
|
30
|
+
dir = path.dirname(dir);
|
|
31
|
+
}
|
|
32
|
+
return path.resolve(__dirname, "../..");
|
|
33
|
+
}
|
|
34
|
+
var PACKAGE_ROOT = findPackageRoot();
|
|
35
|
+
var TEMPLATES_DIR = path.join(PACKAGE_ROOT, "templates");
|
|
36
|
+
async function install(targetPath, options = {}) {
|
|
37
|
+
const resolvedPath = path.resolve(targetPath);
|
|
38
|
+
console.log(`Installing CCKB to: ${resolvedPath}`);
|
|
39
|
+
await validateTargetPath(resolvedPath);
|
|
40
|
+
await checkExistingInstallation(resolvedPath, options.force);
|
|
41
|
+
await createDirectoryStructure(resolvedPath);
|
|
42
|
+
await copyTemplateFiles(resolvedPath);
|
|
43
|
+
await createConfigFile(resolvedPath);
|
|
44
|
+
await installHooks(resolvedPath);
|
|
45
|
+
await updateClaudeMd(resolvedPath);
|
|
46
|
+
await updateGitignore(resolvedPath);
|
|
47
|
+
console.log("\nCCKB installed successfully!");
|
|
48
|
+
console.log("\nNext steps:");
|
|
49
|
+
console.log(" 1. Review cc-knowledge-base/vault/ structure");
|
|
50
|
+
console.log(" 2. Check .claude/settings.json for hook configuration");
|
|
51
|
+
console.log(" 3. Start a new Claude Code session to begin capturing knowledge");
|
|
52
|
+
}
|
|
53
|
+
async function validateTargetPath(targetPath) {
|
|
54
|
+
const exists = await fileExists(targetPath);
|
|
55
|
+
if (!exists) {
|
|
56
|
+
throw new Error(`Target path does not exist: ${targetPath}`);
|
|
57
|
+
}
|
|
58
|
+
const stats = await fs.stat(targetPath);
|
|
59
|
+
if (!stats.isDirectory()) {
|
|
60
|
+
throw new Error(`Target path is not a directory: ${targetPath}`);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
async function checkExistingInstallation(targetPath, force) {
|
|
64
|
+
const kbPath = path.join(targetPath, "cc-knowledge-base");
|
|
65
|
+
const exists = await fileExists(kbPath);
|
|
66
|
+
if (exists && !force) {
|
|
67
|
+
throw new Error(
|
|
68
|
+
"CCKB is already installed in this project. Use --force to reinstall."
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
async function createDirectoryStructure(targetPath) {
|
|
73
|
+
const directories = [
|
|
74
|
+
"cc-knowledge-base",
|
|
75
|
+
"cc-knowledge-base/conversations",
|
|
76
|
+
"cc-knowledge-base/vault",
|
|
77
|
+
"cc-knowledge-base/vault/entities",
|
|
78
|
+
"cc-knowledge-base/vault/apps",
|
|
79
|
+
"cc-knowledge-base/vault/modules",
|
|
80
|
+
"cc-knowledge-base/.cckb-state"
|
|
81
|
+
];
|
|
82
|
+
for (const dir of directories) {
|
|
83
|
+
await ensureDir(path.join(targetPath, dir));
|
|
84
|
+
}
|
|
85
|
+
console.log(" Created directory structure");
|
|
86
|
+
}
|
|
87
|
+
async function copyTemplateFiles(targetPath) {
|
|
88
|
+
const vaultFiles = [
|
|
89
|
+
{ src: "vault/INDEX.md", dest: "cc-knowledge-base/vault/INDEX.md" },
|
|
90
|
+
{
|
|
91
|
+
src: "vault/architecture.md",
|
|
92
|
+
dest: "cc-knowledge-base/vault/architecture.md"
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
src: "vault/general-knowledge.md",
|
|
96
|
+
dest: "cc-knowledge-base/vault/general-knowledge.md"
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
src: "vault/entities/INDEX.md",
|
|
100
|
+
dest: "cc-knowledge-base/vault/entities/INDEX.md"
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
src: "vault/apps/INDEX.md",
|
|
104
|
+
dest: "cc-knowledge-base/vault/apps/INDEX.md"
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
src: "vault/modules/INDEX.md",
|
|
108
|
+
dest: "cc-knowledge-base/vault/modules/INDEX.md"
|
|
109
|
+
}
|
|
110
|
+
];
|
|
111
|
+
for (const file of vaultFiles) {
|
|
112
|
+
const srcPath = path.join(TEMPLATES_DIR, file.src);
|
|
113
|
+
const destPath = path.join(targetPath, file.dest);
|
|
114
|
+
const content = await readTextFile(srcPath);
|
|
115
|
+
if (content) {
|
|
116
|
+
await writeTextFile(destPath, content);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
await writeTextFile(
|
|
120
|
+
path.join(targetPath, "cc-knowledge-base/conversations/.gitkeep"),
|
|
121
|
+
""
|
|
122
|
+
);
|
|
123
|
+
console.log(" Copied vault template files");
|
|
124
|
+
}
|
|
125
|
+
async function createConfigFile(targetPath) {
|
|
126
|
+
const configPath = path.join(
|
|
127
|
+
targetPath,
|
|
128
|
+
"cc-knowledge-base",
|
|
129
|
+
".cckb-config.json"
|
|
130
|
+
);
|
|
131
|
+
await writeJSON(configPath, DEFAULT_CONFIG);
|
|
132
|
+
console.log(" Created configuration file");
|
|
133
|
+
}
|
|
134
|
+
async function installHooks(targetPath) {
|
|
135
|
+
const claudeDir = path.join(targetPath, ".claude");
|
|
136
|
+
const settingsPath = path.join(claudeDir, "settings.json");
|
|
137
|
+
await ensureDir(claudeDir);
|
|
138
|
+
let settings = await readJSON(settingsPath) || {};
|
|
139
|
+
const templatePath = path.join(TEMPLATES_DIR, "settings.json.tmpl");
|
|
140
|
+
const hookSettings = await readJSON(
|
|
141
|
+
templatePath
|
|
142
|
+
);
|
|
143
|
+
if (!hookSettings) {
|
|
144
|
+
throw new Error("Failed to load hook template");
|
|
145
|
+
}
|
|
146
|
+
const existingHooks = settings.hooks || {};
|
|
147
|
+
const newHooks = hookSettings.hooks;
|
|
148
|
+
settings.hooks = mergeHooks(existingHooks, newHooks);
|
|
149
|
+
await writeJSON(settingsPath, settings);
|
|
150
|
+
console.log(" Installed hook configuration");
|
|
151
|
+
}
|
|
152
|
+
function mergeHooks(existing, incoming) {
|
|
153
|
+
const merged = { ...existing };
|
|
154
|
+
for (const [hookType, hooks] of Object.entries(incoming)) {
|
|
155
|
+
if (!merged[hookType]) {
|
|
156
|
+
merged[hookType] = [];
|
|
157
|
+
}
|
|
158
|
+
for (const hook of hooks) {
|
|
159
|
+
const hookObj = hook;
|
|
160
|
+
const command = hookObj.command;
|
|
161
|
+
if (command?.includes("cckb")) {
|
|
162
|
+
merged[hookType] = merged[hookType].filter((h) => {
|
|
163
|
+
const existingCmd = h.command;
|
|
164
|
+
return !existingCmd?.includes("cckb");
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
merged[hookType].push(hook);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
return merged;
|
|
171
|
+
}
|
|
172
|
+
async function updateClaudeMd(targetPath) {
|
|
173
|
+
const claudeMdPath = path.join(targetPath, "CLAUDE.md");
|
|
174
|
+
const templatePath = path.join(TEMPLATES_DIR, "CLAUDE.md.tmpl");
|
|
175
|
+
const template = await readTextFile(templatePath);
|
|
176
|
+
if (!template) {
|
|
177
|
+
throw new Error("Failed to load CLAUDE.md template");
|
|
178
|
+
}
|
|
179
|
+
const marker = "## Project Knowledge Base (CCKB)";
|
|
180
|
+
let existing = await readTextFile(claudeMdPath);
|
|
181
|
+
if (existing) {
|
|
182
|
+
if (existing.includes(marker)) {
|
|
183
|
+
const regex = /## Project Knowledge Base \(CCKB\)[\s\S]*?(?=\n## |$)/;
|
|
184
|
+
existing = existing.replace(regex, template.trim());
|
|
185
|
+
} else {
|
|
186
|
+
existing = existing.trimEnd() + "\n\n" + template;
|
|
187
|
+
}
|
|
188
|
+
await writeTextFile(claudeMdPath, existing);
|
|
189
|
+
} else {
|
|
190
|
+
await writeTextFile(claudeMdPath, template);
|
|
191
|
+
}
|
|
192
|
+
console.log(" Updated CLAUDE.md with vault directives");
|
|
193
|
+
}
|
|
194
|
+
async function updateGitignore(targetPath) {
|
|
195
|
+
const gitignorePath = path.join(targetPath, ".gitignore");
|
|
196
|
+
const entries = [
|
|
197
|
+
"",
|
|
198
|
+
"# CCKB state files",
|
|
199
|
+
"cc-knowledge-base/.cckb-state/"
|
|
200
|
+
];
|
|
201
|
+
let existing = await readTextFile(gitignorePath) || "";
|
|
202
|
+
if (existing.includes("cc-knowledge-base/.cckb-state/")) {
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
existing = existing.trimEnd() + "\n" + entries.join("\n") + "\n";
|
|
206
|
+
await writeTextFile(gitignorePath, existing);
|
|
207
|
+
console.log(" Updated .gitignore");
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// src/core/file-collector.ts
|
|
211
|
+
import * as fs2 from "fs/promises";
|
|
212
|
+
import * as path2 from "path";
|
|
213
|
+
var MANIFEST_LANGUAGE_MAP = {
|
|
214
|
+
"package.json": { language: "typescript", projectType: "node" },
|
|
215
|
+
"tsconfig.json": { language: "typescript", projectType: "node" },
|
|
216
|
+
"Cargo.toml": { language: "rust", projectType: "rust" },
|
|
217
|
+
"go.mod": { language: "go", projectType: "go" },
|
|
218
|
+
"requirements.txt": { language: "python", projectType: "python" },
|
|
219
|
+
"pyproject.toml": { language: "python", projectType: "python" },
|
|
220
|
+
"setup.py": { language: "python", projectType: "python" },
|
|
221
|
+
"pom.xml": { language: "java", projectType: "java" },
|
|
222
|
+
"build.gradle": { language: "java", projectType: "java" },
|
|
223
|
+
"*.csproj": { language: "csharp", projectType: "dotnet" },
|
|
224
|
+
"Gemfile": { language: "ruby", projectType: "ruby" },
|
|
225
|
+
"composer.json": { language: "php", projectType: "php" }
|
|
226
|
+
};
|
|
227
|
+
var EXTENSION_LANGUAGE_MAP = {
|
|
228
|
+
".ts": "typescript",
|
|
229
|
+
".tsx": "typescript",
|
|
230
|
+
".js": "javascript",
|
|
231
|
+
".jsx": "javascript",
|
|
232
|
+
".mjs": "javascript",
|
|
233
|
+
".cjs": "javascript",
|
|
234
|
+
".py": "python",
|
|
235
|
+
".go": "go",
|
|
236
|
+
".rs": "rust",
|
|
237
|
+
".java": "java",
|
|
238
|
+
".cs": "csharp",
|
|
239
|
+
".rb": "ruby",
|
|
240
|
+
".php": "php"
|
|
241
|
+
};
|
|
242
|
+
var FileCollector = class {
|
|
243
|
+
projectPath;
|
|
244
|
+
constructor(projectPath) {
|
|
245
|
+
this.projectPath = projectPath;
|
|
246
|
+
}
|
|
247
|
+
async collect(options) {
|
|
248
|
+
const config = await loadConfig(this.projectPath);
|
|
249
|
+
const mergedOptions = {
|
|
250
|
+
maxFiles: options?.maxFiles ?? config.discover.maxFiles,
|
|
251
|
+
excludePatterns: options?.excludePatterns ?? config.discover.excludePatterns,
|
|
252
|
+
supportedLanguages: options?.supportedLanguages ?? config.discover.supportedLanguages
|
|
253
|
+
};
|
|
254
|
+
const { languages, projectType } = await this.detectLanguages();
|
|
255
|
+
const ignorePatterns = await this.loadIgnorePatterns(mergedOptions.excludePatterns);
|
|
256
|
+
const allFiles = [];
|
|
257
|
+
let totalScanned = 0;
|
|
258
|
+
await this.walkDirectory(
|
|
259
|
+
this.projectPath,
|
|
260
|
+
async (filePath, stats) => {
|
|
261
|
+
totalScanned++;
|
|
262
|
+
const relativePath = path2.relative(this.projectPath, filePath);
|
|
263
|
+
if (this.shouldIgnore(relativePath, ignorePatterns)) {
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
const ext = path2.extname(filePath);
|
|
267
|
+
const language = EXTENSION_LANGUAGE_MAP[ext];
|
|
268
|
+
if (!language || !mergedOptions.supportedLanguages.includes(language)) {
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
const category = this.categorizeFile(relativePath);
|
|
272
|
+
const priority = this.calculatePriority(relativePath, category, stats.size);
|
|
273
|
+
allFiles.push({
|
|
274
|
+
path: relativePath,
|
|
275
|
+
absolutePath: filePath,
|
|
276
|
+
language,
|
|
277
|
+
category,
|
|
278
|
+
size: stats.size,
|
|
279
|
+
priority
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
);
|
|
283
|
+
allFiles.sort((a, b) => b.priority - a.priority);
|
|
284
|
+
const limitedFiles = allFiles.slice(0, mergedOptions.maxFiles);
|
|
285
|
+
return {
|
|
286
|
+
files: limitedFiles,
|
|
287
|
+
languages,
|
|
288
|
+
projectType,
|
|
289
|
+
totalFilesScanned: totalScanned
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
async detectLanguages() {
|
|
293
|
+
const detected = /* @__PURE__ */ new Set();
|
|
294
|
+
let projectType = "unknown";
|
|
295
|
+
for (const [manifest, info] of Object.entries(MANIFEST_LANGUAGE_MAP)) {
|
|
296
|
+
if (manifest.startsWith("*")) {
|
|
297
|
+
continue;
|
|
298
|
+
}
|
|
299
|
+
const manifestPath = path2.join(this.projectPath, manifest);
|
|
300
|
+
if (await fileExists(manifestPath)) {
|
|
301
|
+
detected.add(info.language);
|
|
302
|
+
if (projectType === "unknown") {
|
|
303
|
+
projectType = info.projectType;
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
const packageJsonPath = path2.join(this.projectPath, "package.json");
|
|
308
|
+
if (await fileExists(packageJsonPath)) {
|
|
309
|
+
const content = await readTextFile(packageJsonPath);
|
|
310
|
+
if (content) {
|
|
311
|
+
try {
|
|
312
|
+
const pkg = JSON.parse(content);
|
|
313
|
+
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
314
|
+
if (deps.typescript || await fileExists(path2.join(this.projectPath, "tsconfig.json"))) {
|
|
315
|
+
detected.add("typescript");
|
|
316
|
+
} else {
|
|
317
|
+
detected.add("javascript");
|
|
318
|
+
}
|
|
319
|
+
} catch {
|
|
320
|
+
detected.add("javascript");
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
return {
|
|
325
|
+
languages: Array.from(detected),
|
|
326
|
+
projectType
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
async loadIgnorePatterns(additionalPatterns) {
|
|
330
|
+
const patterns = [
|
|
331
|
+
// Default ignores
|
|
332
|
+
"node_modules/**",
|
|
333
|
+
".git/**",
|
|
334
|
+
"dist/**",
|
|
335
|
+
"build/**",
|
|
336
|
+
"coverage/**",
|
|
337
|
+
"*.log",
|
|
338
|
+
".env*",
|
|
339
|
+
"__pycache__/**",
|
|
340
|
+
"*.pyc",
|
|
341
|
+
"target/**",
|
|
342
|
+
// Rust
|
|
343
|
+
"vendor/**",
|
|
344
|
+
// Go
|
|
345
|
+
".venv/**",
|
|
346
|
+
"venv/**"
|
|
347
|
+
];
|
|
348
|
+
const gitignorePath = path2.join(this.projectPath, ".gitignore");
|
|
349
|
+
const gitignore = await readTextFile(gitignorePath);
|
|
350
|
+
if (gitignore) {
|
|
351
|
+
const lines = gitignore.split("\n").map((line) => line.trim()).filter((line) => line && !line.startsWith("#"));
|
|
352
|
+
patterns.push(...lines);
|
|
353
|
+
}
|
|
354
|
+
patterns.push(...additionalPatterns);
|
|
355
|
+
return patterns;
|
|
356
|
+
}
|
|
357
|
+
shouldIgnore(relativePath, patterns) {
|
|
358
|
+
const normalizedPath = relativePath.replace(/\\/g, "/");
|
|
359
|
+
for (const pattern of patterns) {
|
|
360
|
+
if (this.matchPattern(normalizedPath, pattern)) {
|
|
361
|
+
return true;
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
return false;
|
|
365
|
+
}
|
|
366
|
+
matchPattern(filePath, pattern) {
|
|
367
|
+
const normalizedPattern = pattern.replace(/\\/g, "/");
|
|
368
|
+
if (normalizedPattern.startsWith("!")) {
|
|
369
|
+
return false;
|
|
370
|
+
}
|
|
371
|
+
let regex = normalizedPattern.replace(/\./g, "\\.").replace(/\*\*/g, "{{GLOBSTAR}}").replace(/\*/g, "[^/]*").replace(/{{GLOBSTAR}}/g, ".*").replace(/\?/g, ".");
|
|
372
|
+
if (!regex.startsWith("/") && !regex.startsWith(".*")) {
|
|
373
|
+
regex = "(^|/)" + regex;
|
|
374
|
+
}
|
|
375
|
+
if (regex.endsWith("/")) {
|
|
376
|
+
regex = regex + ".*";
|
|
377
|
+
}
|
|
378
|
+
try {
|
|
379
|
+
const re = new RegExp(regex);
|
|
380
|
+
return re.test(filePath);
|
|
381
|
+
} catch {
|
|
382
|
+
return filePath.includes(pattern.replace(/\*/g, ""));
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
categorizeFile(relativePath) {
|
|
386
|
+
const lowerPath = relativePath.toLowerCase();
|
|
387
|
+
const fileName = path2.basename(lowerPath);
|
|
388
|
+
if (/\.(test|spec)\.[^/]+$/.test(lowerPath) || /\/__tests__\//.test(lowerPath) || /\/test\//.test(lowerPath) || /\/tests\//.test(lowerPath)) {
|
|
389
|
+
return "test";
|
|
390
|
+
}
|
|
391
|
+
if (/^(index|main|app|server)\.[^/]+$/.test(fileName) || /\/src\/(index|main|app)\.[^/]+$/.test(lowerPath)) {
|
|
392
|
+
return "entry";
|
|
393
|
+
}
|
|
394
|
+
if (/\/(models?|entities|domain|types|schemas?)\//i.test(lowerPath) || /\.(model|entity|type)\.[^/]+$/.test(lowerPath)) {
|
|
395
|
+
return "model";
|
|
396
|
+
}
|
|
397
|
+
if (/\/(services?|controllers?|handlers?|api|routes?)\//i.test(lowerPath) || /\.(service|controller|handler)\.[^/]+$/.test(lowerPath)) {
|
|
398
|
+
return "service";
|
|
399
|
+
}
|
|
400
|
+
if (/\.(config|conf)\.[^/]+$/.test(lowerPath) || /\/config\//i.test(lowerPath) || fileName.startsWith(".")) {
|
|
401
|
+
return "config";
|
|
402
|
+
}
|
|
403
|
+
if (/\/(utils?|helpers?|lib|common|shared)\//i.test(lowerPath) || /\.(util|helper)\.[^/]+$/.test(lowerPath)) {
|
|
404
|
+
return "util";
|
|
405
|
+
}
|
|
406
|
+
return "other";
|
|
407
|
+
}
|
|
408
|
+
calculatePriority(relativePath, category, size) {
|
|
409
|
+
let score = 0;
|
|
410
|
+
const categoryScores = {
|
|
411
|
+
entry: 100,
|
|
412
|
+
model: 80,
|
|
413
|
+
service: 70,
|
|
414
|
+
util: 30,
|
|
415
|
+
config: 20,
|
|
416
|
+
other: 10,
|
|
417
|
+
test: -50
|
|
418
|
+
};
|
|
419
|
+
score += categoryScores[category];
|
|
420
|
+
const depth = relativePath.split("/").length;
|
|
421
|
+
score -= depth * 2;
|
|
422
|
+
if (size < 5e3) score += 10;
|
|
423
|
+
if (size > 5e4) score -= 20;
|
|
424
|
+
if (size > 1e5) score -= 30;
|
|
425
|
+
if (/^src\//.test(relativePath)) score += 15;
|
|
426
|
+
return score;
|
|
427
|
+
}
|
|
428
|
+
async walkDirectory(dir, callback) {
|
|
429
|
+
try {
|
|
430
|
+
const entries = await fs2.readdir(dir, { withFileTypes: true });
|
|
431
|
+
for (const entry of entries) {
|
|
432
|
+
const fullPath = path2.join(dir, entry.name);
|
|
433
|
+
if (entry.isDirectory()) {
|
|
434
|
+
if (entry.name === "node_modules" || entry.name === ".git" || entry.name === "dist" || entry.name === "build" || entry.name === "__pycache__" || entry.name === "target" || entry.name === "vendor" || entry.name === ".venv" || entry.name === "venv") {
|
|
435
|
+
continue;
|
|
436
|
+
}
|
|
437
|
+
await this.walkDirectory(fullPath, callback);
|
|
438
|
+
} else if (entry.isFile()) {
|
|
439
|
+
try {
|
|
440
|
+
const stats = await fs2.stat(fullPath);
|
|
441
|
+
await callback(fullPath, { size: stats.size });
|
|
442
|
+
} catch {
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
} catch {
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
};
|
|
450
|
+
|
|
451
|
+
// src/core/chunk-manager.ts
|
|
452
|
+
var DEFAULT_MAX_CHUNK_SIZE = 5e4;
|
|
453
|
+
function estimateTokens(text) {
|
|
454
|
+
return Math.ceil(text.length / 4);
|
|
455
|
+
}
|
|
456
|
+
var ChunkManager = class {
|
|
457
|
+
files;
|
|
458
|
+
maxChunkSize;
|
|
459
|
+
chunks = null;
|
|
460
|
+
constructor(files, options) {
|
|
461
|
+
this.files = files;
|
|
462
|
+
this.maxChunkSize = options?.maxChunkSize ?? DEFAULT_MAX_CHUNK_SIZE;
|
|
463
|
+
}
|
|
464
|
+
async prepareChunks() {
|
|
465
|
+
if (this.chunks) {
|
|
466
|
+
return this.chunks;
|
|
467
|
+
}
|
|
468
|
+
const chunks = [];
|
|
469
|
+
let currentChunk = [];
|
|
470
|
+
let currentContent = "";
|
|
471
|
+
let currentSize = 0;
|
|
472
|
+
for (const file of this.files) {
|
|
473
|
+
const content = await readTextFile(file.absolutePath);
|
|
474
|
+
if (!content) continue;
|
|
475
|
+
const formattedContent = this.formatFileContent(file.path, content);
|
|
476
|
+
const contentSize = formattedContent.length;
|
|
477
|
+
if (contentSize > this.maxChunkSize) {
|
|
478
|
+
if (currentChunk.length > 0) {
|
|
479
|
+
chunks.push(this.createChunk(currentChunk, currentContent, chunks.length));
|
|
480
|
+
currentChunk = [];
|
|
481
|
+
currentContent = "";
|
|
482
|
+
currentSize = 0;
|
|
483
|
+
}
|
|
484
|
+
const truncatedContent = this.truncateContent(formattedContent, this.maxChunkSize);
|
|
485
|
+
chunks.push(this.createChunk([file], truncatedContent, chunks.length));
|
|
486
|
+
continue;
|
|
487
|
+
}
|
|
488
|
+
if (currentSize + contentSize > this.maxChunkSize && currentChunk.length > 0) {
|
|
489
|
+
chunks.push(this.createChunk(currentChunk, currentContent, chunks.length));
|
|
490
|
+
currentChunk = [];
|
|
491
|
+
currentContent = "";
|
|
492
|
+
currentSize = 0;
|
|
493
|
+
}
|
|
494
|
+
currentChunk.push(file);
|
|
495
|
+
currentContent += formattedContent + "\n\n";
|
|
496
|
+
currentSize += contentSize;
|
|
497
|
+
}
|
|
498
|
+
if (currentChunk.length > 0) {
|
|
499
|
+
chunks.push(this.createChunk(currentChunk, currentContent, chunks.length));
|
|
500
|
+
}
|
|
501
|
+
for (const chunk of chunks) {
|
|
502
|
+
chunk.totalChunks = chunks.length;
|
|
503
|
+
}
|
|
504
|
+
this.chunks = chunks;
|
|
505
|
+
return chunks;
|
|
506
|
+
}
|
|
507
|
+
createChunk(files, content, index) {
|
|
508
|
+
return {
|
|
509
|
+
files,
|
|
510
|
+
content: content.trim(),
|
|
511
|
+
estimatedTokens: estimateTokens(content),
|
|
512
|
+
index,
|
|
513
|
+
totalChunks: 0
|
|
514
|
+
// Will be updated after all chunks are created
|
|
515
|
+
};
|
|
516
|
+
}
|
|
517
|
+
formatFileContent(filePath, content) {
|
|
518
|
+
return `===== FILE: ${filePath} =====
|
|
519
|
+
${content}
|
|
520
|
+
===== END: ${filePath} =====`;
|
|
521
|
+
}
|
|
522
|
+
truncateContent(content, maxSize) {
|
|
523
|
+
if (content.length <= maxSize) {
|
|
524
|
+
return content;
|
|
525
|
+
}
|
|
526
|
+
const truncateAt = maxSize - 100;
|
|
527
|
+
const lastNewline = content.lastIndexOf("\n", truncateAt);
|
|
528
|
+
const cutPoint = lastNewline > truncateAt * 0.5 ? lastNewline : truncateAt;
|
|
529
|
+
return content.slice(0, cutPoint) + "\n\n[... TRUNCATED - file too large ...]";
|
|
530
|
+
}
|
|
531
|
+
getTotalChunks() {
|
|
532
|
+
if (!this.chunks) {
|
|
533
|
+
throw new Error("Chunks not prepared. Call prepareChunks() first.");
|
|
534
|
+
}
|
|
535
|
+
return this.chunks.length;
|
|
536
|
+
}
|
|
537
|
+
getChunkSummary() {
|
|
538
|
+
if (!this.chunks) {
|
|
539
|
+
return "Chunks not yet prepared";
|
|
540
|
+
}
|
|
541
|
+
const totalFiles = this.chunks.reduce((sum, c) => sum + c.files.length, 0);
|
|
542
|
+
const totalTokens = this.chunks.reduce((sum, c) => sum + c.estimatedTokens, 0);
|
|
543
|
+
return `${totalFiles} files in ${this.chunks.length} chunks (~${totalTokens} tokens)`;
|
|
544
|
+
}
|
|
545
|
+
// Async generator for processing chunks one at a time
|
|
546
|
+
async *iterateChunks() {
|
|
547
|
+
const chunks = await this.prepareChunks();
|
|
548
|
+
for (const chunk of chunks) {
|
|
549
|
+
yield chunk;
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
};
|
|
553
|
+
|
|
554
|
+
// src/core/auto-discover.ts
|
|
555
|
+
var DISCOVERY_PROMPT = `You are a technical knowledge extractor analyzing a codebase. Extract information for a project knowledge base.
|
|
556
|
+
|
|
557
|
+
PROJECT CONTEXT:
|
|
558
|
+
- Language(s): {languages}
|
|
559
|
+
- Project Type: {projectType}
|
|
560
|
+
|
|
561
|
+
ANALYZE THESE SOURCE FILES:
|
|
562
|
+
|
|
563
|
+
{fileContents}
|
|
564
|
+
|
|
565
|
+
Extract and format the following:
|
|
566
|
+
|
|
567
|
+
## Entities
|
|
568
|
+
For each domain entity (data model, type, class, interface):
|
|
569
|
+
- **Name**: Entity name
|
|
570
|
+
- **Location**: File path
|
|
571
|
+
- **Attributes**: Key fields/properties
|
|
572
|
+
- **Relations**: Related entities
|
|
573
|
+
|
|
574
|
+
## Architecture
|
|
575
|
+
For each architectural pattern or design decision:
|
|
576
|
+
- **Pattern**: Name of pattern (e.g., MVC, Repository, Factory, Singleton, etc.)
|
|
577
|
+
- **Description**: Brief explanation of how it's used
|
|
578
|
+
- **Affected Files**: Relevant file paths
|
|
579
|
+
|
|
580
|
+
## Services
|
|
581
|
+
For each service or component:
|
|
582
|
+
- **Name**: Service name
|
|
583
|
+
- **Location**: File path
|
|
584
|
+
- **Purpose**: Brief description
|
|
585
|
+
- **Methods**: Key methods/functions
|
|
586
|
+
|
|
587
|
+
## Knowledge
|
|
588
|
+
For each convention, rule, or important context discovered:
|
|
589
|
+
- **Topic**: What it's about
|
|
590
|
+
- **Details**: The actual information
|
|
591
|
+
|
|
592
|
+
Guidelines:
|
|
593
|
+
- Only include sections that have content
|
|
594
|
+
- Be concise but complete
|
|
595
|
+
- Use exact file paths as shown
|
|
596
|
+
- Focus on domain logic, not framework boilerplate
|
|
597
|
+
- Identify patterns from code structure, not just naming
|
|
598
|
+
`;
|
|
599
|
+
var AutoDiscover = class {
|
|
600
|
+
projectPath;
|
|
601
|
+
verbose;
|
|
602
|
+
constructor(projectPath, verbose = false) {
|
|
603
|
+
this.projectPath = projectPath;
|
|
604
|
+
this.verbose = verbose;
|
|
605
|
+
}
|
|
606
|
+
async discover(options) {
|
|
607
|
+
const startTime = Date.now();
|
|
608
|
+
const config = await loadConfig(this.projectPath);
|
|
609
|
+
const maxFiles = options?.maxFiles ?? config.discover.maxFiles;
|
|
610
|
+
const maxChunkSize = options?.maxChunkSize ?? config.discover.maxChunkSize;
|
|
611
|
+
this.log("Discovering codebase...\n");
|
|
612
|
+
const collector = new FileCollector(this.projectPath);
|
|
613
|
+
const collection = await collector.collect({ maxFiles });
|
|
614
|
+
this.log(`Detected: ${collection.languages.join(", ")} (${collection.projectType} project)`);
|
|
615
|
+
this.log(`Collected: ${collection.totalFilesScanned} files \u2192 ${collection.files.length} prioritized`);
|
|
616
|
+
if (collection.files.length === 0) {
|
|
617
|
+
this.log("No source files found to analyze.");
|
|
618
|
+
return this.createEmptyResult(startTime);
|
|
619
|
+
}
|
|
620
|
+
const claudeAvailable = await isClaudeAvailable();
|
|
621
|
+
if (!claudeAvailable) {
|
|
622
|
+
this.log("Claude CLI not available. Using fallback analysis...");
|
|
623
|
+
return this.fallbackDiscovery(collection, startTime);
|
|
624
|
+
}
|
|
625
|
+
const chunkManager = new ChunkManager(collection.files, { maxChunkSize });
|
|
626
|
+
const chunks = await chunkManager.prepareChunks();
|
|
627
|
+
this.log(`Grouped into ${chunks.length} chunks
|
|
628
|
+
`);
|
|
629
|
+
this.log("Analyzing with Claude...");
|
|
630
|
+
const partialSummaries = [];
|
|
631
|
+
let chunksProcessed = 0;
|
|
632
|
+
for (const chunk of chunks) {
|
|
633
|
+
this.log(` [${chunk.index + 1}/${chunk.totalChunks}] Analyzing ${chunk.files.length} files...`);
|
|
634
|
+
try {
|
|
635
|
+
const summary = await this.analyzeChunk(chunk, collection);
|
|
636
|
+
partialSummaries.push(summary);
|
|
637
|
+
chunksProcessed++;
|
|
638
|
+
if (chunk.index < chunks.length - 1) {
|
|
639
|
+
await this.delay(1500);
|
|
640
|
+
}
|
|
641
|
+
} catch (error) {
|
|
642
|
+
this.log(` Warning: Chunk ${chunk.index + 1} analysis failed`);
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
if (partialSummaries.length === 0) {
|
|
646
|
+
this.log("\nNo successful analyses. Using fallback...");
|
|
647
|
+
return this.fallbackDiscovery(collection, startTime);
|
|
648
|
+
}
|
|
649
|
+
const mergedSummary = this.mergeSummaries(partialSummaries);
|
|
650
|
+
this.log("\nIntegrating into vault...");
|
|
651
|
+
let integrated = false;
|
|
652
|
+
try {
|
|
653
|
+
const integrator = new VaultIntegrator(this.projectPath);
|
|
654
|
+
await integrator.integrate(mergedSummary);
|
|
655
|
+
integrated = true;
|
|
656
|
+
this.log(` ${mergedSummary.entities.length} entities`);
|
|
657
|
+
this.log(` ${mergedSummary.architecture.length} patterns`);
|
|
658
|
+
this.log(` ${mergedSummary.services.length} services`);
|
|
659
|
+
this.log(` ${mergedSummary.knowledge.length} knowledge items`);
|
|
660
|
+
} catch (error) {
|
|
661
|
+
this.log(` Warning: Vault integration failed: ${error}`);
|
|
662
|
+
}
|
|
663
|
+
const duration = Date.now() - startTime;
|
|
664
|
+
this.log(`
|
|
665
|
+
Discovery complete (${(duration / 1e3).toFixed(1)}s)`);
|
|
666
|
+
return {
|
|
667
|
+
summary: mergedSummary,
|
|
668
|
+
filesAnalyzed: collection.files.length,
|
|
669
|
+
chunksProcessed,
|
|
670
|
+
duration,
|
|
671
|
+
integrated
|
|
672
|
+
};
|
|
673
|
+
}
|
|
674
|
+
async analyzeChunk(chunk, collection) {
|
|
675
|
+
const prompt = DISCOVERY_PROMPT.replace("{languages}", collection.languages.join(", ")).replace("{projectType}", collection.projectType).replace("{fileContents}", chunk.content);
|
|
676
|
+
const response = await spawnClaudeAgent(prompt);
|
|
677
|
+
return this.parseResponse(response, chunk);
|
|
678
|
+
}
|
|
679
|
+
parseResponse(response, _chunk) {
|
|
680
|
+
const summary = {
|
|
681
|
+
sessionId: `discover-${Date.now()}`,
|
|
682
|
+
content: response,
|
|
683
|
+
entities: [],
|
|
684
|
+
architecture: [],
|
|
685
|
+
services: [],
|
|
686
|
+
knowledge: []
|
|
687
|
+
};
|
|
688
|
+
const entitiesMatch = response.match(/## Entities\n([\s\S]*?)(?=\n## |$)/);
|
|
689
|
+
if (entitiesMatch) {
|
|
690
|
+
summary.entities = this.parseEntities(entitiesMatch[1]);
|
|
691
|
+
}
|
|
692
|
+
const archMatch = response.match(/## Architecture\n([\s\S]*?)(?=\n## |$)/);
|
|
693
|
+
if (archMatch) {
|
|
694
|
+
summary.architecture = this.parseArchitecture(archMatch[1]);
|
|
695
|
+
}
|
|
696
|
+
const servicesMatch = response.match(/## Services\n([\s\S]*?)(?=\n## |$)/);
|
|
697
|
+
if (servicesMatch) {
|
|
698
|
+
summary.services = this.parseServices(servicesMatch[1]);
|
|
699
|
+
}
|
|
700
|
+
const knowledgeMatch = response.match(/## Knowledge\n([\s\S]*?)(?=\n## |$)/);
|
|
701
|
+
if (knowledgeMatch) {
|
|
702
|
+
summary.knowledge = this.parseKnowledge(knowledgeMatch[1]);
|
|
703
|
+
}
|
|
704
|
+
return summary;
|
|
705
|
+
}
|
|
706
|
+
parseEntities(section) {
|
|
707
|
+
const entities = [];
|
|
708
|
+
const blocks = section.split(/\n(?=- \*\*Name\*\*:)/);
|
|
709
|
+
for (const block of blocks) {
|
|
710
|
+
if (!block.trim()) continue;
|
|
711
|
+
const nameMatch = block.match(/\*\*Name\*\*:\s*(.+)/);
|
|
712
|
+
const locationMatch = block.match(/\*\*Location\*\*:\s*(.+)/);
|
|
713
|
+
const attributesMatch = block.match(/\*\*Attributes\*\*:\s*(.+)/);
|
|
714
|
+
const relationsMatch = block.match(/\*\*Relations\*\*:\s*(.+)/);
|
|
715
|
+
if (nameMatch) {
|
|
716
|
+
entities.push({
|
|
717
|
+
name: nameMatch[1].trim(),
|
|
718
|
+
location: locationMatch?.[1].trim(),
|
|
719
|
+
attributes: attributesMatch?.[1].split(",").map((a) => a.trim()) || [],
|
|
720
|
+
relations: relationsMatch?.[1].split(",").map((r) => r.trim()) || []
|
|
721
|
+
});
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
return entities;
|
|
725
|
+
}
|
|
726
|
+
parseArchitecture(section) {
|
|
727
|
+
const items = [];
|
|
728
|
+
const blocks = section.split(/\n(?=- \*\*Pattern\*\*:)/);
|
|
729
|
+
for (const block of blocks) {
|
|
730
|
+
if (!block.trim()) continue;
|
|
731
|
+
const patternMatch = block.match(/\*\*Pattern\*\*:\s*(.+)/);
|
|
732
|
+
const descMatch = block.match(/\*\*Description\*\*:\s*(.+)/);
|
|
733
|
+
const filesMatch = block.match(/\*\*Affected Files\*\*:\s*(.+)/);
|
|
734
|
+
if (patternMatch) {
|
|
735
|
+
items.push({
|
|
736
|
+
pattern: patternMatch[1].trim(),
|
|
737
|
+
description: descMatch?.[1].trim() || "",
|
|
738
|
+
affectedFiles: filesMatch?.[1].split(",").map((f) => f.trim()) || []
|
|
739
|
+
});
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
return items;
|
|
743
|
+
}
|
|
744
|
+
parseServices(section) {
|
|
745
|
+
const items = [];
|
|
746
|
+
const blocks = section.split(/\n(?=- \*\*Name\*\*:)/);
|
|
747
|
+
for (const block of blocks) {
|
|
748
|
+
if (!block.trim()) continue;
|
|
749
|
+
const nameMatch = block.match(/\*\*Name\*\*:\s*(.+)/);
|
|
750
|
+
const locationMatch = block.match(/\*\*Location\*\*:\s*(.+)/);
|
|
751
|
+
const purposeMatch = block.match(/\*\*Purpose\*\*:\s*(.+)/);
|
|
752
|
+
const methodsMatch = block.match(/\*\*Methods\*\*:\s*(.+)/);
|
|
753
|
+
if (nameMatch) {
|
|
754
|
+
items.push({
|
|
755
|
+
name: nameMatch[1].trim(),
|
|
756
|
+
location: locationMatch?.[1].trim(),
|
|
757
|
+
purpose: purposeMatch?.[1].trim() || "",
|
|
758
|
+
methods: methodsMatch?.[1].split(",").map((m) => m.trim()) || []
|
|
759
|
+
});
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
return items;
|
|
763
|
+
}
|
|
764
|
+
parseKnowledge(section) {
|
|
765
|
+
const items = [];
|
|
766
|
+
const blocks = section.split(/\n(?=- \*\*Topic\*\*:)/);
|
|
767
|
+
for (const block of blocks) {
|
|
768
|
+
if (!block.trim()) continue;
|
|
769
|
+
const topicMatch = block.match(/\*\*Topic\*\*:\s*(.+)/);
|
|
770
|
+
const detailsMatch = block.match(/\*\*Details\*\*:\s*(.+)/);
|
|
771
|
+
if (topicMatch) {
|
|
772
|
+
items.push({
|
|
773
|
+
topic: topicMatch[1].trim(),
|
|
774
|
+
details: detailsMatch?.[1].trim() || ""
|
|
775
|
+
});
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
return items;
|
|
779
|
+
}
|
|
780
|
+
mergeSummaries(summaries) {
|
|
781
|
+
const merged = {
|
|
782
|
+
sessionId: `discover-${Date.now()}`,
|
|
783
|
+
content: summaries.map((s) => s.content).join("\n\n---\n\n"),
|
|
784
|
+
entities: [],
|
|
785
|
+
architecture: [],
|
|
786
|
+
services: [],
|
|
787
|
+
knowledge: []
|
|
788
|
+
};
|
|
789
|
+
const entityMap = /* @__PURE__ */ new Map();
|
|
790
|
+
const archMap = /* @__PURE__ */ new Map();
|
|
791
|
+
const serviceMap = /* @__PURE__ */ new Map();
|
|
792
|
+
const knowledgeMap = /* @__PURE__ */ new Map();
|
|
793
|
+
for (const summary of summaries) {
|
|
794
|
+
for (const entity of summary.entities) {
|
|
795
|
+
const key = entity.name.toLowerCase();
|
|
796
|
+
if (!entityMap.has(key)) {
|
|
797
|
+
entityMap.set(key, entity);
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
for (const arch of summary.architecture) {
|
|
801
|
+
const key = arch.pattern.toLowerCase();
|
|
802
|
+
if (!archMap.has(key)) {
|
|
803
|
+
archMap.set(key, arch);
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
for (const service of summary.services) {
|
|
807
|
+
const key = service.name.toLowerCase();
|
|
808
|
+
if (!serviceMap.has(key)) {
|
|
809
|
+
serviceMap.set(key, service);
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
for (const knowledge of summary.knowledge) {
|
|
813
|
+
const key = knowledge.topic.toLowerCase();
|
|
814
|
+
if (!knowledgeMap.has(key)) {
|
|
815
|
+
knowledgeMap.set(key, knowledge);
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
merged.entities = Array.from(entityMap.values());
|
|
820
|
+
merged.architecture = Array.from(archMap.values());
|
|
821
|
+
merged.services = Array.from(serviceMap.values());
|
|
822
|
+
merged.knowledge = Array.from(knowledgeMap.values());
|
|
823
|
+
return merged;
|
|
824
|
+
}
|
|
825
|
+
async fallbackDiscovery(collection, startTime) {
|
|
826
|
+
const summary = {
|
|
827
|
+
sessionId: `discover-fallback-${Date.now()}`,
|
|
828
|
+
content: "Fallback discovery - Claude unavailable",
|
|
829
|
+
entities: [],
|
|
830
|
+
architecture: [],
|
|
831
|
+
services: [],
|
|
832
|
+
knowledge: [
|
|
833
|
+
{
|
|
834
|
+
topic: "Project Languages",
|
|
835
|
+
details: collection.languages.join(", ")
|
|
836
|
+
},
|
|
837
|
+
{
|
|
838
|
+
topic: "Project Type",
|
|
839
|
+
details: collection.projectType
|
|
840
|
+
},
|
|
841
|
+
{
|
|
842
|
+
topic: "Source Files Discovered",
|
|
843
|
+
details: `${collection.files.length} files categorized by type`
|
|
844
|
+
}
|
|
845
|
+
]
|
|
846
|
+
};
|
|
847
|
+
const categories = /* @__PURE__ */ new Map();
|
|
848
|
+
for (const file of collection.files) {
|
|
849
|
+
if (!categories.has(file.category)) {
|
|
850
|
+
categories.set(file.category, []);
|
|
851
|
+
}
|
|
852
|
+
categories.get(file.category).push(file.path);
|
|
853
|
+
}
|
|
854
|
+
for (const [category, files] of categories) {
|
|
855
|
+
if (category !== "other" && category !== "test") {
|
|
856
|
+
summary.knowledge.push({
|
|
857
|
+
topic: `${category.charAt(0).toUpperCase() + category.slice(1)} Files`,
|
|
858
|
+
details: files.slice(0, 10).join(", ") + (files.length > 10 ? ` (+${files.length - 10} more)` : "")
|
|
859
|
+
});
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
let integrated = false;
|
|
863
|
+
try {
|
|
864
|
+
const integrator = new VaultIntegrator(this.projectPath);
|
|
865
|
+
await integrator.integrate(summary);
|
|
866
|
+
integrated = true;
|
|
867
|
+
this.log("\nFallback discovery integrated into vault");
|
|
868
|
+
} catch {
|
|
869
|
+
this.log("\nFallback discovery completed (vault integration failed)");
|
|
870
|
+
}
|
|
871
|
+
return {
|
|
872
|
+
summary,
|
|
873
|
+
filesAnalyzed: collection.files.length,
|
|
874
|
+
chunksProcessed: 0,
|
|
875
|
+
duration: Date.now() - startTime,
|
|
876
|
+
integrated
|
|
877
|
+
};
|
|
878
|
+
}
|
|
879
|
+
createEmptyResult(startTime) {
|
|
880
|
+
return {
|
|
881
|
+
summary: {
|
|
882
|
+
sessionId: `discover-empty-${Date.now()}`,
|
|
883
|
+
content: "",
|
|
884
|
+
entities: [],
|
|
885
|
+
architecture: [],
|
|
886
|
+
services: [],
|
|
887
|
+
knowledge: []
|
|
888
|
+
},
|
|
889
|
+
filesAnalyzed: 0,
|
|
890
|
+
chunksProcessed: 0,
|
|
891
|
+
duration: Date.now() - startTime,
|
|
892
|
+
integrated: false
|
|
893
|
+
};
|
|
894
|
+
}
|
|
895
|
+
log(message) {
|
|
896
|
+
if (this.verbose) {
|
|
897
|
+
console.log(message);
|
|
898
|
+
}
|
|
899
|
+
}
|
|
900
|
+
delay(ms) {
|
|
901
|
+
return new Promise((resolve3) => setTimeout(resolve3, ms));
|
|
902
|
+
}
|
|
903
|
+
};
|
|
904
|
+
|
|
905
|
+
// src/cli/discover.ts
|
|
906
|
+
import * as path3 from "path";
|
|
907
|
+
async function discover(options = {}) {
|
|
908
|
+
const targetPath = path3.resolve(options.targetPath || process.cwd());
|
|
909
|
+
const verbose = options.verbose ?? true;
|
|
910
|
+
const exists = await fileExists(targetPath);
|
|
911
|
+
if (!exists) {
|
|
912
|
+
console.error(`Error: Target path does not exist: ${targetPath}`);
|
|
913
|
+
process.exit(1);
|
|
914
|
+
}
|
|
915
|
+
const kbPath = path3.join(targetPath, "cc-knowledge-base");
|
|
916
|
+
const kbExists = await fileExists(kbPath);
|
|
917
|
+
if (!kbExists) {
|
|
918
|
+
console.error("Error: CCKB is not installed in this project.");
|
|
919
|
+
console.error("Run 'cckb init' first to install CCKB.");
|
|
920
|
+
process.exit(1);
|
|
921
|
+
}
|
|
922
|
+
try {
|
|
923
|
+
const autoDiscover = new AutoDiscover(targetPath, verbose);
|
|
924
|
+
const result = await autoDiscover.discover();
|
|
925
|
+
if (!result.integrated) {
|
|
926
|
+
console.error("\nWarning: Vault integration failed. Check the logs above.");
|
|
927
|
+
process.exit(1);
|
|
928
|
+
}
|
|
929
|
+
console.log(`
|
|
930
|
+
Vault populated at: ${kbPath}/vault/`);
|
|
931
|
+
} catch (error) {
|
|
932
|
+
console.error("Discovery failed:", error);
|
|
933
|
+
process.exit(1);
|
|
934
|
+
}
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
export {
|
|
938
|
+
install,
|
|
939
|
+
FileCollector,
|
|
940
|
+
ChunkManager,
|
|
941
|
+
AutoDiscover,
|
|
942
|
+
discover
|
|
943
|
+
};
|
|
944
|
+
//# sourceMappingURL=chunk-P7O44DI3.js.map
|