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.
Files changed (47) hide show
  1. package/LICENSE.md +20 -0
  2. package/README.md +233 -0
  3. package/assets/hiven_logo_v2.png +0 -0
  4. package/bin/hiven.js +225 -0
  5. package/console/.nojekyll +0 -0
  6. package/console/app.js +588 -0
  7. package/console/assets/hiven_logo_v2.png +0 -0
  8. package/console/data/swarms_history.json +38 -0
  9. package/console/index.html +694 -0
  10. package/console/style.css +1039 -0
  11. package/console/webbl.config.json +5 -0
  12. package/dist/api.d.ts +13 -0
  13. package/dist/api.d.ts.map +1 -0
  14. package/dist/api.js +142 -0
  15. package/dist/api.js.map +1 -0
  16. package/dist/honeycombs.d.ts +30 -0
  17. package/dist/honeycombs.d.ts.map +1 -0
  18. package/dist/honeycombs.js +221 -0
  19. package/dist/honeycombs.js.map +1 -0
  20. package/dist/index.d.ts +38 -0
  21. package/dist/index.d.ts.map +1 -0
  22. package/dist/index.js +133 -0
  23. package/dist/index.js.map +1 -0
  24. package/dist/inference.d.ts +33 -0
  25. package/dist/inference.d.ts.map +1 -0
  26. package/dist/inference.js +217 -0
  27. package/dist/inference.js.map +1 -0
  28. package/dist/server.d.ts +10 -0
  29. package/dist/server.d.ts.map +1 -0
  30. package/dist/server.js +54 -0
  31. package/dist/server.js.map +1 -0
  32. package/dist/swarm.d.ts +30 -0
  33. package/dist/swarm.d.ts.map +1 -0
  34. package/dist/swarm.js +196 -0
  35. package/dist/swarm.js.map +1 -0
  36. package/dist/types.d.ts +117 -0
  37. package/dist/types.d.ts.map +1 -0
  38. package/dist/types.js +6 -0
  39. package/dist/types.js.map +1 -0
  40. package/dist/vault.d.ts +19 -0
  41. package/dist/vault.d.ts.map +1 -0
  42. package/dist/vault.js +99 -0
  43. package/dist/vault.js.map +1 -0
  44. package/package.json +63 -0
  45. package/templates/.github/workflows/swarm.yml +229 -0
  46. package/templates/package.json +13 -0
  47. 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();