opencode-swarm 7.44.0 → 7.45.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/dist/cli/index.js CHANGED
@@ -52,7 +52,7 @@ var package_default;
52
52
  var init_package = __esm(() => {
53
53
  package_default = {
54
54
  name: "opencode-swarm",
55
- version: "7.44.0",
55
+ version: "7.45.0",
56
56
  description: "Architect-centric agentic swarm plugin for OpenCode - hub-and-spoke orchestration with SME consultation, code generation, and QA review",
57
57
  main: "dist/index.js",
58
58
  types: "dist/index.d.ts",
@@ -35881,27 +35881,25 @@ function normalizeEntry(raw) {
35881
35881
  ro = {};
35882
35882
  obj.retrieval_outcomes = ro;
35883
35883
  }
35884
- {
35885
- if (typeof ro.shown_count !== "number") {
35886
- ro.shown_count = typeof ro.applied_count === "number" ? ro.applied_count : 0;
35887
- }
35888
- if (typeof ro.acknowledged_count !== "number")
35889
- ro.acknowledged_count = 0;
35890
- if (typeof ro.applied_explicit_count !== "number") {
35891
- ro.applied_explicit_count = 0;
35892
- }
35893
- if (typeof ro.ignored_count !== "number")
35894
- ro.ignored_count = 0;
35895
- if (typeof ro.violated_count !== "number")
35896
- ro.violated_count = 0;
35897
- if (typeof ro.contradicted_count !== "number")
35898
- ro.contradicted_count = 0;
35899
- if (typeof ro.succeeded_after_shown_count !== "number") {
35900
- ro.succeeded_after_shown_count = typeof ro.succeeded_after_count === "number" ? ro.succeeded_after_count : 0;
35901
- }
35902
- if (typeof ro.failed_after_shown_count !== "number") {
35903
- ro.failed_after_shown_count = typeof ro.failed_after_count === "number" ? ro.failed_after_count : 0;
35904
- }
35884
+ if (typeof ro.shown_count !== "number") {
35885
+ ro.shown_count = typeof ro.applied_count === "number" ? ro.applied_count : 0;
35886
+ }
35887
+ if (typeof ro.acknowledged_count !== "number")
35888
+ ro.acknowledged_count = 0;
35889
+ if (typeof ro.applied_explicit_count !== "number") {
35890
+ ro.applied_explicit_count = 0;
35891
+ }
35892
+ if (typeof ro.ignored_count !== "number")
35893
+ ro.ignored_count = 0;
35894
+ if (typeof ro.violated_count !== "number")
35895
+ ro.violated_count = 0;
35896
+ if (typeof ro.contradicted_count !== "number")
35897
+ ro.contradicted_count = 0;
35898
+ if (typeof ro.succeeded_after_shown_count !== "number") {
35899
+ ro.succeeded_after_shown_count = typeof ro.succeeded_after_count === "number" ? ro.succeeded_after_count : 0;
35900
+ }
35901
+ if (typeof ro.failed_after_shown_count !== "number") {
35902
+ ro.failed_after_shown_count = typeof ro.failed_after_count === "number" ? ro.failed_after_count : 0;
35905
35903
  }
35906
35904
  try {
35907
35905
  if (typeof obj.encounter_score !== "number" || Number.isNaN(obj.encounter_score)) {
@@ -51529,6 +51527,19 @@ import path41 from "path";
51529
51527
  function normalizePath(p) {
51530
51528
  return p.replace(/\\/g, "/");
51531
51529
  }
51530
+ function sharedTrailingSegments(a, b) {
51531
+ const aParts = normalizePath(a).split("/").filter(Boolean);
51532
+ const bParts = normalizePath(b).split("/").filter(Boolean);
51533
+ let i = aParts.length - 1;
51534
+ let j = bParts.length - 1;
51535
+ let shared = 0;
51536
+ while (i >= 0 && j >= 0 && aParts[i] === bParts[j]) {
51537
+ shared++;
51538
+ i--;
51539
+ j--;
51540
+ }
51541
+ return shared;
51542
+ }
51532
51543
  function isCacheStale(impactMap, generatedAtMs) {
51533
51544
  for (const sourcePath of Object.keys(impactMap)) {
51534
51545
  try {
@@ -51856,25 +51867,43 @@ async function analyzeImpact(changedFiles, cwd, budget) {
51856
51867
  if (budgetExceeded)
51857
51868
  break;
51858
51869
  } else {
51859
- let found = false;
51860
- for (const [sourcePath, tests2] of Object.entries(impactMap)) {
51870
+ const changedDir = normalizePath(path41.dirname(normalizedChanged));
51871
+ const changedInputDir = normalizePath(path41.dirname(changedFile));
51872
+ const suffixMatches = Object.entries(impactMap).filter(([sourcePath]) => {
51873
+ return sourcePath.endsWith(changedFile) || changedFile.endsWith(sourcePath) || sourcePath.endsWith(normalizedChanged) || normalizedChanged.endsWith(sourcePath);
51874
+ }).sort(([sourceA], [sourceB]) => {
51875
+ const sourceDirA = normalizePath(path41.dirname(sourceA));
51876
+ const sourceDirB = normalizePath(path41.dirname(sourceB));
51877
+ const exactA = sourceDirA === changedDir || changedInputDir !== "." && (sourceDirA === changedInputDir || sourceDirA.endsWith(`/${changedInputDir}`));
51878
+ const exactB = sourceDirB === changedDir || changedInputDir !== "." && (sourceDirB === changedInputDir || sourceDirB.endsWith(`/${changedInputDir}`));
51879
+ if (exactA !== exactB)
51880
+ return exactA ? -1 : 1;
51881
+ const sharedA = Math.max(sharedTrailingSegments(sourceDirA, changedDir), changedInputDir === "." ? 0 : sharedTrailingSegments(sourceDirA, changedInputDir));
51882
+ const sharedB = Math.max(sharedTrailingSegments(sourceDirB, changedDir), changedInputDir === "." ? 0 : sharedTrailingSegments(sourceDirB, changedInputDir));
51883
+ const nearestA = sharedA > 0;
51884
+ const nearestB = sharedB > 0;
51885
+ if (nearestA !== nearestB)
51886
+ return nearestA ? -1 : 1;
51887
+ if (sharedA !== sharedB)
51888
+ return sharedB - sharedA;
51889
+ return sourceA.localeCompare(sourceB);
51890
+ });
51891
+ const found = suffixMatches.length > 0;
51892
+ for (const [, tests2] of suffixMatches) {
51861
51893
  if (budget !== undefined && visitedCount >= budget) {
51862
51894
  budgetExceeded = true;
51863
51895
  break;
51864
51896
  }
51865
- if (sourcePath.endsWith(changedFile) || changedFile.endsWith(sourcePath)) {
51866
- for (const test of tests2) {
51867
- if (budget !== undefined && visitedCount >= budget) {
51868
- budgetExceeded = true;
51869
- break;
51870
- }
51871
- impactedTestsSet.add(test);
51872
- visitedCount++;
51873
- }
51874
- if (budgetExceeded)
51897
+ for (const test of tests2) {
51898
+ if (budget !== undefined && visitedCount >= budget) {
51899
+ budgetExceeded = true;
51875
51900
  break;
51876
- found = true;
51901
+ }
51902
+ impactedTestsSet.add(test);
51903
+ visitedCount++;
51877
51904
  }
51905
+ if (budgetExceeded)
51906
+ break;
51878
51907
  }
51879
51908
  if (budgetExceeded)
51880
51909
  break;
package/dist/index.js CHANGED
@@ -69,7 +69,7 @@ var package_default;
69
69
  var init_package = __esm(() => {
70
70
  package_default = {
71
71
  name: "opencode-swarm",
72
- version: "7.44.0",
72
+ version: "7.45.0",
73
73
  description: "Architect-centric agentic swarm plugin for OpenCode - hub-and-spoke orchestration with SME consultation, code generation, and QA review",
74
74
  main: "dist/index.js",
75
75
  types: "dist/index.d.ts",
@@ -56168,27 +56168,25 @@ function normalizeEntry(raw) {
56168
56168
  ro = {};
56169
56169
  obj.retrieval_outcomes = ro;
56170
56170
  }
56171
- {
56172
- if (typeof ro.shown_count !== "number") {
56173
- ro.shown_count = typeof ro.applied_count === "number" ? ro.applied_count : 0;
56174
- }
56175
- if (typeof ro.acknowledged_count !== "number")
56176
- ro.acknowledged_count = 0;
56177
- if (typeof ro.applied_explicit_count !== "number") {
56178
- ro.applied_explicit_count = 0;
56179
- }
56180
- if (typeof ro.ignored_count !== "number")
56181
- ro.ignored_count = 0;
56182
- if (typeof ro.violated_count !== "number")
56183
- ro.violated_count = 0;
56184
- if (typeof ro.contradicted_count !== "number")
56185
- ro.contradicted_count = 0;
56186
- if (typeof ro.succeeded_after_shown_count !== "number") {
56187
- ro.succeeded_after_shown_count = typeof ro.succeeded_after_count === "number" ? ro.succeeded_after_count : 0;
56188
- }
56189
- if (typeof ro.failed_after_shown_count !== "number") {
56190
- ro.failed_after_shown_count = typeof ro.failed_after_count === "number" ? ro.failed_after_count : 0;
56191
- }
56171
+ if (typeof ro.shown_count !== "number") {
56172
+ ro.shown_count = typeof ro.applied_count === "number" ? ro.applied_count : 0;
56173
+ }
56174
+ if (typeof ro.acknowledged_count !== "number")
56175
+ ro.acknowledged_count = 0;
56176
+ if (typeof ro.applied_explicit_count !== "number") {
56177
+ ro.applied_explicit_count = 0;
56178
+ }
56179
+ if (typeof ro.ignored_count !== "number")
56180
+ ro.ignored_count = 0;
56181
+ if (typeof ro.violated_count !== "number")
56182
+ ro.violated_count = 0;
56183
+ if (typeof ro.contradicted_count !== "number")
56184
+ ro.contradicted_count = 0;
56185
+ if (typeof ro.succeeded_after_shown_count !== "number") {
56186
+ ro.succeeded_after_shown_count = typeof ro.succeeded_after_count === "number" ? ro.succeeded_after_count : 0;
56187
+ }
56188
+ if (typeof ro.failed_after_shown_count !== "number") {
56189
+ ro.failed_after_shown_count = typeof ro.failed_after_count === "number" ? ro.failed_after_count : 0;
56192
56190
  }
56193
56191
  try {
56194
56192
  if (typeof obj.encounter_score !== "number" || Number.isNaN(obj.encounter_score)) {
@@ -74499,6 +74497,19 @@ import path54 from "node:path";
74499
74497
  function normalizePath(p) {
74500
74498
  return p.replace(/\\/g, "/");
74501
74499
  }
74500
+ function sharedTrailingSegments(a, b) {
74501
+ const aParts = normalizePath(a).split("/").filter(Boolean);
74502
+ const bParts = normalizePath(b).split("/").filter(Boolean);
74503
+ let i2 = aParts.length - 1;
74504
+ let j = bParts.length - 1;
74505
+ let shared = 0;
74506
+ while (i2 >= 0 && j >= 0 && aParts[i2] === bParts[j]) {
74507
+ shared++;
74508
+ i2--;
74509
+ j--;
74510
+ }
74511
+ return shared;
74512
+ }
74502
74513
  function isCacheStale(impactMap, generatedAtMs) {
74503
74514
  for (const sourcePath of Object.keys(impactMap)) {
74504
74515
  try {
@@ -74826,25 +74837,43 @@ async function analyzeImpact(changedFiles, cwd, budget) {
74826
74837
  if (budgetExceeded)
74827
74838
  break;
74828
74839
  } else {
74829
- let found = false;
74830
- for (const [sourcePath, tests2] of Object.entries(impactMap)) {
74840
+ const changedDir = normalizePath(path54.dirname(normalizedChanged));
74841
+ const changedInputDir = normalizePath(path54.dirname(changedFile));
74842
+ const suffixMatches = Object.entries(impactMap).filter(([sourcePath]) => {
74843
+ return sourcePath.endsWith(changedFile) || changedFile.endsWith(sourcePath) || sourcePath.endsWith(normalizedChanged) || normalizedChanged.endsWith(sourcePath);
74844
+ }).sort(([sourceA], [sourceB]) => {
74845
+ const sourceDirA = normalizePath(path54.dirname(sourceA));
74846
+ const sourceDirB = normalizePath(path54.dirname(sourceB));
74847
+ const exactA = sourceDirA === changedDir || changedInputDir !== "." && (sourceDirA === changedInputDir || sourceDirA.endsWith(`/${changedInputDir}`));
74848
+ const exactB = sourceDirB === changedDir || changedInputDir !== "." && (sourceDirB === changedInputDir || sourceDirB.endsWith(`/${changedInputDir}`));
74849
+ if (exactA !== exactB)
74850
+ return exactA ? -1 : 1;
74851
+ const sharedA = Math.max(sharedTrailingSegments(sourceDirA, changedDir), changedInputDir === "." ? 0 : sharedTrailingSegments(sourceDirA, changedInputDir));
74852
+ const sharedB = Math.max(sharedTrailingSegments(sourceDirB, changedDir), changedInputDir === "." ? 0 : sharedTrailingSegments(sourceDirB, changedInputDir));
74853
+ const nearestA = sharedA > 0;
74854
+ const nearestB = sharedB > 0;
74855
+ if (nearestA !== nearestB)
74856
+ return nearestA ? -1 : 1;
74857
+ if (sharedA !== sharedB)
74858
+ return sharedB - sharedA;
74859
+ return sourceA.localeCompare(sourceB);
74860
+ });
74861
+ const found = suffixMatches.length > 0;
74862
+ for (const [, tests2] of suffixMatches) {
74831
74863
  if (budget !== undefined && visitedCount >= budget) {
74832
74864
  budgetExceeded = true;
74833
74865
  break;
74834
74866
  }
74835
- if (sourcePath.endsWith(changedFile) || changedFile.endsWith(sourcePath)) {
74836
- for (const test of tests2) {
74837
- if (budget !== undefined && visitedCount >= budget) {
74838
- budgetExceeded = true;
74839
- break;
74840
- }
74841
- impactedTestsSet.add(test);
74842
- visitedCount++;
74843
- }
74844
- if (budgetExceeded)
74867
+ for (const test of tests2) {
74868
+ if (budget !== undefined && visitedCount >= budget) {
74869
+ budgetExceeded = true;
74845
74870
  break;
74846
- found = true;
74871
+ }
74872
+ impactedTestsSet.add(test);
74873
+ visitedCount++;
74847
74874
  }
74875
+ if (budgetExceeded)
74876
+ break;
74848
74877
  }
74849
74878
  if (budgetExceeded)
74850
74879
  break;
@@ -97912,6 +97941,12 @@ function extractSignature(node, languageId) {
97912
97941
  return paramsNode.text;
97913
97942
  }
97914
97943
  }
97944
+ if (node.type === "class_declaration" || node.type === "class_definition" || node.type === "class") {
97945
+ const classBodyNode = node.children.find((c) => c !== null && (c.type === "class_body" || c.type === "declaration_list"));
97946
+ if (classBodyNode) {
97947
+ return classBodyNode.text;
97948
+ }
97949
+ }
97915
97950
  return;
97916
97951
  }
97917
97952
  function compareSymbols(oldSymbols, newSymbols) {
@@ -97973,7 +98008,7 @@ function compareSymbols(oldSymbols, newSymbols) {
97973
98008
  return changes;
97974
98009
  }
97975
98010
  function findRenamedSymbol(newSymbol, oldSymbols, matchedOldSymbols) {
97976
- if (newSymbol.category !== "function" && newSymbol.category !== "type") {
98011
+ if (newSymbol.category !== "function" && newSymbol.category !== "type" && newSymbol.category !== "class") {
97977
98012
  return null;
97978
98013
  }
97979
98014
  if (!newSymbol.signature) {
@@ -98205,11 +98240,51 @@ function generateSummaryMarkdown(summary) {
98205
98240
  `);
98206
98241
  }
98207
98242
 
98243
+ // src/utils/git-binary-missing-error.ts
98244
+ class GitBinaryMissingError extends Error {
98245
+ name = "GitBinaryMissingError";
98246
+ constructor(message = "git binary is not available", options) {
98247
+ super(message, options);
98248
+ }
98249
+ }
98250
+ function isGitBinaryMissing(err2) {
98251
+ return typeof err2 === "object" && err2 !== null && "code" in err2 && err2.code === "ENOENT";
98252
+ }
98253
+
98208
98254
  // src/hooks/semantic-diff-injection.ts
98255
+ async function execGit(directory, args2, options) {
98256
+ try {
98257
+ const stdout = await new Promise((resolve31, reject) => {
98258
+ const execOpts = {
98259
+ encoding: "utf-8",
98260
+ cwd: directory,
98261
+ timeout: options?.timeout,
98262
+ maxBuffer: options?.maxBuffer,
98263
+ stdio: ["ignore", "pipe", "pipe"]
98264
+ };
98265
+ child_process5.execFile("git", args2, execOpts, (error93, output, _stderr) => {
98266
+ if (error93) {
98267
+ reject(error93);
98268
+ return;
98269
+ }
98270
+ resolve31(output ?? "");
98271
+ });
98272
+ });
98273
+ return stdout;
98274
+ } catch (err2) {
98275
+ if (isGitBinaryMissing(err2)) {
98276
+ throw new GitBinaryMissingError("git binary is not available", {
98277
+ cause: err2
98278
+ });
98279
+ }
98280
+ throw err2;
98281
+ }
98282
+ }
98209
98283
  async function buildSemanticDiffBlock(directory, changedFiles, maxFiles = 10) {
98210
98284
  if (changedFiles.length === 0)
98211
98285
  return null;
98212
98286
  try {
98287
+ const realDirectory = fs55.realpathSync(directory);
98213
98288
  const filesToProcess = changedFiles.slice(0, maxFiles);
98214
98289
  const astDiffs = [];
98215
98290
  const graph = getCachedGraph2(directory);
@@ -98229,37 +98304,40 @@ async function buildSemanticDiffBlock(directory, changedFiles, maxFiles = 10) {
98229
98304
  if (relativeToDir.startsWith("..") || path92.isAbsolute(relativeToDir)) {
98230
98305
  continue;
98231
98306
  }
98307
+ let realResolvedPath;
98308
+ try {
98309
+ realResolvedPath = fs55.realpathSync(resolvedPath);
98310
+ } catch {
98311
+ continue;
98312
+ }
98313
+ const realRelativeToDir = path92.relative(realDirectory, realResolvedPath);
98314
+ if (realRelativeToDir.startsWith("..") || path92.isAbsolute(realRelativeToDir)) {
98315
+ continue;
98316
+ }
98232
98317
  try {
98233
98318
  let fileExistsInHead = false;
98234
98319
  try {
98235
- child_process5.execFileSync("git", ["cat-file", "-e", `HEAD:${filePath}`], {
98236
- encoding: "utf-8",
98237
- timeout: 3000,
98238
- cwd: directory,
98239
- stdio: "pipe"
98320
+ await execGit(directory, ["cat-file", "-e", `HEAD:${filePath}`], {
98321
+ timeout: 3000
98240
98322
  });
98241
98323
  fileExistsInHead = true;
98242
98324
  } catch (err2) {
98243
- if (err2.code === "ENOENT") {
98244
- err2._gitBinaryMissing = true;
98325
+ if (err2 instanceof GitBinaryMissingError) {
98245
98326
  throw err2;
98246
98327
  }
98247
98328
  fileExistsInHead = false;
98248
98329
  }
98249
- const oldContent = fileExistsInHead ? child_process5.execFileSync("git", ["show", `HEAD:${filePath}`], {
98250
- encoding: "utf-8",
98330
+ const oldContent = fileExistsInHead ? await execGit(directory, ["show", `HEAD:${filePath}`], {
98251
98331
  timeout: 5000,
98252
- cwd: directory,
98253
- stdio: "pipe",
98254
98332
  maxBuffer: 5 * 1024 * 1024
98255
98333
  }) : "";
98256
- const newContent = fs55.readFileSync(path92.join(directory, filePath), "utf-8");
98334
+ const newContent = await fs55.promises.readFile(realResolvedPath, "utf-8");
98257
98335
  const astResult = await computeASTDiff(filePath, oldContent, newContent);
98258
98336
  if (astResult && (astResult.changes.length > 0 || astResult.error !== undefined)) {
98259
98337
  astDiffs.push(astResult);
98260
98338
  }
98261
98339
  } catch (err2) {
98262
- if (err2.code === "ENOENT" && err2._gitBinaryMissing) {
98340
+ if (err2 instanceof GitBinaryMissingError) {
98263
98341
  throw err2;
98264
98342
  }
98265
98343
  }
@@ -106602,8 +106680,8 @@ ${body2}`);
106602
106680
 
106603
106681
  // src/council/council-evidence-writer.ts
106604
106682
  init_task_file();
106605
- import { appendFileSync as appendFileSync12, existsSync as existsSync62, mkdirSync as mkdirSync27, readFileSync as readFileSync46 } from "node:fs";
106606
- import { join as join94 } from "node:path";
106683
+ import { appendFileSync as appendFileSync12, existsSync as existsSync62, mkdirSync as mkdirSync27, readFileSync as readFileSync45 } from "node:fs";
106684
+ import { join as join93 } from "node:path";
106607
106685
  var EVIDENCE_DIR2 = ".swarm/evidence";
106608
106686
  var VALID_TASK_ID = /^\d+\.\d+(\.\d+)*$/;
106609
106687
  var COUNCIL_GATE_NAME = "council";
@@ -106640,14 +106718,14 @@ async function writeCouncilEvidence(workingDir, synthesis) {
106640
106718
  if (!VALID_TASK_ID.test(synthesis.taskId)) {
106641
106719
  throw new Error(`writeCouncilEvidence: invalid taskId "${synthesis.taskId}" — must match N.M or N.M.P format`);
106642
106720
  }
106643
- const dir = join94(workingDir, EVIDENCE_DIR2);
106721
+ const dir = join93(workingDir, EVIDENCE_DIR2);
106644
106722
  mkdirSync27(dir, { recursive: true });
106645
106723
  const filePath = taskEvidencePath(workingDir, synthesis.taskId);
106646
106724
  await _internals49.withTaskEvidenceLock(workingDir, synthesis.taskId, COUNCIL_AGENT_ID, async () => {
106647
106725
  const existingRoot = Object.create(null);
106648
106726
  if (existsSync62(filePath)) {
106649
106727
  try {
106650
- const parsed = JSON.parse(readFileSync46(filePath, "utf-8"));
106728
+ const parsed = JSON.parse(readFileSync45(filePath, "utf-8"));
106651
106729
  if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
106652
106730
  safeAssignOwnProps(existingRoot, parsed);
106653
106731
  }
@@ -106678,7 +106756,7 @@ async function writeCouncilEvidence(workingDir, synthesis) {
106678
106756
  await atomicWriteFile(filePath, JSON.stringify(updated, null, 2));
106679
106757
  });
106680
106758
  try {
106681
- const councilDir = join94(workingDir, ".swarm", "council");
106759
+ const councilDir = join93(workingDir, ".swarm", "council");
106682
106760
  mkdirSync27(councilDir, { recursive: true });
106683
106761
  const auditLine = JSON.stringify({
106684
106762
  round: synthesis.roundNumber,
@@ -106686,7 +106764,7 @@ async function writeCouncilEvidence(workingDir, synthesis) {
106686
106764
  timestamp: synthesis.timestamp,
106687
106765
  vetoedBy: synthesis.vetoedBy
106688
106766
  });
106689
- appendFileSync12(join94(councilDir, `${synthesis.taskId}.rounds.jsonl`), `${auditLine}
106767
+ appendFileSync12(join93(councilDir, `${synthesis.taskId}.rounds.jsonl`), `${auditLine}
106690
106768
  `);
106691
106769
  } catch (auditError) {
106692
106770
  console.warn(`writeCouncilEvidence: failed to append round-history audit log: ${auditError instanceof Error ? auditError.message : String(auditError)}`);
@@ -107008,25 +107086,25 @@ function buildFinalCouncilFeedback(projectSummary, verdict, vetoedBy, requiredFi
107008
107086
  }
107009
107087
 
107010
107088
  // src/council/criteria-store.ts
107011
- import { existsSync as existsSync63, mkdirSync as mkdirSync28, readFileSync as readFileSync47, writeFileSync as writeFileSync20 } from "node:fs";
107012
- import { join as join95 } from "node:path";
107089
+ import { existsSync as existsSync63, mkdirSync as mkdirSync28, readFileSync as readFileSync46, writeFileSync as writeFileSync20 } from "node:fs";
107090
+ import { join as join94 } from "node:path";
107013
107091
  var COUNCIL_DIR = ".swarm/council";
107014
107092
  function writeCriteria(workingDir, taskId, criteria) {
107015
- const dir = join95(workingDir, COUNCIL_DIR);
107093
+ const dir = join94(workingDir, COUNCIL_DIR);
107016
107094
  mkdirSync28(dir, { recursive: true });
107017
107095
  const payload = {
107018
107096
  taskId,
107019
107097
  criteria,
107020
107098
  declaredAt: new Date().toISOString()
107021
107099
  };
107022
- writeFileSync20(join95(dir, `${safeId(taskId)}.json`), JSON.stringify(payload, null, 2));
107100
+ writeFileSync20(join94(dir, `${safeId(taskId)}.json`), JSON.stringify(payload, null, 2));
107023
107101
  }
107024
107102
  function readCriteria(workingDir, taskId) {
107025
- const filePath = join95(workingDir, COUNCIL_DIR, `${safeId(taskId)}.json`);
107103
+ const filePath = join94(workingDir, COUNCIL_DIR, `${safeId(taskId)}.json`);
107026
107104
  if (!existsSync63(filePath))
107027
107105
  return null;
107028
107106
  try {
107029
- const parsed = JSON.parse(readFileSync47(filePath, "utf-8"));
107107
+ const parsed = JSON.parse(readFileSync46(filePath, "utf-8"));
107030
107108
  if (parsed && typeof parsed === "object" && typeof parsed.taskId === "string" && Array.isArray(parsed.criteria)) {
107031
107109
  return parsed;
107032
107110
  }
@@ -108288,7 +108366,6 @@ var diff = createSwarmTool({
108288
108366
  const fileCount = files.length;
108289
108367
  const astDiffs = [];
108290
108368
  const filesForAST = files.slice(0, MAX_AST_FILES);
108291
- const astSkippedCount = files.length > MAX_AST_FILES ? files.length - MAX_AST_FILES : 0;
108292
108369
  for (const file3 of filesForAST) {
108293
108370
  try {
108294
108371
  let oldContent;
@@ -108347,6 +108424,7 @@ var diff = createSwarmTool({
108347
108424
  markdownSummary = generateSummaryMarkdown(semanticSummary);
108348
108425
  } catch {}
108349
108426
  }
108427
+ const astSkippedCount = files.length > MAX_AST_FILES ? files.length - MAX_AST_FILES : 0;
108350
108428
  const truncated = diffLines.length > MAX_DIFF_LINES;
108351
108429
  const summary = truncated ? `${fileCount} files changed. Contract changes: ${hasContractChanges ? "YES" : "NO"}. (truncated to ${MAX_DIFF_LINES} lines)` : `${fileCount} files changed. Contract changes: ${hasContractChanges ? "YES" : "NO"}`;
108352
108430
  const result = {
@@ -108378,6 +108456,34 @@ import * as fs79 from "node:fs";
108378
108456
  import * as path116 from "node:path";
108379
108457
  init_create_tool();
108380
108458
  init_resolve_working_directory();
108459
+ async function execGit2(workingDir, args2, options) {
108460
+ try {
108461
+ const stdout = await new Promise((resolve40, reject) => {
108462
+ const execOpts = {
108463
+ encoding: "utf-8",
108464
+ cwd: workingDir,
108465
+ timeout: options?.timeout,
108466
+ maxBuffer: options?.maxBuffer,
108467
+ stdio: ["ignore", "pipe", "pipe"]
108468
+ };
108469
+ child_process8.execFile("git", args2, execOpts, (error93, output, _stderr) => {
108470
+ if (error93) {
108471
+ reject(error93);
108472
+ return;
108473
+ }
108474
+ resolve40(output ?? "");
108475
+ });
108476
+ });
108477
+ return stdout;
108478
+ } catch (err2) {
108479
+ if (isGitBinaryMissing(err2)) {
108480
+ throw new GitBinaryMissingError("git binary is not available", {
108481
+ cause: err2
108482
+ });
108483
+ }
108484
+ throw err2;
108485
+ }
108486
+ }
108381
108487
  var diff_summary = createSwarmTool({
108382
108488
  description: "Generate a filtered semantic diff summary from AST analysis. Returns SemanticDiffSummary with optional filtering by classification or riskLevel.",
108383
108489
  args: {
@@ -108403,45 +108509,33 @@ var diff_summary = createSwarmTool({
108403
108509
  const astDiffs = [];
108404
108510
  for (const filePath of typedArgs.files) {
108405
108511
  let astResult = null;
108406
- let gitBinaryMissing = false;
108407
- let gitError = null;
108408
108512
  let fileExistsInHead = false;
108409
108513
  try {
108410
- child_process8.execFileSync("git", ["cat-file", "-e", `HEAD:${filePath}`], {
108411
- encoding: "utf-8",
108412
- timeout: 3000,
108413
- cwd: workingDir,
108414
- stdio: "pipe"
108514
+ await execGit2(workingDir, ["cat-file", "-e", `HEAD:${filePath}`], {
108515
+ timeout: 3000
108415
108516
  });
108416
108517
  fileExistsInHead = true;
108417
108518
  } catch (e) {
108418
- if (e && typeof e === "object" && "code" in e) {
108419
- const err2 = e;
108420
- if (err2.code === "ENOENT") {
108421
- gitBinaryMissing = true;
108422
- gitError = e;
108423
- }
108519
+ if (e instanceof GitBinaryMissingError) {
108520
+ throw e;
108424
108521
  }
108425
108522
  }
108426
- if (gitBinaryMissing && gitError) {
108427
- throw gitError;
108428
- }
108429
108523
  try {
108430
108524
  let oldContent;
108431
- const newContent = fs79.readFileSync(path116.join(workingDir, filePath), "utf-8");
108525
+ const newContent = await fs79.promises.readFile(path116.join(workingDir, filePath), "utf-8");
108432
108526
  if (fileExistsInHead) {
108433
- oldContent = child_process8.execFileSync("git", ["show", `HEAD:${filePath}`], {
108434
- encoding: "utf-8",
108527
+ oldContent = await execGit2(workingDir, ["show", `HEAD:${filePath}`], {
108435
108528
  timeout: 5000,
108436
- cwd: workingDir,
108437
- stdio: "pipe",
108438
108529
  maxBuffer: 5 * 1024 * 1024
108439
108530
  });
108440
108531
  } else {
108441
108532
  oldContent = "";
108442
108533
  }
108443
108534
  astResult = await computeASTDiff(filePath, oldContent, newContent);
108444
- } catch (_e) {
108535
+ } catch (e) {
108536
+ if (e instanceof GitBinaryMissingError) {
108537
+ throw e;
108538
+ }
108445
108539
  astResult = null;
108446
108540
  }
108447
108541
  if (astResult && (astResult.changes.length > 0 || astResult.error !== undefined)) {
@@ -119836,7 +119930,7 @@ init_loader();
119836
119930
  import {
119837
119931
  existsSync as existsSync81,
119838
119932
  mkdirSync as mkdirSync34,
119839
- readFileSync as readFileSync65,
119933
+ readFileSync as readFileSync63,
119840
119934
  renameSync as renameSync21,
119841
119935
  unlinkSync as unlinkSync18,
119842
119936
  writeFileSync as writeFileSync27
@@ -119973,7 +120067,7 @@ var submit_phase_council_verdicts = createSwarmTool({
119973
120067
  function getPhaseMutationGapFinding(phaseNumber, workingDir) {
119974
120068
  const mutationGatePath = path138.join(workingDir, ".swarm", "evidence", String(phaseNumber), "mutation-gate.json");
119975
120069
  try {
119976
- const raw = readFileSync65(mutationGatePath, "utf-8");
120070
+ const raw = readFileSync63(mutationGatePath, "utf-8");
119977
120071
  const parsed = JSON.parse(raw);
119978
120072
  const gateEntry = (parsed.entries ?? []).find((entry) => entry?.type === "mutation-gate");
119979
120073
  if (!gateEntry) {
@@ -13,6 +13,10 @@ export interface DiffResult {
13
13
  astDiffs?: ASTDiffResult[];
14
14
  semanticSummary?: SemanticDiffSummary;
15
15
  markdownSummary?: string;
16
+ /**
17
+ * @deprecated This field is no longer computed and will be removed in a future version.
18
+ * It is retained for backward compatibility with existing consumers.
19
+ */
16
20
  astSkippedCount?: number;
17
21
  }
18
22
  export interface DiffErrorResult {
@@ -1,4 +1,5 @@
1
1
  import type { tool } from '@opencode-ai/plugin';
2
+ import { getAllHistory, type TestRunRecord } from '../test-impact/history-store.js';
2
3
  export declare const MAX_OUTPUT_BYTES = 512000;
3
4
  export declare const MAX_COMMAND_LENGTH = 500;
4
5
  export declare const DEFAULT_TIMEOUT_MS = 60000;
@@ -127,4 +128,10 @@ export declare function isLanguageSpecificTestFile(basename: string): boolean;
127
128
  */
128
129
  export declare function getTestFilesFromConvention(sourceFiles: string[], workingDir?: string): string[];
129
130
  export declare function runTests(framework: TestFramework, scope: 'all' | 'convention' | 'graph' | 'impact', files: string[], coverage: boolean, timeout_ms: number, cwd: string): Promise<TestResult>;
131
+ declare function selectHistoryForAnalysis(history: ReturnType<typeof getAllHistory>): TestRunRecord[];
130
132
  export declare const test_runner: ReturnType<typeof tool>;
133
+ export declare const _internals: {
134
+ readonly selectHistoryForAnalysis: typeof selectHistoryForAnalysis;
135
+ readonly AGGREGATE_TEST_NAME: "(aggregate)";
136
+ };
137
+ export {};
@@ -0,0 +1,9 @@
1
+ export declare class GitBinaryMissingError extends Error {
2
+ readonly name = "GitBinaryMissingError";
3
+ constructor(message?: string, options?: {
4
+ cause?: unknown;
5
+ });
6
+ }
7
+ export declare function isGitBinaryMissing(err: unknown): err is {
8
+ code?: string;
9
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-swarm",
3
- "version": "7.44.0",
3
+ "version": "7.45.0",
4
4
  "description": "Architect-centric agentic swarm plugin for OpenCode - hub-and-spoke orchestration with SME consultation, code generation, and QA review",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",