Asnip / lib /self_improve.mjs
teddyoas's picture
Duplicate from teddyoas/PHANTOM-v2
20f108c
Raw History Blame Contribute Delete
96.2 kB
// Phantom — Self-Improvement Engine
// Reads other projects' source code, analyzes what Phantom lacks,
// and generates code to fill those gaps autonomously.
//
// Capabilities:
// 1. Project Scanner — Clone/read any git repo, parse file structure
// 2. Feature Extractor — Discover what features other projects have
// 3. Gap Analyzer — Compare features against Phantom's own tools
// 4. Pattern Learner — Extract architectural patterns from other projects
// 5. Code Generator — Generate new Phantom modules from learned patterns
// 6. Auto-Applier — Write, validate, test, and commit improvements
import fs from "fs";
import { execFileSync, execSync } from "child_process";
import { resolve, join, dirname, basename, extname, relative } from "path";
import { homedir } from "os";
import { BASE_DIR, TOOLS_DIR, PHANTOM_VERSION } from "./config.mjs";
import { __r } from "./runtime.mjs";
// ── Constants ──────────────────────────────────────────────
const PHANTOM_DIR = resolve(homedir(), "Phantom");
const LEARN_DIR = resolve(BASE_DIR, "learned");
const BLUEPRINTS_DIR = resolve(BASE_DIR, "blueprints");
const EVOLVE_LOG = resolve(BASE_DIR, "evolve.json");
const GENERATED_DIR = resolve(PHANTOM_DIR, "lib", "generated");
const LEARNED_MODULES_DIR = resolve(PHANTOM_DIR, "lib", "learned");
// ── Helpers ────────────────────────────────────────────────
function loadState() {
const defaults = { generation: 1, self_improve: [], imported_features: [], learned_patterns: [], patches: [], wrappers: [], errors_fixed: 0, last_evolve: null, auto_fixes: [] };
try {
if (fs.existsSync(EVOLVE_LOG)) {
const existing = JSON.parse(fs.readFileSync(EVOLVE_LOG, "utf-8"));
// Merge with defaults so new keys are present even on old state files
for (const [k, v] of Object.entries(defaults)) {
if (!(k in existing)) existing[k] = v;
}
return existing;
}
} catch {}
return { ...defaults };
}
function saveState(st) {
try {
if (!fs.existsSync(BASE_DIR)) fs.mkdirSync(BASE_DIR, { recursive: true });
fs.writeFileSync(EVOLVE_LOG, JSON.stringify(st, null, 2), "utf-8");
} catch {}
}
function ensureDir(p) {
if (!fs.existsSync(p)) fs.mkdirSync(p, { recursive: true });
}
function gitRoot() {
try {
const r = execSync("git rev-parse --show-toplevel 2>/dev/null", { encoding: "utf-8", timeout: 5000 }).trim();
return r || null;
} catch { return null; }
}
function safeReadFile(path) {
try {
if (!fs.existsSync(path)) return null;
const stat = fs.statSync(path);
if (stat.size > 500000) return null; // skip >500KB files
return fs.readFileSync(path, "utf-8");
} catch { return null; }
}
// ── 1. PROJECT SCANNER ────────────────────────────────────
// Given a local path or remote git URL, scan and return project structure.
/**
* Clone a remote git repo to a temp dir, or validate a local path.
* Returns { path, name, isRemote, tempDir }
*/
export function cloneOrLocate(target) {
const cleaned = target.trim();
// Already a local directory?
if (fs.existsSync(cleaned) && fs.statSync(cleaned).isDirectory()) {
return { path: resolve(cleaned), name: basename(resolve(cleaned)), isRemote: false, tempDir: null };
}
// Try git clone
if (cleaned.match(/^https?:\/\/.+\.git$/) || cleaned.match(/^git@/)) {
const name = cleaned.split("/").pop().replace(/\.git$/, "") || `repo_${Date.now()}`;
const dest = resolve(BASE_DIR, "cloned", name);
ensureDir(dirname(dest));
if (!fs.existsSync(dest)) {
try {
execSync(`git clone --depth 1 "${cleaned}" "${dest}"`, { encoding: "utf-8", timeout: 60000 });
} catch (e) {
throw new Error(`Git clone failed: ${e.stderr?.slice(0, 200) || e.message}`);
}
}
return { path: dest, name, isRemote: true, tempDir: dest };
}
// Could be a repo URL without .git — try to clone anyway
if (cleaned.startsWith("http") || cleaned.startsWith("git@")) {
const url = cleaned.endsWith(".git") ? cleaned : `${cleaned}.git`;
const name = url.split("/").pop().replace(/\.git$/, "") || `repo_${Date.now()}`;
const dest = resolve(BASE_DIR, "cloned", name);
ensureDir(dirname(dest));
if (!fs.existsSync(dest)) {
try {
execSync(`git clone --depth 1 "${url}" "${dest}"`, { encoding: "utf-8", timeout: 60000 });
} catch (e) {
throw new Error(`Git clone failed: ${e.stderr?.slice(0, 200) || e.message}`);
}
}
return { path: dest, name, isRemote: true, tempDir: dest };
}
throw new Error(`Cannot locate project: ${cleaned}. Provide a local path or git URL.`);
}
/**
* Scan a project directory and return a structured summary:
* { name, fileCount, languages, entryPoints, exports, tools, features, structure }
*/
export function scanProject(projectPath) {
const result = {
name: basename(projectPath),
path: projectPath,
fileCount: 0,
languages: {},
fileTree: [],
entryPoints: [],
exports: [],
features: [],
tools: [],
patterns: {
moduleSystem: "unknown",
hasTests: false,
hasCli: false,
hasHttpServer: false,
hasConfig: false,
hasPluginSystem: false,
},
structure: {},
};
if (!fs.existsSync(projectPath)) return result;
const entries = fs.readdirSync(projectPath, { withFileTypes: true });
// Quick file tree (depth 1)
const dirs = [];
const files = [];
for (const e of entries) {
if (e.name.startsWith(".")) continue;
if (e.isDirectory()) dirs.push(e.name);
else files.push(e.name);
}
result.structure = { dirs, files };
// Detect entry points
for (const f of ["package.json", "index.mjs", "index.js", "app.mjs", "app.js", "main.mjs", "main.js", "cli.mjs", "cli.js"]) {
if (files.includes(f)) result.entryPoints.push(f);
}
// Parse package.json if present
let pkg = null;
const pkgPath = resolve(projectPath, "package.json");
if (fs.existsSync(pkgPath)) {
try {
pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
if (pkg.name) result.name = pkg.name;
if (pkg.bin) {
result.patterns.hasCli = true;
const bins = typeof pkg.bin === "string" ? [pkg.bin] : Object.values(pkg.bin);
result.entryPoints.push(...bins.filter(b => !result.entryPoints.includes(b)));
}
} catch {}
}
// Recursively walk JS files to extract patterns
walkProjectFiles(projectPath, result);
// Detect module system
if (result.languages["mjs"] || pkg?.type === "module") {
result.patterns.moduleSystem = "esm";
} else if (result.languages["cjs"] || result.languages["js"]) {
result.patterns.moduleSystem = "cjs";
}
// Test detection
result.patterns.hasTests = result.languages["test.mjs"] > 0 ||
result.languages["spec.mjs"] > 0 ||
result.languages["test.js"] > 0 ||
result.features.some(f => f.includes("test"));
// HTTP server detection
result.patterns.hasHttpServer = result.features.some(f =>
f.includes("http") || f.includes("server") || f.includes("express") || f.includes("fastify")
);
// Config detection
result.patterns.hasConfig = files.some(f =>
f.includes("config") || f.endsWith("rc") || f === ".env"
);
// Plugin detection
result.patterns.hasPluginSystem = result.features.some(f =>
f.includes("plugin") || f.includes("extension") || f.includes("module")
);
return result;
}
function walkProjectFiles(basePath, result) {
try {
const entries = fs.readdirSync(basePath, { withFileTypes: true });
for (const e of entries) {
if (e.name.startsWith(".")) continue;
if (e.name === "node_modules") continue;
if (e.name === "dist") continue;
if (e.name === "build") continue;
if (e.name === ".git") continue;
const fullPath = resolve(basePath, e.name);
if (e.isDirectory()) {
walkProjectFiles(fullPath, result);
} else if (e.isFile()) {
result.fileCount++;
// Track languages
const ext = extname(e.name).replace(/^\./, "");
if (ext) {
result.languages[ext] = (result.languages[ext] || 0) + 1;
}
// Analyze JS/TS source files
if (e.name.endsWith(".mjs") || e.name.endsWith(".js") || e.name.endsWith(".ts")) {
analyzeJSFile(fullPath, result);
}
// Analyze Python source
if (e.name.endsWith(".py")) {
analyzePythonFile(fullPath, result);
}
// Analyze Go source
if (e.name.endsWith(".go")) {
analyzeGoFile(fullPath, result);
}
}
}
} catch {}
}
// ── Language-specific file analyzers ─────────────────────────
function analyzeJSFile(fullPath, result) {
const content = safeReadFile(fullPath);
if (!content) return;
const exports = content.match(/(?:export\s+(?:async\s+)?function\s+(\w+)|export\s+const\s+(\w+)|export\s+default\s+(?:async\s+)?function\s*[*(]\s*[^)]*\s*\)(?:.*{)?|module\.exports\s*=\s*{(?:[\s\S]*?)}|module\.exports\s*=\s*{)/g);
if (exports) {
for (const ex of exports) {
const name = ex.match(/(?:function\s+|const\s+)(\w+)/)?.[1];
if (name) result.exports.push({ name, file: relative(result.path, fullPath) });
}
}
const toolPatterns = content.matchAll(/(\w+):\s*(?:async\s*)?\(?\s*(input|args|cmd|query|target|url|domain|path|text|data|name)\s*\)?\s*=>\s*{/g);
for (const m of toolPatterns) {
result.tools.push({ name: m[1], param: m[2], file: relative(result.path, fullPath) });
}
const imports = content.matchAll(/from\s+["']([^"']+)["']/g);
for (const m of imports) {
const module = m[1];
if (module.startsWith("http") || module.startsWith("express") || module.startsWith("fastify")) {
result.patterns.hasHttpServer = true;
}
if (!result.features.includes(module)) result.features.push(module);
}
if (content.includes("commander") || content.includes("yargs") || content.includes("arg parse") ||
content.includes("process.argv") || (content.includes("command") && content.includes("description"))) {
result.patterns.hasCli = true;
}
for (const feature of [
["websocket", /websocket|ws:\/\/|wss:\/\//i],
["database", /sqlite|mongo|postgres|mysql|knex|prisma|typeorm/i],
["graphql", /graphql|gql`/i],
["auth", /jwt|oauth|bcrypt|passport|session/i],
["cache", /redis|memcached|cache/i],
["logging", /winston|pino|log4js|bunyan/i],
["sse", /EventSource|Server-Sent Events|text\/event-stream/i],
["streaming", /pipe|stream|Transform|Readable|Writable/i],
["ipc", /child_process|fork|spawn|Worker|parentPort/i],
]) {
if (feature[1].test(content) && !result.features.includes(feature[0])) {
result.features.push(feature[0]);
}
}
}
function analyzePythonFile(fullPath, result) {
const content = safeReadFile(fullPath);
if (!content) return;
// Detect module system
result.patterns.moduleSystem = "python";
// Extract function definitions as exports
const funcs = content.matchAll(/^\s*(?:async\s+)?def\s+(\w+)\s*\(([^)]*)\)\s*:/gm);
for (const m of funcs) {
const name = m[1];
const params = m[2].trim();
result.exports.push({ name, file: relative(result.path, fullPath) });
// Classify as tool if param matches known patterns
const toolParams = ["query", "target", "url", "domain", "path", "text", "data", "name", "input", "args", "cmd", "username", "email", "host", "port"];
const hasToolParam = toolParams.some(p => params.includes(p));
if (hasToolParam || !params) {
result.tools.push({ name, param: params.split(",")[0]?.trim() || "input", file: relative(result.path, fullPath) });
}
}
// Extract class definitions
const classes = content.matchAll(/^\s*class\s+(\w+)\s*[:\(]/gm);
for (const m of classes) {
result.exports.push({ name: m[1], file: relative(result.path, fullPath), type: "class" });
}
// Extract imports as features
const imports = content.matchAll(/(?:from\s+(\S+)\s+import|import\s+(\S+))/g);
for (const m of imports) {
const mod = m[1] || m[2];
if (mod && !result.features.includes(mod)) {
result.features.push(mod.startsWith(".") ? mod.slice(1) : mod);
}
}
// Detect CLI frameworks
if (content.includes("argparse") || content.includes("click") || content.includes("typer") ||
content.includes("fire.Fire") || content.includes("def main")) {
result.patterns.hasCli = true;
}
// Detect HTTP
if (content.includes("from flask") || content.includes("from fastapi") || content.includes("import requests") ||
content.includes("aiohttp") || content.includes("django")) {
result.patterns.hasHttpServer = true;
}
// Feature detection
if (/(sqlite|mongo(engine|db)?|postgres|mysql|psycopg|sqlalchemy)/i.test(content) && !result.features.includes("database"))
result.features.push("database");
if (/(jwt|oauth|bcrypt|passlib|hashlib)/i.test(content) && !result.features.includes("auth"))
result.features.push("auth");
if (/(redis|memcached|cache)/i.test(content) && !result.features.includes("cache"))
result.features.push("cache");
if (/(websocket|wss?:\/\/)/i.test(content) && !result.features.includes("websocket"))
result.features.push("websocket");
if (/(thread|multiprocess|subprocess|concurrent)/i.test(content) && !result.features.includes("ipc"))
result.features.push("ipc");
if (/(logging|loguru)/i.test(content) && !result.features.includes("logging"))
result.features.push("logging");
}
function analyzeGoFile(fullPath, result) {
const content = safeReadFile(fullPath);
if (!content) return;
// Detect module system
result.patterns.moduleSystem = "go";
// Extract function definitions
const funcs = content.matchAll(/^\s*func\s+(?:\([^)]*\)\s+)?([A-Z]\w+)\s*\(([^)]*)\)(?:\s*\(?[^)]*\)?)?\s*\{/gm);
for (const m of funcs) {
const name = m[1];
const params = m[2].trim();
result.exports.push({ name, file: relative(result.path, fullPath) });
const toolParams = ["query", "target", "url", "domain", "path", "text", "data", "name", "input", "args", "cmd", "host", "port", "addr"];
const hasToolParam = toolParams.some(p => params.includes(p));
if (hasToolParam && name[0] >= 'A' && name[0] <= 'Z') {
result.tools.push({ name, param: params.split(",")[0]?.trim() || "input", file: relative(result.path, fullPath) });
}
}
// Detect main function
if (content.includes("func main()")) result.patterns.hasCli = true;
// Detect HTTP
if (/(http\.ListenAndServe|gin\.|fiber\.|echo\.|chi\.|net\/http)/.test(content)) {
result.patterns.hasHttpServer = true;
}
// Imports
const imports = content.matchAll(/(?:"([^"]+)"|`([^`]+)`)/g);
for (const m of imports) {
const imp = (m[1] || m[2]).trim();
if (imp.includes("/") && !result.features.includes(imp)) {
result.features.push(imp);
}
}
// Feature detection
if (/(sqlite|mongo|postgres|mysql|database\/sql)/i.test(content) && !result.features.includes("database"))
result.features.push("database");
if (/(jwt|oauth|bcrypt|golang-jwt)/i.test(content) && !result.features.includes("auth"))
result.features.push("auth");
if (/(websocket|gorilla\/websocket|nhooyr\.io\/websocket)/i.test(content) && !result.features.includes("websocket"))
result.features.push("websocket");
if (/(redis|memcached)/i.test(content) && !result.features.includes("cache"))
result.features.push("cache");
}
// ── 2. SELF-ANALYSIS ──────────────────────────────────────
// Analyze Phantom's own codebase to understand current capabilities.
export function analyzeSelf() {
const toolsPath = resolve(PHANTOM_DIR, "lib", "tools.mjs");
const configPath = resolve(PHANTOM_DIR, "lib", "config.mjs");
const runtimePath = resolve(PHANTOM_DIR, "lib", "runtime.mjs");
const evolvePath = resolve(PHANTOM_DIR, "lib", "evolve.mjs");
const dashboardPath = resolve(PHANTOM_DIR, "lib", "dashboard.mjs");
const serverPath = resolve(PHANTOM_DIR, "lib", "server.mjs");
const self = {
version: PHANTOM_VERSION,
files: [],
tools: [],
exports: [],
features: [],
gaps: [],
architecture: {
moduleCount: 0,
hasMultiAgent: false,
hasRepl: false,
hasApi: false,
hasGui: false,
hasPluginSystem: false,
hasTestSuite: false,
hasConfigManagement: false,
},
};
// Analyze each source file
for (const [label, fp] of [
["phantom.mjs", resolve(PHANTOM_DIR, "phantom.mjs")],
["lib/tools.mjs", toolsPath],
["lib/config.mjs", configPath],
["lib/runtime.mjs", runtimePath],
["lib/evolve.mjs", evolvePath],
["lib/env.mjs", resolve(PHANTOM_DIR, "lib/env.mjs")],
["lib/session.mjs", resolve(PHANTOM_DIR, "lib/session.mjs")],
["lib/dashboard.mjs", dashboardPath],
["lib/server.mjs", serverPath],
]) {
const content = safeReadFile(fp);
if (!content) continue;
self.files.push({ name: label, size: content.length, lines: content.split("\n").length });
// Extract exports
const exports = content.match(/(?:export\s+(?:async\s+)?function\s+(\w+)|export\s+const\s+(\w+))/g);
if (exports) {
for (const ex of exports) {
const name = ex.match(/(?:function\s+|const\s+)(\w+)/)?.[1];
if (name) self.exports.push(name);
}
}
}
self.architecture.moduleCount = self.files.length;
// Extract tools from tools.mjs
const toolsContent = safeReadFile(toolsPath);
if (toolsContent) {
const toolNames = toolsContent.matchAll(/(\w+):\s*async\s*\(/g);
for (const m of toolNames) {
if (m[1] !== "export") self.tools.push(m[1]);
}
}
// Detect architecture features
const mainSrc = safeReadFile(resolve(PHANTOM_DIR, "phantom.mjs")) || "";
self.architecture.hasMultiAgent = mainSrc.includes("AgentManager") && mainSrc.includes("spawn");
self.architecture.hasRepl = mainSrc.includes("readline") || mainSrc.includes("REPL");
self.architecture.hasApi = mainSrc.includes("startApiServer") || mainSrc.includes("REST");
self.architecture.hasGui = mainSrc.includes("startGuiDashboard") || mainSrc.includes("Dashboard");
self.architecture.hasTestSuite = fs.existsSync(resolve(PHANTOM_DIR, "test")) &&
fs.readdirSync(resolve(PHANTOM_DIR, "test")).length > 0;
self.architecture.hasConfigManagement = mainSrc.includes("config") || mainSrc.includes("_config");
// Detect plugin system
const allSrc = (safeReadFile(toolsPath) || "") + (safeReadFile(evolvePath) || "") + mainSrc;
self.architecture.hasPluginSystem = allSrc.includes("plugin") && allSrc.includes("load");
// Identify categories of tools Phantom has
self.features = [
{ name: "dns_lookup", present: self.tools.includes("dns_lookup") },
{ name: "port_scanning", present: self.tools.includes("port_scan") },
{ name: "subdomain_enum", present: self.tools.includes("sub_enum") },
{ name: "http_requests", present: self.tools.includes("web_fetch") },
{ name: "ssl_check", present: self.tools.includes("ssl_check") },
{ name: "whois", present: self.tools.includes("whois") },
{ name: "cve_search", present: self.tools.includes("cve_search") },
{ name: "exploit_search", present: self.tools.includes("searchsploit") },
{ name: "vuln_scan", present: self.tools.includes("vuln_scan") },
{ name: "bruteforce", present: self.tools.includes("bruteforce") },
{ name: "file_analysis", present: self.tools.includes("file_analyze") },
{ name: "hash", present: self.tools.includes("hash") },
{ name: "encoding/decoding", present: self.tools.includes("decode") },
{ name: "crawler", present: self.tools.includes("crawl") },
{ name: "http_headers", present: self.tools.includes("http_headers") },
{ name: "yara", present: self.tools.includes("yara") },
{ name: "virustotal", present: self.tools.includes("vt_check") },
{ name: "shell_execution", present: self.tools.includes("shell") },
{ name: "code_generation", present: self.tools.includes("code_gen") },
{ name: "self_modification", present: self.tools.includes("self_edit") },
{ name: "knowledge_base", present: self.tools.includes("knowledge_add") },
{ name: "playbooks", present: self.tools.includes("playbook_list") },
{ name: "scheduling", present: self.tools.includes("schedule") || allSrc.includes("@schedule") },
{ name: "scope_management", present: self.tools.includes("scope") },
{ name: "pipeline/pipe", present: mainSrc.includes("runPipe") },
{ name: "dashboard/gui", present: self.architecture.hasGui },
{ name: "rest_api", present: self.architecture.hasApi },
{ name: "multi_agent", present: self.architecture.hasMultiAgent },
];
return self;
}
// ── 3. GAP ANALYSIS ───────────────────────────────────────
// Compare a scanned project against Phantom to find missing features.
/**
* Compare project scan against Phantom's current capabilities.
* Returns an array of gaps: { priority, category, feature, description, source, suggestedApproach }
*/
export function gapAnalysis(projectScan, selfScan) {
const gaps = [];
const selfTools = new Set(selfScan.tools);
const projectTools = projectScan.tools || [];
const projectFeatures = projectScan.features || [];
// 1. Missing external tool wrappers
const projectToolNames = new Set(projectTools.map(t => t.name));
for (const tool of projectTools) {
if (!selfTools.has(tool.name) && !tool.name.match(/^[A-Z]/)) {
gaps.push({
priority: "medium",
category: "missing_tool",
feature: tool.name,
description: `Project has a tool/function "${tool.name}" that Phantom doesn't wrap`,
source: `${projectScan.name}/${tool.file}`,
suggestedApproach: "generate_wrapper",
});
}
}
// 2. Missing feature categories
const selfFeaturesSet = new Set(selfScan.features.filter(f => f.present).map(f => f.name));
const featureMapping = {
"websocket": { priority: "high", category: "protocol", description: "WebSocket client for real-time communication" },
"database": { priority: "high", category: "data", description: "Database integration (SQLite, PostgreSQL, etc.)" },
"graphql": { priority: "medium", category: "api", description: "GraphQL queries and schema introspection" },
"auth": { priority: "high", category: "security", description: "Authentication/authorization tools" },
"cache": { priority: "low", category: "performance", description: "Caching layer for repeated queries" },
"logging": { priority: "low", category: "devops", description: "Structured logging" },
"sse": { priority: "medium", category: "protocol", description: "Server-Sent Events streaming" },
"streaming": { priority: "medium", category: "protocol", description: "Stream processing for large data" },
"ipc": { priority: "medium", category: "system", description: "Inter-process communication" },
};
for (const [featureName, mapping] of Object.entries(featureMapping)) {
if (projectFeatures.includes(featureName) && !selfFeaturesSet.has(featureName)) {
gaps.push({
priority: mapping.priority,
category: mapping.category,
feature: featureName,
description: `${mapping.description} — present in ${projectScan.name} but missing from Phantom`,
source: projectScan.name,
suggestedApproach: "generate_module",
});
}
}
// 3. Architecture gaps
const proj = projectScan.patterns;
const arch = selfScan.architecture;
if (proj.hasPluginSystem && !arch.hasPluginSystem) {
gaps.push({
priority: "high",
category: "architecture",
feature: "plugin_system",
description: `${projectScan.name} uses a plugin system — Phantom could support hot-loadable plugins`,
source: projectScan.name,
suggestedApproach: "generate_module",
});
}
// 4. File structure patterns worth adopting
if (projectScan.languages["mjs"] && projectScan.languages["mjs"] > 3) {
// Another ESM project may have useful patterns
gaps.push({
priority: "low",
category: "pattern",
feature: "esm_patterns",
description: `${projectScan.name} uses modular ESM patterns worth studying`,
source: projectScan.name,
suggestedApproach: "learn_pattern",
});
}
// 5. Detect if project has a README/docs system Phantom lacks
const hasDocs = projectScan.fileTree.some(f =>
f.toLowerCase().includes("readme") || f.toLowerCase().includes("docs") || f.endsWith(".md")
);
// Phantom already has --help, but structured docs are worth checking
return gaps;
}
// ── 4. PATTERN LEARNER ────────────────────────────────────
// Extract reusable patterns from other projects.
/**
* Extract coding patterns from a project's source files.
* Returns { modulePattern, errorHandling, toolPattern, cliPattern, middlewarePattern, ... }
*/
export function extractPatterns(projectPath) {
const patterns = {
moduleExports: "named",
errorStyle: "catch_return",
toolInterface: "async_single_param",
configStyle: "flat_object",
eventStyle: "none",
validatedBy: null,
samples: [],
};
// Count export styles
let namedExports = 0, defaultExports = 0, moduleExports = 0;
// Walk all JS/MJS files
try {
const entries = fs.readdirSync(projectPath, { withFileTypes: true });
for (const e of entries) {
if (e.name.startsWith(".") || e.isDirectory()) continue;
if (!e.name.endsWith(".mjs") && !e.name.endsWith(".js")) continue;
const content = safeReadFile(resolve(projectPath, e.name));
if (!content) continue;
if ((content.match(/^export\s+(async\s+)?function/gm) || []).length > 0) namedExports++;
if ((content.match(/export\s+default/g) || []).length > 0) defaultExports++;
if ((content.match(/module\.exports/g) || []).length > 0) moduleExports++;
// Error handling pattern
if (content.includes("try {") && content.includes("catch (e)")) {
if (content.match(/catch\s*\([^)]*\)\s*{[\s\S]{0,100}return\s/)) {
patterns.errorStyle = "catch_return";
} else if (content.match(/catch\s*\([^)]*\)\s*{[\s\S]{0,100}(?:reject|throw)/)) {
patterns.errorStyle = "catch_throw";
} else if (content.match(/catch\s*\([^)]*\)\s*{[\s\S]{0,100}(?:console|log|warn)/)) {
patterns.errorStyle = "catch_log";
}
}
// Tool interface pattern
if (content.match(/(\w+):\s*async\s*\(\s*(input|args|cmd)\s*\)/)) {
patterns.toolInterface = "async_single_param";
} else if (content.match(/(\w+):\s*async\s*\(\s*\)/)) {
patterns.toolInterface = "async_no_param";
}
// Config pattern
if (content.includes("config") && content.includes("from")) {
patterns.configStyle = "module_import";
}
// Extract a sample module as reference
if (namedExports + defaultExports + moduleExports > 0 && !patterns.samples.length) {
const lines = content.split("\n");
const sample = lines.slice(0, 40).map(l => l.trim()).filter(Boolean).slice(0, 20).join("\n");
if (sample.length > 80) {
patterns.samples.push({ file: e.name, code: sample });
}
}
}
} catch {}
return patterns;
}
/**
* Save learned patterns to disk for reference during code generation.
*/
export function savePatterns(projectName, patterns) {
ensureDir(LEARN_DIR);
const fp = resolve(LEARN_DIR, `${projectName.replace(/[^a-z0-9_-]/gi, "_")}_patterns.json`);
const data = {
project: projectName,
learned: new Date().toISOString(),
patterns,
};
fs.writeFileSync(fp, JSON.stringify(data, null, 2), "utf-8");
return fp;
}
/**
* Load all saved patterns for use in code generation.
*/
export function loadAllPatterns() {
const results = {};
ensureDir(LEARN_DIR);
const files = fs.readdirSync(LEARN_DIR).filter(f => f.endsWith("_patterns.json"));
for (const f of files) {
try {
const data = JSON.parse(fs.readFileSync(resolve(LEARN_DIR, f), "utf-8"));
results[data.project] = data.patterns;
} catch {}
}
return results;
}
// ── 5a. TECHNIQUE EXTRACTOR ────────────────────────────────
// Extract actual payloads/techniques from Markdown-heavy repos (PayloadsAllTheThings, etc.)
const IGNORE_DIRS = new Set([".git", "node_modules", "dist", "build", ".github", "images", "assets", ".env"]);
const IGNORE_FILES = new Set(["README.md", "CONTRIBUTING.md", "LICENSE.md", "LICENSE", "CHANGELOG.md"]);
/**
* Recursively find all .md files in a project.
*/
function findMarkdownFiles(dirPath, maxFiles = 500) {
const results = [];
try {
const entries = fs.readdirSync(dirPath, { withFileTypes: true });
for (const e of entries) {
if (e.name.startsWith(".") || IGNORE_DIRS.has(e.name)) continue;
if (results.length >= maxFiles) break;
const fullPath = resolve(dirPath, e.name);
if (e.isDirectory()) {
results.push(...findMarkdownFiles(fullPath, maxFiles - results.length));
} else if (e.isFile() && e.name.endsWith(".md") && !IGNORE_FILES.has(e.name)) {
results.push(fullPath);
}
}
} catch {}
return results;
}
/**
* Parse a single markdown file into techniques: heading, code blocks, descriptions.
*/
function parseMarkdownFile(filePath) {
const content = safeReadFile(filePath);
if (!content || content.length < 50) return null;
const techniques = [];
const lines = content.split("\n");
let currentHeading = "";
let currentDesc = "";
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
// Track headings as technique categories
const headingMatch = line.match(/^(#{1,4})\s+(.+)/);
if (headingMatch) {
currentHeading = headingMatch[2].trim();
currentDesc = "";
continue;
}
// Capture description text (non-code, non-empty lines before a code block)
const trimmed = line.trim();
if (trimmed && !trimmed.startsWith("```") && !trimmed.startsWith("`") && trimmed.length > 20) {
currentDesc = trimmed.substring(0, 300);
}
// Extract code blocks (payloads)
if (trimmed.startsWith("```") && i + 1 < lines.length) {
const lang = trimmed.slice(3).trim().split(/\s/)[0];
const blockLines = [];
let j = i + 1;
while (j < lines.length && !lines[j].trim().startsWith("```")) {
blockLines.push(lines[j]);
j++;
}
const code = blockLines.join("\n").trim();
if (code.length > 10 && code.length < 10000) {
techniques.push({
heading: currentHeading || basename(dirname(filePath)),
language: lang || "text",
code: code.substring(0, 2000),
description: currentDesc.substring(0, 200),
source: filePath,
});
}
i = j; // skip past closing ```
continue;
}
}
return techniques.length > 0 ? techniques : null;
}
/**
* Extract payloads and techniques from a project's markdown files.
* Saves them as knowledge entries and technique books.
* Returns { totalFiles, totalTechniques, categories, bookFiles }
*/
export function extractTechniques(projectPath, projectName) {
const result = {
totalFiles: 0,
totalTechniques: 0,
categories: {},
bookFiles: [],
};
// Ensure books dir
const booksDir = resolve(BASE_DIR, "books");
ensureDir(booksDir);
const knowledgeDir = resolve(BASE_DIR, "knowledge");
ensureDir(knowledgeDir);
// Find all markdown files
const mdFiles = findMarkdownFiles(projectPath);
result.totalFiles = mdFiles.length;
// Category from directory structure
const categoryMap = {}; // category -> [techniques]
for (const filePath of mdFiles) {
// Determine category from relative path
const relPath = relative(projectPath, filePath);
const parts = relPath.split(/[\\/]/);
const category = parts.length > 1 ? parts.slice(0, -1).join(" > ") : "root";
const fileName = parts[parts.length - 1].replace(/\.md$/, "");
const techs = parseMarkdownFile(filePath);
if (!techs) continue;
for (const tech of techs) {
tech.category = category;
tech.fileName = fileName;
const catKey = category || "uncategorized";
if (!categoryMap[catKey]) categoryMap[catKey] = [];
categoryMap[catKey].push(tech);
}
result.totalTechniques += techs.length;
}
// Save per-category technique books
for (const [category, techs] of Object.entries(categoryMap)) {
const safeName = `${projectName}_${category}`.replace(/[^a-z0-9_-]/gi, "_").substring(0, 50).toLowerCase();
const bookPath = resolve(booksDir, `${safeName}.txt`);
let bookContent = `## Techniques from ${projectName} — ${category}\n\n`;
for (const tech of techs.slice(0, 100)) { // cap per category
bookContent += `### ${tech.heading}\n`;
if (tech.description) bookContent += `${tech.description}\n\n`;
if (tech.code) {
bookContent += `\`\`\`${tech.language}\n${tech.code}\n\`\`\`\n\n`;
}
}
fs.writeFileSync(bookPath, bookContent, "utf-8");
result.bookFiles.push(bookPath);
result.categories[category] = techs.length;
// Also save a knowledge entry
const knowledgeEntry = {
tags: ["auto-learned", "technique", projectName, category],
content: `Learned ${techs.length} techniques from ${projectName}/${category}. Saved to ${safeName}.txt. Categories: ${[...new Set(techs.map(t => t.heading))].slice(0, 10).join(", ")}`,
created: new Date().toISOString(),
};
const kfPath = resolve(knowledgeDir, `technique_${safeName}.json`);
fs.writeFileSync(kfPath, JSON.stringify(knowledgeEntry, null, 2), "utf-8");
}
return result;
}
// ── 5. CODE GENERATOR ─────────────────────────────────────
// Generate new Phantom code based on gaps and learned patterns.
/**
* Generate a new tool wrapper for Phantom.
* Takes a gap description and produces { filePath, code }
*/
export function generateTool(feature, description) {
const toolName = camelCase(feature);
const ext = ".mjs";
const code = `// Auto-generated tool: ${toolName}
// Source: Learned from "${feature}" — ${description}
// Generated by Phantom self-improvement engine
export default async function(input) {
try {
const { execSync } = await import("child_process");
const { resolve } = await import("path");
const args = (input || "").trim();
if (!args) {
return \`[${toolName}] Usage: @${toolName}|<args>
Feature: ${description}
Examples:
${toolName}|--help
${toolName}|<target>\`;
}
const result = execSync(\`${toolName} \${args}\`, {
encoding: "utf-8",
timeout: 60000,
maxBuffer: 1024 * 1024,
});
return result.trim() || "(no output)";
} catch (e) {
return \`[${toolName} Error] \${e.stderr?.slice(0, 500) || e.message}\`;
}
}
`;
ensureDir(GENERATED_DIR);
const filePath = resolve(GENERATED_DIR, `${toolName}${ext}`);
fs.writeFileSync(filePath, code, "utf-8");
return { filePath, toolName, code };
}
/**
* Generate a full new module based on a gap analysis result.
* Can generate: tool wrappers, utility modules, feature modules, middleware
*/
export function generateModule(gap, patterns) {
const category = gap.category;
const feature = gap.feature;
switch (gap.suggestedApproach) {
case "generate_wrapper":
return generateTool(feature, gap.description);
case "generate_module": {
return generateFeatureModule(feature, gap.description, patterns);
}
case "learn_pattern":
return { note: `Pattern learning queued for ${feature}`, filePath: null };
default:
return { note: `No generator for ${feature} (${gap.suggestedApproach})`, filePath: null };
}
}
/**
* Generate a new feature module (e.g., websocket client, database layer).
*/
function generateFeatureModule(feature, description, patterns) {
const moduleName = camelCase(feature);
const ext = ".mjs";
const toolStyle = patterns?.toolInterface === "async_no_param" ? "async ()" : "async (input)";
// Generate appropriate module based on feature type
let code = "";
switch (feature) {
case "websocket":
code = generateWebSocketModule(moduleName, description);
break;
case "database":
code = generateDatabaseModule(moduleName, description);
break;
case "plugin_system":
code = generatePluginModule(moduleName, description);
break;
case "graphql":
code = generateGraphQLModule(moduleName, description);
break;
case "sse":
code = generateSSEModule(moduleName, description);
break;
case "streaming":
code = generateStreamingModule(moduleName, description);
break;
case "auth":
code = generateAuthModule(moduleName, description);
break;
case "cache":
code = generateCacheModule(moduleName, description);
break;
case "ipc":
code = generateIPCModule(moduleName, description);
break;
default:
// Generic module template
code = `// Auto-generated module: ${moduleName}
// Feature: ${feature} — ${description}
// Generated by Phantom self-improvement engine
/**
* ${description}
* @param {string} input - Command/query input
* @returns {Promise<string>} Formatted result
*/
export default ${toolStyle} {
try {
const args = (input || "").trim();
if (!args) {
return \`[${moduleName}] Usage: @${moduleName}|<args>
Feature: ${description}
This module was auto-generated from pattern analysis.
Examples:
${moduleName}|--help\`;
}
// TODO: Implement ${feature} logic
// This module was auto-generated as a placeholder from gap analysis
return \`[${moduleName}] Module scaffold generated for: \${args}
Feature "${feature}" requires implementation.
Pattern source: ${description}\`;
} catch (e) {
return \`[${moduleName} Error] \${e.message}\`;
}
};
`;
break;
}
ensureDir(GENERATED_DIR);
const filePath = resolve(GENERATED_DIR, `${moduleName}${ext}`);
fs.writeFileSync(filePath, code, "utf-8");
return { filePath, moduleName, code };
}
function generateWebSocketModule(name, description) {
return `// Auto-generated module: ${name}
// Feature: WebSocket client for real-time communication
// Generated by Phantom self-improvement engine
/**
* WebSocket client tool.
* Usage: @${name}|<url> [--send=<message>] [--listen=<timeout_ms>]
*/
export default async function(input) {
try {
const args = (input || "").trim();
if (!args) {
return \`[${name}] WebSocket Client
Usage: @${name}|<url> [--send=<message>]
Examples:
${name}|wss://echo.websocket.org --send=hello
${name}|wss://stream.binance.com:9443/ws/btcusdt --listen=5000
Sends an optional message and prints responses.
Requires Node.js native WebSocket (Node 22+) or 'ws' package.\`;
}
const urlMatch = args.match(/https?:\\/\\/[^\\s]+|wss?:\\/\\/[^\\s]+/);
if (!urlMatch) return \`[${name}] No WebSocket URL found in: \${args}\`;
const url = urlMatch[0];
const sendMatch = args.match(/--send=([^\\s]+)/);
const listenMatch = args.match(/--listen=(\\d+)/);
const listenMs = listenMatch ? parseInt(listenMatch[1]) : 3000;
// Try native WebSocket (Node 22+)
if (typeof WebSocket !== "undefined") {
return await new Promise((resolve, reject) => {
const ws = new WebSocket(url);
const messages = [];
const timeout = setTimeout(() => {
ws.close();
if (messages.length === 0) resolve(\`[${name}] Connected to \${url} (no messages in \${listenMs}ms)\`);
else resolve(\`[${name}] \${url}\\nMessages (\${messages.length}):\\n\${messages.join("\\n")}\`);
}, listenMs);
ws.onopen = () => {
if (sendMatch) ws.send(sendMatch[1]);
};
ws.onmessage = (event) => {
try {
const parsed = JSON.parse(event.data);
messages.push(typeof parsed === "object" ? JSON.stringify(parsed, null, 2).slice(0, 500) : event.data);
} catch {
messages.push(event.data.slice(0, 500));
}
};
ws.onerror = (err) => { clearTimeout(timeout); resolve(\`[${name} Error] \${err.message || "Connection failed"}\`); };
ws.onclose = () => clearTimeout(timeout);
});
}
return \`[${name}] WebSocket not available in this Node.js version. Install 'ws' package or upgrade to Node 22+.\`;
} catch (e) {
return \`[${name} Error] \${e.message}\`;
}
};
`;
}
function generateDatabaseModule(name, description) {
return `// Auto-generated module: ${name}
// Feature: Database integration (SQLite)
// Generated by Phantom self-improvement engine
import { execSync } from "child_process";
import { resolve } from "path";
/**
* SQLite database query tool.
* Usage: @${name}|<path>|<query>
* Uses sqlite3 CLI. Install: apt install sqlite3
*/
export default async function(input) {
try {
const parts = (input || "").split("|").map(s => s.trim());
if (parts.length < 2) {
return \`[${name}] SQLite Database Tool
Usage: @${name}|<db_path>|<sql_query>
Examples:
${name}|test.db|SELECT * FROM users LIMIT 10;
${name}|/tmp/data.db|.tables
${name}|--tables|test.db List tables
Requires: sqlite3 CLI (apt install sqlite3)\`;
}
const dbPath = parts[0];
const query = parts.slice(1).join("|");
// Check sqlite3 availability
try {
execSync("which sqlite3", { encoding: "utf-8", timeout: 3000 });
} catch {
return \`[${name}] sqlite3 not found. Install: apt install sqlite3\`;
}
const escapedQuery = query.replace(/'/g, "'\\\\''");
const result = execSync(\`sqlite3 -header -column "\${dbPath}" '\${escapedQuery}'\`, {
encoding: "utf-8",
timeout: 30000,
maxBuffer: 1024 * 1024,
}).trim();
return result || "(empty result)";
} catch (e) {
return \`[${name} Error] \${e.stderr?.slice(0, 500) || e.message}\`;
}
};
`;
}
function generatePluginModule(name, description) {
return `// Auto-generated module: ${name}
// Feature: Hot-loadable plugin system for Phantom
// Generated by Phantom self-improvement engine
import fs from "fs";
import { resolve } from "path";
import { homedir } from "os";
const PLUGIN_DIRS = [
resolve(homedir(), ".config", "phantom", "plugins"),
resolve(homedir(), "Phantom", "plugins"),
];
/**
* Plugin system — load, list, enable, disable plugins at runtime.
* Usage: @${name}|list
* @${name}|load|<path>
* @${name}|enable|<name>
* @${name}|disable|<name>
*/
export default async function(input) {
try {
const args = (input || "").trim().split("|").map(s => s.trim());
const command = args[0] || "";
if (!command || command === "--help") {
return \`[${name}] Plugin System
Commands:
@${name}|list List registered plugins
@${name}|load|<path> Load a plugin from file
@${name}|enable|<name> Enable a plugin
@${name}|disable|<name> Disable a plugin
@${name}|reload Reload all plugins
@${name}|scan Scan PLUGIN_DIRS for plugins\`;
}
// Ensure plugin dirs exist
for (const d of PLUGIN_DIRS) {
if (!fs.existsSync(d)) fs.mkdirSync(d, { recursive: true });
}
const registryPath = resolve(PLUGIN_DIRS[0], "registry.json");
let registry = { enabled: {}, disabled: {}, manifest: {} };
try {
if (fs.existsSync(registryPath)) registry = JSON.parse(fs.readFileSync(registryPath, "utf-8"));
} catch {}
switch (command) {
case "list": {
const enabled = Object.keys(registry.enabled);
const disabled = Object.keys(registry.disabled);
const lines = [\`[${name}] Plugin Registry (\${enabled.length} enabled, \${disabled.length} disabled)\`];
for (const p of enabled) {
const info = registry.manifest[p] || {};
lines.push(\` ✅ \${p} — \${info.description || "no description"}\`);
}
for (const p of disabled) {
const info = registry.manifest[p] || {};
lines.push(\` ⏸ \${p} — \${info.description || "no description"} [disabled]\`);
}
if (!enabled.length && !disabled.length) lines.push(" (no plugins registered)");
return lines.join("\\n");
}
case "load": {
const pluginPath = args[1];
if (!pluginPath) return \`[${name}] Usage: @${name}|load|<path>\`;
if (!fs.existsSync(pluginPath)) return \`[${name}] Plugin not found: \${pluginPath}\`;
try {
const mod = await import(resolve(pluginPath) + \`?t=\${Date.now()}\`);
const name = mod.name || basename(pluginPath).replace(/\\.mjs$/, "");
const description = mod.description || "";
const version = mod.version || "1.0.0";
registry.enabled[name] = { path: pluginPath, loaded: new Date().toISOString(), version };
registry.manifest[name] = { description, version, path: pluginPath };
fs.writeFileSync(registryPath, JSON.stringify(registry, null, 2), "utf-8");
return \`[${name}] ✅ Loaded plugin: \${name} v\${version}\`;
} catch (e) {
return \`[${name}] Failed to load plugin: \${e.message}\`;
}
}
case "enable": {
const pname = args[1];
if (!pname || !registry.disabled[pname]) return \`[${name}] Plugin not found in disabled list: \${pname}\`;
registry.enabled[pname] = registry.disabled[pname];
delete registry.disabled[pname];
fs.writeFileSync(registryPath, JSON.stringify(registry, null, 2), "utf-8");
return \`[${name}] ✅ Enabled plugin: \${pname}\`;
}
case "disable": {
const dname = args[1];
if (!dname || !registry.enabled[dname]) return \`[${name}] Plugin not found in enabled list: \${dname}\`;
registry.disabled[dname] = registry.enabled[dname];
delete registry.enabled[dname];
fs.writeFileSync(registryPath, JSON.stringify(registry, null, 2), "utf-8");
return \`[${name}] ⏸ Disabled plugin: \${dname}\`;
}
case "scan": {
let found = 0;
for (const dir of PLUGIN_DIRS) {
if (!fs.existsSync(dir)) continue;
const files = fs.readdirSync(dir).filter(f => f.endsWith(".mjs"));
for (const file of files) {
const fullPath = resolve(dir, file);
if (!registry.enabled[file.replace(/\\.mjs$/, "")]) {
try {
const mod = await import(fullPath + \`?t=\${Date.now()}\`);
const pname = mod.name || file.replace(/\\.mjs$/, "");
registry.enabled[pname] = { path: fullPath, loaded: new Date().toISOString(), version: mod.version || "1.0.0" };
registry.manifest[pname] = { description: mod.description || "", version: mod.version || "1.0.0", path: fullPath };
found++;
} catch {}
}
}
}
fs.writeFileSync(registryPath, JSON.stringify(registry, null, 2), "utf-8");
return \`[${name}] Scan complete: loaded \${found} new plugin(s)\`;
}
default:
return \`[${name}] Unknown command: \${command}. Use @${name}|--help for usage.\`;
}
} catch (e) {
return \`[${name} Error] \${e.message}\`;
}
};
`;
}
function generateGraphQLModule(name, description) {
return `// Auto-generated module: ${name}
// Feature: GraphQL queries and introspection
// Generated by Phantom self-improvement engine
/**
* GraphQL query tool.
* Usage: @${name}|<endpoint>|<query> [--variables=<json>]
*/
export default async function(input) {
try {
const args = (input || "").trim();
if (!args) {
return \`[${name}] GraphQL Client
Usage: @${name}|<url>|<query> [--variables=<json>]
Examples:
${name}|https://api.example.com/graphql|{ __schema { types { name } } }
${name}|https://api.github.com/graphql|query{viewer{login}} --variables={}
Sends a GraphQL query to the endpoint and returns the response.\`;
}
const parts = args.split("|");
let url = parts[0]?.trim();
let query = parts.slice(1).join("|").trim();
// Extract --variables
const varMatch = query.match(/--variables=(\\{[^}]+\\})/);
const variables = varMatch ? JSON.parse(varMatch[1]) : {};
query = query.replace(/--variables=\\{[^}]+\\}/, "").trim();
if (!url || !query) return \`[${name}] Usage: @${name}|<endpoint>|<query>\`;
const response = await fetch(url, {
method: "POST",
headers: { "Content-Type": "application/json", "User-Agent": "Phantom/1.0" },
body: JSON.stringify({ query, variables }),
signal: AbortSignal.timeout(15000),
});
const data = await response.json();
const formatted = JSON.stringify(data, null, 2);
return formatted.slice(0, 8000);
} catch (e) {
return \`[${name} Error] \${e.message}\`;
}
};
`;
}
function generateSSEModule(name, description) {
return `// Auto-generated module: ${name}
// Feature: Server-Sent Events streaming
// Generated by Phantom self-improvement engine
/**
* SSE (Server-Sent Events) stream reader.
* Usage: @${name}|<url> [--events=<count>] [--timeout=<ms>]
*/
export default async function(input) {
try {
const args = (input || "").trim();
if (!args) {
return \`[${name}] SSE Stream Reader
Usage: @${name}|<url> [--events=5] [--timeout=10000]
Examples:
${name}|https://api.example.com/events --events=3
${name}|https://stream.example.com/data --timeout=5000
Connects to an SSE endpoint and captures events.
Outputs event data as they arrive.\`;
}
const urlMatch = args.match(/https?:\\/\\/[^\\s]+/);
if (!urlMatch) return \`[${name}] No URL found in: \${args}\`;
const url = urlMatch[0];
const eventMatch = args.match(/--events=(\\d+)/);
const timeoutMatch = args.match(/--timeout=(\\d+)/);
const maxEvents = eventMatch ? parseInt(eventMatch[1]) : 10;
const timeoutMs = timeoutMatch ? parseInt(timeoutMatch[1]) : 10000;
const response = await fetch(url, {
headers: { Accept: "text/event-stream", "User-Agent": "Phantom/1.0" },
signal: AbortSignal.timeout(timeoutMs),
});
if (!response.ok) return \`[${name}] HTTP \${response.status} from \${url}\`;
if (!response.body) return \`[${name}] No response body\`;
const reader = response.body.getReader();
const decoder = new TextDecoder();
const events = [];
let buffer = "";
while (events.length < maxEvents) {
const { done, value } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
const lines = buffer.split("\\n");
buffer = lines.pop() || "";
for (const line of lines) {
if (line.startsWith("data: ")) {
events.push(line.slice(6));
} else if (line.startsWith("event: ")) {
// track event type if needed
}
}
}
if (events.length === 0) return \`[${name}] Connected to \${url} (no events received)\`;
return \`[${name}] \${url} — \${events.length} events\\n\${events.slice(0, maxEvents).join("\\n")}\`;
} catch (e) {
return \`[${name} Error] \${e.message}\`;
}
};
`;
}
function generateStreamingModule(name, description) {
return `// Auto-generated module: ${name}
// Feature: Stream processing for large data
// Generated by Phantom self-improvement engine
import { createReadStream, createWriteStream, existsSync } from "fs";
import { resolve } from "path";
import { Transform } from "stream";
/**
* Stream processing tool — process large files line-by-line.
* Usage: @${name}|<input>|<transform> [--output=<path>]
* Transforms: upper, lower, reverse, base64, count, grep:<pattern>
*/
export default async function(input) {
try {
const args = (input || "").trim();
if (!args) {
return \`[${name}] Stream Processor
Process large files line-by-line without loading into memory.
Usage: @${name}|<file>|<transform> [--output=<outfile>]
Transforms:
upper Convert to uppercase
lower Convert to lowercase
reverse Reverse each line
base64 Base64 encode each line
count Count lines/words/bytes
grep:<pat> Filter lines matching pattern
sed:s/find/replace Search and replace
Examples:
${name}|large.log|grep:ERROR --output=errors.log
${name}|data.txt|upper
${name}|bigfile.csv|count\`;
}
const parts = args.split("|").map(s => s.trim());
const inputPath = parts[0] ? resolve(parts[0]) : null;
const transform = parts[1] || "";
const outMatch = args.match(/--output=([^\\s]+)/);
const outputPath = outMatch ? resolve(outMatch[1]) : null;
if (!inputPath || !existsSync(inputPath)) return \`[${name}] Input file not found: \${parts[0]}\`;
if (!transform) return \`[${name}] No transform specified\`;
const stat = existsSync(inputPath);
const size = stat ? fs.statSync(inputPath).size : 0;
// Simple counter mode
if (transform === "count") {
return new Promise((resolvePromise) => {
let lines = 0, words = 0, bytes = 0;
const rs = createReadStream(inputPath, { encoding: "utf-8" });
rs.on("data", chunk => {
bytes += Buffer.byteLength(chunk, "utf-8");
lines += (chunk.match(/\\n/g) || []).length;
words += chunk.split(/\\s+/).filter(Boolean).length;
});
rs.on("end", () => resolvePromise(\`[${name}] \${inputPath}\\n Lines: \${lines}\\n Words: \${words}\\n Bytes: \${bytes}\\n Size: \${(size / 1024).toFixed(1)} KB\`));
rs.on("error", e => resolvePromise(\`[${name} Error] \${e.message}\`));
});
}
// Line-by-line transform mode
return new Promise((resolvePromise) => {
const outputs = [];
const rs = createReadStream(inputPath, { encoding: "utf-8" });
const rl = require("readline").createInterface({ input: rs });
rl.on("line", line => {
let result = line;
switch (transform) {
case "upper": result = line.toUpperCase(); break;
case "lower": result = line.toLowerCase(); break;
case "reverse": result = line.split("").reverse().join(""); break;
case "base64": result = Buffer.from(line, "utf-8").toString("base64"); break;
default:
if (transform.startsWith("grep:")) {
const pat = transform.slice(5);
if (!line.includes(pat) && !new RegExp(pat).test(line)) return;
} else if (transform.startsWith("sed:")) {
const m = transform.match(/^sed:(.+?)\\/(.+?)\\/(.*)$/);
if (m) result = line.replace(new RegExp(m[1], "g"), m[2]);
}
}
outputs.push(result);
});
rl.on("close", () => {
const text = outputs.join("\\n");
if (outputPath) {
fs.writeFileSync(outputPath, text, "utf-8");
resolvePromise(\`[${name}] Processed \${outputs.length} lines → \${outputPath} (\${text.length} chars)\`);
} else {
const preview = text.slice(0, 3000);
const truncated = text.length > 3000 ? \`\\n... (truncated, \${text.length} total chars)\` : "";
resolvePromise(\`[${name}] \${inputPath} → \${outputs.length} lines\\n\${preview}\${truncated}\`);
}
});
rl.on("error", e => resolvePromise(\`[${name} Error] \${e.message}\`));
});
} catch (e) {
return \`[${name} Error] \${e.message}\`;
}
};
`;
}
function generateAuthModule(name, description) {
return `// Auto-generated module: ${name}
// Feature: Authentication/Authorization tools
// Generated by Phantom self-improvement engine
import { createHash, randomBytes } from "crypto";
/**
* Auth tools — JWT decode, hash verify, token analysis.
* Usage: @${name}|decode|<jwt_token>
* @${name}|hash|<password> [--algorithm=<sha256|sha512|md5>]
* @${name}|random|<length>
*/
export default async function(input) {
try {
const args = (input || "").trim();
if (!args) {
return \`[${name}] Authentication Tools
Usage:
@${name}|decode|<jwt_token> Decode a JWT (no signature verify)
@${name}|hash|<password> Hash a password (default: sha256)
@${name}|hash|<password> --algo=sha512
@${name}|random|32 Generate random hex string (N bytes)
@${name}|basic|user:pass Encode Basic auth header
Examples:
${name}|decode|eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0.dozjgNryP4J3jVmNHx0I
${name}|hash|mypassword --algo=sha512
${name}|random|16\`;
}
const parts = args.split("|").map(s => s.trim());
const command = parts[0];
const data = parts.slice(1).join("|");
switch (command) {
case "decode": {
const token = data;
if (!token || !token.includes(".")) return \`[${name}] Invalid JWT format\`;
const segments = token.split(".");
if (segments.length < 2) return \`[${name}] Invalid JWT format\`;
const decode = (s) => {
try {
return JSON.parse(Buffer.from(s.replace(/-/g, "+").replace(/_/g, "/"), "base64").toString("utf-8"));
} catch { return { error: "Cannot decode" }; }
};
const header = decode(segments[0]);
const payload = decode(segments[1]);
const lines = [
\`[${name}] JWT Decoded\`,
\`── Header ──\`,
JSON.stringify(header, null, 2),
\`── Payload ──\`,
JSON.stringify(payload, null, 2),
];
if (segments[2]) lines.push(\`── Signature ──\${segments[2].slice(0, 32)}...\`);
return lines.join("\\n");
}
case "hash": {
const algoMatch = args.match(/--algo=(\\w+)/);
const algo = algoMatch ? algoMatch[1] : "sha256";
if (!["sha256", "sha512", "md5", "sha1"].includes(algo)) return \`[${name}] Unsupported algorithm: \${algo}. Use sha256, sha512, md5, or sha1.\`;
const hash = createHash(algo).update(data).digest("hex");
return \`[${name}] \${algo} hash of "\${data.slice(0, 50)}":\n\${hash}\`;
}
case "random": {
const len = parseInt(data) || 16;
const hex = randomBytes(Math.ceil(len / 2)).toString("hex").slice(0, len);
return \`[${name}] Random hex (\${len} chars): \${hex}\`;
}
case "basic": {
// Encode Basic auth header value
const encoded = Buffer.from(data).toString("base64");
return \`[${name}] Basic Auth:\n Header: Authorization: Basic \${encoded}\n Value: \${data}\`;
}
default:
return \`[${name}] Unknown command: \${command}. Use @${name}|--help for usage.\`;
}
} catch (e) {
return \`[${name} Error] \${e.message}\`;
}
};
`;
}
function generateCacheModule(name, description) {
return `// Auto-generated module: ${name}
// Feature: Caching layer for tool outputs
// Generated by Phantom self-improvement engine
import fs from "fs";
import { resolve } from "path";
import { homedir } from "os";
import { createHash } from "crypto";
const CACHE_DIR = resolve(homedir(), ".config", "phantom", "cache");
const DEFAULT_TTL = 5 * 60 * 1000; // 5 minutes
/**
* Cache layer — store and retrieve cached tool outputs.
* Usage: @${name}|get|<key>
* @${name}|set|<key>|<value> [--ttl=<ms>]
* @${name}|del|<key>
* @${name}|clear
* @${name}|stats
*/
export default async function(input) {
try {
if (!fs.existsSync(CACHE_DIR)) fs.mkdirSync(CACHE_DIR, { recursive: true });
const args = (input || "").trim();
if (!args) {
return \`[${name}] Cache System
Usage:
@${name}|get|<key> Get cached value
@${name}|set|<key>|<value> Set cached value (default TTL: 5min)
@${name}|del|<key> Delete cached entry
@${name}|clear Clear entire cache
@${name}|stats Show cache statistics
Used internally by Phantom to cache repeated tool outputs.\`;
}
const cacheFile = resolve(CACHE_DIR, "registry.json");
let registry = {};
try {
if (fs.existsSync(cacheFile)) registry = JSON.parse(fs.readFileSync(cacheFile, "utf-8"));
} catch {}
const parts = args.split("|").map(s => s.trim());
const command = parts[0];
switch (command) {
case "get": {
const key = parts.slice(1).join("|");
if (!key) return \`[${name}] Usage: @${name}|get|<key>\`;
const hashKey = createHash("md5").update(key).digest("hex");
const entry = registry[hashKey];
if (!entry) return \`[${name}] Cache miss for: \${key.slice(0, 60)}\`;
if (Date.now() - entry.ts > (entry.ttl || DEFAULT_TTL)) {
delete registry[hashKey];
fs.writeFileSync(cacheFile, JSON.stringify(registry, null, 2), "utf-8");
return \`[${name}] Cache expired for: \${key.slice(0, 60)}\`;
}
return \`[${name}] Cache hit for: \${key.slice(0, 60)}\\n\${entry.value.slice(0, 3000)}\`;
}
case "set": {
const separatorIdx = args.indexOf("|", args.indexOf("|") + 1);
if (separatorIdx === -1) return \`[${name}] Usage: @${name}|set|<key>|<value>\`;
const key = args.slice(4, separatorIdx).trim();
const value = args.slice(separatorIdx + 1).trim();
const ttlMatch = args.match(/--ttl=(\\d+)/);
const ttl = ttlMatch ? parseInt(ttlMatch[1]) * 1000 : DEFAULT_TTL;
const hashKey = createHash("md5").update(key).digest("hex");
registry[hashKey] = { key, ts: Date.now(), ttl, length: value.length };
if (!fs.existsSync(CACHE_DIR)) fs.mkdirSync(CACHE_DIR, { recursive: true });
fs.writeFileSync(resolve(CACHE_DIR, \`\${hashKey}.cache\`), value, "utf-8");
fs.writeFileSync(cacheFile, JSON.stringify(registry, null, 2), "utf-8");
return \`[${name}] Cached (\${value.length} chars, TTL: \${ttl / 1000}s)\`;
}
case "del": {
const dkey = parts.slice(1).join("|");
const dhash = createHash("md5").update(dkey).digest("hex");
if (registry[dhash]) {
delete registry[dhash];
const cachePath = resolve(CACHE_DIR, \`\${dhash}.cache\`);
if (fs.existsSync(cachePath)) fs.unlinkSync(cachePath);
fs.writeFileSync(cacheFile, JSON.stringify(registry, null, 2), "utf-8");
return \`[${name}] Deleted: \${dkey.slice(0, 60)}\`;
}
return \`[${name}] Not found: \${dkey.slice(0, 60)}\`;
}
case "clear": {
const count = Object.keys(registry).length;
if (fs.existsSync(CACHE_DIR)) {
const files = fs.readdirSync(CACHE_DIR);
for (const f of files) fs.unlinkSync(resolve(CACHE_DIR, f));
}
fs.writeFileSync(cacheFile, "{}", "utf-8");
return \`[${name}] Cache cleared (\${count} entries removed)\`;
}
case "stats": {
const count = Object.keys(registry).length;
let totalSize = 0, validEntries = 0, expiredEntries = 0;
for (const [k, v] of Object.entries(registry)) {
const cachePath = resolve(CACHE_DIR, \`\${k}.cache\`);
if (fs.existsSync(cachePath)) totalSize += fs.statSync(cachePath).size;
if (Date.now() - v.ts > (v.ttl || DEFAULT_TTL)) expiredEntries++;
else validEntries++;
}
return \`[${name}] Cache Stats:
Total entries: \${count}
Valid: \${validEntries}
Expired: \${expiredEntries}
Disk size: \${(totalSize / 1024).toFixed(1)} KB
Cache dir: \${CACHE_DIR}\`;
}
default:
return \`[${name}] Unknown command: \${command}. Use @${name}|--help for usage.\`;
}
} catch (e) {
return \`[${name} Error] \${e.message}\`;
}
};
`;
}
function generateIPCModule(name, description) {
return `// Auto-generated module: ${name}
// Feature: Inter-process communication utilities
// Generated by Phantom self-improvement engine
import { spawn, execSync } from "child_process";
import { existsSync } from "fs";
import { isMainThread, parentPort, Worker } from "worker_threads";
/**
* IPC tools — spawn processes, communicate via workers.
* Usage: @${name}|exec|<command>
* @${name}|bg|<command> Start background process
* @${name}|ps List child processes
*/
export default async function(input) {
try {
const args = (input || "").trim();
if (!args) {
return \`[${name}] IPC Utilities
Usage:
@${name}|exec|<command> Execute and capture output
@${name}|bg|<command> Start background process
@${name}|ps List Phantom child processes
For multi-process coordination and long-running tasks.\`;
}
const parts = args.split("|").map(s => s.trim());
const command = parts[0];
const rest = parts.slice(1).join("|");
switch (command) {
case "exec": {
if (!rest) return \`[${name}] Usage: @${name}|exec|<shell command>\`;
// Use shell for full command support
const result = execSync(rest, {
encoding: "utf-8",
timeout: 60000,
maxBuffer: 1024 * 1024,
});
return \`[${name}] Executed:\n\${rest}\n── Output ──\n\${result.trim().slice(0, 4000) || "(empty)"}\`;
}
case "bg": {
if (!rest) return \`[${name}] Usage: @${name}|bg|<command>\`;
const proc = spawn("sh", ["-c", rest], {
stdio: ["ignore", "pipe", "pipe"],
detached: true,
});
let out = "";
proc.stdout.on("data", d => { out += d.toString(); });
proc.stderr.on("data", d => { out += d.toString(); });
// Don't await — let it run in background
return \`[${name}] Started background process (PID: \${proc.pid})\nCommand: \${rest}\`;
}
case "ps": {
// List phantom-related processes
const result = execSync("ps aux | grep -E 'node|phantom' | grep -v grep", {
encoding: "utf-8",
timeout: 5000,
}).trim();
if (!result) return \`[${name}] No Phantom child processes found\`;
const lines = result.split("\\n");
return \`[${name}] Child Processes (\${lines.length}):\n\${lines.join("\\n")}\`;
}
default:
return \`[${name}] Unknown command: \${command}. Use @${name}|--help for usage.\`;
}
} catch (e) {
return \`[${name} Error] \${e.message}\`;
}
};
`;
}
// ── 6. FULL IMPROVEMENT CYCLE ──────────────────────────────
/**
* Full self-improvement pipeline:
* 1. Take a project URL/path
* 2. Clone/scan it
* 3. Analyze Phantom's own capabilities
* 4. Gap analysis
* 5. Pattern extraction
* 6. Code generation for each gap
* 7. Apply changes
* 8. Test and commit
*
* Returns detailed results report.
*/
export async function fullImprovementCycle(target) {
const startTime = Date.now();
const report = {
target,
started: new Date().toISOString(),
elapsed: null,
project: null,
selfAnalysis: null,
gaps: [],
generated: [],
applied: [],
errors: [],
};
try {
// Step 1: Clone/scan the project
const project = cloneOrLocate(target);
report.project = { name: project.name, path: project.path, isRemote: project.isRemote };
report.project.scan = scanProject(project.path);
// Step 2: Analyze self
const self = analyzeSelf();
report.selfAnalysis = {
tools: self.tools.length,
exports: self.exports.length,
architecture: self.architecture,
featureCount: self.features.filter(f => f.present).length,
};
// Step 3: Gap analysis
const gaps = gapAnalysis(report.project.scan, self);
report.gaps = gaps;
// Step 4: Extract patterns
const patterns = extractPatterns(project.path);
const patternPath = savePatterns(project.name, patterns);
report.patternsSaved = patternPath;
// Step 5: Generate code for each gap (up to 8 per cycle)
const maxGens = Math.min(gaps.length, 8);
for (let i = 0; i < maxGens; i++) {
try {
const gap = gaps[i];
const generated = generateModule(gap, patterns);
if (generated.filePath) {
report.generated.push({
gap: gap.feature,
file: generated.filePath,
toolName: generated.toolName || generated.moduleName,
});
}
} catch (e) {
report.errors.push({ phase: "generate", feature: gaps[i].feature, error: e.message });
}
}
// Step 6: Validate generated code
for (const gen of report.generated) {
try {
if (gen.file && fs.existsSync(gen.file)) {
execSync(`node --check "${gen.file}"`, { encoding: "utf-8", timeout: 10000 });
report.applied.push({ file: gen.file, status: "valid" });
}
} catch (e) {
report.errors.push({ phase: "validate", file: gen.file, error: e.stderr?.slice(0, 200) || e.message });
}
}
// Step 7: Auto-apply validated modules — copy to learned + register
if (report.applied.length > 0) {
try {
const applyResults = autoApplyGenerated();
report.autoApplied = applyResults;
} catch (e) {
report.errors.push({ phase: "auto-apply", error: e.message });
}
}
// Step 8: Check git_sync schedule — remind about pushes
try {
const st = loadState();
if (st?.gitSync?.nextPush && Date.now() >= st.gitSync.nextPush) {
report.gitSyncDue = true;
report.gitSyncNext = new Date(st.gitSync.nextPush).toISOString().slice(0, 10);
}
} catch {}
// Save full report to disk
report.elapsed = Date.now() - startTime;
const st = loadState();
st.self_improve.push({
target,
ts: report.started,
elapsed: report.elapsed,
gaps: gaps.length,
generated: report.generated.length,
validated: report.applied.length,
errors: report.errors.length,
});
st.imported_features.push(...report.generated.map(g => g.toolName));
// Trim history
if (st.self_improve.length > 20) st.self_improve = st.self_improve.slice(-20);
if (st.imported_features.length > 100) st.imported_features = st.imported_features.slice(-100);
saveState(st);
return report;
} catch (e) {
report.elapsed = Date.now() - startTime;
report.errors.push({ phase: "overall", error: e.message });
const st = loadState();
st.self_improve.push({ target, ts: report.started, elapsed: report.elapsed, error: e.message });
saveState(st);
return report;
}
}
/**
* Quick self-scan: show what Phantom knows about itself and any pending gaps.
*/
export function selfImproveStatus() {
const st = loadState();
const self = analyzeSelf();
const patterns = loadAllPatterns();
const learnedModulesDir = GENERATED_DIR;
return {
generation: st.generation,
cyclesRun: st.self_improve?.length || 0,
featuresImported: st.imported_features || [],
patternsLearned: Object.keys(patterns),
pendingImprovements: st.self_improve?.filter(s => s.error)?.length || 0,
selfOverview: {
tools: self.tools.length,
exports: self.exports.length,
features: self.features.filter(f => f.present).length,
gaps: self.features.filter(f => !f.present).map(f => f.name),
architecture: self.architecture,
},
generatedFiles: (() => {
if (!fs.existsSync(learnedModulesDir)) return [];
return fs.readdirSync(learnedModulesDir).filter(f => f.endsWith(".mjs"));
})(),
};
}
// ── Utility ───────────────────────────────────────────────
function camelCase(str) {
return str
.replace(/[^a-zA-Z0-9\s_-]/g, "")
.replace(/[-_\s]+(.)?/g, (_, c) => c ? c.toUpperCase() : "")
.replace(/^(.)/, (c) => c.toLowerCase());
}
// ── 8. AUTO-APPLY — register generated modules at runtime ──
// Copies generated → learned, then hot-loads into hackerTools
export function autoApplyGenerated() {
const results = [];
if (!fs.existsSync(GENERATED_DIR)) return results;
const files = fs.readdirSync(GENERATED_DIR).filter(f => f.endsWith(".mjs"));
ensureDir(LEARNED_MODULES_DIR);
for (const f of files) {
const src = resolve(GENERATED_DIR, f);
const dst = resolve(LEARNED_MODULES_DIR, f);
const toolName = f.replace(/\.mjs$/, "");
// Validate before applying
try {
execSync(`node --check "${src}"`, { encoding: "utf-8", timeout: 10000 });
} catch (e) {
results.push({ file: f, status: "syntax_error", error: e.stderr?.slice(0, 100) });
try { fs.unlinkSync(src); } catch {}
continue;
}
// Copy to learned
fs.copyFileSync(src, dst);
results.push({ file: f, status: "applied", toolName });
}
// Update evolution state
const st = loadState();
st.generation++;
st.last_evolve = new Date().toISOString();
saveState(st);
return results;
}
// ── 10. SELF-EVOLVE — run improvement cycles against Phantom itself ──
export function analyzeSelfGaps() {
const self = analyzeSelf();
const gaps = [];
// Architecture gaps
if (!self.architecture.moduleCount) gaps.push({ area: "architecture", issue: "Module count unknown", priority: "low" });
// Check what Phantom might want from its own patterns
const ownDir = PHANTOM_DIR;
if (fs.existsSync(ownDir)) {
const srcFiles = fs.readdirSync(ownDir).filter(f => f.endsWith(".mjs"));
for (const f of srcFiles) {
try {
const content = fs.readFileSync(resolve(ownDir, f), "utf-8");
// Detect external tool mentions
const extTools = content.match(/execSync\(`(\w+)/g);
if (extTools) {
for (const t of extTools) {
const name = t.replace(/execSync\(`/, "");
if (!self.tools.includes(name)) {
gaps.push({ area: "missing_wrapper", issue: name, priority: "medium", source: f });
}
}
}
} catch {}
}
}
return gaps;
}
export async function selfEvolve() {
const startTime = Date.now();
const report = {
target: "self",
started: new Date().toISOString(),
generated: [],
applied: [],
errors: [],
};
try {
// 1. Scan self
const self = analyzeSelf();
report.selfTools = self.tools.length;
// 2. Find own gaps — detect external binaries referenced but not wrapped
const gaps = analyzeSelfGaps();
report.gapsFound = gaps.length;
// 3. Generate wrappers for missing binaries
for (const gap of gaps) {
if (gap.area === "missing_wrapper") {
try {
const gen = generateTool(gap.issue, `Auto-detected missing wrapper from ${gap.source}`);
if (gen.filePath) {
report.generated.push({ tool: gap.issue, file: gen.filePath });
}
} catch (e) {
report.errors.push({ phase: "generate", tool: gap.issue, error: e.message });
}
}
}
// 4. Auto-apply any generated code
if (report.generated.length > 0) {
const applyResults = autoApplyGenerated();
report.applied = applyResults;
}
// 5. Update state
report.elapsed = Date.now() - startTime;
const st = loadState();
st.self_improve.push({
target: "self",
ts: report.started,
elapsed: report.elapsed,
gaps: gaps.length,
generated: report.generated.length,
applied: report.applied.length,
errors: report.errors.length,
});
saveState(st);
return report;
} catch (e) {
report.elapsed = Date.now() - startTime;
report.errors.push({ phase: "overall", error: e.message });
return report;
}
}
// ── 11. CONTINUOUS EVOLUTION — schedule + health ──
export function scheduleAutoEvolve(intervalMs = 3600000) {
// intervalMs = default 1 hour
const st = loadState();
st.scheduledInterval = intervalMs;
st.nextScheduledEvolve = Date.now() + intervalMs;
saveState(st);
return { ok: true, nextRun: new Date(st.nextScheduledEvolve).toISOString(), intervalMs };
}
export function getScheduledEvolve() {
const st = loadState();
if (!st.nextScheduledEvolve) return { scheduled: false };
return {
scheduled: true,
nextRun: new Date(st.nextScheduledEvolve).toISOString(),
intervalMs: st.scheduledInterval,
due: Date.now() >= st.nextScheduledEvolve,
};
}
export async function checkAndEvolve() {
const sched = getScheduledEvolve();
if (!sched.due) return { skipped: "not due yet", nextRun: sched.nextRun };
return await selfEvolve();
}
// ── 12. READ ANY FILE — for self-editing context ──
export function readSelfSource(filePath) {
const fullPath = resolve(PHANTOM_DIR, filePath);
try {
const content = fs.readFileSync(fullPath, "utf-8");
const lines = content.split("\n");
return {
path: fullPath,
lines: lines.length,
size: content.length,
content: content.slice(0, 50000),
truncated: content.length > 50000,
};
} catch (e) {
return { error: e.message };
}
}
export function editSelfFile(filePath, oldString, newString) {
const fullPath = resolve(PHANTOM_DIR, filePath);
try {
let content = fs.readFileSync(fullPath, "utf-8");
if (!content.includes(oldString)) {
return { ok: false, error: "old_string not found in file" };
}
content = content.replace(oldString, newString);
fs.writeFileSync(fullPath, content, "utf-8");
return { ok: true, path: fullPath };
} catch (e) {
return { ok: false, error: e.message };
}
}
// ── 13. MULTI-LANGUAGE SCANNERS (Rust, Ruby, Java, C/C++, Shell) ──
function analyzeRustFile(fullPath, result) {
const content = safeReadFile(fullPath);
if (!content) return;
result.patterns.moduleSystem = "rust";
// pub fn, pub async fn, fn (private), pub struct, pub enum, pub trait, impl blocks
const exports = content.matchAll(/^\s*(?:pub(?:\s+async)?\s+)?fn\s+(\w+)\s*\(([^)]*)\)/gm);
for (const m of exports) {
const name = m[1]; const params = m[2].trim();
result.exports.push({ name, file: relative(result.path, fullPath), type: "fn" });
const toolParams = ["query","target","url","domain","path","text","data","name","input","args","cmd","host","port","addr"];
if (toolParams.some(p => params.includes(p))) result.tools.push({ name, param: (params.split(",")[0]||"input").trim(), file: relative(result.path, fullPath) });
}
// struct/enum/trait as exports
for (const kw of ["struct", "enum", "trait"]) {
const types = content.matchAll(new RegExp(`^\\s*pub\\s+${kw}\\s+(\\w+)`, "gm"));
for (const m of types) result.exports.push({ name: m[1], file: relative(result.path, fullPath), type: kw });
}
// Feature detection
if (/(tokio|async_std|smol)/.test(content) && !result.features.includes("async"))
result.features.push("async");
if (/(serde|serde_json|bincode)/.test(content) && !result.features.includes("serialization"))
result.features.push("serialization");
if (/(reqwest|hyper|actix_web|warp|axum|rocket)/.test(content)) {
result.patterns.hasHttpServer = true;
result.features.push("http");
}
if (/(clap|structopt|argh)/.test(content)) result.patterns.hasCli = true;
if (/(rusqlite|diesel|sqlx|mongodb|redis)/.test(content) && !result.features.includes("database"))
result.features.push("database");
if (/(jsonwebtoken|oauth2|bcrypt|argon2)/.test(content) && !result.features.includes("auth"))
result.features.push("auth");
if (/(tungstenite|tokio_tungstenite|socket2)/.test(content) && !result.features.includes("websocket"))
result.features.push("websocket");
if (/(lru_cache|moka|cached)/.test(content) && !result.features.includes("cache"))
result.features.push("cache");
}
function analyzeRubyFile(fullPath, result) {
const content = safeReadFile(fullPath);
if (!content) return;
result.patterns.moduleSystem = "ruby";
const funcs = content.matchAll(/^\s*(?:def\s+(?:self\.)?(\w+)|def\s+(\w+))/gm);
for (const m of funcs) {
const name = m[1] || m[2];
if (!name) continue;
result.exports.push({ name, file: relative(result.path, fullPath) });
result.tools.push({ name, param: "input", file: relative(result.path, fullPath) });
}
const classes = content.matchAll(/^\s*class\s+(\w+)/gm);
for (const m of classes) result.exports.push({ name: m[1], file: relative(result.path, fullPath), type: "class" });
const modules = content.matchAll(/^\s*module\s+(\w+)/gm);
for (const m of modules) result.exports.push({ name: m[1], file: relative(result.path, fullPath), type: "module" });
// Feature detection
if (/(sinatra|rails|rack)/.test(content)) result.patterns.hasHttpServer = true;
if (/(thor|rake|optionparser)/.test(content)) result.patterns.hasCli = true;
if (/require\s+['\"]?(net\/http|httparty|faraday|rest-client)/.test(content)) result.features.push("http");
if (/(sequel|activerecord|mongoid|redis)/.test(content) && !result.features.includes("database"))
result.features.push("database");
if (/(jwt|devise|bcrypt|omniauth)/.test(content) && !result.features.includes("auth"))
result.features.push("auth");
}
function analyzeJavaFile(fullPath, result) {
const content = safeReadFile(fullPath);
if (!content) return;
result.patterns.moduleSystem = "java";
const methods = content.matchAll(/^\s*(?:public\s+)?(?:static\s+)?(?:\w+)\s+(\w+)\s*\(([^)]*)\)\s*(?:throws\s+\w+)?\s*\{/gm);
for (const m of methods) {
const name = m[1]; const params = m[2].trim();
result.exports.push({ name, file: relative(result.path, fullPath), type: "method" });
const toolParams = ["query","target","url","domain","path","text","data","name","input","args","cmd","host","port"];
if (toolParams.some(p => params.includes(p))) result.tools.push({ name, param: (params.split(",")[0]||"input").trim(), file: relative(result.path, fullPath) });
}
const classes = content.matchAll(/^\s*(?:public\s+)?(?:abstract\s+)?(?:class|interface|enum)\s+(\w+)/gm);
for (const m of classes) result.exports.push({ name: m[1], file: relative(result.path, fullPath), type: "class" });
// Feature detection
if (/(spring|jakarta\.ws|javax\.ws|rest|@RestController)/.test(content)) result.patterns.hasHttpServer = true;
if (/(jdbc|hibernate|jpa|mybatis|mongo)/.test(content) && !result.features.includes("database"))
result.features.push("database");
if (/(jwt|spring-security|oauth|bcrypt)/.test(content) && !result.features.includes("auth"))
result.features.push("auth");
if (/(okhttp|httpclient|unirest)/.test(content)) result.features.push("http");
if (/(slf4j|log4j|java\.util\.logging)/.test(content) && !result.features.includes("logging"))
result.features.push("logging");
}
function analyzeCppFile(fullPath, result) {
const content = safeReadFile(fullPath);
if (!content) return;
result.patterns.moduleSystem = "cpp";
// Function declarations (returnType functionName(params) | auto functionName(params) ->)
const funcs = content.matchAll(/(?:^\s*(?:\w+[:\*&\s]+)+(\w+)\s*\(([^)]*)\)\s*(?:const|override|noexcept|throw)?\s*\{|^\s*auto\s+(\w+)\s*\(([^)]*)\))/gm);
for (const m of funcs) {
const name = m[1] || m[3]; const params = (m[2] || m[4] || "").trim();
if (!name || name === "if" || name === "while" || name === "for") continue;
result.exports.push({ name, file: relative(result.path, fullPath), type: "fn" });
const toolParams = ["query","target","url","path","data","name","input","args","host","port"];
if (toolParams.some(p => params.includes(p))) result.tools.push({ name, param: (params.split(",")[0]||"input").trim(), file: relative(result.path, fullPath) });
}
const classes = content.matchAll(/^\s*(?:class|struct)\s+(\w+)/gm);
for (const m of classes) result.exports.push({ name: m[1], file: relative(result.path, fullPath), type: "class" });
// Feature detection
if (/(boost\.asio|poco|libcurl|cpr)/.test(content)) result.features.push("http");
if (/(sqlite3|libpq|mysqlclient|soci|redis-plus-plus)/.test(content) && !result.features.includes("database"))
result.features.push("database");
if (/(openssl|ssl|jwt)/.test(content) && !result.features.includes("auth"))
result.features.push("auth");
if (/(nlohmann\/json|rapidjson|jsoncpp)/.test(content) && !result.features.includes("serialization"))
result.features.push("serialization");
if (/(spdlog|glog|boost\.log)/.test(content) && !result.features.includes("logging"))
result.features.push("logging");
}
function analyzeShellFile(fullPath, result) {
const content = safeReadFile(fullPath);
if (!content) return;
result.patterns.moduleSystem = "shell";
const funcs = content.matchAll(/^\s*(?:function\s+)?(\w+)\s*\(\s*\)\s*\{/gm);
for (const m of funcs) {
const name = m[1];
if (name === "if" || name === "while" || name === "for" || name === "case") continue;
result.exports.push({ name, file: relative(result.path, fullPath), type: "fn" });
result.tools.push({ name, param: "input", file: relative(result.path, fullPath) });
}
// Detect curl/wget usage as features
if (/curl\s/.test(content) && !result.features.includes("http"))
result.features.push("http");
if (/(jq|grep|sed|awk)/.test(content) && !result.features.includes("text-processing"))
result.features.push("text-processing");
if (/(docker|kubectl|podman)/.test(content) && !result.features.includes("container"))
result.features.push("container");
}
// ── 14. LEARN FROM EXTERNAL SOURCES ──
const LEARN_CACHE = resolve(BASE_DIR, "learn_cache");
const GH_SEARCH = "https://api.github.com/search/repositories?q=";
function saveLearnedTopic(topic, results) {
const st = loadState();
if (!st.learned_topics) st.learned_topics = {};
st.learned_topics[topic.toLowerCase()] = {
ts: new Date().toISOString(),
repos: results.repos,
exports: results.exports,
features: results.features,
patterns: results.patterns,
};
saveState(st);
}
function getLearnedTopics() {
const st = loadState();
return st.learned_topics || {};
}
/**
* Search GitHub for repos by topic, clone top N, scan them, extract patterns.
*/
export async function learnTopic(topic, maxRepos = 3) {
const already = getLearnedTopics();
const key = topic.toLowerCase().trim();
if (already[key]) {
return { cached: true, topic, ts: already[key].ts, repos: already[key].repos, exports: already[key].exports };
}
const tempDir = resolve(LEARN_CACHE, key.replace(/[^a-z0-9_-]/g, "_"));
fs.mkdirSync(tempDir, { recursive: true });
// Search GitHub
let repos = [];
try {
const searchUrl = `${GH_SEARCH}${encodeURIComponent(topic)}&sort=stars&per_page=${maxRepos}`;
const raw = execSync(`curl -s "${searchUrl}"`, { encoding: "utf-8", timeout: 15000 });
const data = JSON.parse(raw);
if (data.items) {
repos = data.items.slice(0, maxRepos).map(r => ({ name: r.full_name, url: r.clone_url, stars: r.stargazers_count, desc: r.description }));
}
} catch {}
if (!repos.length) return { error: `No GitHub repos found for "${topic}"`, topic };
const results = { repos, exports: [], features: [], patterns: {} };
const aggregated = { exports: [], features: [], tools: [], path: tempDir, patterns: {} };
for (const repo of repos) {
const repoDir = resolve(tempDir, repo.name.replace("/", "_"));
try {
execSync(`git clone --depth 1 "${repo.url}" "${repoDir}" 2>/dev/null`, { timeout: 60000 });
deepScan(repoDir, aggregated);
// Merge features/categories into results
results.exports.push(...aggregated.exports.slice(0, 20));
for (const f of aggregated.features) {
if (!results.features.includes(f)) results.features.push(f);
}
Object.assign(results.patterns, aggregated.patterns);
} catch { continue; } // skip failed clones
}
// Cleanup cloned repos to save disk
try { fs.rmSync(tempDir, { recursive: true, force: true }); } catch {}
saveLearnedTopic(key, results);
return { cached: false, topic, repos: results.repos, exports: results.exports.slice(0, 30), features: results.features, patterns: results.patterns };
}
/**
* Learn from a single GitHub repo URL — clone and scan.
*/
export async function learnFromRepo(url) {
const match = url.match(/github\.com[:/]([^/]+\/[^/.]+)/);
const name = match ? match[1].replace("/", "_") : `repo_${Date.now()}`;
const tempDir = resolve(LEARN_CACHE, name);
fs.mkdirSync(LEARN_CACHE, { recursive: true });
try { fs.rmSync(tempDir, { recursive: true, force: true }); } catch {}
const result = { exports: [], features: [], tools: [], patterns: {} };
try {
execSync(`git clone --depth 1 "${url}" "${tempDir}"`, { timeout: 120000 });
// Read README/doc files for context
const readmeFiles = ["README.md", "README.rst", "README.txt", "README", "docs/index.md", "docs/README.md"];
for (const rf of readmeFiles) {
const rp = resolve(tempDir, rf);
if (fs.existsSync(rp)) {
try {
const content = fs.readFileSync(rp, "utf-8").slice(0, 8000);
const title = (content.match(/^#\s+(.+)/m) || [])[1] || rf;
result.readme = { title, content, source: rf };
// Extract key sections
const sections = content.match(/^##\s+(.+)$/gm) || [];
result.readme.sections = sections.map(s => s.replace(/^##\s+/, ""));
break;
} catch {}
}
}
deepScan(tempDir, result);
try { fs.rmSync(tempDir, { recursive: true, force: true }); } catch {}
return { url, readme: result.readme || null, exports: result.exports.slice(0, 30), features: result.features, toolsFound: result.tools.length, patterns: result.patterns };
} catch (e) {
return { error: `Failed to clone/scan: ${e.message.slice(0, 200)}`, url };
}
}
/**
* Identify gaps in Phantom's tool coverage and learn them from GitHub.
* Analyzes current tool names/categories, finds missing domains, fetches code.
*/
export async function learnGaps() {
const knownDomains = ["http","auth","database","cache","websocket","ipc","logging","dns","ssl","crypto","json","yaml","xml","csv","image","audio","video","compression","archive","docker","k8s","graphql","grpc","ssh","ftp","smtp","ldap","mqtt","serialization","async","container","text-processing"];
const st = loadState();
const learned = getLearnedTopics();
const ownFeatures = new Set(st.imported_features || []);
// Find missing domains
const featureNames = new Set([...ownFeatures, ...Object.keys(learned)]);
const missing = knownDomains.filter(d => !featureNames.has(d));
if (!missing.length) return { message: "All known domains covered. No gaps detected.", missing: [] };
const results = {};
for (const domain of missing.slice(0, 5)) { // learn top 5 gaps
const res = await learnTopic(`${domain} cli tool`, 2);
results[domain] = {
repos: res.repos?.map(r => r.name) || [],
exports: res.exports?.length || 0,
features: res.features,
cached: res.cached,
};
}
return { message: `Learned ${Object.keys(results).length} new domains`, missing, results };
}
/**
* Fetch a URL and extract code patterns from the content.
*/
export async function learnFromWeb(url) {
try {
const raw = execSync(`curl -sL "${url}"`, { encoding: "utf-8", timeout: 15000 });
// Treat as inline "project" with a single file
const result = { exports: [], features: [], tools: [], patterns: {}, path: url };
// Try to detect language and analyze
if (url.endsWith(".py") || url.includes("python")) analyzePythonFile(url, result);
else if (url.endsWith(".rs") || url.includes("rust")) analyzeRustFile(url, result);
else if (url.endsWith(".rb") || url.includes("ruby")) analyzeRubyFile(url, result);
else if (url.endsWith(".java") || url.includes("java")) analyzeJavaFile(url, result);
else if (/\.(c|cpp|h|hpp)$/i.test(url)) analyzeCppFile(url, result);
else if (/\.(sh|bash)$/i.test(url)) analyzeShellFile(url, result);
// Fallback: extract code blocks from HTML/markdown
if (!result.exports.length) {
const codeBlocks = raw.match(/```(?:\w+)?\n([\s\S]*?)```/g);
if (codeBlocks) {
result.exports = codeBlocks.map((b, i) => ({ name: `code_block_${i + 1}`, source: "web", type: "snippet" }));
result.features.push("code-snippet");
}
// Extract links to other resources
const links = raw.match(/https?:\/\/github\.com\/[^/\s]+\/[^/\s"')\]]+/g);
if (links) {
result.features.push(`found ${links.length} github links`);
result.exports.push({ name: "github_links", count: links.length, samples: links.slice(0, 3) });
}
}
return { url, exports: result.exports.slice(0, 20), features: result.features, snippets: result.exports.filter(e => e.type === "snippet").length };
} catch (e) {
return { error: `Failed to fetch/parse: ${e.message.slice(0, 200)}`, url };
}
}
// ── 15. EXTENDED PROJECT SCANNER with multi-language support ──
/**
* Scan a project directory — enhanced to dispatch to language-specific analyzers.
* Replaces the inline file-walker with proper dispatch for .rs, .rb, .java, .c, .cpp, .sh.
*/
function scanFileEnhanced(fullPath, result) {
const ext = extname(fullPath).toLowerCase();
const base = basename(fullPath).toLowerCase();
// Skip non-source and common ignorables
if (/node_modules|\.git|__pycache__|target|build|vendor|dist|\.venv/.test(fullPath)) return;
if (/\.(min\.|bundle|lock|sum|svg|png|jpg|gif|ico|woff2?|ttf|eot|pdf|zip|tar|gz|bz2|xz|exe|dll|so|dylib|bin|dat|pyc|pyo)$/.test(ext)) return;
if (/^(package-lock|yarn\.lock|pnpm-lock|go\.sum|Cargo\.lock|Gemfile\.lock|poetry\.lock)/.test(base)) return;
// Dispatch by extension
switch (ext) {
case ".mjs": case ".js": case ".ts": analyzeJSFile(fullPath, result); break;
case ".py": analyzePythonFile(fullPath, result); break;
case ".go": analyzeGoFile(fullPath, result); break;
case ".rs": analyzeRustFile(fullPath, result); break;
case ".rb": analyzeRubyFile(fullPath, result); break;
case ".java": analyzeJavaFile(fullPath, result); break;
case ".c": case ".h": case ".cpp": case ".hpp": case ".cc": case ".cxx": analyzeCppFile(fullPath, result); break;
case ".sh": case ".bash": case ".zsh": analyzeShellFile(fullPath, result); break;
}
}
/**
* Walk a directory tree and scan all source files with multi-language support.
*/
export function deepScan(dir, result) {
try {
const entries = fs.readdirSync(dir, { withFileTypes: true });
for (const entry of entries) {
const full = resolve(dir, entry.name);
if (entry.isDirectory()) {
if (!/node_modules|\.git|__pycache__|target|build|vendor|dist|\.venv|\.cargo/.test(entry.name))
deepScan(full, result);
} else if (entry.isFile()) {
scanFileEnhanced(full, result);
}
}
} catch {}
}
// ── Re-export learned topic listing ──
export function listLearnedTopics() {
const st = loadState();
const topics = st.learned_topics || {};
return Object.entries(topics).map(([topic, info]) => ({
topic,
learned: info.ts,
repos: info.repos?.length || 0,
exports: info.exports?.length || 0,
}));
}