opencode-swarm 6.68.0 → 6.68.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.
Files changed (54) hide show
  1. package/dist/cli/index.js +844 -162
  2. package/dist/diff/__tests__/semantic-classifier.test.d.ts +1 -0
  3. package/dist/diff/__tests__/summary-generator.test.d.ts +1 -0
  4. package/dist/diff/semantic-classifier.d.ts +55 -0
  5. package/dist/diff/summary-generator.d.ts +33 -0
  6. package/dist/index.js +2389 -833
  7. package/dist/mutation/__tests__/engine.adversarial.test.d.ts +1 -0
  8. package/dist/mutation/__tests__/engine.test.d.ts +1 -0
  9. package/dist/mutation/__tests__/equivalence.adversarial.test.d.ts +1 -0
  10. package/dist/mutation/__tests__/equivalence.test.d.ts +1 -0
  11. package/dist/mutation/__tests__/gate.adversarial.test.d.ts +1 -0
  12. package/dist/mutation/__tests__/gate.test.d.ts +1 -0
  13. package/dist/mutation/engine.d.ts +47 -0
  14. package/dist/mutation/equivalence.d.ts +35 -0
  15. package/dist/mutation/gate.d.ts +28 -0
  16. package/dist/test-impact/__tests__/analyzer-import-fix.adversarial.test.d.ts +1 -0
  17. package/dist/test-impact/__tests__/analyzer-import-fix.test.d.ts +1 -0
  18. package/dist/test-impact/__tests__/analyzer.adversarial.test.d.ts +1 -0
  19. package/dist/test-impact/__tests__/analyzer.test.d.ts +1 -0
  20. package/dist/test-impact/__tests__/council-fixes.test.d.ts +1 -0
  21. package/dist/test-impact/__tests__/failure-classifier.adversarial.test.d.ts +1 -0
  22. package/dist/test-impact/__tests__/failure-classifier.test.d.ts +1 -0
  23. package/dist/test-impact/__tests__/flaky-detector.adversarial.test.d.ts +1 -0
  24. package/dist/test-impact/__tests__/flaky-detector.test.d.ts +1 -0
  25. package/dist/test-impact/__tests__/history-store.adversarial.test.d.ts +1 -0
  26. package/dist/test-impact/__tests__/history-store.test.d.ts +1 -0
  27. package/dist/test-impact/__tests__/test-impact.adversarial.test.d.ts +1 -0
  28. package/dist/test-impact/__tests__/test-impact.test.d.ts +1 -0
  29. package/dist/test-impact/analyzer.d.ts +9 -0
  30. package/dist/test-impact/failure-classifier.d.ts +26 -0
  31. package/dist/test-impact/flaky-detector.d.ts +14 -0
  32. package/dist/test-impact/history-store.d.ts +15 -0
  33. package/dist/tools/__tests__/barrel-exports.test.d.ts +1 -0
  34. package/dist/tools/__tests__/diff-ast-fallback.test.d.ts +1 -0
  35. package/dist/tools/__tests__/diff-markdown-summary.test.d.ts +1 -0
  36. package/dist/tools/__tests__/diff-semantic.test.d.ts +1 -0
  37. package/dist/tools/__tests__/diff-summary.adversarial.test.d.ts +1 -0
  38. package/dist/tools/__tests__/diff-summary.test.d.ts +1 -0
  39. package/dist/tools/__tests__/mutation-test.adversarial.test.d.ts +1 -0
  40. package/dist/tools/__tests__/mutation-test.sourcefiles.test.d.ts +1 -0
  41. package/dist/tools/__tests__/mutation-test.test.d.ts +1 -0
  42. package/dist/tools/__tests__/test-runner-history.test.d.ts +1 -0
  43. package/dist/tools/__tests__/test-runner-impact.adversarial.test.d.ts +1 -0
  44. package/dist/tools/__tests__/test-runner-impact.test.d.ts +1 -0
  45. package/dist/tools/__tests__/test-runner-source-files.test.d.ts +1 -0
  46. package/dist/tools/diff-summary.d.ts +12 -0
  47. package/dist/tools/diff.d.ts +3 -0
  48. package/dist/tools/index.d.ts +7 -0
  49. package/dist/tools/mutation-test.d.ts +2 -0
  50. package/dist/tools/mutation-test.security.test.d.ts +1 -0
  51. package/dist/tools/test-impact.d.ts +2 -0
  52. package/dist/tools/test-runner.d.ts +4 -4
  53. package/dist/tools/tool-names.d.ts +1 -1
  54. package/package.json +1 -1
package/dist/cli/index.js CHANGED
@@ -18314,9 +18314,9 @@ var init_evidence_summary_service = __esm(() => {
18314
18314
  });
18315
18315
 
18316
18316
  // src/cli/index.ts
18317
- import * as fs19 from "fs";
18317
+ import * as fs21 from "fs";
18318
18318
  import * as os6 from "os";
18319
- import * as path29 from "path";
18319
+ import * as path31 from "path";
18320
18320
 
18321
18321
  // src/commands/acknowledge-spec-drift.ts
18322
18322
  init_utils2();
@@ -18461,6 +18461,7 @@ init_zod();
18461
18461
  // src/tools/tool-names.ts
