opencode-swarm 7.19.3 → 7.20.1

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
@@ -34,7 +34,7 @@ var package_default;
34
34
  var init_package = __esm(() => {
35
35
  package_default = {
36
36
  name: "opencode-swarm",
37
- version: "7.19.3",
37
+ version: "7.20.1",
38
38
  description: "Architect-centric agentic swarm plugin for OpenCode - hub-and-spoke orchestration with SME consultation, code generation, and QA review",
39
39
  main: "dist/index.js",
40
40
  types: "dist/index.d.ts",
@@ -46785,18 +46785,36 @@ async function buildImpactMap(cwd) {
46785
46785
  await _internals22.saveImpactMap(cwd, impactMap);
46786
46786
  return impactMap;
46787
46787
  }
46788
- async function loadImpactMap(cwd) {
46788
+ async function loadImpactMap(cwd, options) {
46789
46789
  const cachePath = path34.join(cwd, ".swarm", "cache", "impact-map.json");
46790
46790
  if (fs17.existsSync(cachePath)) {
46791
46791
  try {
46792
46792
  const content = fs17.readFileSync(cachePath, "utf-8");
46793
46793
  const data = JSON.parse(content);
46794
- const map3 = data.map;
46795
- const generatedAt = new Date(data.generatedAt).getTime();
46796
- if (!_internals22.isCacheStale(map3, generatedAt)) {
46797
- return map3;
46794
+ if (data.map !== null && typeof data.map === "object" && !Array.isArray(data.map)) {
46795
+ const map3 = data.map;
46796
+ const hasValidValues = Object.values(map3).every((v) => Array.isArray(v) && v.every((item) => typeof item === "string"));
46797
+ if (hasValidValues) {
46798
+ const generatedAt = new Date(data.generatedAt).getTime();
46799
+ if (!_internals22.isCacheStale(map3, generatedAt)) {
46800
+ return map3;
46801
+ }
46802
+ if (options?.skipRebuild) {
46803
+ return map3;
46804
+ }
46805
+ }
46798
46806
  }
46799
- } catch {}
46807
+ if (options?.skipRebuild) {
46808
+ return {};
46809
+ }
46810
+ } catch {
46811
+ if (options?.skipRebuild) {
46812
+ return {};
46813
+ }
46814
+ }
46815
+ }
46816
+ if (options?.skipRebuild) {
46817
+ return {};
46800
46818
  }
46801
46819
  return _internals22.buildImpactMap(cwd);
46802
46820
  }
@@ -46813,7 +46831,7 @@ async function saveImpactMap(cwd, impactMap) {
46813
46831
  };
46814
46832
  fs17.writeFileSync(cachePath, JSON.stringify(data, null, 2), "utf-8");
46815
46833
  }
46816
- async function analyzeImpact(changedFiles, cwd) {
46834
+ async function analyzeImpact(changedFiles, cwd, budget) {
46817
46835
  if (!Array.isArray(changedFiles)) {
46818
46836
  const emptyMap = {};
46819
46837
  return {
@@ -46827,24 +46845,49 @@ async function analyzeImpact(changedFiles, cwd) {
46827
46845
  const impactMap = await _internals22.loadImpactMap(cwd);
46828
46846
  const impactedTestsSet = new Set;
46829
46847
  const untestedFiles = [];
46848
+ let visitedCount = 0;
46849
+ let budgetExceeded = false;
46830
46850
  for (const changedFile of validFiles) {
46851
+ if (budget !== undefined && visitedCount >= budget) {
46852
+ budgetExceeded = true;
46853
+ break;
46854
+ }
46831
46855
  const normalizedChanged = normalizePath(path34.resolve(changedFile));
46832
46856
  const tests = impactMap[normalizedChanged];
46833
46857
  if (tests && tests.length > 0) {
46834
46858
  for (const test of tests) {
46859
+ if (budget !== undefined && visitedCount >= budget) {
46860
+ budgetExceeded = true;
46861
+ break;
46862
+ }
46835
46863
  impactedTestsSet.add(test);
46864
+ visitedCount++;
46836
46865
  }
46866
+ if (budgetExceeded)
46867
+ break;
46837
46868
  } else {
46838
46869
  let found = false;
46839
46870
  for (const [sourcePath, tests2] of Object.entries(impactMap)) {
46871
+ if (budget !== undefined && visitedCount >= budget) {
46872
+ budgetExceeded = true;
46873
+ break;
46874
+ }
46840
46875
  if (sourcePath.endsWith(changedFile) || changedFile.endsWith(sourcePath)) {
46841
46876
  for (const test of tests2) {
46877
+ if (budget !== undefined && visitedCount >= budget) {
46878
+ budgetExceeded = true;
46879
+ break;
46880
+ }
46842
46881
  impactedTestsSet.add(test);
46882
+ visitedCount++;
46843
46883
  }
46884
+ if (budgetExceeded)
46885
+ break;
46844
46886
  found = true;
46845
- break;
46846
46887
  }
46847
46888
  }
46889
+ if (budgetExceeded)
46890
+ break;
46848
46891
  if (!found) {
46849
46892
  untestedFiles.push(changedFile);
46850
46893
  }
@@ -46862,7 +46905,8 @@ async function analyzeImpact(changedFiles, cwd) {
46862
46905
  impactedTests,
46863
46906
  unrelatedTests,
46864
46907
  untestedFiles,
46865
- impactMap
46908
+ impactMap,
46909
+ budgetExceeded
46866
46910
  };
46867
46911
  }
46868
46912
  var IMPORT_REGEX_ES, IMPORT_REGEX_REQUIRE, IMPORT_REGEX_REEXPORT, TS_EXTENSIONS, PYTHON_EXTENSIONS, GO_EXTENSIONS, EXTENSIONS_TO_TRY, goModuleCache, _internals22;
@@ -47724,6 +47768,25 @@ var init_dispatch = __esm(() => {
47724
47768
  // src/tools/test-runner.ts
47725
47769
  import * as fs22 from "fs";
47726
47770
  import * as path39 from "path";
47771
+ async function estimateFanOut(sourceFiles, cwd) {
47772
+ try {
47773
+ const impactMap = await loadImpactMap(cwd, { skipRebuild: true });
47774
+ const uniqueTestFiles = new Set;
47775
+ for (const sourceFile of sourceFiles) {
47776
+ const resolvedPath = path39.resolve(cwd, sourceFile);
47777
+ const normalizedPath = resolvedPath.replace(/\\/g, "/");
47778
+ const testFiles = impactMap[normalizedPath];
47779
+ if (testFiles) {
47780
+ for (const testFile of testFiles) {
47781
+ uniqueTestFiles.add(testFile);
47782
+ }
47783
+ }
47784
+ }
47785
+ return { estimatedCount: uniqueTestFiles.size };
47786
+ } catch {
47787
+ return { estimatedCount: 0 };
47788
+ }
47789
+ }
47727
47790
  function isAbsolutePath(str) {
47728
47791
  if (str.startsWith("/"))
47729
47792
  return true;
@@ -49068,6 +49131,18 @@ var init_test_runner = __esm(() => {
49068
49131
  };
49069
49132
  return JSON.stringify(errorResult, null, 2);
49070
49133
  }
49134
+ const estimate = await estimateFanOut(sourceFiles, workingDir);
49135
+ if (estimate.estimatedCount > MAX_SAFE_TEST_FILES) {
49136
+ const errorResult = {
49137
+ success: false,
49138
+ framework,
49139
+ scope,
49140
+ error: "Estimated test file count exceeds safe maximum",
49141
+ message: `Scope "graph" resolution would produce approximately ${estimate.estimatedCount} test files, which exceeds the safe limit of ${MAX_SAFE_TEST_FILES}. Break the source files into smaller batches and retry.`,
49142
+ outcome: "scope_exceeded"
49143
+ };
49144
+ return JSON.stringify(errorResult, null, 2);
49145
+ }
49071
49146
  const graphTestFiles = await getTestFilesFromGraph(sourceFiles, workingDir);
49072
49147
  if (graphTestFiles.length > 0) {
49073
49148
  testFiles = graphTestFiles;
@@ -49106,8 +49181,31 @@ var init_test_runner = __esm(() => {
49106
49181
  };
49107
49182
  return JSON.stringify(errorResult, null, 2);
49108
49183
  }
49184
+ const estimate = await estimateFanOut(sourceFiles, workingDir);
49185
+ if (estimate.estimatedCount > MAX_SAFE_TEST_FILES) {
49186
+ const errorResult = {
49187
+ success: false,
49188
+ framework,
49189
+ scope,
49190
+ error: "Estimated test file count exceeds safe maximum",
49191
+ message: `Scope "impact" resolution would produce approximately ${estimate.estimatedCount} test files, which exceeds the safe limit of ${MAX_SAFE_TEST_FILES}. Break the source files into smaller batches and retry.`,
49192
+ outcome: "scope_exceeded"
49193
+ };
49194
+ return JSON.stringify(errorResult, null, 2);
49195
+ }
49109
49196
  try {
49110
- const impactResult = await analyzeImpact(sourceFiles, workingDir);
49197
+ const impactResult = await analyzeImpact(sourceFiles, workingDir, MAX_SAFE_TEST_FILES);
49198
+ if (impactResult.budgetExceeded) {
49199
+ const errorResult = {
49200
+ success: false,
49201
+ framework,
49202
+ scope,
49203
+ error: "Budget exceeded during impact analysis",
49204
+ message: `Impact analysis exceeded safe budget of ${MAX_SAFE_TEST_FILES} test files.`,
49205
+ outcome: "scope_exceeded"
49206
+ };
49207
+ return JSON.stringify(errorResult, null, 2);
49208
+ }
49111
49209
  if (impactResult.impactedTests.length > 0) {
49112
49210
  testFiles = impactResult.impactedTests.map((absPath) => {
49113
49211
  const relativePath = path39.relative(workingDir, absPath);
@@ -7,8 +7,16 @@
7
7
  * - Layer 2 (Hard Block @ 100%): Throws error in toolBefore to block further calls, injects STOP message
8
8
  */
9
9
  import * as path from 'node:path';
10
+ import { getSwarmAgents, resolveFallbackModel } from '../agents/index';
10
11
  import { type AuthorityConfig, type GuardrailsConfig } from '../config/schema';
11
12
  import { type FileZone } from '../context/zone-classifier';
13
+ export declare const _internals: {
14
+ getSwarmAgents: typeof getSwarmAgents;
15
+ getMostRecentAssistantText: typeof getMostRecentAssistantText;
16
+ getProviderFailureFingerprint: typeof getProviderFailureFingerprint;
17
+ isTransientProviderFailureText: typeof isTransientProviderFailureText;
18
+ resolveFallbackModel: typeof resolveFallbackModel;
19
+ };
12
20
  /**
13
21
  * Issue #853 Layer B: tools that are structurally blocked while
14
22
  * `.swarm/spec-staleness.json` exists. Every blocked tool mutates plan
@@ -30,6 +38,19 @@ export declare const SPEC_DRIFT_BLOCKED_TOOLS: Set<string>;
30
38
  * immediately on the next tool call.
31
39
  */
32
40
  export declare function enforceSpecDriftGate(directory: string | undefined, toolName: string): void;
41
+ type ChatMessageLike = {
42
+ info?: {
43
+ role?: string;
44
+ sessionID?: string;
45
+ };
46
+ parts?: Array<{
47
+ type?: string;
48
+ text?: unknown;
49
+ }>;
50
+ };
51
+ declare function getMostRecentAssistantText(messages: ChatMessageLike[]): string;
52
+ declare function isTransientProviderFailureText(text: string): boolean;
53
+ declare function getProviderFailureFingerprint(text: string): string;
33
54
  /**
34
55
  * Retrieves stored input args for a given callID.
35
56
  * Used by other hooks (e.g., delegation-gate) to access tool input args.