hybard-agent 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +41 -0
- package/bin/.cmd +0 -0
- package/bin/agent.js +673 -0
- package/bin/cli.js +326 -0
- package/bin/hybard.cmd +2 -0
- package/knowledge/BENGALI_GUIDE.md +436 -0
- package/knowledge/CAPABILITIES.md +448 -0
- package/knowledge/INDEX.md +204 -0
- package/knowledge/KNOWLEDGE_BASE.md +1174 -0
- package/knowledge/README.md +97 -0
- package/knowledge/SYSTEM_PROMPT.md +310 -0
- package/lib/agent.js +730 -0
- package/lib/analyzer.js +330 -0
- package/lib/coding-agent.js +87 -0
- package/lib/coding-model.js +585 -0
- package/lib/engine.js +591 -0
- package/lib/hybard-agent.js +1063 -0
- package/lib/main.dart +32 -0
- package/lib/models.js +357 -0
- package/lib/online.js +654 -0
- package/lib/server.js +278 -0
- package/lib/ui.js +96 -0
- package/package.json +50 -0
package/lib/engine.js
ADDED
|
@@ -0,0 +1,591 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hybard Execution Engine — Autonomous coding assistant core.
|
|
3
|
+
*
|
|
4
|
+
* Silent background tool execution, robust parsing, autonomous code modification.
|
|
5
|
+
* Security, Git, Production monitoring, AI evaluation built-in.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const fs = require('fs');
|
|
9
|
+
const path = require('path');
|
|
10
|
+
const os = require('os');
|
|
11
|
+
const { spawnSync } = require('child_process');
|
|
12
|
+
const { HybardAI } = require('./online');
|
|
13
|
+
const { ProjectDetector, CodebaseSearch } = require('./hybard-agent');
|
|
14
|
+
|
|
15
|
+
// ─── Security Module ─────────────────────────────────────
|
|
16
|
+
|
|
17
|
+
const BLOCKED_COMMANDS = [
|
|
18
|
+
'rm -rf /', 'rm -rf /*', 'mkfs', 'dd if=', ':(){:|:&};:',
|
|
19
|
+
'chmod -R 777 /', 'wget', 'curl.*|.*sh', '> /dev/sda',
|
|
20
|
+
'shutdown', 'reboot', 'halt', 'init 0', 'init 6',
|
|
21
|
+
'drop table', 'drop database', 'truncate',
|
|
22
|
+
];
|
|
23
|
+
|
|
24
|
+
const BLOCKED_PATHS = [
|
|
25
|
+
'/etc/passwd', '/etc/shadow', '/etc/sudoers',
|
|
26
|
+
'/boot', '/sys', '/proc',
|
|
27
|
+
'C:\\Windows\\System32', 'C:\\Windows\\SysWOW64',
|
|
28
|
+
];
|
|
29
|
+
|
|
30
|
+
const SENSITIVE_PATTERNS = [
|
|
31
|
+
/(?:api[_-]?key|secret[_-]?key|password|token|credential)\s*[=:]\s*['"][^'"]+['"]/gi,
|
|
32
|
+
/(?:GROQ_API_KEY|OPENAI_API_KEY|OPENROUTER_API_KEY)\s*=\s*\S+/g,
|
|
33
|
+
/(?:Bearer|Authorization)\s+[A-Za-z0-9\-._~+/]+=*/g,
|
|
34
|
+
];
|
|
35
|
+
|
|
36
|
+
class Security {
|
|
37
|
+
static validateCommand(command) {
|
|
38
|
+
const lower = command.toLowerCase().trim();
|
|
39
|
+
for (const blocked of BLOCKED_COMMANDS) {
|
|
40
|
+
if (lower.includes(blocked.toLowerCase())) {
|
|
41
|
+
return { ok: false, reason: `Blocked dangerous command: ${blocked}` };
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return { ok: true };
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
static validatePath(filePath) {
|
|
48
|
+
const abs = path.resolve(filePath);
|
|
49
|
+
for (const blocked of BLOCKED_PATHS) {
|
|
50
|
+
if (abs.toLowerCase().startsWith(blocked.toLowerCase())) {
|
|
51
|
+
return { ok: false, reason: `Blocked access to: ${blocked}` };
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return { ok: true };
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
static scanForSecrets(content) {
|
|
58
|
+
const findings = [];
|
|
59
|
+
for (const pattern of SENSITIVE_PATTERNS) {
|
|
60
|
+
const matches = content.match(pattern);
|
|
61
|
+
if (matches) findings.push(...matches.map(m => m.slice(0, 40) + '...'));
|
|
62
|
+
}
|
|
63
|
+
return findings;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
static validateInput(input) {
|
|
67
|
+
// Prompt injection detection
|
|
68
|
+
const injections = [
|
|
69
|
+
/ignore\s+(previous|all|above)\s+(instructions|prompts)/gi,
|
|
70
|
+
/you\s+are\s+now\s+(?:a|an)\s+/gi,
|
|
71
|
+
/system\s*:\s*/gi,
|
|
72
|
+
/<\|im_start\|>/gi,
|
|
73
|
+
/\[INST\]/gi,
|
|
74
|
+
/<<\|SYS\|>>/gi,
|
|
75
|
+
];
|
|
76
|
+
for (const pattern of injections) {
|
|
77
|
+
if (pattern.test(input)) {
|
|
78
|
+
return { ok: false, reason: 'Possible prompt injection detected' };
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return { ok: true };
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// ─── Git Module ──────────────────────────────────────────
|
|
86
|
+
|
|
87
|
+
class Git {
|
|
88
|
+
static _run(args) {
|
|
89
|
+
try {
|
|
90
|
+
const result = spawnSync('git', args, {
|
|
91
|
+
encoding: 'utf8', cwd: process.cwd(), timeout: 10000,
|
|
92
|
+
});
|
|
93
|
+
return { ok: result.status === 0, stdout: result.stdout.trim(), stderr: result.stderr.trim() };
|
|
94
|
+
} catch (e) {
|
|
95
|
+
return { ok: false, error: e.message };
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
static isRepo() { return this._run(['rev-parse', '--is-inside-work-tree']).ok; }
|
|
100
|
+
static status() { return this._run(['status', '--porcelain']); }
|
|
101
|
+
static diff() { return this._run(['diff']); }
|
|
102
|
+
static diffStaged() { return this._run(['diff', '--cached']); }
|
|
103
|
+
static log(n = 10) { return this._run(['log', '--oneline', `-n${n}`]); }
|
|
104
|
+
static branch() { return this._run(['branch', '--show-current']); }
|
|
105
|
+
static branches() { return this._run(['branch', '-a']); }
|
|
106
|
+
|
|
107
|
+
static commit(message) {
|
|
108
|
+
const add = this._run(['add', '-A']);
|
|
109
|
+
if (!add.ok) return add;
|
|
110
|
+
return this._run(['commit', '-m', message]);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
static createBranch(name) { return this._run(['checkout', '-b', name]); }
|
|
114
|
+
static switchBranch(name) { return this._run(['checkout', name]); }
|
|
115
|
+
static merge(branch) { return this._run(['merge', branch]); }
|
|
116
|
+
static rollback(n = 1) { return this._run(['reset', '--hard', `HEAD~${n}`]); }
|
|
117
|
+
|
|
118
|
+
static diffFile(filePath) { return this._run(['diff', filePath]); }
|
|
119
|
+
|
|
120
|
+
static statusSummary() {
|
|
121
|
+
if (!this.isRepo()) return { isRepo: false };
|
|
122
|
+
const s = this.status();
|
|
123
|
+
const lines = s.stdout ? s.stdout.split('\n').filter(Boolean) : [];
|
|
124
|
+
const branch = this.branch().stdout;
|
|
125
|
+
const modified = lines.filter(l => l.startsWith(' M') || l.startsWith('M'));
|
|
126
|
+
const added = lines.filter(l => l.startsWith('A') || l.startsWith('??'));
|
|
127
|
+
const deleted = lines.filter(l => l.startsWith(' D') || l.startsWith('D'));
|
|
128
|
+
return {
|
|
129
|
+
isRepo: true, branch,
|
|
130
|
+
modified: modified.map(l => l.slice(3)),
|
|
131
|
+
added: added.map(l => l.slice(3)),
|
|
132
|
+
deleted: deleted.map(l => l.slice(3)),
|
|
133
|
+
total: lines.length,
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// ─── Production Module ───────────────────────────────────
|
|
139
|
+
|
|
140
|
+
class Production {
|
|
141
|
+
static getSystemInfo() {
|
|
142
|
+
const cpus = os.cpus();
|
|
143
|
+
const totalMem = os.totalmem();
|
|
144
|
+
const freeMem = os.freemem();
|
|
145
|
+
return {
|
|
146
|
+
platform: os.platform(),
|
|
147
|
+
arch: os.arch(),
|
|
148
|
+
hostname: os.hostname(),
|
|
149
|
+
cpuModel: cpus[0]?.model || 'unknown',
|
|
150
|
+
cpuCores: cpus.length,
|
|
151
|
+
totalRam: (totalMem / (1024 ** 3)).toFixed(2) + ' GB',
|
|
152
|
+
freeRam: (freeMem / (1024 ** 3)).toFixed(2) + ' GB',
|
|
153
|
+
usedRam: ((totalMem - freeMem) / (1024 ** 3)).toFixed(2) + ' GB',
|
|
154
|
+
uptime: (os.uptime() / 3600).toFixed(1) + ' hours',
|
|
155
|
+
nodeVersion: process.version,
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
static getProcessInfo() {
|
|
160
|
+
const result = spawnSync('wmic', ['process', 'get', 'Name,WorkingSetSize', '/format:csv'], {
|
|
161
|
+
encoding: 'utf8', timeout: 5000,
|
|
162
|
+
});
|
|
163
|
+
if (result.status !== 0) return { ok: false };
|
|
164
|
+
return { ok: true, raw: result.stdout.slice(0, 2000) };
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
static healthCheck() {
|
|
168
|
+
const info = this.getSystemInfo();
|
|
169
|
+
const memUsed = parseFloat(info.usedRam);
|
|
170
|
+
const memTotal = parseFloat(info.totalRam);
|
|
171
|
+
const memPercent = (memUsed / memTotal * 100).toFixed(1);
|
|
172
|
+
|
|
173
|
+
return {
|
|
174
|
+
status: memPercent > 90 ? 'critical' : memPercent > 70 ? 'warning' : 'healthy',
|
|
175
|
+
memory: `${memPercent}% used`,
|
|
176
|
+
cpu: `${info.cpuCores} cores`,
|
|
177
|
+
uptime: info.uptime,
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// ─── AI Evaluator ────────────────────────────────────────
|
|
183
|
+
|
|
184
|
+
class Evaluator {
|
|
185
|
+
static evaluateCode(code, language) {
|
|
186
|
+
const issues = [];
|
|
187
|
+
|
|
188
|
+
// Basic checks
|
|
189
|
+
if (language === 'python') {
|
|
190
|
+
if (!code.includes('def ') && !code.includes('class ')) {
|
|
191
|
+
issues.push('No function or class definitions found');
|
|
192
|
+
}
|
|
193
|
+
if (code.includes('except:') && !code.includes('except Exception')) {
|
|
194
|
+
issues.push('Bare except clause — should catch specific exceptions');
|
|
195
|
+
}
|
|
196
|
+
if ((code.match(/import \*/g) || []).length > 0) {
|
|
197
|
+
issues.push('Wildcard import used — prefer specific imports');
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
if (language === 'javascript' || language === 'typescript') {
|
|
202
|
+
if (code.includes('==') && !code.includes('===')) {
|
|
203
|
+
issues.push('Loose equality (==) — prefer strict equality (===)');
|
|
204
|
+
}
|
|
205
|
+
if (code.includes('var ')) {
|
|
206
|
+
issues.push('var used — prefer const/let');
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Security checks
|
|
211
|
+
const secrets = Security.scanForSecrets(code);
|
|
212
|
+
if (secrets.length > 0) {
|
|
213
|
+
issues.push(`Potential secrets found: ${secrets[0]}`);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
if (code.includes('eval(') || code.includes('exec(')) {
|
|
217
|
+
issues.push('Dynamic code execution detected — potential security risk');
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// Quality metrics
|
|
221
|
+
const lines = code.split('\n').length;
|
|
222
|
+
const commentLines = code.split('\n').filter(l => l.trim().startsWith('#') || l.trim().startsWith('//')).length;
|
|
223
|
+
const commentRatio = lines > 0 ? (commentLines / lines * 100).toFixed(1) : 0;
|
|
224
|
+
|
|
225
|
+
return {
|
|
226
|
+
score: issues.length === 0 ? 100 : Math.max(0, 100 - issues.length * 15),
|
|
227
|
+
issues,
|
|
228
|
+
metrics: {
|
|
229
|
+
lines,
|
|
230
|
+
commentRatio: `${commentRatio}%`,
|
|
231
|
+
hasDocstring: code.includes('"""') || code.includes("'''"),
|
|
232
|
+
hasTypeHints: code.includes(': int') || code.includes(': str') || code.includes(': float'),
|
|
233
|
+
},
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// ─── Tool Registry ──────────────────────────────────────
|
|
239
|
+
|
|
240
|
+
const TOOLS = {
|
|
241
|
+
read_file: {
|
|
242
|
+
description: 'Read file contents',
|
|
243
|
+
execute(args) {
|
|
244
|
+
const v = Security.validatePath(args.path);
|
|
245
|
+
if (!v.ok) return { ok: false, error: v.reason };
|
|
246
|
+
const abs = path.resolve(args.path);
|
|
247
|
+
if (!fs.existsSync(abs)) return { ok: false, error: 'File not found' };
|
|
248
|
+
return { ok: true, content: fs.readFileSync(abs, 'utf8'), path: abs };
|
|
249
|
+
},
|
|
250
|
+
},
|
|
251
|
+
|
|
252
|
+
write_file: {
|
|
253
|
+
description: 'Write content to a file',
|
|
254
|
+
execute(args) {
|
|
255
|
+
const v = Security.validatePath(args.path);
|
|
256
|
+
if (!v.ok) return { ok: false, error: v.reason };
|
|
257
|
+
const abs = path.resolve(args.path);
|
|
258
|
+
const dir = path.dirname(abs);
|
|
259
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
260
|
+
fs.writeFileSync(abs, args.content, 'utf8');
|
|
261
|
+
return { ok: true, path: abs };
|
|
262
|
+
},
|
|
263
|
+
},
|
|
264
|
+
|
|
265
|
+
edit_file: {
|
|
266
|
+
description: 'Replace a string in a file',
|
|
267
|
+
execute(args) {
|
|
268
|
+
const v = Security.validatePath(args.path);
|
|
269
|
+
if (!v.ok) return { ok: false, error: v.reason };
|
|
270
|
+
const abs = path.resolve(args.path);
|
|
271
|
+
if (!fs.existsSync(abs)) return { ok: false, error: 'File not found' };
|
|
272
|
+
let content = fs.readFileSync(abs, 'utf8');
|
|
273
|
+
if (!content.includes(args.old)) return { ok: false, error: 'Old content not found' };
|
|
274
|
+
content = content.replace(args.old, args.new);
|
|
275
|
+
fs.writeFileSync(abs, content, 'utf8');
|
|
276
|
+
return { ok: true, path: abs };
|
|
277
|
+
},
|
|
278
|
+
},
|
|
279
|
+
|
|
280
|
+
list_dir: {
|
|
281
|
+
description: 'List directory contents',
|
|
282
|
+
execute(args) {
|
|
283
|
+
const abs = path.resolve(args.path || '.');
|
|
284
|
+
if (!fs.existsSync(abs)) return { ok: false, error: 'Directory not found' };
|
|
285
|
+
const items = fs.readdirSync(abs, { withFileTypes: true }).map(d => ({
|
|
286
|
+
name: d.name, type: d.isDirectory() ? 'dir' : 'file',
|
|
287
|
+
}));
|
|
288
|
+
return { ok: true, items, path: abs };
|
|
289
|
+
},
|
|
290
|
+
},
|
|
291
|
+
|
|
292
|
+
search_code: {
|
|
293
|
+
description: 'Search codebase for a keyword',
|
|
294
|
+
execute(args) {
|
|
295
|
+
const searcher = new CodebaseSearch(process.cwd());
|
|
296
|
+
const results = searcher.searchCode(args.query);
|
|
297
|
+
return { ok: true, results, count: results.length };
|
|
298
|
+
},
|
|
299
|
+
},
|
|
300
|
+
|
|
301
|
+
find_files: {
|
|
302
|
+
description: 'Find files by pattern',
|
|
303
|
+
execute(args) {
|
|
304
|
+
const searcher = new CodebaseSearch(process.cwd());
|
|
305
|
+
const results = searcher.findFiles(args.pattern);
|
|
306
|
+
return { ok: true, results, count: results.length };
|
|
307
|
+
},
|
|
308
|
+
},
|
|
309
|
+
|
|
310
|
+
run_command: {
|
|
311
|
+
description: 'Execute a shell command',
|
|
312
|
+
execute(args) {
|
|
313
|
+
const v = Security.validateCommand(args.command);
|
|
314
|
+
if (!v.ok) return { ok: false, error: v.reason };
|
|
315
|
+
try {
|
|
316
|
+
const result = spawnSync(args.command, {
|
|
317
|
+
shell: true, encoding: 'utf8', cwd: process.cwd(), timeout: 30000,
|
|
318
|
+
});
|
|
319
|
+
return { ok: result.status === 0, stdout: result.stdout, stderr: result.stderr, exitCode: result.status };
|
|
320
|
+
} catch (e) {
|
|
321
|
+
return { ok: false, error: e.message };
|
|
322
|
+
}
|
|
323
|
+
},
|
|
324
|
+
},
|
|
325
|
+
|
|
326
|
+
detect_project: {
|
|
327
|
+
description: 'Detect project type and structure',
|
|
328
|
+
execute() {
|
|
329
|
+
const detector = new ProjectDetector(process.cwd());
|
|
330
|
+
return { ok: true, ...detector.detect() };
|
|
331
|
+
},
|
|
332
|
+
},
|
|
333
|
+
|
|
334
|
+
get_context: {
|
|
335
|
+
description: 'Get project context for a query',
|
|
336
|
+
execute(args) {
|
|
337
|
+
const detector = new ProjectDetector(process.cwd());
|
|
338
|
+
const info = detector.detect();
|
|
339
|
+
const searcher = new CodebaseSearch(process.cwd());
|
|
340
|
+
const codeResults = searcher.searchCode(args.query);
|
|
341
|
+
return {
|
|
342
|
+
ok: true,
|
|
343
|
+
project: { type: info.type, languages: info.languages, frameworks: info.frameworks },
|
|
344
|
+
files: codeResults.slice(0, 5).map(r => ({ file: r.file, matches: r.matches.slice(0, 2) })),
|
|
345
|
+
};
|
|
346
|
+
},
|
|
347
|
+
},
|
|
348
|
+
|
|
349
|
+
// ─── Git Tools ─────────────────────────────────────────
|
|
350
|
+
|
|
351
|
+
git_status: {
|
|
352
|
+
description: 'Get git status summary',
|
|
353
|
+
execute() { return { ok: true, ...Git.statusSummary() }; },
|
|
354
|
+
},
|
|
355
|
+
|
|
356
|
+
git_diff: {
|
|
357
|
+
description: 'Get git diff',
|
|
358
|
+
execute(args) {
|
|
359
|
+
const result = args.file ? Git.diffFile(args.file) : Git.diff();
|
|
360
|
+
return { ok: result.ok, diff: result.stdout, error: result.stderr };
|
|
361
|
+
},
|
|
362
|
+
},
|
|
363
|
+
|
|
364
|
+
git_commit: {
|
|
365
|
+
description: 'Stage all changes and commit',
|
|
366
|
+
execute(args) {
|
|
367
|
+
const v = Security.validateInput(args.message);
|
|
368
|
+
if (!v.ok) return { ok: false, error: v.reason };
|
|
369
|
+
return Git.commit(args.message);
|
|
370
|
+
},
|
|
371
|
+
},
|
|
372
|
+
|
|
373
|
+
git_log: {
|
|
374
|
+
description: 'Get recent git log',
|
|
375
|
+
execute(args) { return { ok: true, log: Git.log(args.n || 10).stdout }; },
|
|
376
|
+
},
|
|
377
|
+
|
|
378
|
+
git_branch: {
|
|
379
|
+
description: 'Create or switch branch',
|
|
380
|
+
execute(args) {
|
|
381
|
+
if (args.create) return Git.createBranch(args.name);
|
|
382
|
+
if (args.switch) return Git.switchBranch(args.name);
|
|
383
|
+
return { ok: true, branch: Git.branch().stdout, branches: Git.branches().stdout };
|
|
384
|
+
},
|
|
385
|
+
},
|
|
386
|
+
|
|
387
|
+
git_rollback: {
|
|
388
|
+
description: 'Rollback last N commits',
|
|
389
|
+
execute(args) { return Git.rollback(args.n || 1); },
|
|
390
|
+
},
|
|
391
|
+
|
|
392
|
+
// ─── Security Tools ────────────────────────────────────
|
|
393
|
+
|
|
394
|
+
security_scan: {
|
|
395
|
+
description: 'Scan file for secrets and vulnerabilities',
|
|
396
|
+
execute(args) {
|
|
397
|
+
const abs = path.resolve(args.path);
|
|
398
|
+
if (!fs.existsSync(abs)) return { ok: false, error: 'File not found' };
|
|
399
|
+
const content = fs.readFileSync(abs, 'utf8');
|
|
400
|
+
const secrets = Security.scanForSecrets(content);
|
|
401
|
+
return { ok: true, secrets, hasIssues: secrets.length > 0 };
|
|
402
|
+
},
|
|
403
|
+
},
|
|
404
|
+
|
|
405
|
+
security_validate: {
|
|
406
|
+
description: 'Validate input for prompt injection',
|
|
407
|
+
execute(args) {
|
|
408
|
+
return { ok: true, ...Security.validateInput(args.input) };
|
|
409
|
+
},
|
|
410
|
+
},
|
|
411
|
+
|
|
412
|
+
// ─── Production Tools ──────────────────────────────────
|
|
413
|
+
|
|
414
|
+
system_info: {
|
|
415
|
+
description: 'Get system information',
|
|
416
|
+
execute() { return { ok: true, ...Production.getSystemInfo() }; },
|
|
417
|
+
},
|
|
418
|
+
|
|
419
|
+
health_check: {
|
|
420
|
+
description: 'System health check',
|
|
421
|
+
execute() { return { ok: true, ...Production.healthCheck() }; },
|
|
422
|
+
},
|
|
423
|
+
|
|
424
|
+
// ─── Evaluation Tools ──────────────────────────────────
|
|
425
|
+
|
|
426
|
+
evaluate_code: {
|
|
427
|
+
description: 'Evaluate code quality and security',
|
|
428
|
+
execute(args) {
|
|
429
|
+
const lang = args.language || 'python';
|
|
430
|
+
return { ok: true, ...Evaluator.evaluateCode(args.code, lang) };
|
|
431
|
+
},
|
|
432
|
+
},
|
|
433
|
+
};
|
|
434
|
+
|
|
435
|
+
// ─── Tool Call Parser ────────────────────────────────────
|
|
436
|
+
|
|
437
|
+
function parseToolCalls(text) {
|
|
438
|
+
const calls = [];
|
|
439
|
+
const jsonBlockRegex = /```(?:json)?\s*(\{[\s\S]*?\})\s*```/g;
|
|
440
|
+
let match;
|
|
441
|
+
while ((match = jsonBlockRegex.exec(text)) !== null) {
|
|
442
|
+
try {
|
|
443
|
+
const obj = JSON.parse(match[1]);
|
|
444
|
+
if (obj.tool && TOOLS[obj.tool]) calls.push({ name: obj.tool, args: obj });
|
|
445
|
+
} catch (e) {}
|
|
446
|
+
}
|
|
447
|
+
const inlineRegex = /\{"tool"\s*:\s*"(\w+)"[^}]*\}/g;
|
|
448
|
+
while ((match = inlineRegex.exec(text)) !== null) {
|
|
449
|
+
try {
|
|
450
|
+
const obj = JSON.parse(match[0]);
|
|
451
|
+
if (TOOLS[obj.tool] && !calls.some(c => c.name === obj.tool && JSON.stringify(c.args) === JSON.stringify(obj))) {
|
|
452
|
+
calls.push({ name: obj.tool, args: obj });
|
|
453
|
+
}
|
|
454
|
+
} catch (e) {}
|
|
455
|
+
}
|
|
456
|
+
return calls;
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
function stripToolCalls(text) {
|
|
460
|
+
let clean = text.replace(/```(?:json)?\s*\{"tool"[\s\S]*?\}\s*```/g, '');
|
|
461
|
+
clean = clean.replace(/\{"tool"\s*:\s*"[^"]+"[^}]*\}/g, '');
|
|
462
|
+
return clean.replace(/\n{3,}/g, '\n\n').trim();
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
// ─── Agent Engine ────────────────────────────────────────
|
|
466
|
+
|
|
467
|
+
class Engine {
|
|
468
|
+
constructor(options = {}) {
|
|
469
|
+
this.ai = new HybardAI(options);
|
|
470
|
+
this.maxIterations = 8;
|
|
471
|
+
this.systemPrompt = this._buildSystemPrompt();
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
_buildSystemPrompt() {
|
|
475
|
+
return `You are Hybard — an autonomous AI coding assistant with security, git, and production awareness.
|
|
476
|
+
|
|
477
|
+
CAPABILITIES:
|
|
478
|
+
- Read, write, edit files in the project
|
|
479
|
+
- Search codebase for functions, classes, patterns
|
|
480
|
+
- Detect project type, frameworks, languages
|
|
481
|
+
- Execute shell commands (safety-validated)
|
|
482
|
+
- Git operations: status, diff, commit, branch, rollback
|
|
483
|
+
- Security: scan for secrets, validate input, detect injection
|
|
484
|
+
- Production: system info, health checks
|
|
485
|
+
- Evaluate code quality and suggest improvements
|
|
486
|
+
|
|
487
|
+
WORKFLOW:
|
|
488
|
+
1. Understand the user's request
|
|
489
|
+
2. Gather context (detect project, search relevant files)
|
|
490
|
+
3. For code changes: generate → show diff → get approval → apply
|
|
491
|
+
4. Report what was done in a clean summary
|
|
492
|
+
|
|
493
|
+
SECURITY RULES:
|
|
494
|
+
- Never hardcode secrets or API keys
|
|
495
|
+
- Scan files for sensitive data before writing
|
|
496
|
+
- Block dangerous commands (rm -rf, shutdown, etc.)
|
|
497
|
+
- Validate user input for prompt injection
|
|
498
|
+
- Restrict file access to project directory
|
|
499
|
+
|
|
500
|
+
GIT WORKFLOW:
|
|
501
|
+
When making code changes:
|
|
502
|
+
1. Show the diff of what will change
|
|
503
|
+
2. Wait for user approval
|
|
504
|
+
3. Commit with a meaningful message
|
|
505
|
+
|
|
506
|
+
TOOL USAGE:
|
|
507
|
+
Output a JSON code block to use a tool:
|
|
508
|
+
\`\`\`json
|
|
509
|
+
{"tool": "tool_name", "param1": "value1"}
|
|
510
|
+
\`\`\`
|
|
511
|
+
|
|
512
|
+
Available tools:
|
|
513
|
+
${Object.entries(TOOLS).map(([name, t]) => `- ${name}: ${t.description}`).join('\n')}
|
|
514
|
+
|
|
515
|
+
RULES:
|
|
516
|
+
- Execute tools silently — never show raw JSON or internal steps
|
|
517
|
+
- Always explain what you did in natural language
|
|
518
|
+
- If a tool fails, handle gracefully and try alternatives
|
|
519
|
+
- Write clean, production-ready code
|
|
520
|
+
- Use the user's language for explanations
|
|
521
|
+
- Keep code comments in English`;
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
async process(message) {
|
|
525
|
+
// Security check
|
|
526
|
+
const inputCheck = Security.validateInput(message);
|
|
527
|
+
if (!inputCheck.ok) return `Security: ${inputCheck.reason}`;
|
|
528
|
+
|
|
529
|
+
const context = this._buildContext(message);
|
|
530
|
+
let response;
|
|
531
|
+
try {
|
|
532
|
+
response = await this.ai.chat(message, {
|
|
533
|
+
system: this.systemPrompt + '\n\n' + context,
|
|
534
|
+
});
|
|
535
|
+
} catch (err) {
|
|
536
|
+
return `Error: ${err.message}`;
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
const toolCalls = parseToolCalls(response);
|
|
540
|
+
let toolResults = [];
|
|
541
|
+
|
|
542
|
+
if (toolCalls.length > 0) {
|
|
543
|
+
for (const call of toolCalls) {
|
|
544
|
+
const tool = TOOLS[call.name];
|
|
545
|
+
if (!tool) continue;
|
|
546
|
+
const result = tool.execute(call.args);
|
|
547
|
+
toolResults.push({ name: call.name, result });
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
if (toolResults.length > 0) {
|
|
551
|
+
const toolContext = toolResults.map(r =>
|
|
552
|
+
`Tool: ${r.name}\nResult: ${JSON.stringify(r.result).slice(0, 500)}`
|
|
553
|
+
).join('\n\n');
|
|
554
|
+
|
|
555
|
+
try {
|
|
556
|
+
response = await this.ai.chat(
|
|
557
|
+
`Tools executed:\n\n${toolContext}\n\nOriginal request: ${message}\n\n` +
|
|
558
|
+
`Provide a clean summary. No raw JSON.`,
|
|
559
|
+
{ system: this.systemPrompt }
|
|
560
|
+
);
|
|
561
|
+
} catch (e) {
|
|
562
|
+
response = stripToolCalls(response);
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
return stripToolCalls(response);
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
executeTool(name, args) {
|
|
571
|
+
const tool = TOOLS[name];
|
|
572
|
+
if (!tool) throw new Error(`Unknown tool: ${name}`);
|
|
573
|
+
return tool.execute(args);
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
_buildContext(message) {
|
|
577
|
+
let ctx = '';
|
|
578
|
+
try {
|
|
579
|
+
const detector = new ProjectDetector(process.cwd());
|
|
580
|
+
const info = detector.detect();
|
|
581
|
+
ctx += `\nPROJECT: ${info.type} | ${info.languages.join(', ')} | ${info.frameworks.join(', ')}`;
|
|
582
|
+
} catch (e) {}
|
|
583
|
+
try {
|
|
584
|
+
const git = Git.statusSummary();
|
|
585
|
+
if (git.isRepo) ctx += `\nGIT: branch=${git.branch}, modified=${git.modified.length}, added=${git.added.length}`;
|
|
586
|
+
} catch (e) {}
|
|
587
|
+
return ctx;
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
module.exports = { Engine, TOOLS, Security, Git, Production, Evaluator, parseToolCalls, stripToolCalls };
|