terra-hiven 3.2.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.md +20 -0
- package/README.md +233 -0
- package/assets/hiven_logo_v2.png +0 -0
- package/bin/hiven.js +225 -0
- package/console/.nojekyll +0 -0
- package/console/app.js +588 -0
- package/console/assets/hiven_logo_v2.png +0 -0
- package/console/data/swarms_history.json +38 -0
- package/console/index.html +694 -0
- package/console/style.css +1039 -0
- package/console/webbl.config.json +5 -0
- package/dist/api.d.ts +13 -0
- package/dist/api.d.ts.map +1 -0
- package/dist/api.js +142 -0
- package/dist/api.js.map +1 -0
- package/dist/honeycombs.d.ts +30 -0
- package/dist/honeycombs.d.ts.map +1 -0
- package/dist/honeycombs.js +221 -0
- package/dist/honeycombs.js.map +1 -0
- package/dist/index.d.ts +38 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +133 -0
- package/dist/index.js.map +1 -0
- package/dist/inference.d.ts +33 -0
- package/dist/inference.d.ts.map +1 -0
- package/dist/inference.js +217 -0
- package/dist/inference.js.map +1 -0
- package/dist/server.d.ts +10 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +54 -0
- package/dist/server.js.map +1 -0
- package/dist/swarm.d.ts +30 -0
- package/dist/swarm.d.ts.map +1 -0
- package/dist/swarm.js +196 -0
- package/dist/swarm.js.map +1 -0
- package/dist/types.d.ts +117 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +6 -0
- package/dist/types.js.map +1 -0
- package/dist/vault.d.ts +19 -0
- package/dist/vault.d.ts.map +1 -0
- package/dist/vault.js +99 -0
- package/dist/vault.js.map +1 -0
- package/package.json +63 -0
- package/templates/.github/workflows/swarm.yml +229 -0
- package/templates/package.json +13 -0
- package/templates/swarm_runner.js +1594 -0
|
@@ -0,0 +1,1594 @@
|
|
|
1
|
+
// swarm_runner.js
|
|
2
|
+
// Hiven Worker Swarm - Ephemeral Multi-Agent Execution Engine
|
|
3
|
+
// Role: Principal Distributed Systems & MLOps Architect
|
|
4
|
+
|
|
5
|
+
import { Octokit } from "@octokit/rest";
|
|
6
|
+
import { execSync } from "child_process";
|
|
7
|
+
import fs from "fs";
|
|
8
|
+
import path from "path";
|
|
9
|
+
import http from "http";
|
|
10
|
+
import https from "https";
|
|
11
|
+
import crypto from "crypto";
|
|
12
|
+
import chalk from "chalk";
|
|
13
|
+
|
|
14
|
+
// Ensure Ollama default path is in PATH on Windows
|
|
15
|
+
if (process.platform === "win32" && process.env.USERPROFILE) {
|
|
16
|
+
const defaultOllamaPath = path.join(process.env.USERPROFILE, "AppData", "Local", "Programs", "Ollama");
|
|
17
|
+
if (fs.existsSync(path.join(defaultOllamaPath, "ollama.exe"))) {
|
|
18
|
+
process.env.PATH = `${process.env.PATH};${defaultOllamaPath}`;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// Command line argument parser
|
|
23
|
+
const args = process.argv.slice(2);
|
|
24
|
+
const phaseArg = args.indexOf("--phase");
|
|
25
|
+
const indexArg = args.indexOf("--index");
|
|
26
|
+
const PHASE = phaseArg !== -1 ? args[phaseArg + 1] : "standalone";
|
|
27
|
+
const KOMBEE_INDEX = indexArg !== -1 ? parseInt(args[indexArg + 1], 10) : (process.env.KOMBEE_INDEX ? parseInt(process.env.KOMBEE_INDEX, 10) : 1);
|
|
28
|
+
|
|
29
|
+
// Environment Parameters
|
|
30
|
+
const {
|
|
31
|
+
GITHUB_TOKEN,
|
|
32
|
+
TARGET_REPO,
|
|
33
|
+
TARGET_BRANCH,
|
|
34
|
+
INSTRUCTION,
|
|
35
|
+
FILES_TO_EDIT,
|
|
36
|
+
PR_NUMBER,
|
|
37
|
+
ISSUE_NUMBER,
|
|
38
|
+
STATUS_COMMENT_ID,
|
|
39
|
+
DRONE_UPLINK_URL,
|
|
40
|
+
WORKER_ID
|
|
41
|
+
} = process.env;
|
|
42
|
+
|
|
43
|
+
const OLLAMA_HOST = "http://localhost:11434";
|
|
44
|
+
const TARGET_DIR = "./target_code";
|
|
45
|
+
|
|
46
|
+
// Elastic Model Registry mapping roles to state-of-the-art small models
|
|
47
|
+
const MODELS = {
|
|
48
|
+
PLANNER_LOW: process.env.PLANNER_MODEL_LOW || "deepseek-r1:1.5b", // Fast basic logic reasoning planner
|
|
49
|
+
PLANNER_HIGH: process.env.PLANNER_MODEL_HIGH || "deepseek-r1:8b", // Solid architecture reasoning planner
|
|
50
|
+
CODER_LOW: process.env.CODER_MODEL_LOW || "qwen2.5-coder:3b", // 3B coder (format-aligned)
|
|
51
|
+
CODER_HIGH: process.env.CODER_MODEL_HIGH || "qwen2.5-coder:7b", // 7B heavy coder
|
|
52
|
+
VALIDATOR_LOW: process.env.VALIDATOR_MODEL_LOW || "qwen2.5-coder:3b", // 3B syntax validator
|
|
53
|
+
VALIDATOR_HIGH: process.env.VALIDATOR_MODEL_HIGH || "deepseek-r1:8b" // 8B logical auditor validator
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
console.log("==========================================================");
|
|
57
|
+
console.log(` 🐜 HIVEN KOMBEE SWARM - FASE: ${PHASE.toUpperCase()} (ID: ${KOMBEE_INDEX})`);
|
|
58
|
+
console.log("==========================================================");
|
|
59
|
+
|
|
60
|
+
if (!GITHUB_TOKEN || !TARGET_REPO || !TARGET_BRANCH || !INSTRUCTION) {
|
|
61
|
+
console.error("[!] Missing critical environment variables.");
|
|
62
|
+
process.exit(1);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const octokit = new Octokit({ auth: GITHUB_TOKEN });
|
|
66
|
+
|
|
67
|
+
// Helper to query local Ollama service
|
|
68
|
+
async function queryOllama(model, prompt, systemPrompt = "") {
|
|
69
|
+
console.log(`[*] Querying model '${model}'...`);
|
|
70
|
+
const payload = {
|
|
71
|
+
model,
|
|
72
|
+
prompt,
|
|
73
|
+
stream: false,
|
|
74
|
+
options: { temperature: 0.3, num_predict: 1024 }
|
|
75
|
+
};
|
|
76
|
+
if (systemPrompt) {
|
|
77
|
+
payload.system = systemPrompt;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return new Promise((resolve, reject) => {
|
|
81
|
+
const req = http.request(
|
|
82
|
+
`${OLLAMA_HOST}/api/generate`,
|
|
83
|
+
{
|
|
84
|
+
method: "POST",
|
|
85
|
+
headers: { "Content-Type": "application/json" }
|
|
86
|
+
},
|
|
87
|
+
(res) => {
|
|
88
|
+
let data = "";
|
|
89
|
+
res.on("data", (chunk) => (data += chunk));
|
|
90
|
+
res.on("end", () => {
|
|
91
|
+
try {
|
|
92
|
+
const parsed = JSON.parse(data);
|
|
93
|
+
resolve(parsed.response);
|
|
94
|
+
} catch (e) {
|
|
95
|
+
reject(new Error(`Failed to parse Ollama response: ${e.message}`));
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
);
|
|
100
|
+
req.on("error", reject);
|
|
101
|
+
req.write(JSON.stringify(payload));
|
|
102
|
+
req.end();
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Helper to update the real-time status comment on GitHub
|
|
107
|
+
async function updateStatusComment(currentPhase, state) {
|
|
108
|
+
if (!STATUS_COMMENT_ID || !GITHUB_TOKEN || !TARGET_REPO) {
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
const commentId = parseInt(STATUS_COMMENT_ID, 10);
|
|
112
|
+
if (isNaN(commentId)) return;
|
|
113
|
+
|
|
114
|
+
const [owner, repo] = TARGET_REPO.split("/");
|
|
115
|
+
|
|
116
|
+
// Build status list
|
|
117
|
+
let p1 = "⏳ **Phase 1: Context** (Pending)";
|
|
118
|
+
let p2 = "⏳ **Phase 2: Execution** (Pending)";
|
|
119
|
+
let p3 = "⏳ **Phase 3: Validation** (Pending)";
|
|
120
|
+
let p4 = "⏳ **Phase 4: Consolidation** (Pending)";
|
|
121
|
+
|
|
122
|
+
if (currentPhase === "context") {
|
|
123
|
+
p1 = state === "running" ? "⚙️ **Phase 1: Context** (In Progress...)" : "✅ **Phase 1: Context** (Complete)";
|
|
124
|
+
} else if (currentPhase === "execution") {
|
|
125
|
+
p1 = "✅ **Phase 1: Context** (Complete)";
|
|
126
|
+
p2 = state === "running" ? "⚙️ **Phase 2: Execution** (In Progress...)" : "✅ **Phase 2: Execution** (Complete)";
|
|
127
|
+
} else if (currentPhase === "validation") {
|
|
128
|
+
p1 = "✅ **Phase 1: Context** (Complete)";
|
|
129
|
+
p2 = "✅ **Phase 2: Execution** (Complete)";
|
|
130
|
+
p3 = state === "running" ? "⚙️ **Phase 3: Validation** (In Progress...)" : "✅ **Phase 3: Validation** (Complete)";
|
|
131
|
+
} else if (currentPhase === "consolidation") {
|
|
132
|
+
p1 = "✅ **Phase 1: Context** (Complete)";
|
|
133
|
+
p2 = "✅ **Phase 2: Execution** (Complete)";
|
|
134
|
+
p3 = "✅ **Phase 3: Validation** (Complete)";
|
|
135
|
+
p4 = state === "running" ? "⚙️ **Phase 4: Consolidation** (In Progress...)" : "✅ **Phase 4: Consolidation** (Complete)";
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const body = `🐝 Hiven Swarm Triggered for instruction: **"${INSTRUCTION}"**\n\n* ${p1}\n* ${p2}\n* ${p3}\n* ${p4}`;
|
|
139
|
+
|
|
140
|
+
try {
|
|
141
|
+
const octo = new Octokit({ auth: GITHUB_TOKEN });
|
|
142
|
+
await octo.issues.updateComment({
|
|
143
|
+
owner,
|
|
144
|
+
repo,
|
|
145
|
+
comment_id: commentId,
|
|
146
|
+
body
|
|
147
|
+
});
|
|
148
|
+
console.log(`[+] Status comment updated for Phase [${currentPhase}] (${state}).`);
|
|
149
|
+
} catch (err) {
|
|
150
|
+
console.error("[-] Failed to update status comment on GitHub:", err.message);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Helper to send telemetry to the Queen
|
|
155
|
+
function sendTelemetry(state, message) {
|
|
156
|
+
if (!DRONE_UPLINK_URL) return Promise.resolve();
|
|
157
|
+
|
|
158
|
+
return new Promise((resolve) => {
|
|
159
|
+
let resolved = false;
|
|
160
|
+
const done = () => {
|
|
161
|
+
if (!resolved) {
|
|
162
|
+
resolved = true;
|
|
163
|
+
resolve();
|
|
164
|
+
}
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
// Safety timeout: resolve after 5 seconds to prevent hanging the runner
|
|
168
|
+
const timeoutId = setTimeout(() => {
|
|
169
|
+
console.error("[-] Telemetry request timed out");
|
|
170
|
+
done();
|
|
171
|
+
}, 5000);
|
|
172
|
+
|
|
173
|
+
const payload = JSON.stringify({
|
|
174
|
+
workerId: WORKER_ID,
|
|
175
|
+
kombeeIndex: typeof KOMBEE_INDEX !== 'undefined' ? KOMBEE_INDEX : 1,
|
|
176
|
+
phase: PHASE,
|
|
177
|
+
state,
|
|
178
|
+
message,
|
|
179
|
+
timestamp: new Date().toISOString()
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
try {
|
|
183
|
+
const url = new URL(DRONE_UPLINK_URL);
|
|
184
|
+
const lib = url.protocol === "https:" ? https : http;
|
|
185
|
+
|
|
186
|
+
const req = lib.request(url.toString(), {
|
|
187
|
+
method: "POST",
|
|
188
|
+
headers: {
|
|
189
|
+
"Content-Type": "application/json",
|
|
190
|
+
"Content-Length": Buffer.byteLength(payload)
|
|
191
|
+
}
|
|
192
|
+
}, (res) => {
|
|
193
|
+
res.on("data", () => {});
|
|
194
|
+
res.on("end", () => {
|
|
195
|
+
clearTimeout(timeoutId);
|
|
196
|
+
done();
|
|
197
|
+
});
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
req.on("error", (e) => {
|
|
201
|
+
console.error("[-] Telemetry report failed:", e.message);
|
|
202
|
+
clearTimeout(timeoutId);
|
|
203
|
+
done();
|
|
204
|
+
});
|
|
205
|
+
req.write(payload);
|
|
206
|
+
req.end();
|
|
207
|
+
} catch (err) {
|
|
208
|
+
console.error("[-] Telemetry setup failed:", err.message);
|
|
209
|
+
clearTimeout(timeoutId);
|
|
210
|
+
done();
|
|
211
|
+
}
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// ==========================================
|
|
216
|
+
// FEDERATED PATTERN LEARNING
|
|
217
|
+
// Loads/saves abstract swarm patterns via Queen API
|
|
218
|
+
// ==========================================
|
|
219
|
+
async function loadFederatedPatterns(instruction, fileExtensions) {
|
|
220
|
+
if (!DRONE_UPLINK_URL) return [];
|
|
221
|
+
try {
|
|
222
|
+
const queenBase = DRONE_UPLINK_URL.replace('/telemetry', '');
|
|
223
|
+
const fileType = fileExtensions[0] || 'js';
|
|
224
|
+
const url = `${queenBase}/api/patterns/load?fileType=${encodeURIComponent(fileType)}`;
|
|
225
|
+
const lib = url.startsWith('https') ? https : http;
|
|
226
|
+
return await new Promise((resolve) => {
|
|
227
|
+
lib.get(url, (res) => {
|
|
228
|
+
let data = '';
|
|
229
|
+
res.on('data', c => data += c);
|
|
230
|
+
res.on('end', () => {
|
|
231
|
+
try {
|
|
232
|
+
const parsed = JSON.parse(data);
|
|
233
|
+
resolve(parsed.patterns || []);
|
|
234
|
+
} catch { resolve([]); }
|
|
235
|
+
});
|
|
236
|
+
}).on('error', () => resolve([]));
|
|
237
|
+
});
|
|
238
|
+
} catch { return []; }
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
async function saveFederatedPattern(fileType, instruction, outcome) {
|
|
242
|
+
if (!DRONE_UPLINK_URL) return;
|
|
243
|
+
try {
|
|
244
|
+
const queenBase = DRONE_UPLINK_URL.replace('/telemetry', '');
|
|
245
|
+
const pattern = {
|
|
246
|
+
fileType,
|
|
247
|
+
instruction: instruction.substring(0, 120),
|
|
248
|
+
outcome,
|
|
249
|
+
timestamp: new Date().toISOString()
|
|
250
|
+
};
|
|
251
|
+
const payload = JSON.stringify({ fileType, pattern });
|
|
252
|
+
const url = new URL(`${queenBase}/api/patterns/save`);
|
|
253
|
+
const lib = url.protocol === 'https:' ? https : http;
|
|
254
|
+
const req = lib.request(url, {
|
|
255
|
+
method: 'POST',
|
|
256
|
+
headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(payload) }
|
|
257
|
+
}, (res) => { res.resume(); });
|
|
258
|
+
req.on('error', () => {});
|
|
259
|
+
req.write(payload);
|
|
260
|
+
req.end();
|
|
261
|
+
console.log(`[+] Federated pattern saved for type: ${fileType}`);
|
|
262
|
+
} catch { /* non-blocking */ }
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// Helper to execute git command on target directory
|
|
266
|
+
function runGit(args, options = {}) {
|
|
267
|
+
if (TARGET_REPO === "mock/repo" || process.env.MOCK_GIT === "true") {
|
|
268
|
+
console.log(chalk.blue(`[Simulated Git Command] git ${args}`));
|
|
269
|
+
return "";
|
|
270
|
+
}
|
|
271
|
+
const defaultOpts = { cwd: TARGET_DIR };
|
|
272
|
+
return execSync(`git ${args}`, { ...defaultOpts, ...options }).toString().trim();
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// Checkout codebase
|
|
276
|
+
function checkoutCodebase() {
|
|
277
|
+
if (TARGET_REPO === "mock/repo" || process.env.MOCK_GIT === "true") {
|
|
278
|
+
console.log("[*] [Mock-Git] Bypassing checkout. Using local target_code workspace.");
|
|
279
|
+
if (!fs.existsSync(TARGET_DIR)) {
|
|
280
|
+
fs.mkdirSync(TARGET_DIR);
|
|
281
|
+
}
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
if (fs.existsSync(TARGET_DIR)) {
|
|
285
|
+
fs.rmSync(TARGET_DIR, { recursive: true, force: true });
|
|
286
|
+
}
|
|
287
|
+
console.log(`[*] Cloning target repository ${TARGET_REPO} (${TARGET_BRANCH})...`);
|
|
288
|
+
const cloneUrl = `https://x-access-token:${GITHUB_TOKEN}@github.com/${TARGET_REPO}.git`;
|
|
289
|
+
execSync(`git clone --depth 1 --branch ${TARGET_BRANCH} ${cloneUrl} ${TARGET_DIR}`, { stdio: "ignore" });
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// Load honey.db (Worker local memory cache)
|
|
293
|
+
function loadHoneyDb() {
|
|
294
|
+
const honeyPath = "./honey.db";
|
|
295
|
+
if (fs.existsSync(honeyPath)) {
|
|
296
|
+
try {
|
|
297
|
+
return JSON.parse(fs.readFileSync(honeyPath, "utf-8"));
|
|
298
|
+
} catch (e) {
|
|
299
|
+
console.warn("[-] Failed to parse honey.db, starting fresh.");
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
return { stylePreferences: {}, errorSignatures: {} };
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// Save honey.db safely (Sanitized - no user source code)
|
|
306
|
+
function saveHoneyDb(honeyDb) {
|
|
307
|
+
fs.writeFileSync("./honey.db", JSON.stringify(honeyDb, null, 2), "utf-8");
|
|
308
|
+
try {
|
|
309
|
+
runGit("add honey.db", { cwd: "." });
|
|
310
|
+
runGit('commit -m "chore: persist honey.db style cache [skip ci]"', { cwd: "." });
|
|
311
|
+
runGit("push", { cwd: "." });
|
|
312
|
+
console.log("[+] honey.db updated and pushed to Worker origin.");
|
|
313
|
+
} catch (e) {
|
|
314
|
+
console.warn("[-] Failed to push updated honey.db to worker origin:", e.message);
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
|
|
319
|
+
// ==========================================
|
|
320
|
+
// FASE 0: CONTEXT HARVEST
|
|
321
|
+
// Recursively walks the cloned repo and builds
|
|
322
|
+
// a rich code context bundle for the Architect.
|
|
323
|
+
// ==========================================
|
|
324
|
+
const HARVEST_EXTENSIONS = new Set([
|
|
325
|
+
".js", ".ts", ".jsx", ".tsx", ".mjs", ".cjs",
|
|
326
|
+
".py", ".go", ".rs", ".java", ".kt", ".rb", ".php",
|
|
327
|
+
".tf", ".hcl",
|
|
328
|
+
".yaml", ".yml",
|
|
329
|
+
".json",
|
|
330
|
+
".sh", ".bash",
|
|
331
|
+
".md", ".txt",
|
|
332
|
+
".html", ".css",
|
|
333
|
+
".sql"
|
|
334
|
+
]);
|
|
335
|
+
const HARVEST_EXCLUDE_DIRS = new Set([
|
|
336
|
+
"node_modules", ".git", ".terraform", "dist", "build",
|
|
337
|
+
".next", "coverage", "__pycache__", ".venv", "venv", "env",
|
|
338
|
+
".cache", "tmp", "temp", "logs", "vendor"
|
|
339
|
+
]);
|
|
340
|
+
const HARVEST_EXCLUDE_FILES = new Set([
|
|
341
|
+
"package-lock.json", "yarn.lock", "pnpm-lock.yaml",
|
|
342
|
+
"poetry.lock", "Pipfile.lock", ".terraform.lock.hcl"
|
|
343
|
+
]);
|
|
344
|
+
const MAX_HARVEST_CHARS = 80000; // ~20k tokens — safe for 32k context models
|
|
345
|
+
const MAX_FILE_CHARS = 8000; // Cap individual files to avoid one giant file eating the budget
|
|
346
|
+
|
|
347
|
+
function walkDir(dir, fileList = []) {
|
|
348
|
+
let entries;
|
|
349
|
+
try {
|
|
350
|
+
entries = fs.readdirSync(dir);
|
|
351
|
+
} catch (_) {
|
|
352
|
+
return fileList;
|
|
353
|
+
}
|
|
354
|
+
for (const entry of entries) {
|
|
355
|
+
if (HARVEST_EXCLUDE_DIRS.has(entry)) continue;
|
|
356
|
+
const fullPath = path.join(dir, entry);
|
|
357
|
+
let stat;
|
|
358
|
+
try {
|
|
359
|
+
stat = fs.statSync(fullPath);
|
|
360
|
+
} catch (_) {
|
|
361
|
+
continue;
|
|
362
|
+
}
|
|
363
|
+
if (stat.isDirectory()) {
|
|
364
|
+
walkDir(fullPath, fileList);
|
|
365
|
+
} else if (stat.isFile()) {
|
|
366
|
+
const ext = path.extname(entry).toLowerCase();
|
|
367
|
+
if (HARVEST_EXTENSIONS.has(ext) && !HARVEST_EXCLUDE_FILES.has(entry)) {
|
|
368
|
+
fileList.push({ fullPath, relativePath: path.relative(TARGET_DIR, fullPath), size: stat.size });
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
return fileList;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
function harvestRepoContext(targetDir = TARGET_DIR) {
|
|
376
|
+
console.log("[Phase 0] Harvesting full repository context...");
|
|
377
|
+
|
|
378
|
+
const allFiles = walkDir(targetDir);
|
|
379
|
+
// Sort by size ASC: smaller files first so we maximize file count within budget
|
|
380
|
+
allFiles.sort((a, b) => a.size - b.size);
|
|
381
|
+
|
|
382
|
+
let totalChars = 0;
|
|
383
|
+
let codeContext = "";
|
|
384
|
+
const includedFiles = [];
|
|
385
|
+
const skippedFiles = [];
|
|
386
|
+
|
|
387
|
+
for (const { fullPath, relativePath } of allFiles) {
|
|
388
|
+
if (totalChars >= MAX_HARVEST_CHARS) {
|
|
389
|
+
skippedFiles.push(relativePath);
|
|
390
|
+
continue;
|
|
391
|
+
}
|
|
392
|
+
try {
|
|
393
|
+
let content = fs.readFileSync(fullPath, "utf-8");
|
|
394
|
+
if (content.length > MAX_FILE_CHARS) {
|
|
395
|
+
content = content.slice(0, MAX_FILE_CHARS) + `\n// ... [truncated — ${Math.round(content.length / 1000)}k chars total]`;
|
|
396
|
+
}
|
|
397
|
+
const ext = path.extname(relativePath).slice(1) || "";
|
|
398
|
+
codeContext += `\n### File: ${relativePath}\n\`\`\`${ext}\n${content}\n\`\`\`\n`;
|
|
399
|
+
includedFiles.push(relativePath);
|
|
400
|
+
totalChars += content.length;
|
|
401
|
+
} catch (_) {
|
|
402
|
+
skippedFiles.push(relativePath);
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
console.log(`[Phase 0] Harvest complete: ${includedFiles.length} files included (${Math.round(totalChars / 1000)}k chars), ${skippedFiles.length} skipped (budget limit).`);
|
|
407
|
+
if (skippedFiles.length > 0) {
|
|
408
|
+
console.log("[Phase 0] Skipped (budget):", skippedFiles.slice(0, 10).join(", "));
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
return { codeContext, files: includedFiles, skippedFiles, totalChars };
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
// ==========================================
|
|
415
|
+
// MEJORA 3: LOCAL OFFLINE RAG (TF-IDF)
|
|
416
|
+
// Selects only the most relevant file chunks
|
|
417
|
+
// for the instruction — cuts context 4x
|
|
418
|
+
// ==========================================
|
|
419
|
+
const RAG_MAX_CHARS = 22000; // ~5.5k tokens — focused context for small models
|
|
420
|
+
const RAG_TOP_FILES = 8; // Max files to include in RAG context
|
|
421
|
+
|
|
422
|
+
function tokenize(text) {
|
|
423
|
+
return text.toLowerCase()
|
|
424
|
+
.replace(/[^a-z0-9_\s]/g, ' ')
|
|
425
|
+
.split(/\s+/)
|
|
426
|
+
.filter(t => t.length > 2);
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
function buildRagContext(targetDir, instruction) {
|
|
430
|
+
console.log('[Phase RAG] Building TF-IDF index for instruction-guided context selection...');
|
|
431
|
+
const allFiles = walkDir(targetDir);
|
|
432
|
+
if (allFiles.length === 0) return null;
|
|
433
|
+
|
|
434
|
+
// Tokenize instruction as the query
|
|
435
|
+
const queryTokens = tokenize(instruction);
|
|
436
|
+
const queryFreq = {};
|
|
437
|
+
for (const t of queryTokens) queryFreq[t] = (queryFreq[t] || 0) + 1;
|
|
438
|
+
|
|
439
|
+
// Score each file by term overlap with instruction
|
|
440
|
+
const scored = [];
|
|
441
|
+
for (const { fullPath, relativePath } of allFiles) {
|
|
442
|
+
try {
|
|
443
|
+
let content = fs.readFileSync(fullPath, 'utf-8');
|
|
444
|
+
if (content.length > MAX_FILE_CHARS) content = content.slice(0, MAX_FILE_CHARS);
|
|
445
|
+
|
|
446
|
+
const fileTokens = tokenize(content);
|
|
447
|
+
const fileFreq = {};
|
|
448
|
+
for (const t of fileTokens) fileFreq[t] = (fileFreq[t] || 0) + 1;
|
|
449
|
+
|
|
450
|
+
// TF-IDF-like score: sum of query term frequencies in the file
|
|
451
|
+
let score = 0;
|
|
452
|
+
for (const [term, qf] of Object.entries(queryFreq)) {
|
|
453
|
+
if (fileFreq[term]) score += qf * Math.log(1 + fileFreq[term]);
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
// Boost: filename match with any instruction word
|
|
457
|
+
const lowerPath = relativePath.toLowerCase();
|
|
458
|
+
for (const t of queryTokens) {
|
|
459
|
+
if (lowerPath.includes(t)) score += 3;
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
// Boost: key config files always relevant
|
|
463
|
+
if (['package.json','readme.md','main.tf','main.py','index.js','index.ts',
|
|
464
|
+
'app.js','app.py','server.js'].some(k => lowerPath.endsWith(k))) {
|
|
465
|
+
score += 2;
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
scored.push({ relativePath, fullPath, content, score });
|
|
469
|
+
} catch (_) { /* skip unreadable files */ }
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
// Sort by score descending, take top N
|
|
473
|
+
scored.sort((a, b) => b.score - a.score);
|
|
474
|
+
const topFiles = scored.slice(0, RAG_TOP_FILES);
|
|
475
|
+
|
|
476
|
+
if (topFiles.length === 0 || topFiles[0].score === 0) {
|
|
477
|
+
console.log('[Phase RAG] No relevant files found via TF-IDF. Falling back to full harvest.');
|
|
478
|
+
return null;
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
let ragContext = '';
|
|
482
|
+
let totalChars = 0;
|
|
483
|
+
const includedFiles = [];
|
|
484
|
+
|
|
485
|
+
for (const { relativePath, content } of topFiles) {
|
|
486
|
+
if (totalChars >= RAG_MAX_CHARS) break;
|
|
487
|
+
const ext = path.extname(relativePath).slice(1) || '';
|
|
488
|
+
const chunk = `\n### File: ${relativePath}\n\`\`\`${ext}\n${content}\n\`\`\`\n`;
|
|
489
|
+
ragContext += chunk;
|
|
490
|
+
totalChars += content.length;
|
|
491
|
+
includedFiles.push(relativePath);
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
console.log(`[Phase RAG] Selected ${includedFiles.length}/${allFiles.length} files (${Math.round(totalChars/1000)}k chars). Top: ${includedFiles.slice(0,3).join(', ')}`);
|
|
495
|
+
return { codeContext: ragContext, files: includedFiles, skippedFiles: [], totalChars };
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
// Helper to detect if a file name in plan or instruction represents a new file
|
|
499
|
+
// that does not exist in the repository yet.
|
|
500
|
+
function detectNewFileFromContext(content, plan, instruction) {
|
|
501
|
+
const fileRegex = /([a-zA-Z0-9_\-\/\\\.]+\.(?:js|ts|jsx|tsx|py|go|rs|tf|md|json|yml|yaml|hcl|sh|txt))/g;
|
|
502
|
+
const candidates = new Set();
|
|
503
|
+
|
|
504
|
+
let m;
|
|
505
|
+
if (instruction) {
|
|
506
|
+
fileRegex.lastIndex = 0;
|
|
507
|
+
while ((m = fileRegex.exec(instruction)) !== null) {
|
|
508
|
+
candidates.add(m[1]);
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
if (plan) {
|
|
512
|
+
fileRegex.lastIndex = 0;
|
|
513
|
+
while ((m = fileRegex.exec(plan)) !== null) {
|
|
514
|
+
candidates.add(m[1]);
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
if (content) {
|
|
518
|
+
fileRegex.lastIndex = 0;
|
|
519
|
+
while ((m = fileRegex.exec(content)) !== null) {
|
|
520
|
+
candidates.add(m[1]);
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
// Get list of existing files in TARGET_DIR to prevent matching existing files
|
|
525
|
+
const existingFiles = new Set();
|
|
526
|
+
try {
|
|
527
|
+
const list = walkDir(TARGET_DIR);
|
|
528
|
+
list.forEach(f => existingFiles.add(f.relativePath.toLowerCase().replace(/\\/g, "/")));
|
|
529
|
+
} catch (_) {}
|
|
530
|
+
|
|
531
|
+
for (const cand of candidates) {
|
|
532
|
+
const normalized = cand.replace(/\\/g, "/");
|
|
533
|
+
if (!existingFiles.has(normalized.toLowerCase())) {
|
|
534
|
+
// If the content contains the filename or if it's markdown starting with a header, it's a match
|
|
535
|
+
if (content.toLowerCase().includes(normalized.toLowerCase()) ||
|
|
536
|
+
(normalized.endsWith(".md") && content.trim().startsWith("#"))) {
|
|
537
|
+
console.log(`[Parser Heuristic] Detected target path for NEW file: ${normalized}`);
|
|
538
|
+
return normalized;
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
return null;
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
// Helper to extract, clean, and write code robustly
|
|
546
|
+
function writeCodeCleanly(content, defaultFile, contextFiles, plan = "", instruction = "") {
|
|
547
|
+
let cleanCode = content.trim();
|
|
548
|
+
|
|
549
|
+
// 1. Check if the model returned raw JSON block changes instead of code
|
|
550
|
+
if (cleanCode.startsWith("{") || cleanCode.startsWith("[") || cleanCode.toLowerCase().includes('"changes":')) {
|
|
551
|
+
console.warn("[!] Parser warning: Coder model returned structured changes (JSON/List) instead of clean file content.");
|
|
552
|
+
return null;
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
// 2. Extract markdown code block if present
|
|
556
|
+
const codeBlockRegex = /```[a-zA-Z]*\n([\s\S]*?)\n```/g;
|
|
557
|
+
const matches = [...cleanCode.matchAll(codeBlockRegex)];
|
|
558
|
+
if (matches.length > 0) {
|
|
559
|
+
cleanCode = matches.map(m => m[1]).join("\n");
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
// 3. Remove syntax-breaking markdown headers/comments
|
|
563
|
+
cleanCode = cleanCode.replace(/^###\s*File:\s*\S+/gm, "");
|
|
564
|
+
cleanCode = cleanCode.replace(/^\/\/ File:\s*\S+/gm, "");
|
|
565
|
+
cleanCode = cleanCode.trim();
|
|
566
|
+
|
|
567
|
+
if (cleanCode.length === 0) {
|
|
568
|
+
return null;
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
// Check if this is a new file created by the coder
|
|
572
|
+
let targetFile = defaultFile;
|
|
573
|
+
let isNewFile = false;
|
|
574
|
+
if (plan || instruction) {
|
|
575
|
+
const newFileCandidate = detectNewFileFromContext(cleanCode, plan, instruction);
|
|
576
|
+
if (newFileCandidate) {
|
|
577
|
+
targetFile = newFileCandidate;
|
|
578
|
+
isNewFile = true;
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
// 4. Reject conversational text alucinations (e.g., markdown lists or headers inside code files)
|
|
583
|
+
const isMarkdownFile = targetFile.endsWith(".md");
|
|
584
|
+
if (!isMarkdownFile) {
|
|
585
|
+
const lines = cleanCode.split("\n").map(l => l.trim());
|
|
586
|
+
const hasMarkdownStructure = lines.some(l => l.startsWith("###") || l.startsWith("##") || l.startsWith("1. ") || l.startsWith("- "));
|
|
587
|
+
const containsChatter = cleanCode.toLowerCase().includes("here is the") || cleanCode.toLowerCase().includes("step-by-step") || cleanCode.toLowerCase().includes("the following changes");
|
|
588
|
+
if (hasMarkdownStructure || containsChatter) {
|
|
589
|
+
console.warn(`[!] Parser warning: Rejected conversational text alucination for file ${targetFile}.`);
|
|
590
|
+
return null;
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
if (!isNewFile) {
|
|
595
|
+
// Match text to a file from contextFiles if possible
|
|
596
|
+
for (const file of contextFiles) {
|
|
597
|
+
if (content.toLowerCase().includes(file.toLowerCase())) {
|
|
598
|
+
targetFile = file;
|
|
599
|
+
break;
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
const filePath = path.join(TARGET_DIR, targetFile);
|
|
605
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
606
|
+
fs.writeFileSync(filePath, cleanCode, "utf-8");
|
|
607
|
+
return targetFile;
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
|
|
611
|
+
function setupGitIdentity() {
|
|
612
|
+
if (TARGET_REPO === "mock/repo" || process.env.MOCK_GIT === "true") {
|
|
613
|
+
return;
|
|
614
|
+
}
|
|
615
|
+
try {
|
|
616
|
+
execSync("git config --global user.name 'Hiven Swarm'", { stdio: "ignore" });
|
|
617
|
+
execSync("git config --global user.email 'swarm@hiven.ai'", { stdio: "ignore" });
|
|
618
|
+
console.log("[+] Global Git identity configured successfully.");
|
|
619
|
+
} catch (e) {
|
|
620
|
+
console.warn("[-] Failed to configure global Git identity:", e.message);
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
// ==========================================
|
|
625
|
+
// MEJORA 2: DETERMINISTIC CONTEXT EXTRACTION
|
|
626
|
+
// Reads real project facts before LLM call
|
|
627
|
+
// eliminates hallucinated file names and paths
|
|
628
|
+
// ==========================================
|
|
629
|
+
function extractProjectFacts(targetDir) {
|
|
630
|
+
const facts = {
|
|
631
|
+
name: null,
|
|
632
|
+
version: null,
|
|
633
|
+
entryPoint: null,
|
|
634
|
+
scripts: {},
|
|
635
|
+
dependencies: [],
|
|
636
|
+
devDependencies: [],
|
|
637
|
+
framework: null,
|
|
638
|
+
runtime: null,
|
|
639
|
+
language: null,
|
|
640
|
+
configFiles: []
|
|
641
|
+
};
|
|
642
|
+
|
|
643
|
+
try {
|
|
644
|
+
const pkgPath = path.join(targetDir, 'package.json');
|
|
645
|
+
if (fs.existsSync(pkgPath)) {
|
|
646
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
|
|
647
|
+
facts.name = pkg.name || null;
|
|
648
|
+
facts.version = pkg.version || null;
|
|
649
|
+
facts.entryPoint = pkg.main || pkg.module || null;
|
|
650
|
+
facts.scripts = pkg.scripts || {};
|
|
651
|
+
facts.dependencies = Object.keys(pkg.dependencies || {}).slice(0, 20);
|
|
652
|
+
facts.devDependencies = Object.keys(pkg.devDependencies || {}).slice(0, 10);
|
|
653
|
+
facts.runtime = pkg.engines ? JSON.stringify(pkg.engines) : 'Node.js';
|
|
654
|
+
|
|
655
|
+
// Framework detection
|
|
656
|
+
const allDeps = [...facts.dependencies, ...facts.devDependencies];
|
|
657
|
+
if (allDeps.includes('next')) facts.framework = 'Next.js';
|
|
658
|
+
else if (allDeps.includes('react')) facts.framework = 'React';
|
|
659
|
+
else if (allDeps.includes('express')) facts.framework = 'Express';
|
|
660
|
+
else if (allDeps.includes('fastify')) facts.framework = 'Fastify';
|
|
661
|
+
else if (allDeps.includes('vue')) facts.framework = 'Vue';
|
|
662
|
+
else if (allDeps.includes('svelte')) facts.framework = 'Svelte';
|
|
663
|
+
else if (allDeps.includes('nestjs') || allDeps.includes('@nestjs/core')) facts.framework = 'NestJS';
|
|
664
|
+
|
|
665
|
+
facts.language = 'JavaScript/TypeScript';
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
// Check for Python
|
|
669
|
+
const pyFiles = ['requirements.txt', 'pyproject.toml', 'setup.py'];
|
|
670
|
+
for (const f of pyFiles) {
|
|
671
|
+
if (fs.existsSync(path.join(targetDir, f))) {
|
|
672
|
+
facts.language = 'Python';
|
|
673
|
+
facts.runtime = 'Python';
|
|
674
|
+
break;
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
// Detect Terraform
|
|
679
|
+
const tfFiles = fs.readdirSync(targetDir).filter(f => f.endsWith('.tf')).slice(0, 5);
|
|
680
|
+
if (tfFiles.length > 0) {
|
|
681
|
+
facts.language = facts.language ? facts.language + '/Terraform' : 'Terraform';
|
|
682
|
+
facts.configFiles.push(...tfFiles);
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
// Common config files
|
|
686
|
+
const configCandidates = ['.eslintrc', '.eslintrc.js', '.eslintrc.json', 'tsconfig.json',
|
|
687
|
+
'jest.config.js', 'vite.config.js', 'webpack.config.js', 'Dockerfile', 'docker-compose.yml'];
|
|
688
|
+
for (const c of configCandidates) {
|
|
689
|
+
if (fs.existsSync(path.join(targetDir, c))) facts.configFiles.push(c);
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
fs.writeFileSync('project_facts.json', JSON.stringify(facts, null, 2), 'utf-8');
|
|
693
|
+
console.log('[Phase 0] Project facts extracted:', JSON.stringify(facts).substring(0, 200));
|
|
694
|
+
} catch (e) {
|
|
695
|
+
console.warn('[Phase 0] Project facts extraction partial error (non-blocking):', e.message);
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
return facts;
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
async function main() {
|
|
702
|
+
setupGitIdentity();
|
|
703
|
+
try {
|
|
704
|
+
if (PHASE === "E2E_SIMULATED" || !PHASE) {
|
|
705
|
+
console.log(chalk.cyan("=========================================================="));
|
|
706
|
+
console.log(chalk.cyan(" 🐝 HIVEN SWARM - LOCAL EXECUTION / SIMULATION "));
|
|
707
|
+
console.log(chalk.cyan("=========================================================="));
|
|
708
|
+
|
|
709
|
+
// 1. Run Context
|
|
710
|
+
console.log(chalk.yellow("\n[*] Running Phase 1: Context & Planning..."));
|
|
711
|
+
checkoutCodebase();
|
|
712
|
+
const honeyDb = loadHoneyDb();
|
|
713
|
+
|
|
714
|
+
// FASE 0: Full repo harvest for local simulation
|
|
715
|
+
let files = [];
|
|
716
|
+
let codeContext = "";
|
|
717
|
+
if (FILES_TO_EDIT) {
|
|
718
|
+
files = FILES_TO_EDIT.split(",").map(f => f.trim());
|
|
719
|
+
for (const file of files) {
|
|
720
|
+
const filePath = path.join(TARGET_DIR, file);
|
|
721
|
+
if (fs.existsSync(filePath)) {
|
|
722
|
+
let content = fs.readFileSync(filePath, "utf-8");
|
|
723
|
+
if (content.length > MAX_FILE_CHARS) content = content.slice(0, MAX_FILE_CHARS) + "\n// ... [truncated]";
|
|
724
|
+
codeContext += `\n### File: ${file}\n\`\`\`\n${content}\n\`\`\`\n`;
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
} else {
|
|
728
|
+
const harvest = harvestRepoContext();
|
|
729
|
+
files = harvest.files;
|
|
730
|
+
codeContext = harvest.codeContext;
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
console.log("[*] Assessing task complexity...");
|
|
734
|
+
const plannerModel = MODELS.PLANNER;
|
|
735
|
+
await queryOllama(plannerModel, "Identify task style").catch(() => {});
|
|
736
|
+
|
|
737
|
+
const contextPrompt = `Analyze the developer instruction and files. Rate the task complexity as either LOW or HIGH.\n\nFILES:\n${files.join(", ")}\n\nINSTRUCTION:\n${INSTRUCTION}`;
|
|
738
|
+
const complexityResp = await queryOllama(plannerModel, contextPrompt, "You are Hiven-Router.");
|
|
739
|
+
const complexity = complexityResp.toUpperCase().includes("HIGH") ? "HIGH" : "LOW";
|
|
740
|
+
console.log(`[+] Task Complexity: ${complexity}`);
|
|
741
|
+
|
|
742
|
+
const contextPromptPlan = `Generate a plan to implement the instruction: "${INSTRUCTION}"\n\nFILES:\n${codeContext}`;
|
|
743
|
+
const plan = await queryOllama(plannerModel, contextPromptPlan, "You are Hiven-Architect.");
|
|
744
|
+
console.log("[+] Plan Generated:\n", plan);
|
|
745
|
+
|
|
746
|
+
// 2. Run Execution
|
|
747
|
+
console.log(chalk.yellow("\n[*] Running Phase 2: Code Generation..."));
|
|
748
|
+
const coderModel = complexity === "HIGH" ? MODELS.CODER_HIGH : MODELS.CODER_LOW;
|
|
749
|
+
const coderPrompt = `Implement the plan:\n${plan}\n\nFILES:\n${codeContext}`;
|
|
750
|
+
const coderOutput = await queryOllama(coderModel, coderPrompt, "You are Hiven-Coder.");
|
|
751
|
+
console.log("[+] Code generated.");
|
|
752
|
+
|
|
753
|
+
// 3. Run Validation
|
|
754
|
+
console.log(chalk.yellow("\n[*] Running Phase 3: Validation & Correction..."));
|
|
755
|
+
const fileRegex = /---START_FILE:\s*([^\s-]+)---\n([\s\S]*?)\n---END_FILE:\s*\1---/g;
|
|
756
|
+
let match;
|
|
757
|
+
let modifiedFiles = {};
|
|
758
|
+
|
|
759
|
+
while ((match = fileRegex.exec(coderOutput)) !== null) {
|
|
760
|
+
modifiedFiles[match[1].trim()] = match[2];
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
if (Object.keys(modifiedFiles).length === 0 && files.length > 0) {
|
|
764
|
+
const writtenFile = writeCodeCleanly(coderOutput, files[0], files, plan, INSTRUCTION);
|
|
765
|
+
if (writtenFile) {
|
|
766
|
+
modifiedFiles[writtenFile] = fs.readFileSync(path.join(TARGET_DIR, writtenFile), "utf-8");
|
|
767
|
+
} else {
|
|
768
|
+
console.warn("[-] E2E_SIMULATED: Parser rejected the code output as conversational alucination.");
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
console.log("[+] Validation complete.");
|
|
773
|
+
|
|
774
|
+
// 4. Run Consolidation
|
|
775
|
+
console.log(chalk.yellow("\n[*] Running Phase 4: Patch Consolidation..."));
|
|
776
|
+
for (const [file, content] of Object.entries(modifiedFiles)) {
|
|
777
|
+
const localPath = path.join(TARGET_DIR, file);
|
|
778
|
+
fs.writeFileSync(localPath, content, "utf-8");
|
|
779
|
+
console.log(chalk.green(`[+] Applied local patch successfully: ${localPath}`));
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
console.log(chalk.green("\n[+] HIVEN LOCAL SWARM FULLY COMPLETED!"));
|
|
783
|
+
return;
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
if (PHASE === "context") {
|
|
787
|
+
// ==========================================================
|
|
788
|
+
// FASE 1: CONTEXT (Option A - Decomposition & Option B - Routing)
|
|
789
|
+
// ==========================================================
|
|
790
|
+
console.log("[*] Running Phase 1 Context Kōmbees...");
|
|
791
|
+
await updateStatusComment("context", "running");
|
|
792
|
+
await sendTelemetry("running", "Phase 1 Context Kōmbees starting...");
|
|
793
|
+
checkoutCodebase();
|
|
794
|
+
const honeyDb = loadHoneyDb();
|
|
795
|
+
|
|
796
|
+
// MEJORA 2: Extract deterministic project facts before any LLM call
|
|
797
|
+
const projectFacts = extractProjectFacts(TARGET_DIR);
|
|
798
|
+
await sendTelemetry("running", `[Phase 0] Project facts: lang=${projectFacts.language || 'unknown'}, framework=${projectFacts.framework || 'none'}, entry=${projectFacts.entryPoint || 'n/a'}, deps=${projectFacts.dependencies.slice(0,5).join(',')||'none'}`);
|
|
799
|
+
const factsSection = `
|
|
800
|
+
PROJECT FACTS (deterministic, extracted from repo):
|
|
801
|
+
- Name: ${projectFacts.name || 'unknown'}
|
|
802
|
+
- Language: ${projectFacts.language || 'unknown'}
|
|
803
|
+
- Framework: ${projectFacts.framework || 'none detected'}
|
|
804
|
+
- Entry point: ${projectFacts.entryPoint || 'not specified'}
|
|
805
|
+
- Runtime: ${projectFacts.runtime || 'unknown'}
|
|
806
|
+
- Key dependencies: ${projectFacts.dependencies.slice(0, 10).join(', ') || 'none'}
|
|
807
|
+
- Scripts available: ${Object.keys(projectFacts.scripts).join(', ') || 'none'}
|
|
808
|
+
- Config files: ${projectFacts.configFiles.join(', ') || 'none'}
|
|
809
|
+
`;
|
|
810
|
+
// FASE 0: Deep repository context harvest (replaces shallow 3-file read)
|
|
811
|
+
let files = [];
|
|
812
|
+
let codeContext = "";
|
|
813
|
+
if (FILES_TO_EDIT) {
|
|
814
|
+
// If specific files were requested, read only those
|
|
815
|
+
files = FILES_TO_EDIT.split(",").map(f => f.trim());
|
|
816
|
+
for (const file of files) {
|
|
817
|
+
const filePath = path.join(TARGET_DIR, file);
|
|
818
|
+
if (fs.existsSync(filePath)) {
|
|
819
|
+
let content = fs.readFileSync(filePath, "utf-8");
|
|
820
|
+
if (content.length > MAX_FILE_CHARS) content = content.slice(0, MAX_FILE_CHARS) + "\n// ... [truncated]";
|
|
821
|
+
const ext = path.extname(file).slice(1) || "";
|
|
822
|
+
codeContext += `\n### File: ${file}\n\`\`\`${ext}\n${content}\n\`\`\`\n`;
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
console.log(`[Phase 0] Specific files mode: loaded ${files.length} files.`);
|
|
826
|
+
await sendTelemetry("running", `[Phase 0] Specific files mode: loaded ${files.length} files.`);
|
|
827
|
+
} else {
|
|
828
|
+
// MEJORA 3: Try RAG first, fall back to full harvest
|
|
829
|
+
const ragResult = buildRagContext(TARGET_DIR, INSTRUCTION);
|
|
830
|
+
if (ragResult && ragResult.files.length > 0) {
|
|
831
|
+
files = ragResult.files;
|
|
832
|
+
codeContext = ragResult.codeContext;
|
|
833
|
+
await sendTelemetry("running", `[Phase RAG] Context: ${ragResult.files.length} relevant files selected (${Math.round(ragResult.totalChars/1000)}k chars). Files: ${ragResult.files.slice(0,3).join(', ')}`);
|
|
834
|
+
} else {
|
|
835
|
+
const harvest = harvestRepoContext();
|
|
836
|
+
files = harvest.files;
|
|
837
|
+
codeContext = harvest.codeContext;
|
|
838
|
+
await sendTelemetry("running", `[Phase 0] Repo harvested: ${harvest.files.length} files (${Math.round(harvest.totalChars/1000)}k chars)${harvest.skippedFiles.length > 0 ? ', ' + harvest.skippedFiles.length + ' skipped (budget)' : ''}`);
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
// 1. Complexity Assessment & Conditional Routing (Option B Improved)
|
|
843
|
+
console.log("[*] Assessing task complexity for compute routing...");
|
|
844
|
+
const complexityPrompt = `
|
|
845
|
+
Analyze the developer instruction and files. Rate the task complexity as either LOW, MEDIUM, or HIGH.
|
|
846
|
+
- LOW: Quick fixes, single-file edits, simple adjustments.
|
|
847
|
+
- MEDIUM: Multi-file edits, medium refactoring, minor additions.
|
|
848
|
+
- HIGH: Complex logic, algorithms, core architecture changes, database schema updates.
|
|
849
|
+
${factsSection}
|
|
850
|
+
FILES:
|
|
851
|
+
${files.join(", ")}
|
|
852
|
+
|
|
853
|
+
INSTRUCTION:
|
|
854
|
+
${INSTRUCTION}
|
|
855
|
+
|
|
856
|
+
Respond ONLY with a single JSON object containing "complexity" ("LOW", "MEDIUM", or "HIGH") and "reason".
|
|
857
|
+
`;
|
|
858
|
+
|
|
859
|
+
execSync(`ollama pull ${MODELS.VALIDATOR_LOW}`, { stdio: "inherit" });
|
|
860
|
+
const complexityResp = await queryOllama(MODELS.VALIDATOR_LOW, complexityPrompt, "You are Hiven-Complexity-Evaluator. Output JSON only. Respond with JSON format only.");
|
|
861
|
+
console.log("[+] Evaluator Response:", complexityResp);
|
|
862
|
+
|
|
863
|
+
let complexity = "LOW";
|
|
864
|
+
try {
|
|
865
|
+
const cleanJson = complexityResp.replace(/```[a-zA-Z]*\n([\s\S]*?)\n```/g, "$1").replace(/```json|```/g, "").trim();
|
|
866
|
+
const parsed = JSON.parse(cleanJson);
|
|
867
|
+
complexity = parsed.complexity || "LOW";
|
|
868
|
+
} catch (e) {
|
|
869
|
+
if (complexityResp.toUpperCase().includes("HIGH")) {
|
|
870
|
+
complexity = "HIGH";
|
|
871
|
+
} else if (complexityResp.toUpperCase().includes("MEDIUM")) {
|
|
872
|
+
complexity = "MEDIUM";
|
|
873
|
+
}
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
// Heuristic Safeguard: Upgrade to MEDIUM if multiple files are affected
|
|
877
|
+
if (files.length > 1 && complexity === "LOW") {
|
|
878
|
+
complexity = "MEDIUM";
|
|
879
|
+
console.log(`[Queen Guard] Automatically upgraded complexity to MEDIUM because multiple files (${files.length}) are affected.`);
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
console.log(`[+] Task Complexity Assessed: ${complexity}.`);
|
|
883
|
+
const requiresHeavyCoder = complexity === "HIGH";
|
|
884
|
+
|
|
885
|
+
// Determine planning model (Expert Routing)
|
|
886
|
+
let plannerModel = MODELS.PLANNER_LOW;
|
|
887
|
+
if (complexity === "MEDIUM" || complexity === "HIGH") {
|
|
888
|
+
plannerModel = MODELS.PLANNER_HIGH;
|
|
889
|
+
}
|
|
890
|
+
console.log(chalk.magenta(`[!] Selected Architect Kōmbee model: ${plannerModel}`));
|
|
891
|
+
|
|
892
|
+
console.log(`[*] Pulling planner model ${plannerModel}...`);
|
|
893
|
+
execSync(`ollama pull ${plannerModel}`, { stdio: "inherit" });
|
|
894
|
+
|
|
895
|
+
// 2. Task Decomposition with Federated Pattern Injection
|
|
896
|
+
console.log("[*] Running Architect Kōmbee to decompose instruction...");
|
|
897
|
+
|
|
898
|
+
// Load federated patterns from previous swarms
|
|
899
|
+
const fileExts = files.map(f => f.split('.').pop()).filter(Boolean);
|
|
900
|
+
const uniqueExts = [...new Set(fileExts)].slice(0, 3);
|
|
901
|
+
let patternsSection = '';
|
|
902
|
+
try {
|
|
903
|
+
const allPatterns = [];
|
|
904
|
+
for (const ext of uniqueExts) {
|
|
905
|
+
const p = await loadFederatedPatterns(INSTRUCTION, [ext]);
|
|
906
|
+
allPatterns.push(...p);
|
|
907
|
+
}
|
|
908
|
+
if (allPatterns.length > 0) {
|
|
909
|
+
const dosAndDonts = allPatterns.slice(-5).map(p =>
|
|
910
|
+
`- [${p.outcome}] ${p.instruction}`
|
|
911
|
+
).join('\n');
|
|
912
|
+
patternsSection = `\n\nFEDERATED SWARM MEMORY (Do's and Don'ts from previous runs):\n${dosAndDonts}`;
|
|
913
|
+
console.log(`[+] Federated patterns loaded: ${allPatterns.length} patterns found.`);
|
|
914
|
+
} else {
|
|
915
|
+
console.log('[*] No federated patterns yet for this file type. Starting fresh.');
|
|
916
|
+
}
|
|
917
|
+
} catch (e) {
|
|
918
|
+
console.warn('[-] Failed to load federated patterns (non-blocking):', e.message);
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
const contextPrompt = `
|
|
922
|
+
You are a senior software architect. Analyze the COMPLETE source code of the repository below and the developer instruction.
|
|
923
|
+
Write a precise, step-by-step implementation plan separating the work into atomic micro-tasks.
|
|
924
|
+
Each task must reference REAL file names and REAL function/variable names from the code provided.
|
|
925
|
+
Do NOT invent new files or functions that don't exist. Reference what you actually see below.
|
|
926
|
+
${factsSection}
|
|
927
|
+
REPOSITORY FILES (${files.length} files):
|
|
928
|
+
${codeContext}
|
|
929
|
+
|
|
930
|
+
DEVELOPER INSTRUCTION:
|
|
931
|
+
${INSTRUCTION}
|
|
932
|
+
|
|
933
|
+
STYLE CACHE (previous preferences):
|
|
934
|
+
${JSON.stringify(honeyDb.stylePreferences)}${patternsSection}
|
|
935
|
+
|
|
936
|
+
Output a numbered list of concrete implementation steps, each referencing specific files and functions.
|
|
937
|
+
`;
|
|
938
|
+
|
|
939
|
+
const plan = await queryOllama(plannerModel, contextPrompt, "You are Hiven-Architect, a principal code planning agent.");
|
|
940
|
+
console.log("[+] Plan Generated:\n", plan);
|
|
941
|
+
|
|
942
|
+
// MEJORA 5: Build Parallel Sub-Swarm Partitioning
|
|
943
|
+
// Group nodes by file subsets if multiple files exist
|
|
944
|
+
const partitions = [];
|
|
945
|
+
if (files.length > 1) {
|
|
946
|
+
const mid = Math.ceil(files.length / 2);
|
|
947
|
+
partitions.push({ group: 1, files: files.slice(0, mid), nodes: [1, 2, 3, 4, 5] });
|
|
948
|
+
partitions.push({ group: 2, files: files.slice(mid), nodes: [6, 7, 8, 9, 10] });
|
|
949
|
+
console.log(`[Sub-Swarms] Created 2 parallel sub-swarm partitions: Group 1 (${files.slice(0, mid).join(', ')}), Group 2 (${files.slice(mid).join(', ')})`);
|
|
950
|
+
} else {
|
|
951
|
+
partitions.push({ group: 1, files: files, nodes: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] });
|
|
952
|
+
console.log(`[Sub-Swarms] Single file task — all 10 nodes assigned to Group 1 (${files.join(', ')})`);
|
|
953
|
+
}
|
|
954
|
+
|
|
955
|
+
// Save context metadata for next stages
|
|
956
|
+
const swarmContext = {
|
|
957
|
+
plan,
|
|
958
|
+
complexity,
|
|
959
|
+
requiresHeavyCoder,
|
|
960
|
+
files,
|
|
961
|
+
codeContext,
|
|
962
|
+
projectFacts,
|
|
963
|
+
partitions
|
|
964
|
+
};
|
|
965
|
+
fs.writeFileSync("swarm_context.json", JSON.stringify(swarmContext, null, 2), "utf-8");
|
|
966
|
+
console.log("[+] Context saved to swarm_context.json.");
|
|
967
|
+
await updateStatusComment("context", "done");
|
|
968
|
+
await sendTelemetry("done", "Phase 1 Context complete. plan generated.");
|
|
969
|
+
}
|
|
970
|
+
|
|
971
|
+
else if (PHASE === "execution") {
|
|
972
|
+
// ==========================================================
|
|
973
|
+
// FASE 2: EXECUTION (Conditional Routing & Inferencia Híbrida)
|
|
974
|
+
// ==========================================================
|
|
975
|
+
console.log(`[*] Running Coder Kōmbee Node #${KOMBEE_INDEX}...`);
|
|
976
|
+
if (KOMBEE_INDEX === 1) {
|
|
977
|
+
await updateStatusComment("execution", "running");
|
|
978
|
+
}
|
|
979
|
+
await sendTelemetry("running", `Coder Node #${KOMBEE_INDEX} processing...`);
|
|
980
|
+
const context = JSON.parse(fs.readFileSync("swarm_context.json", "utf-8"));
|
|
981
|
+
// MEJORA 5: Filter files for this node's partition group
|
|
982
|
+
const myPartition = (context.partitions || []).find(p => p.nodes.includes(KOMBEE_INDEX)) || { group: 1, files: context.files };
|
|
983
|
+
const nodeTargetFiles = myPartition.files.length > 0 ? myPartition.files : context.files;
|
|
984
|
+
console.log(`[Sub-Swarm Node #${KOMBEE_INDEX}] Assigned Partition Group ${myPartition.group} -> Target Files: ${nodeTargetFiles.join(', ')}`);
|
|
985
|
+
|
|
986
|
+
// Re-read assigned partition files from actual checkout
|
|
987
|
+
const CODER_FILE_MAX = 40000;
|
|
988
|
+
let fullCoderContext = '';
|
|
989
|
+
let coderTotalChars = 0;
|
|
990
|
+
const truncatedFiles = [];
|
|
991
|
+
for (const file of nodeTargetFiles) {
|
|
992
|
+
const filePath = path.join(TARGET_DIR, file);
|
|
993
|
+
if (fs.existsSync(filePath)) {
|
|
994
|
+
let content = fs.readFileSync(filePath, 'utf-8');
|
|
995
|
+
if (content.length > CODER_FILE_MAX) {
|
|
996
|
+
content = content.slice(0, CODER_FILE_MAX);
|
|
997
|
+
truncatedFiles.push(file);
|
|
998
|
+
}
|
|
999
|
+
const ext = path.extname(file).slice(1) || '';
|
|
1000
|
+
fullCoderContext += `\n### File: ${file}\n\`\`\`${ext}\n${content}\n\`\`\`\n`;
|
|
1001
|
+
coderTotalChars += content.length;
|
|
1002
|
+
}
|
|
1003
|
+
}
|
|
1004
|
+
console.log(`[Coder Node #${KOMBEE_INDEX}] Context: ${nodeTargetFiles.length} files, ${Math.round(coderTotalChars/1000)}k chars${truncatedFiles.length > 0 ? `. Truncated (>40k): ${truncatedFiles.join(', ')}` : ''}`);
|
|
1005
|
+
|
|
1006
|
+
const truncationWarning = truncatedFiles.length > 0
|
|
1007
|
+
? `\nWARNING: The following files were too large to fit fully in context and were truncated: ${truncatedFiles.join(', ')}. For these files, output ONLY the specific functions/sections that need to change — do NOT output the beginning of the file followed by nothing. The system will merge your changes into the original file.`
|
|
1008
|
+
: '';
|
|
1009
|
+
|
|
1010
|
+
// Determine model based on 3-tier complexity
|
|
1011
|
+
let model = MODELS.CODER_LOW;
|
|
1012
|
+
if (context.complexity === "HIGH") {
|
|
1013
|
+
model = MODELS.CODER_HIGH;
|
|
1014
|
+
console.log(chalk.magenta(`[!] High Complexity: Routing Coder Kōmbee Node #${KOMBEE_INDEX} to ${model}.`));
|
|
1015
|
+
} else if (context.complexity === "MEDIUM") {
|
|
1016
|
+
if (KOMBEE_INDEX >= 9) {
|
|
1017
|
+
model = MODELS.CODER_HIGH;
|
|
1018
|
+
console.log(chalk.magenta(`[!] Medium Complexity: Scaling up Core Coder Node #${KOMBEE_INDEX} to ${model}!`));
|
|
1019
|
+
} else {
|
|
1020
|
+
console.log(`[+] Medium Complexity: Node #${KOMBEE_INDEX} running model ${model}.`);
|
|
1021
|
+
}
|
|
1022
|
+
} else {
|
|
1023
|
+
console.log(`[+] Low Complexity: Node #${KOMBEE_INDEX} running model ${model}.`);
|
|
1024
|
+
}
|
|
1025
|
+
|
|
1026
|
+
// Ensure model is pulled (Ollama cache handles this instantly if cached)
|
|
1027
|
+
execSync(`ollama pull ${model}`, { stdio: "inherit" });
|
|
1028
|
+
|
|
1029
|
+
const coderPrompt = `
|
|
1030
|
+
Perform the implementation steps detailed in the plan. Return the complete refactored files.
|
|
1031
|
+
PLAN:
|
|
1032
|
+
${context.plan}
|
|
1033
|
+
|
|
1034
|
+
FILES TO EDIT:
|
|
1035
|
+
${fullCoderContext}
|
|
1036
|
+
|
|
1037
|
+
INSTRUCTION:
|
|
1038
|
+
${INSTRUCTION}${truncationWarning}
|
|
1039
|
+
|
|
1040
|
+
FORMAT REQUIREMENT:
|
|
1041
|
+
You must output each modified file wrapped strictly inside the boundary markers like this:
|
|
1042
|
+
|
|
1043
|
+
---START_FILE: example.js---
|
|
1044
|
+
function example() {
|
|
1045
|
+
return "example";
|
|
1046
|
+
}
|
|
1047
|
+
module.exports = { example };
|
|
1048
|
+
---END_FILE: example.js---
|
|
1049
|
+
|
|
1050
|
+
CRITICAL: You must preserve the existing outer file structure, function signatures, and module exports (e.g., module.exports = ...). Never delete the export statements. NEVER shorten the file by omitting pre-existing functions.
|
|
1051
|
+
`;
|
|
1052
|
+
|
|
1053
|
+
const coderOutput = await queryOllama(model, coderPrompt, `You are Hiven-Coder-${KOMBEE_INDEX}, an elite coding Kōmbee.`);
|
|
1054
|
+
|
|
1055
|
+
// Save raw output to file
|
|
1056
|
+
const resultPayload = {
|
|
1057
|
+
kombeeIndex: KOMBEE_INDEX,
|
|
1058
|
+
partitionGroup: myPartition.group,
|
|
1059
|
+
model,
|
|
1060
|
+
coderOutput
|
|
1061
|
+
};
|
|
1062
|
+
fs.writeFileSync(`coder_output_${KOMBEE_INDEX}.json`, JSON.stringify(resultPayload, null, 2), "utf-8");
|
|
1063
|
+
console.log(`[+] Coder Kōmbee output saved to coder_output_${KOMBEE_INDEX}.json.`);
|
|
1064
|
+
await sendTelemetry("done", `Coder Node #${KOMBEE_INDEX} completed.`);
|
|
1065
|
+
}
|
|
1066
|
+
|
|
1067
|
+
else if (PHASE === "validation") {
|
|
1068
|
+
// ==========================================================
|
|
1069
|
+
// FASE 3: VALIDATION & CORRECCIÓN (Tester & Reviewer Kōmbees)
|
|
1070
|
+
// ==========================================================
|
|
1071
|
+
console.log(`[*] Running Validator Kōmbee Node #${KOMBEE_INDEX}...`);
|
|
1072
|
+
if (KOMBEE_INDEX === 1) {
|
|
1073
|
+
await updateStatusComment("execution", "done");
|
|
1074
|
+
await updateStatusComment("validation", "running");
|
|
1075
|
+
}
|
|
1076
|
+
await sendTelemetry("running", `Validator Node #${KOMBEE_INDEX} checking syntax...`);
|
|
1077
|
+
checkoutCodebase();
|
|
1078
|
+
const context = JSON.parse(fs.readFileSync("swarm_context.json", "utf-8"));
|
|
1079
|
+
const coderData = JSON.parse(fs.readFileSync(`coder_output_${KOMBEE_INDEX}.json`, "utf-8"));
|
|
1080
|
+
|
|
1081
|
+
// Parse output files and write to target directory
|
|
1082
|
+
const fileRegex = /---START_FILE:\s*([^\s-]+)---\n([\s\S]*?)\n---END_FILE:\s*\1---/g;
|
|
1083
|
+
let match;
|
|
1084
|
+
let modifiedFiles = [];
|
|
1085
|
+
|
|
1086
|
+
while ((match = fileRegex.exec(coderData.coderOutput)) !== null) {
|
|
1087
|
+
const fileName = match[1].trim();
|
|
1088
|
+
const newContent = match[2];
|
|
1089
|
+
const filePath = path.join(TARGET_DIR, fileName);
|
|
1090
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
1091
|
+
fs.writeFileSync(filePath, newContent, "utf-8");
|
|
1092
|
+
modifiedFiles.push(fileName);
|
|
1093
|
+
}
|
|
1094
|
+
|
|
1095
|
+
if (modifiedFiles.length === 0 && context.files.length > 0) {
|
|
1096
|
+
// Fallback writing using smart helper
|
|
1097
|
+
const writtenFile = writeCodeCleanly(coderData.coderOutput, context.files[0], context.files, context.plan, INSTRUCTION);
|
|
1098
|
+
if (writtenFile) {
|
|
1099
|
+
modifiedFiles.push(writtenFile);
|
|
1100
|
+
}
|
|
1101
|
+
}
|
|
1102
|
+
|
|
1103
|
+
// Helper: Code Deletion Guard
|
|
1104
|
+
// Compares modified file size against the original in git HEAD
|
|
1105
|
+
const checkCodeDeletion = (file) => {
|
|
1106
|
+
const filePath = path.join(TARGET_DIR, file);
|
|
1107
|
+
let originalSize = 0;
|
|
1108
|
+
try {
|
|
1109
|
+
if (TARGET_REPO !== "mock/repo" && process.env.MOCK_GIT !== "true") {
|
|
1110
|
+
// file is already relative to repo root
|
|
1111
|
+
const gitShow = execSync(`git show HEAD:${file}`, { cwd: TARGET_DIR, stdio: 'pipe' }).toString();
|
|
1112
|
+
originalSize = gitShow.length;
|
|
1113
|
+
}
|
|
1114
|
+
} catch (_) {
|
|
1115
|
+
// File is likely new (not in HEAD) — no deletion guard needed
|
|
1116
|
+
return { shrunk: false };
|
|
1117
|
+
}
|
|
1118
|
+
|
|
1119
|
+
if (originalSize > 500) {
|
|
1120
|
+
const newSize = fs.existsSync(filePath) ? fs.readFileSync(filePath, 'utf-8').length : 0;
|
|
1121
|
+
const ratio = newSize / originalSize;
|
|
1122
|
+
const isExplicitDelete = /delete|remove|cleanup|clear|vaciar|eliminar|reemplazar/i.test(INSTRUCTION);
|
|
1123
|
+
if (ratio < 0.5 && !isExplicitDelete) {
|
|
1124
|
+
return {
|
|
1125
|
+
shrunk: true,
|
|
1126
|
+
originalSize,
|
|
1127
|
+
newSize,
|
|
1128
|
+
msg: `Rejection: Code deletion safety guard triggered. File '${file}' shrunk by ${Math.round((1 - ratio) * 100)}% (from ${originalSize} to ${newSize} chars). Critical functions were likely deleted. You MUST return the ENTIRE file with ONLY the requested changes applied, preserving all pre-existing functions, helpers, and exports.`
|
|
1129
|
+
};
|
|
1130
|
+
}
|
|
1131
|
+
}
|
|
1132
|
+
return { shrunk: false };
|
|
1133
|
+
};
|
|
1134
|
+
|
|
1135
|
+
const isOllamaAvailable = () => {
|
|
1136
|
+
try { execSync('ollama --version', { stdio: 'pipe' }); return true; } catch { return false; }
|
|
1137
|
+
};
|
|
1138
|
+
|
|
1139
|
+
let isSuccess = false;
|
|
1140
|
+
let errorLog = "";
|
|
1141
|
+
const maxAttempts = 3;
|
|
1142
|
+
|
|
1143
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
1144
|
+
console.log(`[*] Validation Attempt ${attempt}/${maxAttempts} for Node #${KOMBEE_INDEX}...`);
|
|
1145
|
+
isSuccess = true;
|
|
1146
|
+
errorLog = "";
|
|
1147
|
+
|
|
1148
|
+
if (modifiedFiles.length === 0) {
|
|
1149
|
+
isSuccess = false;
|
|
1150
|
+
errorLog += "Failed to parse code output: Coder model returned invalid layout or empty/JSON response.\n";
|
|
1151
|
+
}
|
|
1152
|
+
|
|
1153
|
+
// 1. Check syntax and code deletion for all modified files
|
|
1154
|
+
for (const file of modifiedFiles) {
|
|
1155
|
+
const filePath = path.join(TARGET_DIR, file);
|
|
1156
|
+
|
|
1157
|
+
// Code Deletion Guard check
|
|
1158
|
+
const delCheck = checkCodeDeletion(file);
|
|
1159
|
+
if (delCheck.shrunk) {
|
|
1160
|
+
isSuccess = false;
|
|
1161
|
+
errorLog += `Logic error in ${file}:\n${delCheck.msg}\n`;
|
|
1162
|
+
console.log(chalk.red(`[-] ${delCheck.msg}`));
|
|
1163
|
+
continue;
|
|
1164
|
+
}
|
|
1165
|
+
|
|
1166
|
+
if (file.endsWith(".js")) {
|
|
1167
|
+
try {
|
|
1168
|
+
execSync(`node -c ${filePath}`, { stdio: "pipe" });
|
|
1169
|
+
} catch (e) {
|
|
1170
|
+
isSuccess = false;
|
|
1171
|
+
const stderrStr = e.stderr ? e.stderr.toString() : e.message;
|
|
1172
|
+
errorLog += `Syntax error in ${file}:\n${stderrStr}\n`;
|
|
1173
|
+
console.log(chalk.red(`[-] Syntax check failed: ${file}`));
|
|
1174
|
+
}
|
|
1175
|
+
}
|
|
1176
|
+
}
|
|
1177
|
+
|
|
1178
|
+
// MEJORA 6: SHADOW SANDBOX DRY-RUN EXECUTION
|
|
1179
|
+
// Discovers and executes local unit tests / test scripts to verify changes in runtime
|
|
1180
|
+
if (isSuccess) {
|
|
1181
|
+
try {
|
|
1182
|
+
// Check for test files in repo
|
|
1183
|
+
const testFiles = [];
|
|
1184
|
+
for (const f of context.files) {
|
|
1185
|
+
if (/test[._-]|spec[._-]|[._-]test\.|[._-]spec\./i.test(f) && fs.existsSync(path.join(TARGET_DIR, f))) {
|
|
1186
|
+
testFiles.push(f);
|
|
1187
|
+
}
|
|
1188
|
+
}
|
|
1189
|
+
|
|
1190
|
+
if (testFiles.length > 0) {
|
|
1191
|
+
console.log(`[Shadow Sandbox] Found ${testFiles.length} test files. Executing dry-run runtime validation...`);
|
|
1192
|
+
for (const tf of testFiles.slice(0, 3)) { // Run up to 3 test files
|
|
1193
|
+
const tfPath = path.join(TARGET_DIR, tf);
|
|
1194
|
+
if (tf.endsWith('.js')) {
|
|
1195
|
+
try {
|
|
1196
|
+
execSync(`node ${tfPath}`, { cwd: TARGET_DIR, stdio: 'pipe', timeout: 10000 });
|
|
1197
|
+
console.log(chalk.green(`[+] Shadow Sandbox: Test '${tf}' PASSED cleanly.`));
|
|
1198
|
+
} catch (e) {
|
|
1199
|
+
isSuccess = false;
|
|
1200
|
+
const testErr = e.stderr ? e.stderr.toString() : (e.stdout ? e.stdout.toString() : e.message);
|
|
1201
|
+
errorLog += `Shadow Sandbox Test Failure in '${tf}':\n${testErr.substring(0, 1000)}\n`;
|
|
1202
|
+
console.log(chalk.red(`[-] Shadow Sandbox Test FAILED: '${tf}'`));
|
|
1203
|
+
}
|
|
1204
|
+
}
|
|
1205
|
+
}
|
|
1206
|
+
} else if (context.projectFacts && context.projectFacts.scripts && context.projectFacts.scripts.test) {
|
|
1207
|
+
const testCmd = context.projectFacts.scripts.test;
|
|
1208
|
+
if (!testCmd.includes('no test specified') && !testCmd.includes('exit 1')) {
|
|
1209
|
+
console.log(`[Shadow Sandbox] Executing package test script: '${testCmd}'...`);
|
|
1210
|
+
try {
|
|
1211
|
+
execSync(`npm test --if-present`, { cwd: TARGET_DIR, stdio: 'pipe', timeout: 15000 });
|
|
1212
|
+
console.log(chalk.green(`[+] Shadow Sandbox: 'npm test' PASSED cleanly.`));
|
|
1213
|
+
} catch (e) {
|
|
1214
|
+
isSuccess = false;
|
|
1215
|
+
const testErr = e.stderr ? e.stderr.toString() : (e.stdout ? e.stdout.toString() : e.message);
|
|
1216
|
+
errorLog += `Shadow Sandbox 'npm test' Failure:\n${testErr.substring(0, 1000)}\n`;
|
|
1217
|
+
console.log(chalk.red(`[-] Shadow Sandbox 'npm test' FAILED.`));
|
|
1218
|
+
}
|
|
1219
|
+
}
|
|
1220
|
+
}
|
|
1221
|
+
} catch (sandboxErr) {
|
|
1222
|
+
console.warn(`[!] Shadow Sandbox execution skipped/errored:`, sandboxErr.message);
|
|
1223
|
+
}
|
|
1224
|
+
}
|
|
1225
|
+
|
|
1226
|
+
// 2. Run logical audit for MEDIUM / HIGH tasks using deepseek-r1:8b (only if syntax/deletion checks passed)
|
|
1227
|
+
if (isSuccess && (context.complexity === "MEDIUM" || context.complexity === "HIGH") && isOllamaAvailable()) {
|
|
1228
|
+
console.log(`[*] Run logical code audit with ${MODELS.VALIDATOR_HIGH}...`);
|
|
1229
|
+
|
|
1230
|
+
let codeChanges = "";
|
|
1231
|
+
for (const file of modifiedFiles) {
|
|
1232
|
+
const filePath = path.join(TARGET_DIR, file);
|
|
1233
|
+
if (fs.existsSync(filePath)) {
|
|
1234
|
+
codeChanges += `\n### File: ${file}\n\`\`\`\n${fs.readFileSync(filePath, "utf-8")}\n\`\`\`\n`;
|
|
1235
|
+
}
|
|
1236
|
+
}
|
|
1237
|
+
|
|
1238
|
+
const auditPrompt = `
|
|
1239
|
+
You are Hiven-Validator-Auditor. You must verify if the refactored code correctly implements the developer's instructions and has no logical bugs, security vulnerabilities, or regression errors.
|
|
1240
|
+
|
|
1241
|
+
INSTRUCTION:
|
|
1242
|
+
${INSTRUCTION}
|
|
1243
|
+
|
|
1244
|
+
REFACTORED CODE:
|
|
1245
|
+
${codeChanges}
|
|
1246
|
+
|
|
1247
|
+
Determine if the code is correct and free of logical bugs.
|
|
1248
|
+
Respond ONLY with a JSON object containing:
|
|
1249
|
+
"approved": true or false,
|
|
1250
|
+
"reason": "Detail why it is approved or what logical bug was found."
|
|
1251
|
+
`;
|
|
1252
|
+
|
|
1253
|
+
try {
|
|
1254
|
+
execSync(`ollama pull ${MODELS.VALIDATOR_HIGH}`, { stdio: "inherit" });
|
|
1255
|
+
const auditResp = await queryOllama(MODELS.VALIDATOR_HIGH, auditPrompt, "You are Hiven-Validator-Auditor. Output JSON only.");
|
|
1256
|
+
console.log("[+] Auditor Response:", auditResp);
|
|
1257
|
+
|
|
1258
|
+
const cleanJson = auditResp.replace(/```[a-zA-Z]*\n([\s\S]*?)\n```/g, "$1").replace(/```json|```/g, "").trim();
|
|
1259
|
+
const parsed = JSON.parse(cleanJson);
|
|
1260
|
+
if (parsed.approved === false) {
|
|
1261
|
+
isSuccess = false;
|
|
1262
|
+
errorLog += `Logical Audit Bug Found by Validator Auditor:\n${parsed.reason}\n`;
|
|
1263
|
+
console.log(chalk.red(`[-] Logical audit failed: ${parsed.reason}`));
|
|
1264
|
+
} else {
|
|
1265
|
+
console.log(chalk.green(`[+] Logical audit approved: ${parsed.reason}`));
|
|
1266
|
+
}
|
|
1267
|
+
} catch (e) {
|
|
1268
|
+
console.error("[-] Logical audit crashed/skipped:", e.message);
|
|
1269
|
+
}
|
|
1270
|
+
}
|
|
1271
|
+
|
|
1272
|
+
// If validation passed, we are done
|
|
1273
|
+
if (isSuccess) {
|
|
1274
|
+
console.log(chalk.green(`[+] Validation passed on attempt ${attempt}!`));
|
|
1275
|
+
break;
|
|
1276
|
+
}
|
|
1277
|
+
|
|
1278
|
+
// If validation failed, and we have attempts remaining, run correction loop
|
|
1279
|
+
if (attempt < maxAttempts) {
|
|
1280
|
+
console.warn(`[-] Attempt ${attempt} failed. Triggering correction cycle...`);
|
|
1281
|
+
|
|
1282
|
+
if (!isOllamaAvailable()) {
|
|
1283
|
+
// Ollama not available — we cannot run LLM correction.
|
|
1284
|
+
// If deletion guard triggered, restore the original file from git
|
|
1285
|
+
// so worst case is "no change" instead of "file destroyed".
|
|
1286
|
+
let restoredAny = false;
|
|
1287
|
+
for (const file of modifiedFiles) {
|
|
1288
|
+
try {
|
|
1289
|
+
const gitOriginal = execSync(`git show HEAD:${file}`, { cwd: TARGET_DIR, stdio: 'pipe' }).toString();
|
|
1290
|
+
fs.writeFileSync(path.join(TARGET_DIR, file), gitOriginal, 'utf-8');
|
|
1291
|
+
console.warn(`[!] Restored original '${file}' from git HEAD (Ollama unavailable for correction).`);
|
|
1292
|
+
restoredAny = true;
|
|
1293
|
+
} catch (_) {
|
|
1294
|
+
console.warn(`[!] Could not restore '${file}' from git — file may be new.`);
|
|
1295
|
+
}
|
|
1296
|
+
}
|
|
1297
|
+
if (restoredAny) {
|
|
1298
|
+
// Mark as passed=false so consolidation knows this node had no valid change
|
|
1299
|
+
console.warn("[!] Validation marked as failed (original restored). Consolidation will prefer other nodes.");
|
|
1300
|
+
} else {
|
|
1301
|
+
// New file or unrestorable — pass as-is
|
|
1302
|
+
isSuccess = true;
|
|
1303
|
+
errorLog = "";
|
|
1304
|
+
}
|
|
1305
|
+
break;
|
|
1306
|
+
}
|
|
1307
|
+
|
|
1308
|
+
let correctionModel = MODELS.CODER_LOW;
|
|
1309
|
+
if (context.complexity === "HIGH") {
|
|
1310
|
+
correctionModel = MODELS.CODER_HIGH;
|
|
1311
|
+
} else if (context.complexity === "MEDIUM") {
|
|
1312
|
+
correctionModel = KOMBEE_INDEX >= 9 ? MODELS.CODER_HIGH : MODELS.CODER_LOW;
|
|
1313
|
+
}
|
|
1314
|
+
|
|
1315
|
+
console.log(`[*] Using correction model '${correctionModel}'...`);
|
|
1316
|
+
execSync(`ollama pull ${correctionModel}`, { stdio: "inherit" });
|
|
1317
|
+
|
|
1318
|
+
// Include the original file contents in the correction prompt so the model has the code to restore
|
|
1319
|
+
let originalFilesContext = "";
|
|
1320
|
+
for (const file of modifiedFiles) {
|
|
1321
|
+
try {
|
|
1322
|
+
if (TARGET_REPO !== "mock/repo" && process.env.MOCK_GIT !== "true") {
|
|
1323
|
+
// file is already relative to repo root (e.g. src/zenon.js) — no src/ prefix
|
|
1324
|
+
const gitShow = execSync(`git show HEAD:${file}`, { cwd: TARGET_DIR, stdio: 'pipe' }).toString();
|
|
1325
|
+
originalFilesContext += `\n### ORIGINAL File: ${file}\n\`\`\`\n${gitShow.slice(0, 40000)}\n\`\`\`\n`;
|
|
1326
|
+
}
|
|
1327
|
+
} catch (_) {}
|
|
1328
|
+
}
|
|
1329
|
+
|
|
1330
|
+
const correctionPrompt = `
|
|
1331
|
+
The modified code failed validation checks. You must correct the errors while ensuring all original helper functions, classes, and structure are preserved. Do not delete pre-existing code.
|
|
1332
|
+
|
|
1333
|
+
ORIGINAL SOURCE FILES FOR REFERENCE (DO NOT DELETE THE LOGIC INSIDE THESE):
|
|
1334
|
+
${originalFilesContext}
|
|
1335
|
+
|
|
1336
|
+
ERRORS FOUND:
|
|
1337
|
+
${errorLog}
|
|
1338
|
+
|
|
1339
|
+
INSTRUCTION TO IMPLEMENT:
|
|
1340
|
+
${INSTRUCTION}
|
|
1341
|
+
|
|
1342
|
+
Correct the code. Output the full file wrapping strictly in:
|
|
1343
|
+
---START_FILE: filename---
|
|
1344
|
+
code here
|
|
1345
|
+
---END_FILE: filename---
|
|
1346
|
+
|
|
1347
|
+
CRITICAL: You must preserve the existing outer file structure, helper functions, and module exports. Never delete the export statements or shorten the file.
|
|
1348
|
+
`;
|
|
1349
|
+
await sendTelemetry("running", `Validator Node #${KOMBEE_INDEX} running correction loop (Attempt ${attempt}/${maxAttempts})...`);
|
|
1350
|
+
const corrected = await queryOllama(correctionModel, correctionPrompt, "You are Hiven-Correction-Kōmbee.");
|
|
1351
|
+
|
|
1352
|
+
// Rewrite modified files with correction output
|
|
1353
|
+
let matchCorr;
|
|
1354
|
+
modifiedFiles = [];
|
|
1355
|
+
const regexCorr = /---START_FILE:\s*([^\s-]+)---\n([\s\S]*?)\n---END_FILE:\s*\1---/g;
|
|
1356
|
+
while ((matchCorr = regexCorr.exec(corrected)) !== null) {
|
|
1357
|
+
const fileName = matchCorr[1].trim();
|
|
1358
|
+
const newContent = matchCorr[2];
|
|
1359
|
+
const filePath = path.join(TARGET_DIR, fileName);
|
|
1360
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
1361
|
+
fs.writeFileSync(filePath, newContent, "utf-8");
|
|
1362
|
+
modifiedFiles.push(fileName);
|
|
1363
|
+
}
|
|
1364
|
+
|
|
1365
|
+
if (modifiedFiles.length === 0 && context.files.length > 0) {
|
|
1366
|
+
const writtenFile = writeCodeCleanly(corrected, context.files[0], context.files, context.plan, INSTRUCTION);
|
|
1367
|
+
if (writtenFile) {
|
|
1368
|
+
modifiedFiles.push(writtenFile);
|
|
1369
|
+
}
|
|
1370
|
+
}
|
|
1371
|
+
}
|
|
1372
|
+
} // end attempt loop
|
|
1373
|
+
|
|
1374
|
+
const fileMap = {};
|
|
1375
|
+
for (const file of modifiedFiles) {
|
|
1376
|
+
fileMap[file] = fs.readFileSync(path.join(TARGET_DIR, file), "utf-8");
|
|
1377
|
+
}
|
|
1378
|
+
|
|
1379
|
+
const validationPayload = {
|
|
1380
|
+
kombeeIndex: KOMBEE_INDEX,
|
|
1381
|
+
partitionGroup: coderData.partitionGroup || 1,
|
|
1382
|
+
model: coderData.model,
|
|
1383
|
+
passed: isSuccess,
|
|
1384
|
+
errors: errorLog,
|
|
1385
|
+
modifiedFiles: fileMap
|
|
1386
|
+
};
|
|
1387
|
+
|
|
1388
|
+
fs.writeFileSync(`validation_output_${KOMBEE_INDEX}.json`, JSON.stringify(validationPayload, null, 2), "utf-8");
|
|
1389
|
+
console.log(`[+] Validation results for Node #${KOMBEE_INDEX} saved.`);
|
|
1390
|
+
await sendTelemetry("done", `Validator Node #${KOMBEE_INDEX} completed.`);
|
|
1391
|
+
}
|
|
1392
|
+
|
|
1393
|
+
else if (PHASE === "consolidation") {
|
|
1394
|
+
// ==========================================================
|
|
1395
|
+
// FASE 4: CONSOLIDATION (Committer & Telemetry Kōmbees)
|
|
1396
|
+
// ==========================================================
|
|
1397
|
+
console.log("[*] Running Phase 4 Consolidation Kōmbees...");
|
|
1398
|
+
await updateStatusComment("validation", "done");
|
|
1399
|
+
await updateStatusComment("consolidation", "running");
|
|
1400
|
+
await sendTelemetry("running", "Phase 4 Consolidation starting...");
|
|
1401
|
+
checkoutCodebase();
|
|
1402
|
+
const context = JSON.parse(fs.readFileSync("swarm_context.json", "utf-8"));
|
|
1403
|
+
const honeyDb = loadHoneyDb();
|
|
1404
|
+
|
|
1405
|
+
// Collect all validation logs
|
|
1406
|
+
const candidates = [];
|
|
1407
|
+
for (let i = 1; i <= 10; i++) {
|
|
1408
|
+
const filePath = `validation_output_${i}.json`;
|
|
1409
|
+
if (fs.existsSync(filePath)) {
|
|
1410
|
+
candidates.push(JSON.parse(fs.readFileSync(filePath, "utf-8")));
|
|
1411
|
+
}
|
|
1412
|
+
}
|
|
1413
|
+
|
|
1414
|
+
// MEJORA 5: Multi-Group Parallel Sub-Swarm Consolidation
|
|
1415
|
+
// Collect best passing candidates for EACH partition group and merge them together
|
|
1416
|
+
const groups = [...new Set(candidates.map(c => c.partitionGroup || 1))];
|
|
1417
|
+
const selectedCandidates = [];
|
|
1418
|
+
|
|
1419
|
+
for (const grp of groups) {
|
|
1420
|
+
const groupCandidates = candidates.filter(c => (c.partitionGroup || 1) === grp);
|
|
1421
|
+
let bestGroupCand = groupCandidates.find(c => c.passed && c.model.includes("7b"));
|
|
1422
|
+
if (!bestGroupCand) {
|
|
1423
|
+
bestGroupCand = groupCandidates.find(c => c.passed);
|
|
1424
|
+
}
|
|
1425
|
+
if (bestGroupCand) {
|
|
1426
|
+
selectedCandidates.push(bestGroupCand);
|
|
1427
|
+
}
|
|
1428
|
+
}
|
|
1429
|
+
|
|
1430
|
+
// If no node in ANY group passed validation, do NOT create a PR
|
|
1431
|
+
if (selectedCandidates.length === 0) {
|
|
1432
|
+
const failSummary = candidates.map(c => `Node #${c.kombeeIndex} (Group ${c.partitionGroup || 1}): ${c.errors?.split('\n')[0] || 'no error detail'}`).join('\n');
|
|
1433
|
+
console.error("[!] No valid candidates found. All validation nodes failed:");
|
|
1434
|
+
console.error(failSummary);
|
|
1435
|
+
await sendTelemetry("done", `[!] Swarm completed but NO valid code generated. All ${candidates.length} nodes failed validation. Check logs for details.`);
|
|
1436
|
+
await updateStatusComment("consolidation", "done");
|
|
1437
|
+
for (const ext of [...new Set(context.files.map(f => f.split('.').pop()).filter(Boolean))]) {
|
|
1438
|
+
await saveFederatedPattern(ext, INSTRUCTION, 'FAILED');
|
|
1439
|
+
}
|
|
1440
|
+
process.exit(0);
|
|
1441
|
+
}
|
|
1442
|
+
|
|
1443
|
+
console.log(`[+] Selected ${selectedCandidates.length} winning sub-swarm candidates across ${groups.length} partition groups:`);
|
|
1444
|
+
for (const cand of selectedCandidates) {
|
|
1445
|
+
console.log(` - Group ${cand.partitionGroup}: Node #${cand.kombeeIndex} (${cand.model})`);
|
|
1446
|
+
}
|
|
1447
|
+
|
|
1448
|
+
// Merge modified files from all winning group candidates
|
|
1449
|
+
const mergedModifiedFiles = {};
|
|
1450
|
+
for (const cand of selectedCandidates) {
|
|
1451
|
+
for (const [file, content] of Object.entries(cand.modifiedFiles)) {
|
|
1452
|
+
mergedModifiedFiles[file] = content;
|
|
1453
|
+
}
|
|
1454
|
+
}
|
|
1455
|
+
|
|
1456
|
+
// Apply merged changes to target directory
|
|
1457
|
+
for (const [file, content] of Object.entries(mergedModifiedFiles)) {
|
|
1458
|
+
const filePath = path.join(TARGET_DIR, file);
|
|
1459
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
1460
|
+
fs.writeFileSync(filePath, content, "utf-8");
|
|
1461
|
+
console.log(`[+] Applied code modifications to: ${file}`);
|
|
1462
|
+
}
|
|
1463
|
+
|
|
1464
|
+
let prUrl = null;
|
|
1465
|
+
const hasChanges = Object.keys(mergedModifiedFiles).length > 0;
|
|
1466
|
+
const mainCandidate = selectedCandidates[0];
|
|
1467
|
+
|
|
1468
|
+
if (hasChanges) {
|
|
1469
|
+
// Git Commit & Push
|
|
1470
|
+
runGit("config user.name 'Hiven Swarm'");
|
|
1471
|
+
runGit("config user.email 'swarm@hiven.ai'");
|
|
1472
|
+
|
|
1473
|
+
const patchBranch = `hiven/patch-${crypto.randomUUID().substring(0, 6)}`;
|
|
1474
|
+
runGit(`checkout -b ${patchBranch}`);
|
|
1475
|
+
runGit("add .");
|
|
1476
|
+
|
|
1477
|
+
try {
|
|
1478
|
+
runGit(`commit -m "feat(hiven): swarm modification for instruction\n\nInstruction: ${INSTRUCTION}"`);
|
|
1479
|
+
runGit(`push origin ${patchBranch}`);
|
|
1480
|
+
|
|
1481
|
+
if (TARGET_REPO === "mock/repo" || process.env.MOCK_GIT === "true") {
|
|
1482
|
+
console.log(chalk.green(`[Simulated Octokit] Opened Pull Request for ${patchBranch} -> ${TARGET_BRANCH}`));
|
|
1483
|
+
prUrl = `https://github.com/${TARGET_REPO}/pull/mock-1`;
|
|
1484
|
+
} else {
|
|
1485
|
+
// Open Pull Request
|
|
1486
|
+
const [owner, repo] = TARGET_REPO.split("/");
|
|
1487
|
+
const candSummary = selectedCandidates.map(c => `Node #${c.kombeeIndex} (Group ${c.partitionGroup}, Model: ${c.model})`).join(', ');
|
|
1488
|
+
const pr = await octokit.pulls.create({
|
|
1489
|
+
owner,
|
|
1490
|
+
repo,
|
|
1491
|
+
title: `Hiven Patch: ${INSTRUCTION.substring(0, 50)}...`,
|
|
1492
|
+
head: patchBranch,
|
|
1493
|
+
base: TARGET_BRANCH,
|
|
1494
|
+
body: `### 🐝 Hiven Swarm Refactoring Summary
|
|
1495
|
+
|
|
1496
|
+
Autonomous PR triggered by user instruction: **"${INSTRUCTION}"**
|
|
1497
|
+
|
|
1498
|
+
#### 🏗️ Swarm Plan:
|
|
1499
|
+
${context.plan}
|
|
1500
|
+
|
|
1501
|
+
#### ⚙️ Execution Metrics:
|
|
1502
|
+
* **Selected Coders:** ${candSummary}
|
|
1503
|
+
* **Validation:** PASSED (All Node Tests Verified)
|
|
1504
|
+
|
|
1505
|
+
---
|
|
1506
|
+
*Generated autonomously by [Hiven AI](https://hiven.ai)*`
|
|
1507
|
+
});
|
|
1508
|
+
|
|
1509
|
+
console.log(chalk.green(`[+] Pull Request created successfully: ${pr.data.html_url}`));
|
|
1510
|
+
prUrl = pr.data.html_url;
|
|
1511
|
+
}
|
|
1512
|
+
} catch (e) {
|
|
1513
|
+
console.warn("[-] Failed to commit or push codebase edits (likely read-only query):", e.message);
|
|
1514
|
+
}
|
|
1515
|
+
} else {
|
|
1516
|
+
console.log("[*] No code changes detected. Bypassing branch creation, commit, and Pull Request.");
|
|
1517
|
+
}
|
|
1518
|
+
|
|
1519
|
+
// Post comment back to the issue/PR if ISSUE_NUMBER is provided
|
|
1520
|
+
if (ISSUE_NUMBER && TARGET_REPO !== "mock/repo" && process.env.MOCK_GIT !== "true") {
|
|
1521
|
+
try {
|
|
1522
|
+
const [owner, repo] = TARGET_REPO.split("/");
|
|
1523
|
+
const issueNum = parseInt(ISSUE_NUMBER, 10);
|
|
1524
|
+
console.log(`[*] Posting final response to issue #${issueNum}...`);
|
|
1525
|
+
|
|
1526
|
+
let commentBody = "";
|
|
1527
|
+
if (prUrl) {
|
|
1528
|
+
commentBody = `### 🐝 Hiven Swarm Execution Complete!
|
|
1529
|
+
|
|
1530
|
+
I have successfully implemented your requested changes in a new Pull Request!
|
|
1531
|
+
|
|
1532
|
+
👉 **[View Pull Request](${prUrl})**
|
|
1533
|
+
|
|
1534
|
+
#### 📋 Swarm Plan:
|
|
1535
|
+
${context.plan}
|
|
1536
|
+
|
|
1537
|
+
#### ⚙️ Metrics:
|
|
1538
|
+
* **Selected Coder:** Kōmbee Node #${mainCandidate.kombeeIndex} (\`${mainCandidate.model}\`)
|
|
1539
|
+
* **Complexity Level:** \`${context.complexity}\`
|
|
1540
|
+
`;
|
|
1541
|
+
} else {
|
|
1542
|
+
commentBody = `### 🐝 Swarm Analysis Complete!
|
|
1543
|
+
|
|
1544
|
+
I have completed the analysis for your request: **"${INSTRUCTION}"**
|
|
1545
|
+
|
|
1546
|
+
${context.plan}
|
|
1547
|
+
|
|
1548
|
+
---
|
|
1549
|
+
*Metrics: Coder Kōmbee Node #${mainCandidate.kombeeIndex} (${mainCandidate.model}) | Complexity: ${context.complexity}*`;
|
|
1550
|
+
}
|
|
1551
|
+
|
|
1552
|
+
await octokit.issues.createComment({
|
|
1553
|
+
owner,
|
|
1554
|
+
repo,
|
|
1555
|
+
issue_number: issueNum,
|
|
1556
|
+
body: commentBody
|
|
1557
|
+
});
|
|
1558
|
+
console.log("[+] Response comment posted successfully!");
|
|
1559
|
+
} catch (commentErr) {
|
|
1560
|
+
console.error("[-] Failed to post comment back to issue:", commentErr.message);
|
|
1561
|
+
}
|
|
1562
|
+
}
|
|
1563
|
+
|
|
1564
|
+
// Persist honey.db
|
|
1565
|
+
honeyDb.stylePreferences.lastModel = mainCandidate.model;
|
|
1566
|
+
if (!mainCandidate.passed && mainCandidate.errors) {
|
|
1567
|
+
honeyDb.errorSignatures[crypto.randomUUID().substring(0, 4)] = "Syntax issue handled via correction loop.";
|
|
1568
|
+
}
|
|
1569
|
+
saveHoneyDb(honeyDb);
|
|
1570
|
+
|
|
1571
|
+
// Save federated pattern if PR was created successfully
|
|
1572
|
+
if (prUrl && mergedModifiedFiles) {
|
|
1573
|
+
const fileExts = Object.keys(mergedModifiedFiles)
|
|
1574
|
+
.map(f => f.split('.').pop()).filter(Boolean);
|
|
1575
|
+
const primaryExt = [...new Set(fileExts)][0] || 'js';
|
|
1576
|
+
await saveFederatedPattern(primaryExt, INSTRUCTION,
|
|
1577
|
+
mainCandidate.passed ? 'SUCCESS: PR created, validation passed' : 'PARTIAL: PR created, validation had issues'
|
|
1578
|
+
);
|
|
1579
|
+
}
|
|
1580
|
+
|
|
1581
|
+
if (DRONE_UPLINK_URL) {
|
|
1582
|
+
console.log(`[+] Routing telemetry to Drone: ${DRONE_UPLINK_URL}`);
|
|
1583
|
+
}
|
|
1584
|
+
await updateStatusComment("consolidation", "done");
|
|
1585
|
+
await sendTelemetry("done", prUrl ? `Swarm complete. PR created: ${prUrl}` : `Swarm complete. Response: ${context.plan.substring(0, 150)}...`);
|
|
1586
|
+
}
|
|
1587
|
+
} catch (error) {
|
|
1588
|
+
console.error(chalk.red(`[!] Critical error in Phase [${PHASE}] execution:`), error);
|
|
1589
|
+
await sendTelemetry("error", `Critical error in Phase [${PHASE}]: ${error.message}`);
|
|
1590
|
+
process.exit(1);
|
|
1591
|
+
}
|
|
1592
|
+
}
|
|
1593
|
+
|
|
1594
|
+
main();
|