kernelbot 1.0.30 → 1.0.32
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/.env.example +0 -0
- package/README.md +0 -0
- package/bin/kernel.js +56 -2
- package/config.example.yaml +31 -0
- package/package.json +1 -1
- package/src/agent.js +150 -20
- package/src/automation/automation-manager.js +0 -0
- package/src/automation/automation.js +0 -0
- package/src/automation/index.js +0 -0
- package/src/automation/scheduler.js +0 -0
- package/src/bot.js +303 -4
- package/src/claude-auth.js +0 -0
- package/src/coder.js +0 -0
- package/src/conversation.js +0 -0
- package/src/intents/detector.js +0 -0
- package/src/intents/index.js +0 -0
- package/src/intents/planner.js +0 -0
- package/src/life/codebase.js +388 -0
- package/src/life/engine.js +1317 -0
- package/src/life/evolution.js +244 -0
- package/src/life/improvements.js +81 -0
- package/src/life/journal.js +109 -0
- package/src/life/memory.js +283 -0
- package/src/life/share-queue.js +136 -0
- package/src/persona.js +0 -0
- package/src/prompts/orchestrator.js +19 -1
- package/src/prompts/persona.md +0 -0
- package/src/prompts/system.js +0 -0
- package/src/prompts/workers.js +10 -9
- package/src/providers/anthropic.js +0 -0
- package/src/providers/base.js +0 -0
- package/src/providers/index.js +0 -0
- package/src/providers/models.js +8 -1
- package/src/providers/openai-compat.js +0 -0
- package/src/security/audit.js +0 -0
- package/src/security/auth.js +0 -0
- package/src/security/confirm.js +0 -0
- package/src/self.js +0 -0
- package/src/services/stt.js +0 -0
- package/src/services/tts.js +0 -0
- package/src/skills/catalog.js +0 -0
- package/src/skills/custom.js +0 -0
- package/src/swarm/job-manager.js +0 -0
- package/src/swarm/job.js +0 -0
- package/src/swarm/worker-registry.js +0 -0
- package/src/tools/browser.js +0 -0
- package/src/tools/categories.js +0 -0
- package/src/tools/coding.js +1 -1
- package/src/tools/docker.js +0 -0
- package/src/tools/git.js +0 -0
- package/src/tools/github.js +0 -0
- package/src/tools/index.js +0 -0
- package/src/tools/jira.js +0 -0
- package/src/tools/monitor.js +0 -0
- package/src/tools/network.js +0 -0
- package/src/tools/orchestrator-tools.js +18 -3
- package/src/tools/os.js +0 -0
- package/src/tools/persona.js +0 -0
- package/src/tools/process.js +0 -0
- package/src/utils/config.js +0 -0
- package/src/utils/display.js +0 -0
- package/src/utils/logger.js +0 -0
- package/src/worker.js +10 -8
|
@@ -0,0 +1,388 @@
|
|
|
1
|
+
import { readFileSync, writeFileSync, mkdirSync, existsSync } from 'fs';
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
import { homedir } from 'os';
|
|
4
|
+
import { execSync } from 'child_process';
|
|
5
|
+
import { createHash } from 'crypto';
|
|
6
|
+
import { getLogger } from '../utils/logger.js';
|
|
7
|
+
|
|
8
|
+
const LIFE_DIR = join(homedir(), '.kernelbot', 'life');
|
|
9
|
+
const CODEBASE_DIR = join(LIFE_DIR, 'codebase');
|
|
10
|
+
const SUMMARIES_FILE = join(CODEBASE_DIR, 'file-summaries.json');
|
|
11
|
+
const ARCHITECTURE_FILE = join(CODEBASE_DIR, 'architecture.md');
|
|
12
|
+
|
|
13
|
+
// Files to always skip during scanning
|
|
14
|
+
const SKIP_PATTERNS = [
|
|
15
|
+
'node_modules', '.git', 'package-lock.json', 'yarn.lock',
|
|
16
|
+
'.env', '.DS_Store', 'dist/', 'build/', 'coverage/',
|
|
17
|
+
];
|
|
18
|
+
|
|
19
|
+
export class CodebaseKnowledge {
|
|
20
|
+
constructor({ config } = {}) {
|
|
21
|
+
this.config = config || {};
|
|
22
|
+
this._projectRoot = null;
|
|
23
|
+
this._summaries = {};
|
|
24
|
+
this._agent = null;
|
|
25
|
+
|
|
26
|
+
mkdirSync(CODEBASE_DIR, { recursive: true });
|
|
27
|
+
this._summaries = this._loadSummaries();
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** Set the agent reference (called after agent is created). */
|
|
31
|
+
setAgent(agent) {
|
|
32
|
+
this._agent = agent;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** Set/detect project root. */
|
|
36
|
+
setProjectRoot(root) {
|
|
37
|
+
this._projectRoot = root;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
getProjectRoot() {
|
|
41
|
+
if (this._projectRoot) return this._projectRoot;
|
|
42
|
+
// Try to detect from git
|
|
43
|
+
try {
|
|
44
|
+
this._projectRoot = execSync('git rev-parse --show-toplevel', { encoding: 'utf-8' }).trim();
|
|
45
|
+
} catch {
|
|
46
|
+
this._projectRoot = process.cwd();
|
|
47
|
+
}
|
|
48
|
+
return this._projectRoot;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// ── Persistence ───────────────────────────────────────────────
|
|
52
|
+
|
|
53
|
+
_loadSummaries() {
|
|
54
|
+
if (existsSync(SUMMARIES_FILE)) {
|
|
55
|
+
try {
|
|
56
|
+
return JSON.parse(readFileSync(SUMMARIES_FILE, 'utf-8'));
|
|
57
|
+
} catch {
|
|
58
|
+
return {};
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return {};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
_saveSummaries() {
|
|
65
|
+
writeFileSync(SUMMARIES_FILE, JSON.stringify(this._summaries, null, 2), 'utf-8');
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
_saveArchitecture(content) {
|
|
69
|
+
writeFileSync(ARCHITECTURE_FILE, content, 'utf-8');
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// ── File Hashing ──────────────────────────────────────────────
|
|
73
|
+
|
|
74
|
+
_hashFile(filePath) {
|
|
75
|
+
try {
|
|
76
|
+
const content = readFileSync(filePath, 'utf-8');
|
|
77
|
+
return createHash('md5').update(content).digest('hex').slice(0, 12);
|
|
78
|
+
} catch {
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
_lineCount(filePath) {
|
|
84
|
+
try {
|
|
85
|
+
const content = readFileSync(filePath, 'utf-8');
|
|
86
|
+
return content.split('\n').length;
|
|
87
|
+
} catch {
|
|
88
|
+
return 0;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// ── Scanning ──────────────────────────────────────────────────
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Scan a single file using the LLM to generate a summary.
|
|
96
|
+
* Requires this._agent to be set.
|
|
97
|
+
*/
|
|
98
|
+
async scanFile(filePath) {
|
|
99
|
+
const logger = getLogger();
|
|
100
|
+
const root = this.getProjectRoot();
|
|
101
|
+
const fullPath = filePath.startsWith('/') ? filePath : join(root, filePath);
|
|
102
|
+
const relativePath = filePath.startsWith('/') ? filePath.replace(root + '/', '') : filePath;
|
|
103
|
+
|
|
104
|
+
// Check if file should be skipped
|
|
105
|
+
if (SKIP_PATTERNS.some(p => relativePath.includes(p))) return null;
|
|
106
|
+
|
|
107
|
+
const hash = this._hashFile(fullPath);
|
|
108
|
+
if (!hash) return null;
|
|
109
|
+
|
|
110
|
+
// Skip if already scanned and unchanged
|
|
111
|
+
const existing = this._summaries[relativePath];
|
|
112
|
+
if (existing && existing.lastHash === hash) return existing;
|
|
113
|
+
|
|
114
|
+
// Read file content
|
|
115
|
+
let content;
|
|
116
|
+
try {
|
|
117
|
+
content = readFileSync(fullPath, 'utf-8');
|
|
118
|
+
} catch {
|
|
119
|
+
return null;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Truncate very large files
|
|
123
|
+
const maxChars = 8000;
|
|
124
|
+
const truncated = content.length > maxChars
|
|
125
|
+
? content.slice(0, maxChars) + '\n... (truncated)'
|
|
126
|
+
: content;
|
|
127
|
+
|
|
128
|
+
// Use LLM to summarize if agent is available
|
|
129
|
+
let summary;
|
|
130
|
+
if (this._agent) {
|
|
131
|
+
try {
|
|
132
|
+
const prompt = `Analyze this source file and respond with ONLY a JSON object (no markdown, no code blocks):
|
|
133
|
+
{
|
|
134
|
+
"summary": "one-paragraph description of what this file does",
|
|
135
|
+
"exports": ["list", "of", "exported", "names"],
|
|
136
|
+
"dependencies": ["list", "of", "local", "imports"]
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
File: ${relativePath}
|
|
140
|
+
\`\`\`
|
|
141
|
+
${truncated}
|
|
142
|
+
\`\`\``;
|
|
143
|
+
|
|
144
|
+
const response = await this._agent.orchestratorProvider.chat({
|
|
145
|
+
system: 'You are a code analysis assistant. Respond with only valid JSON, no markdown formatting.',
|
|
146
|
+
messages: [{ role: 'user', content: prompt }],
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
const text = (response.text || '').trim();
|
|
150
|
+
// Try to parse JSON from the response
|
|
151
|
+
const jsonMatch = text.match(/\{[\s\S]*\}/);
|
|
152
|
+
if (jsonMatch) {
|
|
153
|
+
const parsed = JSON.parse(jsonMatch[0]);
|
|
154
|
+
summary = {
|
|
155
|
+
summary: parsed.summary || 'No summary generated',
|
|
156
|
+
exports: parsed.exports || [],
|
|
157
|
+
dependencies: parsed.dependencies || [],
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
} catch (err) {
|
|
161
|
+
logger.debug(`[Codebase] LLM scan failed for ${relativePath}: ${err.message}`);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Fallback: basic static analysis
|
|
166
|
+
if (!summary) {
|
|
167
|
+
summary = this._staticAnalysis(content, relativePath);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const entry = {
|
|
171
|
+
...summary,
|
|
172
|
+
lineCount: this._lineCount(fullPath),
|
|
173
|
+
lastHash: hash,
|
|
174
|
+
lastScanned: Date.now(),
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
this._summaries[relativePath] = entry;
|
|
178
|
+
this._saveSummaries();
|
|
179
|
+
logger.debug(`[Codebase] Scanned: ${relativePath}`);
|
|
180
|
+
return entry;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Scan only files that have changed since last scan (git-based).
|
|
185
|
+
*/
|
|
186
|
+
async scanChanged() {
|
|
187
|
+
const logger = getLogger();
|
|
188
|
+
const root = this.getProjectRoot();
|
|
189
|
+
|
|
190
|
+
let changedFiles = [];
|
|
191
|
+
try {
|
|
192
|
+
// Get all tracked files that differ from what we've scanned
|
|
193
|
+
const allFiles = execSync('git ls-files --full-name', {
|
|
194
|
+
cwd: root,
|
|
195
|
+
encoding: 'utf-8',
|
|
196
|
+
}).trim().split('\n').filter(Boolean);
|
|
197
|
+
|
|
198
|
+
// Filter to source files
|
|
199
|
+
changedFiles = allFiles.filter(f =>
|
|
200
|
+
(f.endsWith('.js') || f.endsWith('.mjs') || f.endsWith('.json') || f.endsWith('.yaml') || f.endsWith('.yml') || f.endsWith('.md')) &&
|
|
201
|
+
!SKIP_PATTERNS.some(p => f.includes(p))
|
|
202
|
+
);
|
|
203
|
+
|
|
204
|
+
// Only scan files whose hash has changed
|
|
205
|
+
changedFiles = changedFiles.filter(f => {
|
|
206
|
+
const fullPath = join(root, f);
|
|
207
|
+
const hash = this._hashFile(fullPath);
|
|
208
|
+
const existing = this._summaries[f];
|
|
209
|
+
return !existing || existing.lastHash !== hash;
|
|
210
|
+
});
|
|
211
|
+
} catch (err) {
|
|
212
|
+
logger.warn(`[Codebase] Git scan failed: ${err.message}`);
|
|
213
|
+
return 0;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
logger.info(`[Codebase] Scanning ${changedFiles.length} changed files...`);
|
|
217
|
+
let scanned = 0;
|
|
218
|
+
|
|
219
|
+
for (const file of changedFiles) {
|
|
220
|
+
try {
|
|
221
|
+
await this.scanFile(file);
|
|
222
|
+
scanned++;
|
|
223
|
+
} catch (err) {
|
|
224
|
+
logger.debug(`[Codebase] Failed to scan ${file}: ${err.message}`);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
logger.info(`[Codebase] Scan complete: ${scanned} files updated`);
|
|
229
|
+
return scanned;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Full scan of all source files. Heavy operation — use sparingly.
|
|
234
|
+
*/
|
|
235
|
+
async scanAll() {
|
|
236
|
+
const logger = getLogger();
|
|
237
|
+
const root = this.getProjectRoot();
|
|
238
|
+
|
|
239
|
+
let allFiles = [];
|
|
240
|
+
try {
|
|
241
|
+
allFiles = execSync('git ls-files --full-name', {
|
|
242
|
+
cwd: root,
|
|
243
|
+
encoding: 'utf-8',
|
|
244
|
+
}).trim().split('\n').filter(Boolean);
|
|
245
|
+
|
|
246
|
+
allFiles = allFiles.filter(f =>
|
|
247
|
+
(f.endsWith('.js') || f.endsWith('.mjs') || f.endsWith('.json') || f.endsWith('.yaml') || f.endsWith('.yml') || f.endsWith('.md')) &&
|
|
248
|
+
!SKIP_PATTERNS.some(p => f.includes(p))
|
|
249
|
+
);
|
|
250
|
+
} catch (err) {
|
|
251
|
+
logger.warn(`[Codebase] Git ls-files failed: ${err.message}`);
|
|
252
|
+
return 0;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
logger.info(`[Codebase] Full scan: ${allFiles.length} files...`);
|
|
256
|
+
let scanned = 0;
|
|
257
|
+
|
|
258
|
+
for (const file of allFiles) {
|
|
259
|
+
try {
|
|
260
|
+
await this.scanFile(file);
|
|
261
|
+
scanned++;
|
|
262
|
+
} catch (err) {
|
|
263
|
+
logger.debug(`[Codebase] Failed to scan ${file}: ${err.message}`);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
logger.info(`[Codebase] Full scan complete: ${scanned} files`);
|
|
268
|
+
return scanned;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// ── Queries ───────────────────────────────────────────────────
|
|
272
|
+
|
|
273
|
+
getFileSummary(path) {
|
|
274
|
+
return this._summaries[path] || null;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
getAllSummaries() {
|
|
278
|
+
return { ...this._summaries };
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
getArchitecture() {
|
|
282
|
+
if (existsSync(ARCHITECTURE_FILE)) {
|
|
283
|
+
try {
|
|
284
|
+
return readFileSync(ARCHITECTURE_FILE, 'utf-8');
|
|
285
|
+
} catch {
|
|
286
|
+
return null;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
return null;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* Find files relevant to a proposed change description.
|
|
294
|
+
* Returns file paths sorted by relevance.
|
|
295
|
+
*/
|
|
296
|
+
getRelevantFiles(description) {
|
|
297
|
+
const descLower = description.toLowerCase();
|
|
298
|
+
const keywords = descLower.split(/\W+/).filter(w => w.length > 2);
|
|
299
|
+
|
|
300
|
+
const scored = Object.entries(this._summaries).map(([path, info]) => {
|
|
301
|
+
let score = 0;
|
|
302
|
+
const text = `${path} ${info.summary || ''}`.toLowerCase();
|
|
303
|
+
|
|
304
|
+
for (const keyword of keywords) {
|
|
305
|
+
if (text.includes(keyword)) score++;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
return { path, score, summary: info.summary };
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
return scored
|
|
312
|
+
.filter(s => s.score > 0)
|
|
313
|
+
.sort((a, b) => b.score - a.score)
|
|
314
|
+
.slice(0, 15);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* Regenerate the architecture overview from all summaries.
|
|
319
|
+
*/
|
|
320
|
+
async updateArchitecture() {
|
|
321
|
+
const logger = getLogger();
|
|
322
|
+
const entries = Object.entries(this._summaries);
|
|
323
|
+
if (entries.length === 0) return;
|
|
324
|
+
|
|
325
|
+
// Group by directory
|
|
326
|
+
const byDir = {};
|
|
327
|
+
for (const [path, info] of entries) {
|
|
328
|
+
const dir = path.includes('/') ? path.split('/').slice(0, -1).join('/') : '.';
|
|
329
|
+
if (!byDir[dir]) byDir[dir] = [];
|
|
330
|
+
byDir[dir].push({ path, ...info });
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// Build a compact summary for LLM
|
|
334
|
+
const summaryText = Object.entries(byDir)
|
|
335
|
+
.map(([dir, files]) => {
|
|
336
|
+
const fileLines = files
|
|
337
|
+
.map(f => ` - ${f.path}: ${(f.summary || 'no summary').slice(0, 120)}`)
|
|
338
|
+
.join('\n');
|
|
339
|
+
return `### ${dir}/\n${fileLines}`;
|
|
340
|
+
})
|
|
341
|
+
.join('\n\n');
|
|
342
|
+
|
|
343
|
+
if (this._agent) {
|
|
344
|
+
try {
|
|
345
|
+
const prompt = `Based on these file summaries, write a concise architecture overview document in Markdown. Include: project structure, key components, data flow, and patterns used.
|
|
346
|
+
|
|
347
|
+
${summaryText}`;
|
|
348
|
+
|
|
349
|
+
const response = await this._agent.orchestratorProvider.chat({
|
|
350
|
+
system: 'You are a software architect. Write clear, concise architecture documentation.',
|
|
351
|
+
messages: [{ role: 'user', content: prompt }],
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
if (response.text) {
|
|
355
|
+
this._saveArchitecture(response.text);
|
|
356
|
+
logger.info(`[Codebase] Architecture doc updated (${response.text.length} chars)`);
|
|
357
|
+
}
|
|
358
|
+
} catch (err) {
|
|
359
|
+
logger.warn(`[Codebase] Architecture update failed: ${err.message}`);
|
|
360
|
+
}
|
|
361
|
+
} else {
|
|
362
|
+
// Fallback: just dump the summaries
|
|
363
|
+
const doc = `# KERNEL Architecture\n\n_Auto-generated on ${new Date().toISOString()}_\n\n${summaryText}`;
|
|
364
|
+
this._saveArchitecture(doc);
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
// ── Static Analysis Fallback ──────────────────────────────────
|
|
369
|
+
|
|
370
|
+
_staticAnalysis(content, filePath) {
|
|
371
|
+
const exports = [];
|
|
372
|
+
const dependencies = [];
|
|
373
|
+
|
|
374
|
+
// Extract exports
|
|
375
|
+
const exportMatches = content.matchAll(/export\s+(?:default\s+)?(?:class|function|const|let|var)\s+(\w+)/g);
|
|
376
|
+
for (const m of exportMatches) exports.push(m[1]);
|
|
377
|
+
|
|
378
|
+
// Extract local imports
|
|
379
|
+
const importMatches = content.matchAll(/from\s+['"](\.[^'"]+)['"]/g);
|
|
380
|
+
for (const m of importMatches) dependencies.push(m[1]);
|
|
381
|
+
|
|
382
|
+
// Simple summary based on file path
|
|
383
|
+
let summary = `Source file at ${filePath}`;
|
|
384
|
+
if (exports.length > 0) summary += ` — exports: ${exports.join(', ')}`;
|
|
385
|
+
|
|
386
|
+
return { summary, exports, dependencies };
|
|
387
|
+
}
|
|
388
|
+
}
|