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