opencode-swarm 7.50.0 → 7.50.2

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.50.0",
55
+ version: "7.50.2",
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",
@@ -52414,10 +52414,33 @@ function classifyAndCluster(testResults, history) {
52414
52414
  }
52415
52415
 
52416
52416
  // src/test-impact/flaky-detector.ts
52417
+ function computeCombinedFlakyScore(recent) {
52418
+ const totalRuns = recent.length;
52419
+ if (totalRuns < 2) {
52420
+ return { alternationCount: 0, flakyScore: 0 };
52421
+ }
52422
+ let alternationCount = 0;
52423
+ let passCount = 0;
52424
+ for (let i = 0;i < recent.length; i++) {
52425
+ if (recent[i].result === "pass") {
52426
+ passCount++;
52427
+ }
52428
+ if (i > 0 && recent[i].result !== recent[i - 1].result) {
52429
+ alternationCount++;
52430
+ }
52431
+ }
52432
+ const alternationScore = alternationCount / totalRuns;
52433
+ const passRate = passCount / totalRuns;
52434
+ const passRateVarianceScore = 4 * passRate * (1 - passRate);
52435
+ return {
52436
+ alternationCount,
52437
+ flakyScore: (alternationScore + passRateVarianceScore) / 2
52438
+ };
52439
+ }
52417
52440
  function detectFlakyTests(allHistory) {
52418
52441
  const grouped = new Map;
52419
52442
  for (const record3 of allHistory) {
52420
- const key = `${record3.testFile.toLowerCase()}|${record3.testName.toLowerCase()}`;
52443
+ const key = `${record3.testFile.toLowerCase()}\x00${record3.testName.toLowerCase()}`;
52421
52444
  if (!grouped.has(key)) {
52422
52445
  grouped.set(key, {
52423
52446
  records: [],
@@ -52439,13 +52462,7 @@ function detectFlakyTests(allHistory) {
52439
52462
  if (totalRuns < 2) {
52440
52463
  continue;
52441
52464
  }
52442
- let alternationCount = 0;
52443
- for (let i = 1;i < recent.length; i++) {
52444
- if (recent[i].result !== recent[i - 1].result) {
52445
- alternationCount++;
52446
- }
52447
- }
52448
- const flakyScore = totalRuns >= 2 ? alternationCount / totalRuns : 0;
52465
+ const { alternationCount, flakyScore } = computeCombinedFlakyScore(recent);
52449
52466
  const isQuarantined = flakyScore > FLAKY_THRESHOLD && totalRuns >= MIN_RUNS_FOR_QUARANTINE;
52450
52467
  const recentResults = recent.map((r) => r.result);
52451
52468
  const testFile = entry.originalFile;
@@ -52577,47 +52594,90 @@ function batchAppendTestRuns(records, workingDir) {
52577
52594
  if (!fs19.existsSync(historyDir)) {
52578
52595
  fs19.mkdirSync(historyDir, { recursive: true });
52579
52596
  }
52580
- const existingRecords = readAllRecords(historyPath);
52581
- const sanitizedRecords = records.map((record3) => ({
52582
- ...record3,
52583
- timestamp: record3.timestamp || new Date().toISOString(),
52584
- durationMs: Math.max(0, record3.durationMs),
52585
- errorMessage: sanitizeErrorMessage(record3.errorMessage),
52586
- stackPrefix: sanitizeStackPrefix(record3.stackPrefix),
52587
- changedFiles: sanitizeChangedFiles(record3.changedFiles || [])
52588
- }));
52589
- existingRecords.push(...sanitizedRecords);
52590
- const recordsByTest = new Map;
52591
- for (const rec of existingRecords) {
52592
- const normalizedKey = `${rec.testFile.toLowerCase()}|${rec.testName.toLowerCase()}`;
52593
- if (!recordsByTest.has(normalizedKey)) {
52594
- recordsByTest.set(normalizedKey, []);
52595
- }
52596
- recordsByTest.get(normalizedKey).push(rec);
52597
- }
52598
- const prunedRecords = [];
52599
- for (const [, recs] of recordsByTest) {
52600
- recs.sort((a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime());
52601
- const toKeep = recs.slice(-MAX_HISTORY_PER_TEST);
52602
- prunedRecords.push(...toKeep);
52603
- }
52604
- prunedRecords.sort((a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime());
52605
- try {
52606
- const lines = prunedRecords.map((rec) => JSON.stringify(rec));
52607
- const content = `${lines.join(`
52597
+ withHistoryWriteLock(historyPath, () => {
52598
+ const existingRecords = readAllRecords(historyPath);
52599
+ const sanitizedRecords = records.map((record3) => ({
52600
+ ...record3,
52601
+ timestamp: record3.timestamp || new Date().toISOString(),
52602
+ durationMs: Math.max(0, record3.durationMs),
52603
+ errorMessage: sanitizeErrorMessage(record3.errorMessage),
52604
+ stackPrefix: sanitizeStackPrefix(record3.stackPrefix),
52605
+ changedFiles: sanitizeChangedFiles(record3.changedFiles || [])
52606
+ }));
52607
+ existingRecords.push(...sanitizedRecords);
52608
+ const recordsByTest = new Map;
52609
+ for (const rec of existingRecords) {
52610
+ const normalizedKey = `${rec.testFile.toLowerCase()}|${rec.testName.toLowerCase()}`;
52611
+ if (!recordsByTest.has(normalizedKey)) {
52612
+ recordsByTest.set(normalizedKey, []);
52613
+ }
52614
+ recordsByTest.get(normalizedKey).push(rec);
52615
+ }
52616
+ const prunedRecords = [];
52617
+ for (const [, recs] of recordsByTest) {
52618
+ recs.sort((a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime());
52619
+ const toKeep = recs.slice(-MAX_HISTORY_PER_TEST);
52620
+ prunedRecords.push(...toKeep);
52621
+ }
52622
+ prunedRecords.sort((a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime());
52623
+ try {
52624
+ const lines = prunedRecords.map((rec) => JSON.stringify(rec));
52625
+ const content = `${lines.join(`
52608
52626
  `)}
52609
52627
  `;
52610
- const tempPath = `${historyPath}.tmp`;
52611
- fs19.writeFileSync(tempPath, content, "utf-8");
52612
- fs19.renameSync(tempPath, historyPath);
52613
- } catch (err) {
52614
- try {
52615
52628
  const tempPath = `${historyPath}.tmp`;
52616
- if (fs19.existsSync(tempPath)) {
52617
- fs19.unlinkSync(tempPath);
52629
+ fs19.writeFileSync(tempPath, content, "utf-8");
52630
+ fs19.renameSync(tempPath, historyPath);
52631
+ } catch (err) {
52632
+ try {
52633
+ const tempPath = `${historyPath}.tmp`;
52634
+ if (fs19.existsSync(tempPath)) {
52635
+ fs19.unlinkSync(tempPath);
52636
+ }
52637
+ } catch {}
52638
+ throw new Error(`Failed to write test history: ${err instanceof Error ? err.message : String(err)}`);
52639
+ }
52640
+ });
52641
+ }
52642
+ function withHistoryWriteLock(historyPath, fn) {
52643
+ const lockPath = `${historyPath}.write-lock`;
52644
+ const deadline = Date.now() + HISTORY_WRITE_LOCK_TIMEOUT_MS;
52645
+ while (true) {
52646
+ try {
52647
+ fs19.mkdirSync(lockPath);
52648
+ break;
52649
+ } catch (error93) {
52650
+ const code = error93 instanceof Error && "code" in error93 ? error93.code : undefined;
52651
+ if (code !== "EEXIST") {
52652
+ throw new Error(`Failed to acquire test history lock: ${error93 instanceof Error ? error93.message : String(error93)}`);
52653
+ }
52654
+ if (Date.now() >= deadline) {
52655
+ throw new Error(`Timed out waiting for test history lock: ${historyPath}`);
52618
52656
  }
52657
+ try {
52658
+ const lockStat = fs19.statSync(lockPath);
52659
+ if (Date.now() - lockStat.mtimeMs >= HISTORY_WRITE_LOCK_STALE_MS) {
52660
+ fs19.rmSync(lockPath, { recursive: true, force: true });
52661
+ continue;
52662
+ }
52663
+ } catch {}
52664
+ const remainingMs = Math.max(0, deadline - Date.now());
52665
+ const sleepMs = Math.min(HISTORY_WRITE_LOCK_BACKOFF_MS, remainingMs);
52666
+ if (sleepMs > 0) {
52667
+ sleepSync(sleepMs);
52668
+ }
52669
+ }
52670
+ }
52671
+ try {
52672
+ return fn();
52673
+ } finally {
52674
+ try {
52675
+ fs19.rmSync(lockPath, { recursive: true, force: true });
52619
52676
  } catch {}
52620
- throw new Error(`Failed to write test history: ${err instanceof Error ? err.message : String(err)}`);
52677
+ }
52678
+ function sleepSync(ms) {
52679
+ const until = Date.now() + ms;
52680
+ while (Date.now() < until) {}
52621
52681
  }
52622
52682
  }
52623
52683
  function readAllRecords(historyPath) {
@@ -52653,7 +52713,7 @@ function getAllHistory(workingDir) {
52653
52713
  records.sort((a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime());
52654
52714
  return records;
52655
52715
  }
52656
- var MAX_HISTORY_PER_TEST = 20, MAX_ERROR_LENGTH = 500, MAX_STACK_LENGTH = 200, MAX_CHANGED_FILES = 50, DANGEROUS_PROPERTY_NAMES, _internals26;
52716
+ var MAX_HISTORY_PER_TEST = 20, MAX_ERROR_LENGTH = 500, MAX_STACK_LENGTH = 200, MAX_CHANGED_FILES = 50, HISTORY_WRITE_LOCK_TIMEOUT_MS = 5000, HISTORY_WRITE_LOCK_STALE_MS = 60000, HISTORY_WRITE_LOCK_BACKOFF_MS = 10, DANGEROUS_PROPERTY_NAMES, _internals26;
52657
52717
  var init_history_store = __esm(() => {
52658
52718
  init_manager2();
52659
52719
  DANGEROUS_PROPERTY_NAMES = new Set([
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.50.0",
72
+ version: "7.50.2",
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",
@@ -75784,10 +75784,33 @@ function classifyAndCluster(testResults, history) {
75784
75784
  }
75785
75785
 
75786
75786
  // src/test-impact/flaky-detector.ts
75787
+ function computeCombinedFlakyScore(recent) {
75788
+ const totalRuns = recent.length;
75789
+ if (totalRuns < 2) {
75790
+ return { alternationCount: 0, flakyScore: 0 };
75791
+ }
75792
+ let alternationCount = 0;
75793
+ let passCount = 0;
75794
+ for (let i2 = 0;i2 < recent.length; i2++) {
75795
+ if (recent[i2].result === "pass") {
75796
+ passCount++;
75797
+ }
75798
+ if (i2 > 0 && recent[i2].result !== recent[i2 - 1].result) {
75799
+ alternationCount++;
75800
+ }
75801
+ }
75802
+ const alternationScore = alternationCount / totalRuns;
75803
+ const passRate = passCount / totalRuns;
75804
+ const passRateVarianceScore = 4 * passRate * (1 - passRate);
75805
+ return {
75806
+ alternationCount,
75807
+ flakyScore: (alternationScore + passRateVarianceScore) / 2
75808
+ };
75809
+ }
75787
75810
  function detectFlakyTests(allHistory) {
75788
75811
  const grouped = new Map;
75789
75812
  for (const record3 of allHistory) {
75790
- const key = `${record3.testFile.toLowerCase()}|${record3.testName.toLowerCase()}`;
75813
+ const key = `${record3.testFile.toLowerCase()}\x00${record3.testName.toLowerCase()}`;
75791
75814
  if (!grouped.has(key)) {
75792
75815
  grouped.set(key, {
75793
75816
  records: [],
@@ -75809,13 +75832,7 @@ function detectFlakyTests(allHistory) {
75809
75832
  if (totalRuns < 2) {
75810
75833
  continue;
75811
75834
  }
75812
- let alternationCount = 0;
75813
- for (let i2 = 1;i2 < recent.length; i2++) {
75814
- if (recent[i2].result !== recent[i2 - 1].result) {
75815
- alternationCount++;
75816
- }
75817
- }
75818
- const flakyScore = totalRuns >= 2 ? alternationCount / totalRuns : 0;
75835
+ const { alternationCount, flakyScore } = computeCombinedFlakyScore(recent);
75819
75836
  const isQuarantined = flakyScore > FLAKY_THRESHOLD && totalRuns >= MIN_RUNS_FOR_QUARANTINE;
75820
75837
  const recentResults = recent.map((r) => r.result);
75821
75838
  const testFile = entry.originalFile;
@@ -75947,47 +75964,90 @@ function batchAppendTestRuns(records, workingDir) {
75947
75964
  if (!fs29.existsSync(historyDir)) {
75948
75965
  fs29.mkdirSync(historyDir, { recursive: true });
75949
75966
  }
75950
- const existingRecords = readAllRecords(historyPath);
75951
- const sanitizedRecords = records.map((record3) => ({
75952
- ...record3,
75953
- timestamp: record3.timestamp || new Date().toISOString(),
75954
- durationMs: Math.max(0, record3.durationMs),
75955
- errorMessage: sanitizeErrorMessage(record3.errorMessage),
75956
- stackPrefix: sanitizeStackPrefix(record3.stackPrefix),
75957
- changedFiles: sanitizeChangedFiles(record3.changedFiles || [])
75958
- }));
75959
- existingRecords.push(...sanitizedRecords);
75960
- const recordsByTest = new Map;
75961
- for (const rec of existingRecords) {
75962
- const normalizedKey = `${rec.testFile.toLowerCase()}|${rec.testName.toLowerCase()}`;
75963
- if (!recordsByTest.has(normalizedKey)) {
75964
- recordsByTest.set(normalizedKey, []);
75965
- }
75966
- recordsByTest.get(normalizedKey).push(rec);
75967
- }
75968
- const prunedRecords = [];
75969
- for (const [, recs] of recordsByTest) {
75970
- recs.sort((a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime());
75971
- const toKeep = recs.slice(-MAX_HISTORY_PER_TEST);
75972
- prunedRecords.push(...toKeep);
75973
- }
75974
- prunedRecords.sort((a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime());
75975
- try {
75976
- const lines = prunedRecords.map((rec) => JSON.stringify(rec));
75977
- const content = `${lines.join(`
75967
+ withHistoryWriteLock(historyPath, () => {
75968
+ const existingRecords = readAllRecords(historyPath);
75969
+ const sanitizedRecords = records.map((record3) => ({
75970
+ ...record3,
75971
+ timestamp: record3.timestamp || new Date().toISOString(),
75972
+ durationMs: Math.max(0, record3.durationMs),
75973
+ errorMessage: sanitizeErrorMessage(record3.errorMessage),
75974
+ stackPrefix: sanitizeStackPrefix(record3.stackPrefix),
75975
+ changedFiles: sanitizeChangedFiles(record3.changedFiles || [])
75976
+ }));
75977
+ existingRecords.push(...sanitizedRecords);
75978
+ const recordsByTest = new Map;
75979
+ for (const rec of existingRecords) {
75980
+ const normalizedKey = `${rec.testFile.toLowerCase()}|${rec.testName.toLowerCase()}`;
75981
+ if (!recordsByTest.has(normalizedKey)) {
75982
+ recordsByTest.set(normalizedKey, []);
75983
+ }
75984
+ recordsByTest.get(normalizedKey).push(rec);
75985
+ }
75986
+ const prunedRecords = [];
75987
+ for (const [, recs] of recordsByTest) {
75988
+ recs.sort((a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime());
75989
+ const toKeep = recs.slice(-MAX_HISTORY_PER_TEST);
75990
+ prunedRecords.push(...toKeep);
75991
+ }
75992
+ prunedRecords.sort((a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime());
75993
+ try {
75994
+ const lines = prunedRecords.map((rec) => JSON.stringify(rec));
75995
+ const content = `${lines.join(`
75978
75996
  `)}
75979
75997
  `;
75980
- const tempPath = `${historyPath}.tmp`;
75981
- fs29.writeFileSync(tempPath, content, "utf-8");
75982
- fs29.renameSync(tempPath, historyPath);
75983
- } catch (err2) {
75984
- try {
75985
75998
  const tempPath = `${historyPath}.tmp`;
75986
- if (fs29.existsSync(tempPath)) {
75987
- fs29.unlinkSync(tempPath);
75999
+ fs29.writeFileSync(tempPath, content, "utf-8");
76000
+ fs29.renameSync(tempPath, historyPath);
76001
+ } catch (err2) {
76002
+ try {
76003
+ const tempPath = `${historyPath}.tmp`;
76004
+ if (fs29.existsSync(tempPath)) {
76005
+ fs29.unlinkSync(tempPath);
76006
+ }
76007
+ } catch {}
76008
+ throw new Error(`Failed to write test history: ${err2 instanceof Error ? err2.message : String(err2)}`);
76009
+ }
76010
+ });
76011
+ }
76012
+ function withHistoryWriteLock(historyPath, fn2) {
76013
+ const lockPath = `${historyPath}.write-lock`;
76014
+ const deadline = Date.now() + HISTORY_WRITE_LOCK_TIMEOUT_MS;
76015
+ while (true) {
76016
+ try {
76017
+ fs29.mkdirSync(lockPath);
76018
+ break;
76019
+ } catch (error93) {
76020
+ const code = error93 instanceof Error && "code" in error93 ? error93.code : undefined;
76021
+ if (code !== "EEXIST") {
76022
+ throw new Error(`Failed to acquire test history lock: ${error93 instanceof Error ? error93.message : String(error93)}`);
76023
+ }
76024
+ if (Date.now() >= deadline) {
76025
+ throw new Error(`Timed out waiting for test history lock: ${historyPath}`);
76026
+ }
76027
+ try {
76028
+ const lockStat = fs29.statSync(lockPath);
76029
+ if (Date.now() - lockStat.mtimeMs >= HISTORY_WRITE_LOCK_STALE_MS) {
76030
+ fs29.rmSync(lockPath, { recursive: true, force: true });
76031
+ continue;
76032
+ }
76033
+ } catch {}
76034
+ const remainingMs = Math.max(0, deadline - Date.now());
76035
+ const sleepMs = Math.min(HISTORY_WRITE_LOCK_BACKOFF_MS, remainingMs);
76036
+ if (sleepMs > 0) {
76037
+ sleepSync(sleepMs);
75988
76038
  }
76039
+ }
76040
+ }
76041
+ try {
76042
+ return fn2();
76043
+ } finally {
76044
+ try {
76045
+ fs29.rmSync(lockPath, { recursive: true, force: true });
75989
76046
  } catch {}
75990
- throw new Error(`Failed to write test history: ${err2 instanceof Error ? err2.message : String(err2)}`);
76047
+ }
76048
+ function sleepSync(ms) {
76049
+ const until = Date.now() + ms;
76050
+ while (Date.now() < until) {}
75991
76051
  }
75992
76052
  }
75993
76053
  function readAllRecords(historyPath) {
@@ -76023,7 +76083,7 @@ function getAllHistory(workingDir) {
76023
76083
  records.sort((a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime());
76024
76084
  return records;
76025
76085
  }
76026
- var MAX_HISTORY_PER_TEST = 20, MAX_ERROR_LENGTH = 500, MAX_STACK_LENGTH = 200, MAX_CHANGED_FILES = 50, DANGEROUS_PROPERTY_NAMES, _internals36;
76086
+ var MAX_HISTORY_PER_TEST = 20, MAX_ERROR_LENGTH = 500, MAX_STACK_LENGTH = 200, MAX_CHANGED_FILES = 50, HISTORY_WRITE_LOCK_TIMEOUT_MS = 5000, HISTORY_WRITE_LOCK_STALE_MS = 60000, HISTORY_WRITE_LOCK_BACKOFF_MS = 10, DANGEROUS_PROPERTY_NAMES, _internals36;
76027
76087
  var init_history_store = __esm(() => {
76028
76088
  init_manager2();
76029
76089
  DANGEROUS_PROPERTY_NAMES = new Set([
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-swarm",
3
- "version": "7.50.0",
3
+ "version": "7.50.2",
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",