opencode-swarm 6.25.3 → 6.25.4

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
@@ -33858,6 +33858,7 @@ var MAX_OUTPUT_BYTES3 = 512000;
33858
33858
  var MAX_COMMAND_LENGTH2 = 500;
33859
33859
  var DEFAULT_TIMEOUT_MS = 60000;
33860
33860
  var MAX_TIMEOUT_MS = 300000;
33861
+ var MAX_SAFE_TEST_FILES = 50;
33861
33862
  function containsPathTraversal2(str) {
33862
33863
  if (/\.\.[/\\]/.test(str))
33863
33864
  return true;
@@ -34714,35 +34715,6 @@ var SKIP_DIRECTORIES = new Set([
34714
34715
  ".bundle",
34715
34716
  ".tox"
34716
34717
  ]);
34717
- function findSourceFiles(dir, files = []) {
34718
- let entries;
34719
- try {
34720
- entries = fs5.readdirSync(dir);
34721
- } catch {
34722
- return files;
34723
- }
34724
- entries.sort((a, b) => a.toLowerCase().localeCompare(b.toLowerCase()));
34725
- for (const entry of entries) {
34726
- if (SKIP_DIRECTORIES.has(entry))
34727
- continue;
34728
- const fullPath = path13.join(dir, entry);
34729
- let stat2;
34730
- try {
34731
- stat2 = fs5.statSync(fullPath);
34732
- } catch {
34733
- continue;
34734
- }
34735
- if (stat2.isDirectory()) {
34736
- findSourceFiles(fullPath, files);
34737
- } else if (stat2.isFile()) {
34738
- const ext = path13.extname(fullPath).toLowerCase();
34739
- if (SOURCE_EXTENSIONS.has(ext)) {
34740
- files.push(fullPath);
34741
- }
34742
- }
34743
- }
34744
- return files;
34745
- }
34746
34718
  var test_runner = createSwarmTool({
34747
34719
  description: 'Run project tests with framework detection. Supports bun, vitest, jest, mocha, pytest, cargo, pester, go-test, maven, gradle, dotnet-test, ctest, swift-test, dart-test, rspec, and minitest. Returns deterministic normalized JSON with framework, scope, command, totals, coverage, duration, success status, and failures. Use scope "all" for full suite, "convention" to map source files to test files, or "graph" to find related tests via imports.',
34748
34720
  args: {
@@ -34800,6 +34772,26 @@ var test_runner = createSwarmTool({
34800
34772
  return JSON.stringify(errorResult, null, 2);
34801
34773
  }
34802
34774
  const scope = args.scope || "all";
34775
+ if (scope === "all") {
34776
+ const errorResult = {
34777
+ success: false,
34778
+ framework: "none",
34779
+ scope: "all",
34780
+ error: 'Full-suite test execution (scope: "all") is prohibited in interactive sessions',
34781
+ message: 'Use scope "convention" or "graph" with explicit files to run targeted tests in interactive mode. Full-suite runs are restricted to prevent excessive resource consumption.'
34782
+ };
34783
+ return JSON.stringify(errorResult, null, 2);
34784
+ }
34785
+ if ((scope === "convention" || scope === "graph") && (!args.files || args.files.length === 0)) {
34786
+ const errorResult = {
34787
+ success: false,
34788
+ framework: "none",
34789
+ scope,
34790
+ error: 'scope "convention" and "graph" require explicit files array - omitting files causes unsafe full-project discovery',
34791
+ message: 'When using scope "convention" or "graph", you must provide a non-empty "files" array. Use scope "all" for full project test suite without specifying files.'
34792
+ };
34793
+ return JSON.stringify(errorResult, null, 2);
34794
+ }
34803
34795
  const _files = args.files || [];
34804
34796
  const coverage = args.coverage || false;
34805
34797
  const timeout_ms = Math.min(args.timeout_ms || DEFAULT_TIMEOUT_MS, MAX_TIMEOUT_MS);
@@ -34823,19 +34815,37 @@ var test_runner = createSwarmTool({
34823
34815
  let testFiles = [];
34824
34816
  let graphFallbackReason;
34825
34817
  let effectiveScope = scope;
34826
- if (scope === "all") {
34827
- testFiles = [];
34828
- } else if (scope === "convention") {
34829
- const sourceFiles = args.files && args.files.length > 0 ? args.files.filter((f) => {
34818
+ if (scope === "convention") {
34819
+ const sourceFiles = args.files.filter((f) => {
34830
34820
  const ext = path13.extname(f).toLowerCase();
34831
34821
  return SOURCE_EXTENSIONS.has(ext);
34832
- }) : findSourceFiles(workingDir);
34822
+ });
34823
+ if (sourceFiles.length === 0) {
34824
+ const errorResult = {
34825
+ success: false,
34826
+ framework,
34827
+ scope,
34828
+ error: "Provided files contain no source files with recognized extensions",
34829
+ message: "The files array must contain at least one source file with a recognized extension (.ts, .tsx, .js, .jsx, .py, .rs, .ps1, etc.). Non-source files like README.md or config.json are not valid for test discovery."
34830
+ };
34831
+ return JSON.stringify(errorResult, null, 2);
34832
+ }
34833
34833
  testFiles = getTestFilesFromConvention(sourceFiles);
34834
34834
  } else if (scope === "graph") {
34835
- const sourceFiles = args.files && args.files.length > 0 ? args.files.filter((f) => {
34835
+ const sourceFiles = args.files.filter((f) => {
34836
34836
  const ext = path13.extname(f).toLowerCase();
34837
34837
  return SOURCE_EXTENSIONS.has(ext);
34838
- }) : findSourceFiles(workingDir);
34838
+ });
34839
+ if (sourceFiles.length === 0) {
34840
+ const errorResult = {
34841
+ success: false,
34842
+ framework,
34843
+ scope,
34844
+ error: "Provided files contain no source files with recognized extensions",
34845
+ message: "The files array must contain at least one source file with a recognized extension (.ts, .tsx, .js, .jsx, .py, .rs, .ps1, etc.). Non-source files like README.md or config.json are not valid for test discovery."
34846
+ };
34847
+ return JSON.stringify(errorResult, null, 2);
34848
+ }
34839
34849
  const graphTestFiles = await getTestFilesFromGraph(sourceFiles);
34840
34850
  if (graphTestFiles.length > 0) {
34841
34851
  testFiles = graphTestFiles;
@@ -34845,6 +34855,27 @@ var test_runner = createSwarmTool({
34845
34855
  testFiles = getTestFilesFromConvention(sourceFiles);
34846
34856
  }
34847
34857
  }
34858
+ if (testFiles.length === 0) {
34859
+ const errorResult = {
34860
+ success: false,
34861
+ framework,
34862
+ scope: effectiveScope,
34863
+ error: "Provided source files resolved to zero test files",
34864
+ message: "No matching test files found for the provided source files. Check that test files exist with matching naming conventions (.spec.*, .test.*, __tests__/, tests/, test/)."
34865
+ };
34866
+ return JSON.stringify(errorResult, null, 2);
34867
+ }
34868
+ if (testFiles.length > MAX_SAFE_TEST_FILES) {
34869
+ const sampleFiles = testFiles.slice(0, 5);
34870
+ const errorResult = {
34871
+ success: false,
34872
+ framework,
34873
+ scope: effectiveScope,
34874
+ error: `Resolved test file count (${testFiles.length}) exceeds safe maximum (${MAX_SAFE_TEST_FILES})`,
34875
+ message: `Too many test files resolved (${testFiles.length}). Maximum allowed is ${MAX_SAFE_TEST_FILES}. Provide more specific source files to narrow down test scope. First few resolved: ${sampleFiles.join(", ")}`
34876
+ };
34877
+ return JSON.stringify(errorResult, null, 2);
34878
+ }
34848
34879
  const result = await runTests(framework, effectiveScope, testFiles, coverage, timeout_ms, workingDir);
34849
34880
  if (graphFallbackReason && result.message) {
34850
34881
  result.message = `${result.message} (${graphFallbackReason})`;
package/dist/index.js CHANGED
@@ -35070,36 +35070,7 @@ async function runTests(framework, scope, files, coverage, timeout_ms, cwd) {
35070
35070
  };
35071
35071
  }
35072
35072
  }
35073
- function findSourceFiles(dir, files = []) {
35074
- let entries;
35075
- try {
35076
- entries = fs10.readdirSync(dir);
35077
- } catch {
35078
- return files;
35079
- }
35080
- entries.sort((a, b) => a.toLowerCase().localeCompare(b.toLowerCase()));
35081
- for (const entry of entries) {
35082
- if (SKIP_DIRECTORIES.has(entry))
35083
- continue;
35084
- const fullPath = path22.join(dir, entry);
35085
- let stat2;
35086
- try {
35087
- stat2 = fs10.statSync(fullPath);
35088
- } catch {
35089
- continue;
35090
- }
35091
- if (stat2.isDirectory()) {
35092
- findSourceFiles(fullPath, files);
35093
- } else if (stat2.isFile()) {
35094
- const ext = path22.extname(fullPath).toLowerCase();
35095
- if (SOURCE_EXTENSIONS.has(ext)) {
35096
- files.push(fullPath);
35097
- }
35098
- }
35099
- }
35100
- return files;
35101
- }
35102
- var MAX_OUTPUT_BYTES3 = 512000, MAX_COMMAND_LENGTH2 = 500, DEFAULT_TIMEOUT_MS = 60000, MAX_TIMEOUT_MS = 300000, POWERSHELL_METACHARACTERS, TEST_PATTERNS, COMPOUND_TEST_EXTENSIONS, SOURCE_EXTENSIONS, SKIP_DIRECTORIES, test_runner;
35073
+ var MAX_OUTPUT_BYTES3 = 512000, MAX_COMMAND_LENGTH2 = 500, DEFAULT_TIMEOUT_MS = 60000, MAX_TIMEOUT_MS = 300000, MAX_SAFE_TEST_FILES = 50, POWERSHELL_METACHARACTERS, TEST_PATTERNS, COMPOUND_TEST_EXTENSIONS, SOURCE_EXTENSIONS, SKIP_DIRECTORIES, test_runner;
35103
35074
  var init_test_runner = __esm(() => {
35104
35075
  init_dist();
35105
35076
  init_discovery();
@@ -35233,6 +35204,26 @@ var init_test_runner = __esm(() => {
35233
35204
  return JSON.stringify(errorResult, null, 2);
35234
35205
  }
35235
35206
  const scope = args2.scope || "all";
35207
+ if (scope === "all") {
35208
+ const errorResult = {
35209
+ success: false,
35210
+ framework: "none",
35211
+ scope: "all",
35212
+ error: 'Full-suite test execution (scope: "all") is prohibited in interactive sessions',
35213
+ message: 'Use scope "convention" or "graph" with explicit files to run targeted tests in interactive mode. Full-suite runs are restricted to prevent excessive resource consumption.'
35214
+ };
35215
+ return JSON.stringify(errorResult, null, 2);
35216
+ }
35217
+ if ((scope === "convention" || scope === "graph") && (!args2.files || args2.files.length === 0)) {
35218
+ const errorResult = {
35219
+ success: false,
35220
+ framework: "none",
35221
+ scope,
35222
+ error: 'scope "convention" and "graph" require explicit files array - omitting files causes unsafe full-project discovery',
35223
+ message: 'When using scope "convention" or "graph", you must provide a non-empty "files" array. Use scope "all" for full project test suite without specifying files.'
35224
+ };
35225
+ return JSON.stringify(errorResult, null, 2);
35226
+ }
35236
35227
  const _files = args2.files || [];
35237
35228
  const coverage = args2.coverage || false;
35238
35229
  const timeout_ms = Math.min(args2.timeout_ms || DEFAULT_TIMEOUT_MS, MAX_TIMEOUT_MS);
@@ -35256,19 +35247,37 @@ var init_test_runner = __esm(() => {
35256
35247
  let testFiles = [];
35257
35248
  let graphFallbackReason;
35258
35249
  let effectiveScope = scope;
35259
- if (scope === "all") {
35260
- testFiles = [];
35261
- } else if (scope === "convention") {
35262
- const sourceFiles = args2.files && args2.files.length > 0 ? args2.files.filter((f) => {
35250
+ if (scope === "convention") {
35251
+ const sourceFiles = args2.files.filter((f) => {
35263
35252
  const ext = path22.extname(f).toLowerCase();
35264
35253
  return SOURCE_EXTENSIONS.has(ext);
35265
- }) : findSourceFiles(workingDir);
35254
+ });
35255
+ if (sourceFiles.length === 0) {
35256
+ const errorResult = {
35257
+ success: false,
35258
+ framework,
35259
+ scope,
35260
+ error: "Provided files contain no source files with recognized extensions",
35261
+ message: "The files array must contain at least one source file with a recognized extension (.ts, .tsx, .js, .jsx, .py, .rs, .ps1, etc.). Non-source files like README.md or config.json are not valid for test discovery."
35262
+ };
35263
+ return JSON.stringify(errorResult, null, 2);
35264
+ }
35266
35265
  testFiles = getTestFilesFromConvention(sourceFiles);
35267
35266
  } else if (scope === "graph") {
35268
- const sourceFiles = args2.files && args2.files.length > 0 ? args2.files.filter((f) => {
35267
+ const sourceFiles = args2.files.filter((f) => {
35269
35268
  const ext = path22.extname(f).toLowerCase();
35270
35269
  return SOURCE_EXTENSIONS.has(ext);
35271
- }) : findSourceFiles(workingDir);
35270
+ });
35271
+ if (sourceFiles.length === 0) {
35272
+ const errorResult = {
35273
+ success: false,
35274
+ framework,
35275
+ scope,
35276
+ error: "Provided files contain no source files with recognized extensions",
35277
+ message: "The files array must contain at least one source file with a recognized extension (.ts, .tsx, .js, .jsx, .py, .rs, .ps1, etc.). Non-source files like README.md or config.json are not valid for test discovery."
35278
+ };
35279
+ return JSON.stringify(errorResult, null, 2);
35280
+ }
35272
35281
  const graphTestFiles = await getTestFilesFromGraph(sourceFiles);
35273
35282
  if (graphTestFiles.length > 0) {
35274
35283
  testFiles = graphTestFiles;
@@ -35278,6 +35287,27 @@ var init_test_runner = __esm(() => {
35278
35287
  testFiles = getTestFilesFromConvention(sourceFiles);
35279
35288
  }
35280
35289
  }
35290
+ if (testFiles.length === 0) {
35291
+ const errorResult = {
35292
+ success: false,
35293
+ framework,
35294
+ scope: effectiveScope,
35295
+ error: "Provided source files resolved to zero test files",
35296
+ message: "No matching test files found for the provided source files. Check that test files exist with matching naming conventions (.spec.*, .test.*, __tests__/, tests/, test/)."
35297
+ };
35298
+ return JSON.stringify(errorResult, null, 2);
35299
+ }
35300
+ if (testFiles.length > MAX_SAFE_TEST_FILES) {
35301
+ const sampleFiles = testFiles.slice(0, 5);
35302
+ const errorResult = {
35303
+ success: false,
35304
+ framework,
35305
+ scope: effectiveScope,
35306
+ error: `Resolved test file count (${testFiles.length}) exceeds safe maximum (${MAX_SAFE_TEST_FILES})`,
35307
+ message: `Too many test files resolved (${testFiles.length}). Maximum allowed is ${MAX_SAFE_TEST_FILES}. Provide more specific source files to narrow down test scope. First few resolved: ${sampleFiles.join(", ")}`
35308
+ };
35309
+ return JSON.stringify(errorResult, null, 2);
35310
+ }
35281
35311
  const result = await runTests(framework, effectiveScope, testFiles, coverage, timeout_ms, workingDir);
35282
35312
  if (graphFallbackReason && result.message) {
35283
35313
  result.message = `${result.message} (${graphFallbackReason})`;
@@ -41343,12 +41373,30 @@ When tests fail:
41343
41373
  OUTPUT FORMAT (MANDATORY \u2014 deviations will be rejected):
41344
41374
  Begin directly with the VERDICT line. Do NOT prepend "Here's my analysis..." or any conversational preamble.
41345
41375
 
41346
- VERDICT: PASS [N/N tests passed] | FAIL [N passed, M failed]
41347
- TESTS: [total count] tests, [pass count] passed, [fail count] failed
41376
+ VERDICT: PASS [N/N tests passed] | FAIL [N passed, M failed] | SKIPPED [reason]
41377
+ TESTS: [total count] tests, [pass count] passed, [fail count] failed, [skip count] skipped
41348
41378
  FAILURES: [list of failed test names + error messages, if any]
41349
41379
  COVERAGE: [X]% of public functions \u2014 [areas covered]
41350
41380
  BUGS FOUND: [list any source code bugs discovered during testing, or "none"]
41351
41381
 
41382
+ ## SKIP CONDITIONS
41383
+
41384
+ Use \`VERDICT: SKIPPED [reason]\` when tests CANNOT be executed due to environment or configuration issues \u2014 NOT when tests can run but fail. SKIPPED is not a bypass to avoid reporting real failures.
41385
+
41386
+ SKIP CONDITIONS (any of these justifies SKIPPED):
41387
+ 1. PROHIBITED SCOPE: test_runner refuses scope: "all" \u2014 this is blocked for safety
41388
+ 2. EXCESSIVE FILE COUNT: resolved test file count exceeds safe threshold (exceeds MAX_FILES limit)
41389
+ 3. FRAMEWORK DETECTION NONE: test_runner reports framework detection returns "none"
41390
+ 4. MISSING TEST FILE: test file does not exist after write (write failed or path error)
41391
+ 5. SESSION INSTABILITY: timeout, spawn failure, or runner crash that prevents execution
41392
+
41393
+ SKIPPED is NOT appropriate when:
41394
+ - Tests exist and can run but produce failures (use FAIL verdict)
41395
+ - Tests pass but coverage is low (use PASS verdict, note coverage warning)
41396
+ - You chose not to write tests (write them or explain why impossible)
41397
+
41398
+ When reporting SKIPPED, include the specific reason from the conditions above.
41399
+
41352
41400
  COVERAGE REPORTING:
41353
41401
  - After running tests, report the line/branch coverage percentage if the test runner provides it.
41354
41402
  - Format: COVERAGE_PCT: [N]% (or "N/A" if not available)
@@ -54848,7 +54896,7 @@ var SKIP_DIRECTORIES2 = new Set([
54848
54896
  ".svn",
54849
54897
  ".hg"
54850
54898
  ]);
54851
- function findSourceFiles2(dir, files = [], stats = { skippedDirs: [], skippedFiles: 0, fileErrors: [] }) {
54899
+ function findSourceFiles(dir, files = [], stats = { skippedDirs: [], skippedFiles: 0, fileErrors: [] }) {
54852
54900
  let entries;
54853
54901
  try {
54854
54902
  entries = fs25.readdirSync(dir);
@@ -54877,7 +54925,7 @@ function findSourceFiles2(dir, files = [], stats = { skippedDirs: [], skippedFil
54877
54925
  continue;
54878
54926
  }
54879
54927
  if (stat2.isDirectory()) {
54880
- findSourceFiles2(fullPath, files, stats);
54928
+ findSourceFiles(fullPath, files, stats);
54881
54929
  } else if (stat2.isFile()) {
54882
54930
  const ext = path38.extname(fullPath).toLowerCase();
54883
54931
  if (SUPPORTED_EXTENSIONS.includes(ext)) {
@@ -54963,7 +55011,7 @@ var imports = tool({
54963
55011
  skippedFiles: 0,
54964
55012
  fileErrors: []
54965
55013
  };
54966
- const sourceFiles = findSourceFiles2(baseDir, [], scanStats);
55014
+ const sourceFiles = findSourceFiles(baseDir, [], scanStats);
54967
55015
  const filesToScan = sourceFiles.filter((f) => f !== targetFile).sort((a, b) => a.toLowerCase().localeCompare(b.toLowerCase())).slice(0, MAX_CONSUMERS * 10);
54968
55016
  const consumers = [];
54969
55017
  let skippedFileCount = 0;
@@ -61440,7 +61488,7 @@ function isSupportedExtension(filePath) {
61440
61488
  const ext = path48.extname(filePath).toLowerCase();
61441
61489
  return SUPPORTED_EXTENSIONS2.has(ext);
61442
61490
  }
61443
- function findSourceFiles3(dir, files = []) {
61491
+ function findSourceFiles2(dir, files = []) {
61444
61492
  let entries;
61445
61493
  try {
61446
61494
  entries = fs35.readdirSync(dir);
@@ -61460,7 +61508,7 @@ function findSourceFiles3(dir, files = []) {
61460
61508
  continue;
61461
61509
  }
61462
61510
  if (stat2.isDirectory()) {
61463
- findSourceFiles3(fullPath, files);
61511
+ findSourceFiles2(fullPath, files);
61464
61512
  } else if (stat2.isFile()) {
61465
61513
  if (isSupportedExtension(fullPath)) {
61466
61514
  files.push(fullPath);
@@ -61572,7 +61620,7 @@ var todo_extract = createSwarmTool({
61572
61620
  return JSON.stringify(errorResult, null, 2);
61573
61621
  }
61574
61622
  } else {
61575
- findSourceFiles3(scanPath, filesToScan);
61623
+ findSourceFiles2(scanPath, filesToScan);
61576
61624
  }
61577
61625
  const allEntries = [];
61578
61626
  for (const filePath of filesToScan) {
@@ -3,6 +3,7 @@ export declare const MAX_OUTPUT_BYTES = 512000;
3
3
  export declare const MAX_COMMAND_LENGTH = 500;
4
4
  export declare const DEFAULT_TIMEOUT_MS = 60000;
5
5
  export declare const MAX_TIMEOUT_MS = 300000;
6
+ export declare const MAX_SAFE_TEST_FILES = 50;
6
7
  export declare const SUPPORTED_FRAMEWORKS: readonly ["bun", "vitest", "jest", "mocha", "pytest", "cargo", "pester", "go-test", "maven", "gradle", "dotnet-test", "ctest", "swift-test", "dart-test", "rspec", "minitest"];
7
8
  export type TestFramework = (typeof SUPPORTED_FRAMEWORKS)[number] | 'none';
8
9
  export interface TestRunnerArgs {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-swarm",
3
- "version": "6.25.3",
3
+ "version": "6.25.4",
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",