18462
18462
  var TOOL_NAMES = [
18463
18463
  "diff",
18464
+ "diff_summary",
18464
18465
  "syntax_check",
18465
18466
  "placeholder_scan",
18466
18467
  "imports",
@@ -18483,6 +18484,8 @@ var TOOL_NAMES = [
18483
18484
  "checkpoint",
18484
18485
  "pkg_audit",
18485
18486
  "test_runner",
18487
+ "test_impact",
18488
+ "mutation_test",
18486
18489
  "detect_domains",
18487
18490
  "gitingest",
18488
18491
  "retrieve_summary",
@@ -18548,6 +18551,7 @@ var AGENT_TOOL_MAP = {
18548
18551
  "knowledge_query",
18549
18552
  "lint",
18550
18553
  "diff",
18554
+ "diff_summary",
18551
18555
  "pkg_audit",
18552
18556
  "pre_check_batch",
18553
18557
  "quality_budget",
@@ -18559,6 +18563,8 @@ var AGENT_TOOL_MAP = {
18559
18563
  "secretscan",
18560
18564
  "symbols",
18561
18565
  "test_runner",
18566
+ "test_impact",
18567
+ "mutation_test",
18562
18568
  "todo_extract",
18563
18569
  "update_task_status",
18564
18570
  "lint_spec",
@@ -18615,6 +18621,8 @@ var AGENT_TOOL_MAP = {
18615
18621
  ],
18616
18622
  test_engineer: [
18617
18623
  "test_runner",
18624
+ "test_impact",
18625
+ "mutation_test",
18618
18626
  "diff",
18619
18627
  "symbols",
18620
18628
  "extract_code_blocks",
@@ -18638,6 +18646,7 @@ var AGENT_TOOL_MAP = {
18638
18646
  ],
18639
18647
  reviewer: [
18640
18648
  "diff",
18649
+ "diff_summary",
18641
18650
  "imports",
18642
18651
  "lint",
18643
18652
  "pkg_audit",
@@ -18648,6 +18657,7 @@ var AGENT_TOOL_MAP = {
18648
18657
  "retrieve_summary",
18649
18658
  "extract_code_blocks",
18650
18659
  "test_runner",
18660
+ "test_impact",
18651
18661
  "sast_scan",
18652
18662
  "placeholder_scan",
18653
18663
  "knowledge_recall",
@@ -38471,8 +38481,8 @@ async function handlePlanCommand(directory, args) {
38471
38481
  // src/services/preflight-service.ts
38472
38482
  init_manager2();
38473
38483
  init_manager();
38474
- import * as fs14 from "fs";
38475
- import * as path24 from "path";
38484
+ import * as fs16 from "fs";
38485
+ import * as path26 from "path";
38476
38486
 
38477
38487
  // src/tools/lint.ts
38478
38488
  import * as fs10 from "fs";
@@ -39607,12 +39617,577 @@ async function runSecretscan(directory) {
39607
39617
  }
39608
39618
 
39609
39619
  // src/tools/test-runner.ts
39610
- import * as fs13 from "fs";
39611
- import * as path23 from "path";
39620
+ import * as fs15 from "fs";
39621
+ import * as path25 from "path";
39622
+
39623
+ // src/test-impact/analyzer.ts
39624
+ import fs12 from "fs";
39625
+ import path22 from "path";
39626
+ var IMPORT_REGEX_ES = /import\s+.*?\s+from\s+['"]([^'"]+)['"]/g;
39627
+ var IMPORT_REGEX_REQUIRE = /require\s*\(\s*['"]([^'"]+)['"]\s*\)/g;
39628
+ var IMPORT_REGEX_REEXPORT = /export\s+(?:\{[^}]*\}|\*)\s+from\s+['"]([^'"]+)['"]/g;
39629
+ var EXTENSIONS_TO_TRY = [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"];
39630
+ function normalizePath(p) {
39631
+ return p.replace(/\\/g, "/");
39632
+ }
39633
+ function isCacheStale(impactMap, generatedAtMs) {
39634
+ for (const sourcePath of Object.keys(impactMap)) {
39635
+ try {
39636
+ const stat2 = fs12.statSync(sourcePath);
39637
+ if (stat2.mtimeMs > generatedAtMs) {
39638
+ return true;
39639
+ }
39640
+ } catch {
39641
+ return true;
39642
+ }
39643
+ }
39644
+ return false;
39645
+ }
39646
+ function resolveRelativeImport(fromDir, importPath) {
39647
+ if (!importPath.startsWith(".")) {
39648
+ return null;
39649
+ }
39650
+ const resolved = path22.resolve(fromDir, importPath);
39651
+ if (path22.extname(resolved)) {
39652
+ if (fs12.existsSync(resolved) && fs12.statSync(resolved).isFile()) {
39653
+ return normalizePath(resolved);
39654
+ }
39655
+ } else {
39656
+ for (const ext of EXTENSIONS_TO_TRY) {
39657
+ const withExt = resolved + ext;
39658
+ if (fs12.existsSync(withExt) && fs12.statSync(withExt).isFile()) {
39659
+ return normalizePath(withExt);
39660
+ }
39661
+ }
39662
+ }
39663
+ return null;
39664
+ }
39665
+ function findTestFilesSync(cwd) {
39666
+ const testFiles = [];
39667
+ const skipDirs = new Set([
39668
+ "node_modules",
39669
+ "dist",
39670
+ ".git",
39671
+ ".swarm",
39672
+ ".cache"
39673
+ ]);
39674
+ function walk(dir, visitedInodes) {
39675
+ let entries;
39676
+ try {
39677
+ entries = fs12.readdirSync(dir, { withFileTypes: true });
39678
+ } catch {
39679
+ return;
39680
+ }
39681
+ let dirInode;
39682
+ try {
39683
+ dirInode = fs12.statSync(dir).ino;
39684
+ } catch {
39685
+ return;
39686
+ }
39687
+ if (dirInode !== 0) {
39688
+ if (visitedInodes.has(dirInode)) {
39689
+ return;
39690
+ }
39691
+ visitedInodes.add(dirInode);
39692
+ }
39693
+ for (const entry of entries) {
39694
+ if (entry.isDirectory()) {
39695
+ if (!skipDirs.has(entry.name)) {
39696
+ walk(path22.join(dir, entry.name), visitedInodes);
39697
+ }
39698
+ } else if (entry.isFile()) {
39699
+ const name = entry.name;
39700
+ if (/\.(test|spec)\.(ts|tsx|js|jsx)$/.test(name) || dir.includes("__tests__") && /\.(ts|tsx|js|jsx)$/.test(name)) {
39701
+ testFiles.push(normalizePath(path22.join(dir, entry.name)));
39702
+ }
39703
+ }
39704
+ }
39705
+ }
39706
+ walk(cwd, new Set);
39707
+ return [...new Set(testFiles)];
39708
+ }
39709
+ function extractImports(content) {
39710
+ function execRegex(regex, content2) {
39711
+ const results = [];
39712
+ regex.lastIndex = 0;
39713
+ let match;
39714
+ while ((match = regex.exec(content2)) !== null) {
39715
+ results.push(match[1]);
39716
+ }
39717
+ return results;
39718
+ }
39719
+ return [
39720
+ ...execRegex(IMPORT_REGEX_ES, content),
39721
+ ...execRegex(IMPORT_REGEX_REQUIRE, content),
39722
+ ...execRegex(IMPORT_REGEX_REEXPORT, content)
39723
+ ];
39724
+ }
39725
+ async function buildImpactMapInternal(cwd) {
39726
+ const testFiles = findTestFilesSync(cwd);
39727
+ const impactMap = {};
39728
+ for (const testFile of testFiles) {
39729
+ let content;
39730
+ try {
39731
+ content = fs12.readFileSync(testFile, "utf-8");
39732
+ } catch {
39733
+ continue;
39734
+ }
39735
+ if (content.substring(0, 8192).includes("\x00")) {
39736
+ continue;
39737
+ }
39738
+ const imports = extractImports(content);
39739
+ const testDir = path22.dirname(testFile);
39740
+ for (const importPath of imports) {
39741
+ const resolvedSource = resolveRelativeImport(testDir, importPath);
39742
+ if (resolvedSource === null) {
39743
+ continue;
39744
+ }
39745
+ if (!impactMap[resolvedSource]) {
39746
+ impactMap[resolvedSource] = [];
39747
+ }
39748
+ if (!impactMap[resolvedSource].includes(testFile)) {
39749
+ impactMap[resolvedSource].push(testFile);
39750
+ }
39751
+ }
39752
+ }
39753
+ return impactMap;
39754
+ }
39755
+ async function buildImpactMap(cwd) {
39756
+ const impactMap = await buildImpactMapInternal(cwd);
39757
+ await saveImpactMap(cwd, impactMap);
39758
+ return impactMap;
39759
+ }
39760
+ async function loadImpactMap(cwd) {
39761
+ const cachePath = path22.join(cwd, ".swarm", "cache", "impact-map.json");
39762
+ if (fs12.existsSync(cachePath)) {
39763
+ try {
39764
+ const content = fs12.readFileSync(cachePath, "utf-8");
39765
+ const data = JSON.parse(content);
39766
+ const map3 = data.map;
39767
+ const generatedAt = new Date(data.generatedAt).getTime();
39768
+ if (!isCacheStale(map3, generatedAt)) {
39769
+ return map3;
39770
+ }
39771
+ } catch {}
39772
+ }
39773
+ return buildImpactMap(cwd);
39774
+ }
39775
+ async function saveImpactMap(cwd, impactMap) {
39776
+ const cacheDir = path22.join(cwd, ".swarm", "cache");
39777
+ const cachePath = path22.join(cacheDir, "impact-map.json");
39778
+ if (!fs12.existsSync(cacheDir)) {
39779
+ fs12.mkdirSync(cacheDir, { recursive: true });
39780
+ }
39781
+ const data = {
39782
+ generatedAt: new Date().toISOString(),
39783
+ fileCount: Object.keys(impactMap).length,
39784
+ map: impactMap
39785
+ };
39786
+ fs12.writeFileSync(cachePath, JSON.stringify(data, null, 2), "utf-8");
39787
+ }
39788
+ async function analyzeImpact(changedFiles, cwd) {
39789
+ if (!Array.isArray(changedFiles)) {
39790
+ const emptyMap = {};
39791
+ return {
39792
+ impactedTests: [],
39793
+ unrelatedTests: [],
39794
+ untestedFiles: [],
39795
+ impactMap: emptyMap
39796
+ };
39797
+ }
39798
+ const validFiles = changedFiles.filter((f) => typeof f === "string" && f.length > 0 && !f.includes("\x00"));
39799
+ const impactMap = await loadImpactMap(cwd);
39800
+ const impactedTestsSet = new Set;
39801
+ const untestedFiles = [];
39802
+ for (const changedFile of validFiles) {
39803
+ const normalizedChanged = normalizePath(path22.resolve(changedFile));
39804
+ const tests = impactMap[normalizedChanged];
39805
+ if (tests && tests.length > 0) {
39806
+ for (const test of tests) {
39807
+ impactedTestsSet.add(test);
39808
+ }
39809
+ } else {
39810
+ let found = false;
39811
+ for (const [sourcePath, tests2] of Object.entries(impactMap)) {
39812
+ if (sourcePath.endsWith(changedFile) || changedFile.endsWith(sourcePath)) {
39813
+ for (const test of tests2) {
39814
+ impactedTestsSet.add(test);
39815
+ }
39816
+ found = true;
39817
+ break;
39818
+ }
39819
+ }
39820
+ if (!found) {
39821
+ untestedFiles.push(changedFile);
39822
+ }
39823
+ }
39824
+ }
39825
+ const impactedTests = [...impactedTestsSet];
39826
+ const allTestFiles = new Set;
39827
+ for (const tests of Object.values(impactMap)) {
39828
+ for (const test of tests) {
39829
+ allTestFiles.add(test);
39830
+ }
39831
+ }
39832
+ const unrelatedTests = [...allTestFiles].filter((t) => !impactedTestsSet.has(t));
39833
+ return {
39834
+ impactedTests,
39835
+ unrelatedTests,
39836
+ untestedFiles,
39837
+ impactMap
39838
+ };
39839
+ }
39840
+
39841
+ // src/test-impact/failure-classifier.ts
39842
+ function computeConfidence2(historyLength) {
39843
+ if (historyLength >= 5) {
39844
+ return 1;
39845
+ }
39846
+ if (historyLength >= 3) {
39847
+ return 0.5;
39848
+ }
39849
+ if (historyLength >= 1) {
39850
+ return 0.3;
39851
+ }
39852
+ return 0.1;
39853
+ }
39854
+ function countAlternations(results) {
39855
+ if (results.length < 2) {
39856
+ return 0;
39857
+ }
39858
+ let alternations = 0;
39859
+ for (let i = 1;i < results.length; i++) {
39860
+ if (results[i] !== results[i - 1]) {
39861
+ alternations++;
39862
+ }
39863
+ }
39864
+ return alternations;
39865
+ }
39866
+ function stringHash(str) {
39867
+ let h1 = 3735928559;
39868
+ let h2 = 1103547991;
39869
+ for (let i = 0;i < str.length; i++) {
39870
+ const ch = str.charCodeAt(i);
39871
+ h1 = Math.imul(h1 ^ ch, 2654435761);
39872
+ h2 = Math.imul(h2 ^ ch, 1597334677);
39873
+ }
39874
+ h1 = Math.imul(h1 ^ h1 >>> 16, 2246822507);
39875
+ h1 ^= Math.imul(h2 ^ h2 >>> 13, 3266489909);
39876
+ h2 = Math.imul(h2 ^ h2 >>> 16, 2246822507);
39877
+ h2 ^= Math.imul(h1 ^ h1 >>> 13, 3266489909);
39878
+ return (4294967296 * (2097151 & h2) + (h1 >>> 0)).toString(16);
39879
+ }
39880
+ function classifyFailure(currentResult, history) {
39881
+ const normalizedFile = currentResult.testFile.toLowerCase();
39882
+ const normalizedName = currentResult.testName.toLowerCase();
39883
+ const testHistory = history.filter((r) => r.testFile.toLowerCase() === normalizedFile && r.testName.toLowerCase() === normalizedName).sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime());
39884
+ const lastThree = testHistory.slice(0, 3);
39885
+ const lastTen = testHistory.slice(0, 10);
39886
+ const normalizedTestFile = currentResult.testFile.toLowerCase();
39887
+ const isInChangedFiles = currentResult.changedFiles.some((f) => f.toLowerCase() === normalizedTestFile);
39888
+ const hasRecentPass = lastThree.every((r) => r.result === "pass");
39889
+ const hasRecentFailure = lastThree.some((r) => r.result === "fail");
39890
+ const alternationCount = countAlternations(lastTen.map((r) => r.result));
39891
+ if (lastThree.length >= 3 && hasRecentPass && currentResult.result === "fail" && isInChangedFiles) {
39892
+ return {
39893
+ testFile: currentResult.testFile,
39894
+ testName: currentResult.testName,
39895
+ classification: "new_regression",
39896
+ errorMessage: currentResult.errorMessage,
39897
+ stackPrefix: currentResult.stackPrefix,
39898
+ durationMs: currentResult.durationMs,
39899
+ confidence: computeConfidence2(testHistory.length)
39900
+ };
39901
+ }
39902
+ if (lastThree.length > 0 && hasRecentFailure && !isInChangedFiles) {
39903
+ return {
39904
+ testFile: currentResult.testFile,
39905
+ testName: currentResult.testName,
39906
+ classification: "pre_existing",
39907
+ errorMessage: currentResult.errorMessage,
39908
+ stackPrefix: currentResult.stackPrefix,
39909
+ durationMs: currentResult.durationMs,
39910
+ confidence: computeConfidence2(testHistory.length)
39911
+ };
39912
+ }
39913
+ if (alternationCount >= 2) {
39914
+ return {
39915
+ testFile: currentResult.testFile,
39916
+ testName: currentResult.testName,
39917
+ classification: "flaky",
39918
+ errorMessage: currentResult.errorMessage,
39919
+ stackPrefix: currentResult.stackPrefix,
39920
+ durationMs: currentResult.durationMs,
39921
+ confidence: computeConfidence2(testHistory.length)
39922
+ };
39923
+ }
39924
+ return {
39925
+ testFile: currentResult.testFile,
39926
+ testName: currentResult.testName,
39927
+ classification: "unknown",
39928
+ errorMessage: currentResult.errorMessage,
39929
+ stackPrefix: currentResult.stackPrefix,
39930
+ durationMs: currentResult.durationMs,
39931
+ confidence: computeConfidence2(testHistory.length)
39932
+ };
39933
+ }
39934
+ function clusterFailures(failures) {
39935
+ const clusterMap = new Map;
39936
+ for (const failure of failures) {
39937
+ const key = (failure.stackPrefix || "") + (failure.errorMessage || "");
39938
+ if (!clusterMap.has(key)) {
39939
+ clusterMap.set(key, {
39940
+ failures: [],
39941
+ stackPrefix: failure.stackPrefix,
39942
+ errorMessage: failure.errorMessage
39943
+ });
39944
+ }
39945
+ clusterMap.get(key).failures.push(failure);
39946
+ }
39947
+ const clusters = [];
39948
+ for (const [key, data] of clusterMap) {
39949
+ const classificationCounts = new Map;
39950
+ for (const f of data.failures) {
39951
+ const count = classificationCounts.get(f.classification) || 0;
39952
+ classificationCounts.set(f.classification, count + 1);
39953
+ }
39954
+ let dominantClassification = "unknown";
39955
+ let maxCount = 0;
39956
+ for (const [cls, count] of classificationCounts) {
39957
+ if (count > maxCount) {
39958
+ maxCount = count;
39959
+ dominantClassification = cls;
39960
+ }
39961
+ }
39962
+ const affectedTestFiles = [
39963
+ ...new Set(data.failures.map((f) => f.testFile))
39964
+ ];
39965
+ clusters.push({
39966
+ clusterId: stringHash(key),
39967
+ rootCause: key,
39968
+ stackPrefix: data.stackPrefix,
39969
+ errorMessage: data.errorMessage,
39970
+ failures: data.failures,
39971
+ classification: dominantClassification,
39972
+ affectedTestFiles
39973
+ });
39974
+ }
39975
+ return clusters;
39976
+ }
39977
+ function classifyAndCluster(testResults, history) {
39978
+ const failingResults = testResults.filter((r) => r.result === "fail");
39979
+ const classified = failingResults.map((r) => classifyFailure(r, history));
39980
+ const clusters = clusterFailures(classified);
39981
+ return { classified, clusters };
39982
+ }
39983
+
39984
+ // src/test-impact/flaky-detector.ts
39985
+ var FLAKY_THRESHOLD = 0.3;
39986
+ var MIN_RUNS_FOR_QUARANTINE = 5;
39987
+ var MAX_HISTORY_RUNS = 20;
39988
+ function detectFlakyTests(allHistory) {
39989
+ const grouped = new Map;
39990
+ for (const record3 of allHistory) {
39991
+ const key = `${record3.testFile.toLowerCase()}|${record3.testName.toLowerCase()}`;
39992
+ if (!grouped.has(key)) {
39993
+ grouped.set(key, {
39994
+ records: [],
39995
+ originalFile: record3.testFile,
39996
+ originalName: record3.testName
39997
+ });
39998
+ }
39999
+ grouped.get(key).records.push(record3);
40000
+ }
40001
+ const results = [];
40002
+ for (const [_key, entry] of grouped) {
40003
+ const records = entry.records;
40004
+ if (records.length === 0) {
40005
+ continue;
40006
+ }
40007
+ const sorted = records.slice().sort((a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime());
40008
+ const recent = sorted.slice(-MAX_HISTORY_RUNS);
40009
+ const totalRuns = recent.length;
40010
+ if (totalRuns < 2) {
40011
+ continue;
40012
+ }
40013
+ let alternationCount = 0;
40014
+ for (let i = 1;i < recent.length; i++) {
40015
+ if (recent[i].result !== recent[i - 1].result) {
40016
+ alternationCount++;
40017
+ }
40018
+ }
40019
+ const flakyScore = totalRuns >= 2 ? alternationCount / totalRuns : 0;
40020
+ const isQuarantined = flakyScore > FLAKY_THRESHOLD && totalRuns >= MIN_RUNS_FOR_QUARANTINE;
40021
+ const recentResults = recent.map((r) => r.result);
40022
+ const testFile = entry.originalFile;
40023
+ const testName = entry.originalName;
40024
+ let recommendation;
40025
+ if (isQuarantined) {
40026
+ if (alternationCount === totalRuns - 1) {
40027
+ recommendation = "Highly unstable \u2014 investigate test isolation issues, mock cleanup, or shared state";
40028
+ } else if (flakyScore > 0.5) {
40029
+ recommendation = "Severely flaky \u2014 consider quarantining and rewriting test with proper isolation";
40030
+ } else if (flakyScore > FLAKY_THRESHOLD) {
40031
+ recommendation = "Moderately flaky \u2014 review for timing dependencies, async issues, or environmental factors";
40032
+ }
40033
+ }
40034
+ results.push({
40035
+ testFile,
40036
+ testName,
40037
+ flakyScore,
40038
+ totalRuns,
40039
+ alternationCount,
40040
+ isQuarantined,
40041
+ recentResults,
40042
+ recommendation
40043
+ });
40044
+ }
40045
+ return results;
40046
+ }
40047
+
40048
+ // src/test-impact/history-store.ts
40049
+ import fs13 from "fs";
40050
+ import path23 from "path";
40051
+ var MAX_HISTORY_PER_TEST = 20;
40052
+ var MAX_ERROR_LENGTH = 500;
40053
+ var MAX_STACK_LENGTH = 200;
40054
+ var MAX_CHANGED_FILES = 50;
40055
+ function getHistoryPath(workingDir) {
40056
+ return path23.join(workingDir || process.cwd(), ".swarm", "cache", "test-history.jsonl");
40057
+ }
40058
+ function sanitizeErrorMessage(errorMessage) {
40059
+ if (errorMessage === undefined) {
40060
+ return;
40061
+ }
40062
+ if (errorMessage.length > MAX_ERROR_LENGTH) {
40063
+ return errorMessage.substring(0, MAX_ERROR_LENGTH);
40064
+ }
40065
+ return errorMessage;
40066
+ }
40067
+ function sanitizeStackPrefix(stackPrefix) {
40068
+ if (stackPrefix === undefined) {
40069
+ return;
40070
+ }
40071
+ if (stackPrefix.length > MAX_STACK_LENGTH) {
40072
+ return stackPrefix.substring(0, MAX_STACK_LENGTH);
40073
+ }
40074
+ return stackPrefix;
40075
+ }
40076
+ var DANGEROUS_PROPERTY_NAMES = new Set([
40077
+ "__proto__",
40078
+ "constructor",
40079
+ "prototype"
40080
+ ]);
40081
+ function sanitizeChangedFiles(changedFiles) {
40082
+ const validFiles = changedFiles.filter((f) => typeof f === "string" && f.length > 0 && !DANGEROUS_PROPERTY_NAMES.has(f));
40083
+ return validFiles.slice(0, MAX_CHANGED_FILES);
40084
+ }
40085
+ function appendTestRun(record3, workingDir) {
40086
+ if (typeof record3.testFile !== "string" || record3.testFile.length === 0) {
40087
+ throw new TypeError("testFile must be a non-empty string");
40088
+ }
40089
+ if (typeof record3.testName !== "string" || record3.testName.length === 0) {
40090
+ throw new TypeError("testName must be a non-empty string");
40091
+ }
40092
+ if (typeof record3.taskId !== "string" || record3.taskId.length === 0) {
40093
+ throw new TypeError("taskId must be a non-empty string");
40094
+ }
40095
+ if (record3.result !== "pass" && record3.result !== "fail" && record3.result !== "skip") {
40096
+ throw new TypeError('result must be "pass", "fail", or "skip"');
40097
+ }
40098
+ if (typeof record3.durationMs !== "number" || !Number.isFinite(record3.durationMs)) {
40099
+ throw new TypeError("durationMs must be a finite number");
40100
+ }
40101
+ if (record3.timestamp !== undefined && Number.isNaN(Date.parse(record3.timestamp))) {
40102
+ throw new TypeError("timestamp must be a valid ISO 8601 string");
40103
+ }
40104
+ if (record3.changedFiles !== undefined && !Array.isArray(record3.changedFiles)) {
40105
+ throw new TypeError("changedFiles must be an array");
40106
+ }
40107
+ const sanitizedRecord = {
40108
+ ...record3,
40109
+ timestamp: record3.timestamp || new Date().toISOString(),
40110
+ durationMs: Math.max(0, record3.durationMs),
40111
+ errorMessage: sanitizeErrorMessage(record3.errorMessage),
40112
+ stackPrefix: sanitizeStackPrefix(record3.stackPrefix),
40113
+ changedFiles: sanitizeChangedFiles(record3.changedFiles || [])
40114
+ };
40115
+ const historyPath = getHistoryPath(workingDir);
40116
+ const historyDir = path23.dirname(historyPath);
40117
+ if (!fs13.existsSync(historyDir)) {
40118
+ fs13.mkdirSync(historyDir, { recursive: true });
40119
+ }
40120
+ const existingRecords = readAllRecords(historyPath);
40121
+ existingRecords.push(sanitizedRecord);
40122
+ const recordsByFile = new Map;
40123
+ for (const rec of existingRecords) {
40124
+ const normalizedFile = rec.testFile.toLowerCase();
40125
+ if (!recordsByFile.has(normalizedFile)) {
40126
+ recordsByFile.set(normalizedFile, []);
40127
+ }
40128
+ recordsByFile.get(normalizedFile).push(rec);
40129
+ }
40130
+ const prunedRecords = [];
40131
+ for (const [, records] of recordsByFile) {
40132
+ records.sort((a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime());
40133
+ const toKeep = records.slice(-MAX_HISTORY_PER_TEST);
40134
+ prunedRecords.push(...toKeep);
40135
+ }
40136
+ prunedRecords.sort((a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime());
40137
+ try {
40138
+ const lines = prunedRecords.map((rec) => JSON.stringify(rec));
40139
+ const content = lines.join(`
40140
+ `) + `
40141
+ `;
40142
+ const tempPath = historyPath + ".tmp";
40143
+ fs13.writeFileSync(tempPath, content, "utf-8");
40144
+ fs13.renameSync(tempPath, historyPath);
40145
+ } catch (err) {
40146
+ try {
40147
+ const tempPath = historyPath + ".tmp";
40148
+ if (fs13.existsSync(tempPath)) {
40149
+ fs13.unlinkSync(tempPath);
40150
+ }
40151
+ } catch {}
40152
+ throw new Error(`Failed to write test history: ${err instanceof Error ? err.message : String(err)}`);
40153
+ }
40154
+ }
40155
+ function readAllRecords(historyPath) {
40156
+ if (!fs13.existsSync(historyPath)) {
40157
+ return [];
40158
+ }
40159
+ try {
40160
+ const content = fs13.readFileSync(historyPath, "utf-8");
40161
+ const lines = content.split(`
40162
+ `);
40163
+ const records = [];
40164
+ for (const line of lines) {
40165
+ const trimmed = line.trim();
40166
+ if (trimmed.length === 0) {
40167
+ continue;
40168
+ }
40169
+ try {
40170
+ const parsed = JSON.parse(trimmed);
40171
+ if (typeof parsed === "object" && parsed !== null && "testFile" in parsed && "testName" in parsed && "result" in parsed) {
40172
+ records.push(parsed);
40173
+ }
40174
+ } catch {}
40175
+ }
40176
+ return records;
40177
+ } catch (err) {
40178
+ throw new Error(`Failed to read test history: ${err instanceof Error ? err.message : String(err)}`);
40179
+ }
40180
+ }
40181
+ function getAllHistory(workingDir) {
40182
+ const historyPath = getHistoryPath(workingDir);
40183
+ const records = readAllRecords(historyPath);
40184
+ records.sort((a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime());
40185
+ return records;
40186
+ }
39612
40187
 
39613
40188
  // src/tools/resolve-working-directory.ts
39614
- import * as fs12 from "fs";
39615
- import * as path22 from "path";
40189
+ import * as fs14 from "fs";
40190
+ import * as path24 from "path";
39616
40191
  function resolveWorkingDirectory(workingDirectory, fallbackDirectory) {
39617
40192
  if (workingDirectory == null || workingDirectory === "") {
39618
40193
  return { success: true, directory: fallbackDirectory };
@@ -39632,17 +40207,17 @@ function resolveWorkingDirectory(workingDirectory, fallbackDirectory) {
39632
40207
  };
39633
40208
  }
39634
40209
  }
39635
- const normalizedDir = path22.normalize(workingDirectory);
39636
- const pathParts = normalizedDir.split(path22.sep);
40210
+ const normalizedDir = path24.normalize(workingDirectory);
40211
+ const pathParts = normalizedDir.split(path24.sep);
39637
40212
  if (pathParts.includes("..")) {
39638
40213
  return {
39639
40214
  success: false,
39640
40215
  message: "Invalid working_directory: path traversal sequences (..) are not allowed"
39641
40216
  };
39642
40217
  }
39643
- const resolvedDir = path22.resolve(normalizedDir);
40218
+ const resolvedDir = path24.resolve(normalizedDir);
39644
40219
  try {
39645
- const realPath = fs12.realpathSync(resolvedDir);
40220
+ const realPath = fs14.realpathSync(resolvedDir);
39646
40221
  return { success: true, directory: realPath };
39647
40222
  } catch {
39648
40223
  return {
@@ -39678,7 +40253,7 @@ function validateArgs2(args) {
39678
40253
  return false;
39679
40254
  const obj = args;
39680
40255
  if (obj.scope !== undefined) {
39681
- if (obj.scope !== "all" && obj.scope !== "convention" && obj.scope !== "graph") {
40256
+ if (obj.scope !== "all" && obj.scope !== "convention" && obj.scope !== "graph" && obj.scope !== "impact") {
39682
40257
  return false;
39683
40258
  }
39684
40259
  }
@@ -39723,19 +40298,19 @@ function hasDevDependency(devDeps, ...patterns) {
39723
40298
  return hasPackageJsonDependency(devDeps, ...patterns);
39724
40299
  }
39725
40300
  function detectGoTest(cwd) {
39726
- return fs13.existsSync(path23.join(cwd, "go.mod")) && isCommandAvailable("go");
40301
+ return fs15.existsSync(path25.join(cwd, "go.mod")) && isCommandAvailable("go");
39727
40302
  }
39728
40303
  function detectJavaMaven(cwd) {
39729
- return fs13.existsSync(path23.join(cwd, "pom.xml")) && isCommandAvailable("mvn");
40304
+ return fs15.existsSync(path25.join(cwd, "pom.xml")) && isCommandAvailable("mvn");
39730
40305
  }
39731
40306
  function detectGradle(cwd) {
39732
- const hasBuildFile = fs13.existsSync(path23.join(cwd, "build.gradle")) || fs13.existsSync(path23.join(cwd, "build.gradle.kts"));
39733
- const hasGradlew = fs13.existsSync(path23.join(cwd, "gradlew")) || fs13.existsSync(path23.join(cwd, "gradlew.bat"));
40307
+ const hasBuildFile = fs15.existsSync(path25.join(cwd, "build.gradle")) || fs15.existsSync(path25.join(cwd, "build.gradle.kts"));
40308
+ const hasGradlew = fs15.existsSync(path25.join(cwd, "gradlew")) || fs15.existsSync(path25.join(cwd, "gradlew.bat"));
39734
40309
  return hasBuildFile && (hasGradlew || isCommandAvailable("gradle"));
39735
40310
  }
39736
40311
  function detectDotnetTest(cwd) {
39737
40312
  try {
39738
- const files = fs13.readdirSync(cwd);
40313
+ const files = fs15.readdirSync(cwd);
39739
40314
  const hasCsproj = files.some((f) => f.endsWith(".csproj"));
39740
40315
  return hasCsproj && isCommandAvailable("dotnet");
39741
40316
  } catch {
@@ -39743,32 +40318,32 @@ function detectDotnetTest(cwd) {
39743
40318
  }
39744
40319
  }
39745
40320
  function detectCTest(cwd) {
39746
- const hasSource = fs13.existsSync(path23.join(cwd, "CMakeLists.txt"));
39747
- const hasBuildCache = fs13.existsSync(path23.join(cwd, "CMakeCache.txt")) || fs13.existsSync(path23.join(cwd, "build", "CMakeCache.txt"));
40321
+ const hasSource = fs15.existsSync(path25.join(cwd, "CMakeLists.txt"));
40322
+ const hasBuildCache = fs15.existsSync(path25.join(cwd, "CMakeCache.txt")) || fs15.existsSync(path25.join(cwd, "build", "CMakeCache.txt"));
39748
40323
  return (hasSource || hasBuildCache) && isCommandAvailable("ctest");
39749
40324
  }
39750
40325
  function detectSwiftTest(cwd) {
39751
- return fs13.existsSync(path23.join(cwd, "Package.swift")) && isCommandAvailable("swift");
40326
+ return fs15.existsSync(path25.join(cwd, "Package.swift")) && isCommandAvailable("swift");
39752
40327
  }
39753
40328
  function detectDartTest(cwd) {
39754
- return fs13.existsSync(path23.join(cwd, "pubspec.yaml")) && (isCommandAvailable("dart") || isCommandAvailable("flutter"));
40329
+ return fs15.existsSync(path25.join(cwd, "pubspec.yaml")) && (isCommandAvailable("dart") || isCommandAvailable("flutter"));
39755
40330
  }
39756
40331
  function detectRSpec(cwd) {
39757
- const hasRSpecFile = fs13.existsSync(path23.join(cwd, ".rspec"));
39758
- const hasGemfile = fs13.existsSync(path23.join(cwd, "Gemfile"));
39759
- const hasSpecDir = fs13.existsSync(path23.join(cwd, "spec"));
40332
+ const hasRSpecFile = fs15.existsSync(path25.join(cwd, ".rspec"));
40333
+ const hasGemfile = fs15.existsSync(path25.join(cwd, "Gemfile"));
40334
+ const hasSpecDir = fs15.existsSync(path25.join(cwd, "spec"));
39760
40335
  const hasRSpec = hasRSpecFile || hasGemfile && hasSpecDir;
39761
40336
  return hasRSpec && (isCommandAvailable("bundle") || isCommandAvailable("rspec"));
39762
40337
  }
39763
40338
  function detectMinitest(cwd) {
39764
- return fs13.existsSync(path23.join(cwd, "test")) && (fs13.existsSync(path23.join(cwd, "Gemfile")) || fs13.existsSync(path23.join(cwd, "Rakefile"))) && isCommandAvailable("ruby");
40339
+ return fs15.existsSync(path25.join(cwd, "test")) && (fs15.existsSync(path25.join(cwd, "Gemfile")) || fs15.existsSync(path25.join(cwd, "Rakefile"))) && isCommandAvailable("ruby");
39765
40340
  }
39766
40341
  async function detectTestFramework(cwd) {
39767
40342
  const baseDir = cwd;
39768
40343
  try {
39769
- const packageJsonPath = path23.join(baseDir, "package.json");
39770
- if (fs13.existsSync(packageJsonPath)) {
39771
- const content = fs13.readFileSync(packageJsonPath, "utf-8");
40344
+ const packageJsonPath = path25.join(baseDir, "package.json");
40345
+ if (fs15.existsSync(packageJsonPath)) {
40346
+ const content = fs15.readFileSync(packageJsonPath, "utf-8");
39772
40347
  const pkg = JSON.parse(content);
39773
40348
  const _deps = pkg.dependencies || {};
39774
40349
  const devDeps = pkg.devDependencies || {};
@@ -39787,38 +40362,38 @@ async function detectTestFramework(cwd) {
39787
40362
  return "jest";
39788
40363
  if (hasDevDependency(devDeps, "mocha", "@types/mocha"))
39789
40364
  return "mocha";
39790
- if (fs13.existsSync(path23.join(baseDir, "bun.lockb")) || fs13.existsSync(path23.join(baseDir, "bun.lock"))) {
40365
+ if (fs15.existsSync(path25.join(baseDir, "bun.lockb")) || fs15.existsSync(path25.join(baseDir, "bun.lock"))) {
39791
40366
  if (scripts.test?.includes("bun"))
39792
40367
  return "bun";
39793
40368
  }
39794
40369
  }
39795
40370
  } catch {}
39796
40371
  try {
39797
- const pyprojectTomlPath = path23.join(baseDir, "pyproject.toml");
39798
- const setupCfgPath = path23.join(baseDir, "setup.cfg");
39799
- const requirementsTxtPath = path23.join(baseDir, "requirements.txt");
39800
- if (fs13.existsSync(pyprojectTomlPath)) {
39801
- const content = fs13.readFileSync(pyprojectTomlPath, "utf-8");
40372
+ const pyprojectTomlPath = path25.join(baseDir, "pyproject.toml");
40373
+ const setupCfgPath = path25.join(baseDir, "setup.cfg");
40374
+ const requirementsTxtPath = path25.join(baseDir, "requirements.txt");
40375
+ if (fs15.existsSync(pyprojectTomlPath)) {
40376
+ const content = fs15.readFileSync(pyprojectTomlPath, "utf-8");
39802
40377
  if (content.includes("[tool.pytest"))
39803
40378
  return "pytest";
39804
40379
  if (content.includes("pytest"))
39805
40380
  return "pytest";
39806
40381
  }
39807
- if (fs13.existsSync(setupCfgPath)) {
39808
- const content = fs13.readFileSync(setupCfgPath, "utf-8");
40382
+ if (fs15.existsSync(setupCfgPath)) {
40383
+ const content = fs15.readFileSync(setupCfgPath, "utf-8");
39809
40384
  if (content.includes("[pytest]"))
39810
40385
  return "pytest";
39811
40386
  }
39812
- if (fs13.existsSync(requirementsTxtPath)) {
39813
- const content = fs13.readFileSync(requirementsTxtPath, "utf-8");
40387
+ if (fs15.existsSync(requirementsTxtPath)) {
40388
+ const content = fs15.readFileSync(requirementsTxtPath, "utf-8");
39814
40389
  if (content.includes("pytest"))
39815
40390
  return "pytest";
39816
40391
  }
39817
40392
  } catch {}
39818
40393
  try {
39819
- const cargoTomlPath = path23.join(baseDir, "Cargo.toml");
39820
- if (fs13.existsSync(cargoTomlPath)) {
39821
- const content = fs13.readFileSync(cargoTomlPath, "utf-8");
40394
+ const cargoTomlPath = path25.join(baseDir, "Cargo.toml");
40395
+ if (fs15.existsSync(cargoTomlPath)) {
40396
+ const content = fs15.readFileSync(cargoTomlPath, "utf-8");
39822
40397
  if (content.includes("[dev-dependencies]")) {
39823
40398
  if (content.includes("tokio") || content.includes("mockall") || content.includes("pretty_assertions")) {
39824
40399
  return "cargo";
@@ -39827,10 +40402,10 @@ async function detectTestFramework(cwd) {
39827
40402
  }
39828
40403
  } catch {}
39829
40404
  try {
39830
- const pesterConfigPath = path23.join(baseDir, "pester.config.ps1");
39831
- const pesterConfigJsonPath = path23.join(baseDir, "pester.config.ps1.json");
39832
- const pesterPs1Path = path23.join(baseDir, "tests.ps1");
39833
- if (fs13.existsSync(pesterConfigPath) || fs13.existsSync(pesterConfigJsonPath) || fs13.existsSync(pesterPs1Path)) {
40405
+ const pesterConfigPath = path25.join(baseDir, "pester.config.ps1");
40406
+ const pesterConfigJsonPath = path25.join(baseDir, "pester.config.ps1.json");
40407
+ const pesterPs1Path = path25.join(baseDir, "tests.ps1");
40408
+ if (fs15.existsSync(pesterConfigPath) || fs15.existsSync(pesterConfigJsonPath) || fs15.existsSync(pesterPs1Path)) {
39834
40409
  return "pester";
39835
40410
  }
39836
40411
  } catch {}
@@ -39881,8 +40456,8 @@ function getTestFilesFromConvention(sourceFiles) {
39881
40456
  const testFiles = [];
39882
40457
  for (const file3 of sourceFiles) {
39883
40458
  const normalizedPath = file3.replace(/\\/g, "/");
39884
- const basename4 = path23.basename(file3);
39885
- const dirname10 = path23.dirname(file3);
40459
+ const basename4 = path25.basename(file3);
40460
+ const dirname10 = path25.dirname(file3);
39886
40461
  if (hasCompoundTestExtension(basename4) || basename4.includes(".spec.") || basename4.includes(".test.") || normalizedPath.includes("/__tests__/") || normalizedPath.includes("/tests/") || normalizedPath.includes("/test/")) {
39887
40462
  if (!testFiles.includes(file3)) {
39888
40463
  testFiles.push(file3);
@@ -39891,16 +40466,16 @@ function getTestFilesFromConvention(sourceFiles) {
39891
40466
  }
39892
40467
  for (const _pattern of TEST_PATTERNS) {
39893
40468
  const nameWithoutExt = basename4.replace(/\.[^.]+$/, "");
39894
- const ext = path23.extname(basename4);
40469
+ const ext = path25.extname(basename4);
39895
40470
  const possibleTestFiles = [
39896
- path23.join(dirname10, `${nameWithoutExt}.spec${ext}`),
39897
- path23.join(dirname10, `${nameWithoutExt}.test${ext}`),
39898
- path23.join(dirname10, "__tests__", `${nameWithoutExt}${ext}`),
39899
- path23.join(dirname10, "tests", `${nameWithoutExt}${ext}`),
39900
- path23.join(dirname10, "test", `${nameWithoutExt}${ext}`)
40471
+ path25.join(dirname10, `${nameWithoutExt}.spec${ext}`),
40472
+ path25.join(dirname10, `${nameWithoutExt}.test${ext}`),
40473
+ path25.join(dirname10, "__tests__", `${nameWithoutExt}${ext}`),
40474
+ path25.join(dirname10, "tests", `${nameWithoutExt}${ext}`),
40475
+ path25.join(dirname10, "test", `${nameWithoutExt}${ext}`)
39901
40476
  ];
39902
40477
  for (const testFile of possibleTestFiles) {
39903
- if (fs13.existsSync(testFile) && !testFiles.includes(testFile)) {
40478
+ if (fs15.existsSync(testFile) && !testFiles.includes(testFile)) {
39904
40479
  testFiles.push(testFile);
39905
40480
  }
39906
40481
  }
@@ -39916,8 +40491,8 @@ async function getTestFilesFromGraph(sourceFiles) {
39916
40491
  }
39917
40492
  for (const testFile of candidateTestFiles) {
39918
40493
  try {
39919
- const content = fs13.readFileSync(testFile, "utf-8");
39920
- const testDir = path23.dirname(testFile);
40494
+ const content = fs15.readFileSync(testFile, "utf-8");
40495
+ const testDir = path25.dirname(testFile);
39921
40496
  const importRegex = /import\s+.*?\s+from\s+['"]([^'"]+)['"]/g;
39922
40497
  let match;
39923
40498
  match = importRegex.exec(content);
@@ -39925,8 +40500,8 @@ async function getTestFilesFromGraph(sourceFiles) {
39925
40500
  const importPath = match[1];
39926
40501
  let resolvedImport;
39927
40502
  if (importPath.startsWith(".")) {
39928
- resolvedImport = path23.resolve(testDir, importPath);
39929
- const existingExt = path23.extname(resolvedImport);
40503
+ resolvedImport = path25.resolve(testDir, importPath);
40504
+ const existingExt = path25.extname(resolvedImport);
39930
40505
  if (!existingExt) {
39931
40506
  for (const extToTry of [
39932
40507
  ".ts",
@@ -39937,7 +40512,7 @@ async function getTestFilesFromGraph(sourceFiles) {
39937
40512
  ".cjs"
39938
40513
  ]) {
39939
40514
  const withExt = resolvedImport + extToTry;
39940
- if (sourceFiles.includes(withExt) || fs13.existsSync(withExt)) {
40515
+ if (sourceFiles.includes(withExt) || fs15.existsSync(withExt)) {
39941
40516
  resolvedImport = withExt;
39942
40517
  break;
39943
40518
  }
@@ -39946,12 +40521,12 @@ async function getTestFilesFromGraph(sourceFiles) {
39946
40521
  } else {
39947
40522
  continue;
39948
40523
  }
39949
- const importBasename = path23.basename(resolvedImport, path23.extname(resolvedImport));
39950
- const importDir = path23.dirname(resolvedImport);
40524
+ const importBasename = path25.basename(resolvedImport, path25.extname(resolvedImport));
40525
+ const importDir = path25.dirname(resolvedImport);
39951
40526
  for (const sourceFile of sourceFiles) {
39952
- const sourceDir = path23.dirname(sourceFile);
39953
- const sourceBasename = path23.basename(sourceFile, path23.extname(sourceFile));
39954
- const isRelatedDir = importDir === sourceDir || importDir === path23.join(sourceDir, "__tests__") || importDir === path23.join(sourceDir, "tests") || importDir === path23.join(sourceDir, "test");
40527
+ const sourceDir = path25.dirname(sourceFile);
40528
+ const sourceBasename = path25.basename(sourceFile, path25.extname(sourceFile));
40529
+ const isRelatedDir = importDir === sourceDir || importDir === path25.join(sourceDir, "__tests__") || importDir === path25.join(sourceDir, "tests") || importDir === path25.join(sourceDir, "test");
39955
40530
  if (resolvedImport === sourceFile || importBasename === sourceBasename && isRelatedDir) {
39956
40531
  if (!testFiles.includes(testFile)) {
39957
40532
  testFiles.push(testFile);
@@ -39966,8 +40541,8 @@ async function getTestFilesFromGraph(sourceFiles) {
39966
40541
  while (match !== null) {
39967
40542
  const importPath = match[1];
39968
40543
  if (importPath.startsWith(".")) {
39969
- let resolvedImport = path23.resolve(testDir, importPath);
39970
- const existingExt = path23.extname(resolvedImport);
40544
+ let resolvedImport = path25.resolve(testDir, importPath);
40545
+ const existingExt = path25.extname(resolvedImport);
39971
40546
  if (!existingExt) {
39972
40547
  for (const extToTry of [
39973
40548
  ".ts",
@@ -39978,18 +40553,18 @@ async function getTestFilesFromGraph(sourceFiles) {
39978
40553
  ".cjs"
39979
40554
  ]) {
39980
40555
  const withExt = resolvedImport + extToTry;
39981
- if (sourceFiles.includes(withExt) || fs13.existsSync(withExt)) {
40556
+ if (sourceFiles.includes(withExt) || fs15.existsSync(withExt)) {
39982
40557
  resolvedImport = withExt;
39983
40558
  break;
39984
40559
  }
39985
40560
  }
39986
40561
  }
39987
- const importDir = path23.dirname(resolvedImport);
39988
- const importBasename = path23.basename(resolvedImport, path23.extname(resolvedImport));
40562
+ const importDir = path25.dirname(resolvedImport);
40563
+ const importBasename = path25.basename(resolvedImport, path25.extname(resolvedImport));
39989
40564
  for (const sourceFile of sourceFiles) {
39990
- const sourceDir = path23.dirname(sourceFile);
39991
- const sourceBasename = path23.basename(sourceFile, path23.extname(sourceFile));
39992
- const isRelatedDir = importDir === sourceDir || importDir === path23.join(sourceDir, "__tests__") || importDir === path23.join(sourceDir, "tests") || importDir === path23.join(sourceDir, "test");
40565
+ const sourceDir = path25.dirname(sourceFile);
40566
+ const sourceBasename = path25.basename(sourceFile, path25.extname(sourceFile));
40567
+ const isRelatedDir = importDir === sourceDir || importDir === path25.join(sourceDir, "__tests__") || importDir === path25.join(sourceDir, "tests") || importDir === path25.join(sourceDir, "test");
39993
40568
  if (resolvedImport === sourceFile || importBasename === sourceBasename && isRelatedDir) {
39994
40569
  if (!testFiles.includes(testFile)) {
39995
40570
  testFiles.push(testFile);
@@ -40074,8 +40649,8 @@ function buildTestCommand(framework, scope, files, coverage, baseDir) {
40074
40649
  return ["mvn", "test"];
40075
40650
  case "gradle": {
40076
40651
  const isWindows = process.platform === "win32";
40077
- const hasGradlewBat = fs13.existsSync(path23.join(baseDir, "gradlew.bat"));
40078
- const hasGradlew = fs13.existsSync(path23.join(baseDir, "gradlew"));
40652
+ const hasGradlewBat = fs15.existsSync(path25.join(baseDir, "gradlew.bat"));
40653
+ const hasGradlew = fs15.existsSync(path25.join(baseDir, "gradlew"));
40079
40654
  if (hasGradlewBat && isWindows)
40080
40655
  return ["gradlew.bat", "test"];
40081
40656
  if (hasGradlew)
@@ -40092,7 +40667,7 @@ function buildTestCommand(framework, scope, files, coverage, baseDir) {
40092
40667
  "cmake-build-release",
40093
40668
  "out"
40094
40669
  ];
40095
- const actualBuildDir = buildDirCandidates.find((d) => fs13.existsSync(path23.join(baseDir, d, "CMakeCache.txt"))) ?? "build";
40670
+ const actualBuildDir = buildDirCandidates.find((d) => fs15.existsSync(path25.join(baseDir, d, "CMakeCache.txt"))) ?? "build";
40096
40671
  return ["ctest", "--test-dir", actualBuildDir];
40097
40672
  }
40098
40673
  case "swift-test":
@@ -40518,10 +41093,57 @@ var SKIP_DIRECTORIES = new Set([
40518
41093
  ".bundle",
40519
41094
  ".tox"
40520
41095
  ]);
41096
+ function recordAndAnalyzeResults(result, testFiles, workingDir, sourceFiles) {
41097
+ if (!result.totals || result.totals.total === 0)
41098
+ return;
41099
+ const now = new Date().toISOString();
41100
+ const changedFiles = (sourceFiles && sourceFiles.length > 0 ? sourceFiles : testFiles).map((f) => f.replace(/\\/g, "/"));
41101
+ for (const testFile of testFiles) {
41102
+ try {
41103
+ appendTestRun({
41104
+ timestamp: now,
41105
+ taskId: "auto",
41106
+ testFile: testFile.replace(/\\/g, "/"),
41107
+ testName: "(aggregate)",
41108
+ result: result.success ? "pass" : "fail",
41109
+ durationMs: result.duration_ms || 0,
41110
+ changedFiles
41111
+ }, workingDir);
41112
+ } catch {}
41113
+ }
41114
+ }
41115
+ function analyzeFailures(workingDir) {
41116
+ const report = {
41117
+ flakyTests: [],
41118
+ failureClusters: [],
41119
+ quarantinedFailures: []
41120
+ };
41121
+ try {
41122
+ const history = getAllHistory(workingDir);
41123
+ if (history.length === 0)
41124
+ return report;
41125
+ report.flakyTests = detectFlakyTests(history);
41126
+ const failingResults = history.filter((r) => r.result === "fail");
41127
+ if (failingResults.length > 0) {
41128
+ const { clusters } = classifyAndCluster(failingResults, history);
41129
+ report.failureClusters = clusters.map((c) => ({
41130
+ rootCause: c.rootCause,
41131
+ affectedFiles: c.affectedTestFiles,
41132
+ classification: c.classification
41133
+ }));
41134
+ }
41135
+ for (const entry of report.flakyTests) {
41136
+ if (entry.isQuarantined) {
41137
+ report.quarantinedFailures.push(`${entry.testFile}: ${entry.testName}`);
41138
+ }
41139
+ }
41140
+ } catch {}
41141
+ return report;
41142
+ }
40521
41143
  var test_runner = createSwarmTool({
40522
- 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.',
41144
+ 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, "graph" to find related tests via imports, or "impact" to find tests covering changed files using test-impact analysis.',
40523
41145
  args: {
40524
- scope: tool.schema.enum(["all", "convention", "graph"]).optional().describe('Test scope: "all" runs full suite, "convention" maps source files to test files by naming, "graph" finds related tests via imports'),
41146
+ scope: tool.schema.enum(["all", "convention", "graph", "impact"]).optional().describe('Test scope: "all" runs full suite, "convention" maps source files to test files by naming, "graph" finds related tests via imports, "impact" finds tests covering changed files via test-impact analysis'),
40525
41147
  files: tool.schema.array(tool.schema.string()).optional().describe("Specific files to test (used with convention or graph scope)"),
40526
41148
  coverage: tool.schema.boolean().optional().describe("Enable coverage reporting if supported"),
40527
41149
  timeout_ms: tool.schema.number().optional().describe("Timeout in milliseconds (default 60000, max 300000)"),
@@ -40592,7 +41214,7 @@ var test_runner = createSwarmTool({
40592
41214
  framework: "none",
40593
41215
  scope: "all",
40594
41216
  error: "Invalid arguments",
40595
- message: 'scope must be "all", "convention", or "graph"; files must be array of strings; coverage must be boolean; timeout_ms must be a positive number',
41217
+ message: 'scope must be "all", "convention", "graph", or "impact"; files must be array of strings; coverage must be boolean; timeout_ms must be a positive number',
40596
41218
  outcome: "error"
40597
41219
  };
40598
41220
  return JSON.stringify(errorResult, null, 2);
@@ -40611,7 +41233,7 @@ var test_runner = createSwarmTool({
40611
41233
  return JSON.stringify(errorResult, null, 2);
40612
41234
  }
40613
41235
  }
40614
- if ((scope === "convention" || scope === "graph") && (!args.files || args.files.length === 0)) {
41236
+ if ((scope === "convention" || scope === "graph" || scope === "impact") && (!args.files || args.files.length === 0)) {
40615
41237
  const errorResult = {
40616
41238
  success: false,
40617
41239
  framework: "none",
@@ -40648,7 +41270,7 @@ var test_runner = createSwarmTool({
40648
41270
  let effectiveScope = scope;
40649
41271
  if (scope === "all") {} else if (scope === "convention") {
40650
41272
  const sourceFiles = args.files.filter((f) => {
40651
- const ext = path23.extname(f).toLowerCase();
41273
+ const ext = path25.extname(f).toLowerCase();
40652
41274
  return SOURCE_EXTENSIONS.has(ext);
40653
41275
  });
40654
41276
  if (sourceFiles.length === 0) {
@@ -40665,7 +41287,7 @@ var test_runner = createSwarmTool({
40665
41287
  testFiles = getTestFilesFromConvention(sourceFiles);
40666
41288
  } else if (scope === "graph") {
40667
41289
  const sourceFiles = args.files.filter((f) => {
40668
- const ext = path23.extname(f).toLowerCase();
41290
+ const ext = path25.extname(f).toLowerCase();
40669
41291
  return SOURCE_EXTENSIONS.has(ext);
40670
41292
  });
40671
41293
  if (sourceFiles.length === 0) {
@@ -40687,6 +41309,53 @@ var test_runner = createSwarmTool({
40687
41309
  effectiveScope = "convention";
40688
41310
  testFiles = getTestFilesFromConvention(sourceFiles);
40689
41311
  }
41312
+ } else if (scope === "impact") {
41313
+ const sourceFiles = args.files.filter((f) => {
41314
+ const ext = path25.extname(f).toLowerCase();
41315
+ return SOURCE_EXTENSIONS.has(ext);
41316
+ });
41317
+ if (sourceFiles.length === 0) {
41318
+ const errorResult = {
41319
+ success: false,
41320
+ framework,
41321
+ scope,
41322
+ error: "Provided files contain no source files with recognized extensions",
41323
+ message: "The files array must contain at least one source file with a recognized extension (.ts, .tsx, .js, .jsx, .py, .rs, .ps1, etc.).",
41324
+ outcome: "error"
41325
+ };
41326
+ return JSON.stringify(errorResult, null, 2);
41327
+ }
41328
+ try {
41329
+ const impactResult = await analyzeImpact(sourceFiles, workingDir);
41330
+ if (impactResult.impactedTests.length > 0) {
41331
+ testFiles = impactResult.impactedTests.map((absPath) => {
41332
+ const relativePath = path25.relative(workingDir, absPath);
41333
+ return path25.isAbsolute(relativePath) ? absPath : relativePath;
41334
+ });
41335
+ } else {
41336
+ graphFallbackReason = "no impacted tests found via impact analysis, falling back to graph";
41337
+ effectiveScope = "graph";
41338
+ const graphTestFiles = await getTestFilesFromGraph(sourceFiles);
41339
+ if (graphTestFiles.length > 0) {
41340
+ testFiles = graphTestFiles;
41341
+ } else {
41342
+ graphFallbackReason = "imports resolution returned no results, falling back to convention";
41343
+ effectiveScope = "convention";
41344
+ testFiles = getTestFilesFromConvention(sourceFiles);
41345
+ }
41346
+ }
41347
+ } catch {
41348
+ graphFallbackReason = "impact analysis failed, falling back to graph";
41349
+ effectiveScope = "graph";
41350
+ const graphTestFiles = await getTestFilesFromGraph(sourceFiles);
41351
+ if (graphTestFiles.length > 0) {
41352
+ testFiles = graphTestFiles;
41353
+ } else {
41354
+ graphFallbackReason = "imports resolution returned no results, falling back to convention";
41355
+ effectiveScope = "convention";
41356
+ testFiles = getTestFilesFromConvention(sourceFiles);
41357
+ }
41358
+ }
40690
41359
  }
40691
41360
  if (scope !== "all" && testFiles.length === 0) {
40692
41361
  const baseMessage = "No matching test files found for the provided source files. Check that test files exist with matching naming conventions (.spec.*, .test.*, __tests__/, tests/, test/).";
@@ -40697,7 +41366,8 @@ var test_runner = createSwarmTool({
40697
41366
  error: "Provided source files resolved to zero test files",
40698
41367
  message: graphFallbackReason ? `${baseMessage} (${graphFallbackReason})` : baseMessage,
40699
41368
  outcome: "skip",
40700
- ...scope === "graph" && { attempted_scope: "graph" }
41369
+ ...scope === "graph" && { attempted_scope: "graph" },
41370
+ ...scope === "impact" && { attempted_scope: "graph" }
40701
41371
  };
40702
41372
  return JSON.stringify(errorResult, null, 2);
40703
41373
  }
@@ -40714,6 +41384,18 @@ var test_runner = createSwarmTool({
40714
41384
  return JSON.stringify(errorResult, null, 2);
40715
41385
  }
40716
41386
  const result = await runTests(framework, effectiveScope, testFiles, coverage, timeout_ms, workingDir);
41387
+ recordAndAnalyzeResults(result, testFiles, workingDir, _files.length > 0 ? _files : undefined);
41388
+ let historyReport;
41389
+ if (!result.success && result.totals && result.totals.failed > 0) {
41390
+ historyReport = analyzeFailures(workingDir);
41391
+ if (historyReport.quarantinedFailures.length > 0) {
41392
+ result.message = (result.message || "") + ` | QUARANTINED (flaky): ${historyReport.quarantinedFailures.join(", ")}`;
41393
+ }
41394
+ if (historyReport.failureClusters.length > 0) {
41395
+ const clusterSummary = historyReport.failureClusters.slice(0, 3).map((c) => `${c.classification}: ${c.rootCause.substring(0, 80)}`).join("; ");
41396
+ result.message = `${result.message || ""} | FAILURE ANALYSIS: ${clusterSummary}`;
41397
+ }
41398
+ }
40717
41399
  if (graphFallbackReason && result.message) {
40718
41400
  result.message = `${result.message} (${graphFallbackReason})`;
40719
41401
  }
@@ -40741,8 +41423,8 @@ function validateDirectoryPath(dir) {
40741
41423
  if (dir.includes("..")) {
40742
41424
  throw new Error("Directory path must not contain path traversal sequences");
40743
41425
  }
40744
- const normalized = path24.normalize(dir);
40745
- const absolutePath = path24.isAbsolute(normalized) ? normalized : path24.resolve(normalized);
41426
+ const normalized = path26.normalize(dir);
41427
+ const absolutePath = path26.isAbsolute(normalized) ? normalized : path26.resolve(normalized);
40746
41428
  return absolutePath;
40747
41429
  }
40748
41430
  function validateTimeout(timeoutMs, defaultValue) {
@@ -40765,9 +41447,9 @@ function validateTimeout(timeoutMs, defaultValue) {
40765
41447
  }
40766
41448
  function getPackageVersion(dir) {
40767
41449
  try {
40768
- const packagePath = path24.join(dir, "package.json");
40769
- if (fs14.existsSync(packagePath)) {
40770
- const content = fs14.readFileSync(packagePath, "utf-8");
41450
+ const packagePath = path26.join(dir, "package.json");
41451
+ if (fs16.existsSync(packagePath)) {
41452
+ const content = fs16.readFileSync(packagePath, "utf-8");
40771
41453
  const pkg = JSON.parse(content);
40772
41454
  return pkg.version ?? null;
40773
41455
  }
@@ -40776,9 +41458,9 @@ function getPackageVersion(dir) {
40776
41458
  }
40777
41459
  function getChangelogVersion(dir) {
40778
41460
  try {
40779
- const changelogPath = path24.join(dir, "CHANGELOG.md");
40780
- if (fs14.existsSync(changelogPath)) {
40781
- const content = fs14.readFileSync(changelogPath, "utf-8");
41461
+ const changelogPath = path26.join(dir, "CHANGELOG.md");
41462
+ if (fs16.existsSync(changelogPath)) {
41463
+ const content = fs16.readFileSync(changelogPath, "utf-8");
40782
41464
  const match = content.match(/^##\s*\[?(\d+\.\d+\.\d+)\]?/m);
40783
41465
  if (match) {
40784
41466
  return match[1];
@@ -40790,10 +41472,10 @@ function getChangelogVersion(dir) {
40790
41472
  function getVersionFileVersion(dir) {
40791
41473
  const possibleFiles = ["VERSION.txt", "version.txt", "VERSION", "version"];
40792
41474
  for (const file3 of possibleFiles) {
40793
- const filePath = path24.join(dir, file3);
40794
- if (fs14.existsSync(filePath)) {
41475
+ const filePath = path26.join(dir, file3);
41476
+ if (fs16.existsSync(filePath)) {
40795
41477
  try {
40796
- const content = fs14.readFileSync(filePath, "utf-8").trim();
41478
+ const content = fs16.readFileSync(filePath, "utf-8").trim();
40797
41479
  const match = content.match(/(\d+\.\d+\.\d+)/);
40798
41480
  if (match) {
40799
41481
  return match[1];
@@ -41117,8 +41799,8 @@ async function runEvidenceCheck(dir) {
41117
41799
  async function runRequirementCoverageCheck(dir, currentPhase) {
41118
41800
  const startTime = Date.now();
41119
41801
  try {
41120
- const specPath = path24.join(dir, ".swarm", "spec.md");
41121
- if (!fs14.existsSync(specPath)) {
41802
+ const specPath = path26.join(dir, ".swarm", "spec.md");
41803
+ if (!fs16.existsSync(specPath)) {
41122
41804
  return {
41123
41805
  type: "req_coverage",
41124
41806
  status: "skip",
@@ -41331,9 +42013,9 @@ async function handlePreflightCommand(directory, _args) {
41331
42013
  return formatPreflightMarkdown(report);
41332
42014
  }
41333
42015
  // src/knowledge/hive-promoter.ts
41334
- import * as fs15 from "fs";
42016
+ import * as fs17 from "fs";
41335
42017
  import * as os5 from "os";
41336
- import * as path25 from "path";
42018
+ import * as path27 from "path";
41337
42019
  var DANGEROUS_PATTERNS = [
41338
42020
  [/rm\s+-rf/, "rm\\s+-rf"],
41339
42021
  [/:\s*!\s*\|/, ":\\s*!\\s*\\|"],
@@ -41379,13 +42061,13 @@ function getHiveFilePath() {
41379
42061
  const home = os5.homedir();
41380
42062
  let dataDir;
41381
42063
  if (platform === "win32") {
41382
- dataDir = path25.join(process.env.LOCALAPPDATA || path25.join(home, "AppData", "Local"), "opencode-swarm", "Data");
42064
+ dataDir = path27.join(process.env.LOCALAPPDATA || path27.join(home, "AppData", "Local"), "opencode-swarm", "Data");
41383
42065
  } else if (platform === "darwin") {
41384
- dataDir = path25.join(home, "Library", "Application Support", "opencode-swarm");
42066
+ dataDir = path27.join(home, "Library", "Application Support", "opencode-swarm");
41385
42067
  } else {
41386
- dataDir = path25.join(process.env.XDG_DATA_HOME || path25.join(home, ".local", "share"), "opencode-swarm");
42068
+ dataDir = path27.join(process.env.XDG_DATA_HOME || path27.join(home, ".local", "share"), "opencode-swarm");
41387
42069
  }
41388
- return path25.join(dataDir, "hive-knowledge.jsonl");
42070
+ return path27.join(dataDir, "hive-knowledge.jsonl");
41389
42071
  }
41390
42072
  async function promoteToHive(_directory, lesson, category) {
41391
42073
  const trimmed = (lesson ?? "").trim();
@@ -41397,9 +42079,9 @@ async function promoteToHive(_directory, lesson, category) {
41397
42079
  throw new Error(`Lesson rejected by validator: ${validation.reason}`);
41398
42080
  }
41399
42081
  const hivePath = getHiveFilePath();
41400
- const hiveDir = path25.dirname(hivePath);
41401
- if (!fs15.existsSync(hiveDir)) {
41402
- fs15.mkdirSync(hiveDir, { recursive: true });
42082
+ const hiveDir = path27.dirname(hivePath);
42083
+ if (!fs17.existsSync(hiveDir)) {
42084
+ fs17.mkdirSync(hiveDir, { recursive: true });
41403
42085
  }
41404
42086
  const now = new Date;
41405
42087
  const entry = {
@@ -41413,16 +42095,16 @@ async function promoteToHive(_directory, lesson, category) {
41413
42095
  promotedAt: now.toISOString(),
41414
42096
  retrievalOutcomes: { applied: 0, succeededAfter: 0, failedAfter: 0 }
41415
42097
  };
41416
- fs15.appendFileSync(hivePath, `${JSON.stringify(entry)}
42098
+ fs17.appendFileSync(hivePath, `${JSON.stringify(entry)}
41417
42099
  `, "utf-8");
41418
42100
  const preview = `${trimmed.slice(0, 50)}${trimmed.length > 50 ? "..." : ""}`;
41419
42101
  return `Promoted to hive: "${preview}" (confidence: 1.0, source: manual)`;
41420
42102
  }
41421
42103
  async function promoteFromSwarm(directory, lessonId) {
41422
- const knowledgePath = path25.join(directory, ".swarm", "knowledge.jsonl");
42104
+ const knowledgePath = path27.join(directory, ".swarm", "knowledge.jsonl");
41423
42105
  const entries = [];
41424
- if (fs15.existsSync(knowledgePath)) {
41425
- const content = fs15.readFileSync(knowledgePath, "utf-8");
42106
+ if (fs17.existsSync(knowledgePath)) {
42107
+ const content = fs17.readFileSync(knowledgePath, "utf-8");
41426
42108
  for (const line of content.split(`
41427
42109
  `)) {
41428
42110
  const t = line.trim();
@@ -41446,9 +42128,9 @@ async function promoteFromSwarm(directory, lessonId) {
41446
42128
  throw new Error(`Lesson rejected by validator: ${validation.reason}`);
41447
42129
  }
41448
42130
  const hivePath = getHiveFilePath();
41449
- const hiveDir = path25.dirname(hivePath);
41450
- if (!fs15.existsSync(hiveDir)) {
41451
- fs15.mkdirSync(hiveDir, { recursive: true });
42131
+ const hiveDir = path27.dirname(hivePath);
42132
+ if (!fs17.existsSync(hiveDir)) {
42133
+ fs17.mkdirSync(hiveDir, { recursive: true });
41452
42134
  }
41453
42135
  const now = new Date;
41454
42136
  const hiveEntry = {
@@ -41462,7 +42144,7 @@ async function promoteFromSwarm(directory, lessonId) {
41462
42144
  promotedAt: now.toISOString(),
41463
42145
  retrievalOutcomes: { applied: 0, succeededAfter: 0, failedAfter: 0 }
41464
42146
  };
41465
- fs15.appendFileSync(hivePath, `${JSON.stringify(hiveEntry)}
42147
+ fs17.appendFileSync(hivePath, `${JSON.stringify(hiveEntry)}
41466
42148
  `, "utf-8");
41467
42149
  const preview = `${lessonText.slice(0, 50)}${lessonText.length > 50 ? "..." : ""}`;
41468
42150
  return `Promoted to hive: "${preview}" (confidence: 1.0, source: manual)`;
@@ -41834,7 +42516,7 @@ async function handleQaGatesCommand(directory, args, sessionID) {
41834
42516
  }
41835
42517
 
41836
42518
  // src/commands/reset.ts
41837
- import * as fs16 from "fs";
42519
+ import * as fs18 from "fs";
41838
42520
 
41839
42521
  // src/background/manager.ts
41840
42522
  init_utils();
@@ -42535,8 +43217,8 @@ async function handleResetCommand(directory, args) {
42535
43217
  for (const filename of filesToReset) {
42536
43218
  try {
42537
43219
  const resolvedPath = validateSwarmPath(directory, filename);
42538
- if (fs16.existsSync(resolvedPath)) {
42539
- fs16.unlinkSync(resolvedPath);
43220
+ if (fs18.existsSync(resolvedPath)) {
43221
+ fs18.unlinkSync(resolvedPath);
42540
43222
  results.push(`- \u2705 Deleted ${filename}`);
42541
43223
  } else {
42542
43224
  results.push(`- \u23ED\uFE0F ${filename} not found (skipped)`);
@@ -42553,8 +43235,8 @@ async function handleResetCommand(directory, args) {
42553
43235
  }
42554
43236
  try {
42555
43237
  const summariesPath = validateSwarmPath(directory, "summaries");
42556
- if (fs16.existsSync(summariesPath)) {
42557
- fs16.rmSync(summariesPath, { recursive: true, force: true });
43238
+ if (fs18.existsSync(summariesPath)) {
43239
+ fs18.rmSync(summariesPath, { recursive: true, force: true });
42558
43240
  results.push("- \u2705 Deleted summaries/ directory");
42559
43241
  } else {
42560
43242
  results.push("- \u23ED\uFE0F summaries/ not found (skipped)");
@@ -42574,14 +43256,14 @@ async function handleResetCommand(directory, args) {
42574
43256
 
42575
43257
  // src/commands/reset-session.ts
42576
43258
  init_utils2();
42577
- import * as fs17 from "fs";
42578
- import * as path26 from "path";
43259
+ import * as fs19 from "fs";
43260
+ import * as path28 from "path";
42579
43261
  async function handleResetSessionCommand(directory, _args) {
42580
43262
  const results = [];
42581
43263
  try {
42582
43264
  const statePath = validateSwarmPath(directory, "session/state.json");
42583
- if (fs17.existsSync(statePath)) {
42584
- fs17.unlinkSync(statePath);
43265
+ if (fs19.existsSync(statePath)) {
43266
+ fs19.unlinkSync(statePath);
42585
43267
  results.push("\u2705 Deleted .swarm/session/state.json");
42586
43268
  } else {
42587
43269
  results.push("\u23ED\uFE0F state.json not found (already clean)");
@@ -42590,15 +43272,15 @@ async function handleResetSessionCommand(directory, _args) {
42590
43272
  results.push("\u274C Failed to delete state.json");
42591
43273
  }
42592
43274
  try {
42593
- const sessionDir = path26.dirname(validateSwarmPath(directory, "session/state.json"));
42594
- if (fs17.existsSync(sessionDir)) {
42595
- const files = fs17.readdirSync(sessionDir);
43275
+ const sessionDir = path28.dirname(validateSwarmPath(directory, "session/state.json"));
43276
+ if (fs19.existsSync(sessionDir)) {
43277
+ const files = fs19.readdirSync(sessionDir);
42596
43278
  const otherFiles = files.filter((f) => f !== "state.json");
42597
43279
  let deletedCount = 0;
42598
43280
  for (const file3 of otherFiles) {
42599
- const filePath = path26.join(sessionDir, file3);
42600
- if (fs17.lstatSync(filePath).isFile()) {
42601
- fs17.unlinkSync(filePath);
43281
+ const filePath = path28.join(sessionDir, file3);
43282
+ if (fs19.lstatSync(filePath).isFile()) {
43283
+ fs19.unlinkSync(filePath);
42602
43284
  deletedCount++;
42603
43285
  }
42604
43286
  }
@@ -42626,7 +43308,7 @@ async function handleResetSessionCommand(directory, _args) {
42626
43308
  // src/summaries/manager.ts
42627
43309
  init_utils2();
42628
43310
  init_utils();
42629
- import * as path27 from "path";
43311
+ import * as path29 from "path";
42630
43312
  var SUMMARY_ID_REGEX = /^S\d+$/;
42631
43313
  function sanitizeSummaryId(id) {
42632
43314
  if (!id || id.length === 0) {
@@ -42650,7 +43332,7 @@ function sanitizeSummaryId(id) {
42650
43332
  }
42651
43333
  async function loadFullOutput(directory, id) {
42652
43334
  const sanitizedId = sanitizeSummaryId(id);
42653
- const relativePath = path27.join("summaries", `${sanitizedId}.json`);
43335
+ const relativePath = path29.join("summaries", `${sanitizedId}.json`);
42654
43336
  validateSwarmPath(directory, relativePath);
42655
43337
  const content = await readSwarmFileAsync(directory, relativePath);
42656
43338
  if (content === null) {
@@ -42703,18 +43385,18 @@ ${error93 instanceof Error ? error93.message : String(error93)}`;
42703
43385
 
42704
43386
  // src/commands/rollback.ts
42705
43387
  init_utils2();
42706
- import * as fs18 from "fs";
42707
- import * as path28 from "path";
43388
+ import * as fs20 from "fs";
43389
+ import * as path30 from "path";
42708
43390
  async function handleRollbackCommand(directory, args) {
42709
43391
  const phaseArg = args[0];
42710
43392
  if (!phaseArg) {
42711
43393
  const manifestPath2 = validateSwarmPath(directory, "checkpoints/manifest.json");
42712
- if (!fs18.existsSync(manifestPath2)) {
43394
+ if (!fs20.existsSync(manifestPath2)) {
42713
43395
  return "No checkpoints found. Use `/swarm checkpoint` to create checkpoints.";
42714
43396
  }
42715
43397
  let manifest2;
42716
43398
  try {
42717
- manifest2 = JSON.parse(fs18.readFileSync(manifestPath2, "utf-8"));
43399
+ manifest2 = JSON.parse(fs20.readFileSync(manifestPath2, "utf-8"));
42718
43400
  } catch {
42719
43401
  return "Error: Checkpoint manifest is corrupted. Delete .swarm/checkpoints/manifest.json and re-checkpoint.";
42720
43402
  }
@@ -42736,12 +43418,12 @@ async function handleRollbackCommand(directory, args) {
42736
43418
  return "Error: Phase number must be a positive integer.";
42737
43419
  }
42738
43420
  const manifestPath = validateSwarmPath(directory, "checkpoints/manifest.json");
42739
- if (!fs18.existsSync(manifestPath)) {
43421
+ if (!fs20.existsSync(manifestPath)) {
42740
43422
  return `Error: No checkpoints found. Cannot rollback to phase ${targetPhase}.`;
42741
43423
  }
42742
43424
  let manifest;
42743
43425
  try {
42744
- manifest = JSON.parse(fs18.readFileSync(manifestPath, "utf-8"));
43426
+ manifest = JSON.parse(fs20.readFileSync(manifestPath, "utf-8"));
42745
43427
  } catch {
42746
43428
  return `Error: Checkpoint manifest is corrupted. Delete .swarm/checkpoints/manifest.json and re-checkpoint.`;
42747
43429
  }
@@ -42751,10 +43433,10 @@ async function handleRollbackCommand(directory, args) {
42751
43433
  return `Error: Checkpoint for phase ${targetPhase} not found. Available phases: ${available}`;
42752
43434
  }
42753
43435
  const checkpointDir = validateSwarmPath(directory, `checkpoints/phase-${targetPhase}`);
42754
- if (!fs18.existsSync(checkpointDir)) {
43436
+ if (!fs20.existsSync(checkpointDir)) {
42755
43437
  return `Error: Checkpoint directory for phase ${targetPhase} does not exist.`;
42756
43438
  }
42757
- const checkpointFiles = fs18.readdirSync(checkpointDir);
43439
+ const checkpointFiles = fs20.readdirSync(checkpointDir);
42758
43440
  if (checkpointFiles.length === 0) {
42759
43441
  return `Error: Checkpoint for phase ${targetPhase} is empty. Cannot rollback.`;
42760
43442
  }
@@ -42762,10 +43444,10 @@ async function handleRollbackCommand(directory, args) {
42762
43444
  const successes = [];
42763
43445
  const failures = [];
42764
43446
  for (const file3 of checkpointFiles) {
42765
- const src = path28.join(checkpointDir, file3);
42766
- const dest = path28.join(swarmDir, file3);
43447
+ const src = path30.join(checkpointDir, file3);
43448
+ const dest = path30.join(swarmDir, file3);
42767
43449
  try {
42768
- fs18.cpSync(src, dest, { recursive: true, force: true });
43450
+ fs20.cpSync(src, dest, { recursive: true, force: true });
42769
43451
  successes.push(file3);
42770
43452
  } catch (error93) {
42771
43453
  failures.push({ file: file3, error: error93.message });
@@ -42782,7 +43464,7 @@ async function handleRollbackCommand(directory, args) {
42782
43464
  timestamp: new Date().toISOString()
42783
43465
  };
42784
43466
  try {
42785
- fs18.appendFileSync(eventsPath, `${JSON.stringify(rollbackEvent)}
43467
+ fs20.appendFileSync(eventsPath, `${JSON.stringify(rollbackEvent)}
42786
43468
  `);
42787
43469
  } catch (error93) {
42788
43470
  console.error("Failed to write rollback event:", error93 instanceof Error ? error93.message : String(error93));
@@ -42825,11 +43507,11 @@ async function handleSimulateCommand(directory, args) {
42825
43507
  ];
42826
43508
  const report = reportLines.filter(Boolean).join(`
42827
43509
  `);
42828
- const fs19 = await import("fs/promises");
42829
- const path29 = await import("path");
42830
- const reportPath = path29.join(directory, ".swarm", "simulate-report.md");
42831
- await fs19.mkdir(path29.dirname(reportPath), { recursive: true });
42832
- await fs19.writeFile(reportPath, report, "utf-8");
43510
+ const fs21 = await import("fs/promises");
43511
+ const path31 = await import("path");
43512
+ const reportPath = path31.join(directory, ".swarm", "simulate-report.md");
43513
+ await fs21.mkdir(path31.dirname(reportPath), { recursive: true });
43514
+ await fs21.writeFile(reportPath, report, "utf-8");
42833
43515
  return `${darkMatterPairs.length} hidden coupling pairs detected`;
42834
43516
  }
42835
43517
 
@@ -43392,18 +44074,18 @@ function resolveCommand(tokens) {
43392
44074
  }
43393
44075
 
43394
44076
  // src/cli/index.ts
43395
- var CONFIG_DIR = path29.join(process.env.XDG_CONFIG_HOME || path29.join(os6.homedir(), ".config"), "opencode");
43396
- var OPENCODE_CONFIG_PATH = path29.join(CONFIG_DIR, "opencode.json");
43397
- var PLUGIN_CONFIG_PATH = path29.join(CONFIG_DIR, "opencode-swarm.json");
43398
- var PROMPTS_DIR = path29.join(CONFIG_DIR, "opencode-swarm");
44077
+ var CONFIG_DIR = path31.join(process.env.XDG_CONFIG_HOME || path31.join(os6.homedir(), ".config"), "opencode");
44078
+ var OPENCODE_CONFIG_PATH = path31.join(CONFIG_DIR, "opencode.json");
44079
+ var PLUGIN_CONFIG_PATH = path31.join(CONFIG_DIR, "opencode-swarm.json");
44080
+ var PROMPTS_DIR = path31.join(CONFIG_DIR, "opencode-swarm");
43399
44081
  function ensureDir(dir) {
43400
- if (!fs19.existsSync(dir)) {
43401
- fs19.mkdirSync(dir, { recursive: true });
44082
+ if (!fs21.existsSync(dir)) {
44083
+ fs21.mkdirSync(dir, { recursive: true });
43402
44084
  }
43403
44085
  }
43404
44086
  function loadJson(filepath) {
43405
44087
  try {
43406
- const content = fs19.readFileSync(filepath, "utf-8");
44088
+ const content = fs21.readFileSync(filepath, "utf-8");
43407
44089
  const stripped = content.replace(/\\"|"(?:\\"|[^"])*"|(\/\/.*|\/\*[\s\S]*?\*\/)/g, (match, comment) => comment ? "" : match).replace(/,(\s*[}\]])/g, "$1");
43408
44090
  return JSON.parse(stripped);
43409
44091
  } catch {
@@ -43411,7 +44093,7 @@ function loadJson(filepath) {
43411
44093
  }
43412
44094
  }
43413
44095
  function saveJson(filepath, data) {
43414
- fs19.writeFileSync(filepath, `${JSON.stringify(data, null, 2)}
44096
+ fs21.writeFileSync(filepath, `${JSON.stringify(data, null, 2)}
43415
44097
  `, "utf-8");
43416
44098
  }
43417
44099
  async function install() {
@@ -43419,7 +44101,7 @@ async function install() {
43419
44101
  `);
43420
44102
  ensureDir(CONFIG_DIR);
43421
44103
  ensureDir(PROMPTS_DIR);
43422
- const LEGACY_CONFIG_PATH = path29.join(CONFIG_DIR, "config.json");
44104
+ const LEGACY_CONFIG_PATH = path31.join(CONFIG_DIR, "config.json");
43423
44105
  let opencodeConfig = loadJson(OPENCODE_CONFIG_PATH);
43424
44106
  if (!opencodeConfig) {
43425
44107
  const legacyConfig = loadJson(LEGACY_CONFIG_PATH);
@@ -43444,7 +44126,7 @@ async function install() {
43444
44126
  saveJson(OPENCODE_CONFIG_PATH, opencodeConfig);
43445
44127
  console.log("\u2713 Added opencode-swarm to OpenCode plugins");
43446
44128
  console.log("\u2713 Disabled default OpenCode agents (explore, general)");
43447
- if (!fs19.existsSync(PLUGIN_CONFIG_PATH)) {
44129
+ if (!fs21.existsSync(PLUGIN_CONFIG_PATH)) {
43448
44130
  const defaultConfig = {
43449
44131
  agents: {
43450
44132
  coder: { model: "opencode/minimax-m2.5-free" },
@@ -43487,7 +44169,7 @@ async function uninstall() {
43487
44169
  `);
43488
44170
  const opencodeConfig = loadJson(OPENCODE_CONFIG_PATH);
43489
44171
  if (!opencodeConfig) {
43490
- if (fs19.existsSync(OPENCODE_CONFIG_PATH)) {
44172
+ if (fs21.existsSync(OPENCODE_CONFIG_PATH)) {
43491
44173
  console.log(`\u2717 Could not parse opencode config at: ${OPENCODE_CONFIG_PATH}`);
43492
44174
  return 1;
43493
44175
  } else {
@@ -43519,13 +44201,13 @@ async function uninstall() {
43519
44201
  console.log("\u2713 Re-enabled default OpenCode agents (explore, general)");
43520
44202
  if (process.argv.includes("--clean")) {
43521
44203
  let cleaned = false;
43522
- if (fs19.existsSync(PLUGIN_CONFIG_PATH)) {
43523
- fs19.unlinkSync(PLUGIN_CONFIG_PATH);
44204
+ if (fs21.existsSync(PLUGIN_CONFIG_PATH)) {
44205
+ fs21.unlinkSync(PLUGIN_CONFIG_PATH);
43524
44206
  console.log(`\u2713 Removed plugin config: ${PLUGIN_CONFIG_PATH}`);
43525
44207
  cleaned = true;
43526
44208
  }
43527
- if (fs19.existsSync(PROMPTS_DIR)) {
43528
- fs19.rmSync(PROMPTS_DIR, { recursive: true });
44209
+ if (fs21.existsSync(PROMPTS_DIR)) {
44210
+ fs21.rmSync(PROMPTS_DIR, { recursive: true });
43529
44211
  console.log(`\u2713 Removed custom prompts: ${PROMPTS_DIR}`);
43530
44212
  cleaned = true;
43531
44213
  }