closed-loop-cli 1.0.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.
Potentially problematic release.
This version of closed-loop-cli might be problematic. Click here for more details.
- package/dist/dashboard/server.js +237 -0
- package/dist/index.js +272 -0
- package/dist/orchestrator/agent-prompts.js +42 -0
- package/dist/orchestrator/autogenesis.js +973 -0
- package/dist/orchestrator/dgm-archive.js +223 -0
- package/dist/orchestrator/event-stream.js +103 -0
- package/dist/orchestrator/fitness-evaluator.js +99 -0
- package/dist/orchestrator/meta-agent.js +421 -0
- package/dist/orchestrator/microagent-registry.js +134 -0
- package/dist/orchestrator/mutation-strategies.js +174 -0
- package/dist/orchestrator/prompt-benchmark.js +102 -0
- package/dist/orchestrator/prompt-optimizer.js +169 -0
- package/dist/orchestrator/refactor-scanner.js +222 -0
- package/dist/orchestrator/research-manager.js +104 -0
- package/dist/orchestrator/rulez.js +135 -0
- package/dist/orchestrator/sahoo-gateway.js +261 -0
- package/dist/orchestrator/state-manager.js +121 -0
- package/dist/orchestrator/task-agent.js +444 -0
- package/dist/orchestrator/telegram-bot.js +374 -0
- package/dist/orchestrator/types.js +2 -0
- package/dist/tests/dynamic/dependencies.test.js +37 -0
- package/dist/tests/dynamic/dummy.test.js +7 -0
- package/dist/tests/dynamic/fuzzy-patch.test.js +68 -0
- package/dist/tests/dynamic/indexer.test.js +60 -0
- package/dist/tests/dynamic/openhands.test.js +83 -0
- package/dist/tests/dynamic/skills.test.js +88 -0
- package/dist/tests/run-tests.js +294 -0
- package/dist/tools/diff-tools.js +24 -0
- package/dist/tools/file-tools.js +191 -0
- package/dist/tools/indexer.js +301 -0
- package/dist/tools/math-helper.js +6 -0
- package/dist/tools/repo-map.js +122 -0
- package/dist/tools/search-tools.js +271 -0
- package/dist/tools/shell-tools.js +75 -0
- package/dist/tools/skills.js +122 -0
- package/dist/tools/tui-tools.js +82 -0
- package/docs/AI_Arch_Opt_Anti_Gaming.md +227 -0
- package/docs/AI_Self_Improvement_Safety.md +457 -0
- package/docs/Anthropic AI Agents_ Capabilities and Concerns.md +134 -0
- package/docs/Auto_ClosedLoop_AI_Agent.md +415 -0
- package/docs/Autonomous AI Agents_ Closing the Loop.docx +0 -0
- package/docs/Secure_AI_Sandbox_Framework.md +358 -0
- package/docs/skills/add-file-existence-check-utility.json +9 -0
- package/docs/skills/add-utility-function-for-file-existence-check.json +9 -0
- package/docs/skills/add-utility-function-to-module.json +9 -0
- package/docs/skills/extract-command-runner-utility.json +9 -0
- package/docs/skills/file-existence-check-utility.json +9 -0
- package/package.json +36 -0
- package/src/dashboard/public/index.css +1334 -0
- package/src/dashboard/public/index.html +385 -0
- package/src/dashboard/public/index.js +1059 -0
- package/src/dashboard/server.ts +209 -0
- package/src/index.ts +256 -0
- package/src/orchestrator/agent-prompts.ts +43 -0
- package/src/orchestrator/autogenesis.ts +1078 -0
- package/src/orchestrator/dgm-archive.ts +257 -0
- package/src/orchestrator/event-stream.ts +90 -0
- package/src/orchestrator/fitness-evaluator.ts +154 -0
- package/src/orchestrator/meta-agent.ts +434 -0
- package/src/orchestrator/microagent-registry.ts +115 -0
- package/src/orchestrator/microagents/git-helper.md +11 -0
- package/src/orchestrator/microagents/test-fixer.md +10 -0
- package/src/orchestrator/microagents/typescript-expert.md +11 -0
- package/src/orchestrator/mutation-strategies.ts +214 -0
- package/src/orchestrator/research-manager.ts +88 -0
- package/src/orchestrator/rulez.ts +118 -0
- package/src/orchestrator/sahoo-gateway.ts +300 -0
- package/src/orchestrator/state-manager.ts +161 -0
- package/src/orchestrator/system-prompt.txt +1 -0
- package/src/orchestrator/task-agent.ts +461 -0
- package/src/orchestrator/telegram-bot.ts +358 -0
- package/src/tests/dynamic/dependencies.test.ts +48 -0
- package/src/tests/dynamic/dummy.test.ts +4 -0
- package/src/tests/dynamic/fuzzy-patch.test.ts +42 -0
- package/src/tests/dynamic/indexer.test.ts +31 -0
- package/src/tests/dynamic/openhands.test.ts +59 -0
- package/src/tests/dynamic/skills.test.ts +63 -0
- package/src/tests/run-tests.ts +296 -0
- package/src/tools/diff-tools.ts +27 -0
- package/src/tools/file-tools.ts +187 -0
- package/src/tools/indexer.ts +325 -0
- package/src/tools/repo-map.ts +96 -0
- package/src/tools/search-tools.ts +258 -0
- package/src/tools/shell-tools.ts +90 -0
- package/src/tools/skills.ts +101 -0
- package/src/tools/tui-tools.ts +87 -0
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { spawn } from 'child_process';
|
|
2
|
+
|
|
3
|
+
export interface CommandResult {
|
|
4
|
+
stdout: string;
|
|
5
|
+
stderr: string;
|
|
6
|
+
exitCode: number | null;
|
|
7
|
+
error?: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Executes a terminal command within a specified working directory.
|
|
12
|
+
* Supports interactive stdin input streams and streams output in real-time.
|
|
13
|
+
* Safely caps execution time using a timeout.
|
|
14
|
+
*/
|
|
15
|
+
export function runCommand(
|
|
16
|
+
command: string,
|
|
17
|
+
cwd: string = process.cwd(),
|
|
18
|
+
timeoutMs: number = 120000 // 2 minutes timeout
|
|
19
|
+
): Promise<CommandResult> {
|
|
20
|
+
return new Promise((resolve) => {
|
|
21
|
+
const isWindows = process.platform === 'win32';
|
|
22
|
+
const shell = isWindows ? 'cmd.exe' : '/bin/sh';
|
|
23
|
+
const args = isWindows ? ['/d', '/s', '/c', command] : ['-c', command];
|
|
24
|
+
|
|
25
|
+
let stdoutData = '';
|
|
26
|
+
let stderrData = '';
|
|
27
|
+
let isFinished = false;
|
|
28
|
+
|
|
29
|
+
// Spawn child with stdin inherited so standard keyboard input goes through
|
|
30
|
+
const child = spawn(shell, args, {
|
|
31
|
+
cwd,
|
|
32
|
+
windowsVerbatimArguments: isWindows,
|
|
33
|
+
stdio: ['inherit', 'pipe', 'pipe']
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
const timeout = setTimeout(() => {
|
|
37
|
+
if (!isFinished) {
|
|
38
|
+
isFinished = true;
|
|
39
|
+
try {
|
|
40
|
+
child.kill('SIGKILL');
|
|
41
|
+
} catch (e) {}
|
|
42
|
+
resolve({
|
|
43
|
+
stdout: stdoutData,
|
|
44
|
+
stderr: stderrData,
|
|
45
|
+
exitCode: 1,
|
|
46
|
+
error: `Command timed out after ${timeoutMs}ms.`
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
}, timeoutMs);
|
|
50
|
+
|
|
51
|
+
child.stdout.on('data', (data) => {
|
|
52
|
+
const chunk = data.toString();
|
|
53
|
+
stdoutData += chunk;
|
|
54
|
+
process.stdout.write(chunk);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
child.stderr.on('data', (data) => {
|
|
58
|
+
const chunk = data.toString();
|
|
59
|
+
stderrData += chunk;
|
|
60
|
+
process.stderr.write(chunk);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
child.on('error', (err) => {
|
|
64
|
+
if (!isFinished) {
|
|
65
|
+
isFinished = true;
|
|
66
|
+
clearTimeout(timeout);
|
|
67
|
+
resolve({
|
|
68
|
+
stdout: stdoutData,
|
|
69
|
+
stderr: stderrData,
|
|
70
|
+
exitCode: 1,
|
|
71
|
+
error: err.message
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
child.on('close', (code) => {
|
|
77
|
+
if (!isFinished) {
|
|
78
|
+
isFinished = true;
|
|
79
|
+
clearTimeout(timeout);
|
|
80
|
+
resolve({
|
|
81
|
+
stdout: stdoutData,
|
|
82
|
+
stderr: stderrData,
|
|
83
|
+
exitCode: code,
|
|
84
|
+
error: code !== 0 ? `Command exited with code ${code}` : undefined
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import * as fs from 'fs';
|
|
2
|
+
import * as path from 'path';
|
|
3
|
+
import Anthropic from '@anthropic-ai/sdk';
|
|
4
|
+
import * as dotenv from 'dotenv';
|
|
5
|
+
|
|
6
|
+
dotenv.config();
|
|
7
|
+
|
|
8
|
+
const apiKey = process.env.ANTHROPIC_API_KEY || process.env.ANTHROPIC_AUTH_TOKEN || '';
|
|
9
|
+
const baseURL = process.env.ANTHROPIC_BASE_URL || undefined;
|
|
10
|
+
const defaultModel = process.env.ANTHROPIC_MODEL || 'mimo-v2.5-pro[1m]';
|
|
11
|
+
|
|
12
|
+
export interface SkillRecipe {
|
|
13
|
+
id: string;
|
|
14
|
+
title: string;
|
|
15
|
+
description: string;
|
|
16
|
+
filesModified: string[];
|
|
17
|
+
recipe: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Summarizes the recent successful evolution step and saves it to docs/skills/
|
|
22
|
+
*/
|
|
23
|
+
export async function extractAndSaveSkill(task: string, diffText: string): Promise<void> {
|
|
24
|
+
const workspaceRoot = process.cwd();
|
|
25
|
+
const skillsDir = path.join(workspaceRoot, 'docs', 'skills');
|
|
26
|
+
|
|
27
|
+
if (!fs.existsSync(skillsDir)) {
|
|
28
|
+
fs.mkdirSync(skillsDir, { recursive: true });
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const anthropic = new Anthropic({
|
|
32
|
+
apiKey: apiKey,
|
|
33
|
+
baseURL: baseURL,
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
const prompt = `You are a technical documenter. Your task is to analyze the user's refactoring/coding task and the successful git diff, then extract a general reusable development recipe/skill.
|
|
37
|
+
|
|
38
|
+
Task: "${task}"
|
|
39
|
+
|
|
40
|
+
Git Diff:
|
|
41
|
+
"""
|
|
42
|
+
${diffText}
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
Output your response strictly as a JSON object of a reusable playbook. Do not write any markdown wrappers (like \`\`\`json or \`\`\`), greetings, or conversational headers/footers. Output ONLY the raw JSON.
|
|
46
|
+
|
|
47
|
+
Format:
|
|
48
|
+
{
|
|
49
|
+
"id": "hyphenated-lowercase-slug-id",
|
|
50
|
+
"title": "Clear Action-Oriented Title",
|
|
51
|
+
"description": "Concise summary of what this recipe achieves",
|
|
52
|
+
"filesModified": ["src/relative/path/to/file.ts"],
|
|
53
|
+
"recipe": "Step-by-step general instructions of the technique used, focusing on design patterns, APIs, and syntax."
|
|
54
|
+
}`;
|
|
55
|
+
|
|
56
|
+
try {
|
|
57
|
+
const response = await anthropic.messages.create({
|
|
58
|
+
model: defaultModel,
|
|
59
|
+
max_tokens: 1500,
|
|
60
|
+
messages: [{ role: 'user', content: prompt }]
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
const block = response.content.find(b => b.type === 'text') as Anthropic.TextBlock | undefined;
|
|
64
|
+
if (block && block.text) {
|
|
65
|
+
const text = block.text.trim().replace(/^```json/, '').replace(/```$/, '').trim();
|
|
66
|
+
const recipe: SkillRecipe = JSON.parse(text);
|
|
67
|
+
|
|
68
|
+
const fileName = `${recipe.id}.json`;
|
|
69
|
+
fs.writeFileSync(path.join(skillsDir, fileName), JSON.stringify(recipe, null, 2), 'utf-8');
|
|
70
|
+
console.log(`\n\x1b[32m[Skill Registry] Reusable skill saved successfully to docs/skills/${fileName}\x1b[0m`);
|
|
71
|
+
}
|
|
72
|
+
} catch (err: any) {
|
|
73
|
+
console.error(`\x1b[31m[Skill Registry] Failed to extract skill: ${err.message}\x1b[0m`);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Returns a concatenated summary of all available skills for prompt context.
|
|
79
|
+
*/
|
|
80
|
+
export function getSkillsSummary(): string {
|
|
81
|
+
const workspaceRoot = process.cwd();
|
|
82
|
+
const skillsDir = path.join(workspaceRoot, 'docs', 'skills');
|
|
83
|
+
if (!fs.existsSync(skillsDir)) {
|
|
84
|
+
return 'No custom development skills recorded yet.';
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const files = fs.readdirSync(skillsDir).filter(f => f.endsWith('.json'));
|
|
88
|
+
if (files.length === 0) {
|
|
89
|
+
return 'No custom development skills recorded yet.';
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const summaries: string[] = [];
|
|
93
|
+
for (const file of files) {
|
|
94
|
+
try {
|
|
95
|
+
const recipe: SkillRecipe = JSON.parse(fs.readFileSync(path.join(skillsDir, file), 'utf-8'));
|
|
96
|
+
summaries.push(`- **${recipe.title}** (${recipe.id}): ${recipe.description}\n Recipe: ${recipe.recipe}`);
|
|
97
|
+
} catch (e) {}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return summaries.join('\n\n');
|
|
101
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
export class Spinner {
|
|
2
|
+
private timer: NodeJS.Timeout | null = null;
|
|
3
|
+
private frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
|
4
|
+
private currentFrame = 0;
|
|
5
|
+
private message: string;
|
|
6
|
+
private isStarted = false;
|
|
7
|
+
|
|
8
|
+
constructor(message: string) {
|
|
9
|
+
this.message = message;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Starts the spinner animation in the terminal.
|
|
14
|
+
* Hides the terminal cursor for a cleaner look.
|
|
15
|
+
*/
|
|
16
|
+
start() {
|
|
17
|
+
if (this.isStarted) return;
|
|
18
|
+
this.isStarted = true;
|
|
19
|
+
|
|
20
|
+
// Hide cursor
|
|
21
|
+
process.stdout.write('\x1b[?25l');
|
|
22
|
+
|
|
23
|
+
this.timer = setInterval(() => {
|
|
24
|
+
const frame = this.frames[this.currentFrame];
|
|
25
|
+
this.currentFrame = (this.currentFrame + 1) % this.frames.length;
|
|
26
|
+
// Clear line and write spinner frame with cyan color
|
|
27
|
+
process.stdout.write(`\r\x1b[36m${frame}\x1b[0m ${this.message}`);
|
|
28
|
+
}, 80);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Updates the message text displayed alongside the spinner.
|
|
33
|
+
*/
|
|
34
|
+
update(newMessage: string) {
|
|
35
|
+
this.message = newMessage;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Stops the spinner and shows the terminal cursor again.
|
|
40
|
+
* Prints the final status indicator (green checkmark or red X).
|
|
41
|
+
*/
|
|
42
|
+
stop(success: boolean = true, finalMessage?: string) {
|
|
43
|
+
if (!this.isStarted) return;
|
|
44
|
+
this.isStarted = false;
|
|
45
|
+
|
|
46
|
+
if (this.timer) {
|
|
47
|
+
clearInterval(this.timer);
|
|
48
|
+
this.timer = null;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const msg = finalMessage || this.message;
|
|
52
|
+
const symbol = success ? '\x1b[32m✔\x1b[0m' : '\x1b[31m✖\x1b[0m';
|
|
53
|
+
|
|
54
|
+
// Clear spinner line, write final result, and restore cursor
|
|
55
|
+
process.stdout.write(`\r${symbol} ${msg}\n\x1b[?25h`);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Prints a beautiful styled header block for the currently active agent.
|
|
61
|
+
*/
|
|
62
|
+
export function printAgentHeader(agentRole: string) {
|
|
63
|
+
const roles: Record<string, { title: string; color: string; emoji: string }> = {
|
|
64
|
+
architect: { title: 'ARCHITECT / PLANNER', color: '\x1b[35;1m', emoji: '📐' }, // bold magenta
|
|
65
|
+
coder: { title: 'CODER / IMPLEMENTOR', color: '\x1b[32;1m', emoji: '💻' }, // bold green
|
|
66
|
+
debugger: { title: 'DIAGNOSTICS & DEBUGGER', color: '\x1b[33;1m', emoji: '🔧' }, // bold yellow
|
|
67
|
+
gatekeeper: { title: 'GATEKEEPER & REVIEWER', color: '\x1b[36;1m', emoji: '🛡️' } // bold cyan
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
const details = roles[agentRole.toLowerCase()] || { title: agentRole.toUpperCase(), color: '\x1b[37;1m', emoji: '🤖' };
|
|
71
|
+
const border = '='.repeat(60);
|
|
72
|
+
console.log(`\n${details.color}${border}\x1b[0m`);
|
|
73
|
+
console.log(`${details.color} ${details.emoji} ${details.title}\x1b[0m`);
|
|
74
|
+
console.log(`${details.color}${border}\x1b[0m\n`);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Renders block contents inside a beautiful box layout with color accents.
|
|
79
|
+
*/
|
|
80
|
+
export function printCollapsibleBlock(title: string, content: string, color: string = '\x1b[34m') {
|
|
81
|
+
console.log(`${color}┌── ${title} ───────────────────────────────────────────────────────────────┐\x1b[0m`);
|
|
82
|
+
content.split('\n').forEach(line => {
|
|
83
|
+
console.log(`${color}│\x1b[0m ${line}`);
|
|
84
|
+
});
|
|
85
|
+
console.log(`${color}└───────────────────────────────────────────────────────────────────────────────┘\x1b[0m\n`);
|
|
86
|
+
}
|
|
87
|
+
|