claude-skills/engineering/hivemind/scripts/oc-aggregate.mjs
N Hanish 2c05206907
feat(engineering): add hivemind orchestration skill
Hivemind delegates grunt work to headless opencode workers (scout/coder/tester)
while the orchestrating agent stays the planner, reviewer, and merger.

- placed under engineering/ per repo layout; targets dev per CONTRIBUTING
- frontmatter is name + description only; SKILL.md is 165 lines
- anti-patterns and cross-references sections included
- external opencode CLI dependency and free-model caveats documented up front
- runtime state (.runs/) gitignored, not committed
2026-08-23 19:39:42 +05:30

79 lines
2.7 KiB
JavaScript

import { readFileSync } from "node:fs";
function readInput() {
const args = process.argv.slice(2).filter(a => a !== "--findings-only");
const findingsOnly = process.argv.includes("--findings-only");
let raw = "";
if (args.length) {
for (const f of args) {
try { raw += readFileSync(f, "utf8") + "\n"; }
catch { console.log(JSON.stringify({ ok: false, stage: "aggregate", error: `cannot read file: ${f}` })); process.exit(0); }
}
} else raw = readFileSync(0, "utf8");
return { raw, findingsOnly };
}
function normalizeLine(s) {
return s.toLowerCase().replace(/[\s\p{P}]+/gu, " ").trim();
}
function extractFindings(text) {
const out = [];
for (const line of text.split(/\r?\n/)) {
const t = line.trim();
if (/^([-*•]|\d+[.)])\s+/.test(t) && t.length > 12) out.push(t.replace(/^([-*•]|\d+[.)])\s+/, "").trim());
else if (/^(file|path|src|lib)\b.*:\d+/i.test(t) && t.length > 12) out.push(t);
}
return out;
}
function main() {
const { raw, findingsOnly } = readInput();
const workers = [];
for (const line of raw.split(/\r?\n/).filter(l => l.trim())) {
try {
const j = JSON.parse(line.trim());
if (!("ok" in j)) continue;
workers.push(j);
} catch {}
}
if (!workers.length) { console.log(JSON.stringify({ ok: false, stage: "aggregate", error: "no worker JSON lines found in input" })); return; }
const ok = workers.filter(w => w.ok === true);
const failed = workers.filter(w => w.ok !== true);
const totalTokens = ok.reduce((n, w) => n + (w.tokens?.total ?? 0), 0);
const seen = new Map();
let anonIdx = 0;
for (const w of ok) {
const src = w.label || w.agent || `worker-${++anonIdx}`;
for (const f of extractFindings(w.result || "")) {
const key = normalizeLine(f);
if (!seen.has(key)) seen.set(key, { finding: f, sources: [src] });
else if (!seen.get(key).sources.includes(src)) seen.get(key).sources.push(src);
}
}
const unique = [...seen.values()].sort((a, b) => b.sources.length - a.sources.length).slice(0, 60);
const digest = {
ok: true,
workers: workers.length,
succeeded: ok.length,
failed: failed.length,
total_tokens: totalTokens,
unique_findings: unique.length,
failures: failed.map(w => ({ label: w.label || w.agent, error: (w.error || "unknown").slice(0, 120) })),
consensus: unique.filter(u => u.sources.length > 1),
findings: unique,
};
if (findingsOnly) {
console.log(digest.consensus.map(c => `[${c.sources.join("+")}] ${c.finding}`).join("\n"));
console.log("---");
console.log(digest.findings.filter(f => f.sources.length === 1).map(c => `[${c.sources[0]}] ${c.finding}`).join("\n"));
return;
}
console.log(JSON.stringify(digest, null, 1));
}
main();