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.
Files changed (63) hide show
  1. package/.env.example +0 -0
  2. package/README.md +0 -0
  3. package/bin/kernel.js +56 -2
  4. package/config.example.yaml +31 -0
  5. package/package.json +1 -1
  6. package/src/agent.js +150 -20
  7. package/src/automation/automation-manager.js +0 -0
  8. package/src/automation/automation.js +0 -0
  9. package/src/automation/index.js +0 -0
  10. package/src/automation/scheduler.js +0 -0
  11. package/src/bot.js +303 -4
  12. package/src/claude-auth.js +0 -0
  13. package/src/coder.js +0 -0
  14. package/src/conversation.js +0 -0
  15. package/src/intents/detector.js +0 -0
  16. package/src/intents/index.js +0 -0
  17. package/src/intents/planner.js +0 -0
  18. package/src/life/codebase.js +388 -0
  19. package/src/life/engine.js +1317 -0
  20. package/src/life/evolution.js +244 -0
  21. package/src/life/improvements.js +81 -0
  22. package/src/life/journal.js +109 -0
  23. package/src/life/memory.js +283 -0
  24. package/src/life/share-queue.js +136 -0
  25. package/src/persona.js +0 -0
  26. package/src/prompts/orchestrator.js +19 -1
  27. package/src/prompts/persona.md +0 -0
  28. package/src/prompts/system.js +0 -0
  29. package/src/prompts/workers.js +10 -9
  30. package/src/providers/anthropic.js +0 -0
  31. package/src/providers/base.js +0 -0
  32. package/src/providers/index.js +0 -0
  33. package/src/providers/models.js +8 -1
  34. package/src/providers/openai-compat.js +0 -0
  35. package/src/security/audit.js +0 -0
  36. package/src/security/auth.js +0 -0
  37. package/src/security/confirm.js +0 -0
  38. package/src/self.js +0 -0
  39. package/src/services/stt.js +0 -0
  40. package/src/services/tts.js +0 -0
  41. package/src/skills/catalog.js +0 -0
  42. package/src/skills/custom.js +0 -0
  43. package/src/swarm/job-manager.js +0 -0
  44. package/src/swarm/job.js +0 -0
  45. package/src/swarm/worker-registry.js +0 -0
  46. package/src/tools/browser.js +0 -0
  47. package/src/tools/categories.js +0 -0
  48. package/src/tools/coding.js +1 -1
  49. package/src/tools/docker.js +0 -0
  50. package/src/tools/git.js +0 -0
  51. package/src/tools/github.js +0 -0
  52. package/src/tools/index.js +0 -0
  53. package/src/tools/jira.js +0 -0
  54. package/src/tools/monitor.js +0 -0
  55. package/src/tools/network.js +0 -0
  56. package/src/tools/orchestrator-tools.js +18 -3
  57. package/src/tools/os.js +0 -0
  58. package/src/tools/persona.js +0 -0
  59. package/src/tools/process.js +0 -0
  60. package/src/utils/config.js +0 -0
  61. package/src/utils/display.js +0 -0
  62. package/src/utils/logger.js +0 -0
  63. 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
+ }