opencode-swarm 7.3.3 → 7.3.5
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/README.md +11 -0
- package/dist/agents/index.d.ts +28 -0
- package/dist/cli/index.js +7 -2
- package/dist/config/schema.d.ts +1 -20
- package/dist/hooks/diff-scope.d.ts +10 -0
- package/dist/index.js +277 -153
- package/dist/utils/gitignore-warning.d.ts +37 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -788,6 +788,17 @@ Prefixed agents (e.g., `paid_coder`, `mega_reviewer`, `local_architect`) inherit
|
|
|
788
788
|
|
|
789
789
|
In this example, `paid_coder` gets its own explicit rule, while other prefixed coders (e.g., `mega_coder`) fall back to `coder`.
|
|
790
790
|
|
|
791
|
+
#### Selecting the primary agent in multi-swarm configs (`default_agent`)
|
|
792
|
+
|
|
793
|
+
The top-level `default_agent` field controls which generated agents OpenCode treats as primary. **It is optional.** Behavior:
|
|
794
|
+
|
|
795
|
+
- **Omitted** — every architect-role agent is primary. In a multi-swarm config that means each swarm exposes its own architect (`local_architect`, `mega_architect`, `paid_architect`, `modelrelay_architect`, …) as a selectable session default. This is the v7.0.0-compatible behavior and the recommended setup.
|
|
796
|
+
- **Base role** (e.g. `"coder"`) — every generated agent whose canonical base role matches becomes primary (`local_coder`, `mega_coder`, …).
|
|
797
|
+
- **Exact generated name** (e.g. `"local_architect"`) — only that agent is primary.
|
|
798
|
+
- **Unknown / invalid value** — a one-time warning is logged and the resolver falls back to architect-role primaries (or the first generated agent if architects are disabled). The plugin never produces zero primaries when at least one agent exists.
|
|
799
|
+
|
|
800
|
+
See [`docs/configuration.md`](docs/configuration.md) for the full table.
|
|
801
|
+
|
|
791
802
|
### Runtime Enforcement
|
|
792
803
|
|
|
793
804
|
Architect direct writes are enforced at runtime via `toolBefore` hook. This tracks writes to source code paths outside `.swarm/` and protects `.swarm/plan.md` and `.swarm/plan.json` from direct modification.
|
package/dist/agents/index.d.ts
CHANGED
|
@@ -34,6 +34,34 @@ export declare function getSwarmAgents(): Record<string, {
|
|
|
34
34
|
* Create all agent definitions with configuration applied
|
|
35
35
|
*/
|
|
36
36
|
export declare function createAgents(config?: PluginConfig): AgentDefinition[];
|
|
37
|
+
/**
|
|
38
|
+
* Resolve the set of generated agent names that should be marked as primary
|
|
39
|
+
* for OpenCode's session-default-agent resolution.
|
|
40
|
+
*
|
|
41
|
+
* Resolution rules (see schema.ts default_agent comment for full semantics):
|
|
42
|
+
* - default_agent omitted ⇒ every architect-role agent is primary
|
|
43
|
+
* (canonical base role === "architect"). This restores v7.0.0 behavior in
|
|
44
|
+
* multi-swarm configs where there is no unprefixed `architect` agent.
|
|
45
|
+
* - default_agent exactly matches a generated agent name ⇒ only that agent.
|
|
46
|
+
* Exact match wins over base-role match — `local_architect` resolves to
|
|
47
|
+
* just `local_architect`, never the entire architect role.
|
|
48
|
+
* - default_agent is a base role in ALL_AGENT_NAMES ⇒ every generated agent
|
|
49
|
+
* whose canonical base role matches that role.
|
|
50
|
+
* - default_agent is invalid (matches nothing) ⇒ fall back to architect-role
|
|
51
|
+
* primaries; if no architect roles exist (architects disabled), fall back
|
|
52
|
+
* to the first generated agent. Always warns. Never returns empty when
|
|
53
|
+
* `agentNames` is non-empty.
|
|
54
|
+
*
|
|
55
|
+
* Important matching detail: a value like "not_an_architect" is NOT treated
|
|
56
|
+
* as a base-role request even though stripKnownSwarmPrefix() returns
|
|
57
|
+
* "architect" for it. Base-role matching only fires when the user-supplied
|
|
58
|
+
* value is itself one of ALL_AGENT_NAMES.
|
|
59
|
+
*/
|
|
60
|
+
export declare function resolvePrimaryAgentNames(agentNames: string[], defaultAgent?: string): {
|
|
61
|
+
primaryNames: Set<string>;
|
|
62
|
+
reason: 'implicit-architects' | 'exact' | 'base-role' | 'fallback-architects' | 'fallback-first';
|
|
63
|
+
warning?: string;
|
|
64
|
+
};
|
|
37
65
|
/**
|
|
38
66
|
* Get agent configurations formatted for the OpenCode SDK.
|
|
39
67
|
*/
|
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.3.
|
|
37
|
+
version: "7.3.5",
|
|
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",
|
|
@@ -16915,7 +16915,12 @@ var init_schema = __esm(() => {
|
|
|
16915
16915
|
});
|
|
16916
16916
|
PluginConfigSchema = exports_external.object({
|
|
16917
16917
|
agents: exports_external.record(exports_external.string(), AgentOverrideConfigSchema).optional(),
|
|
16918
|
-
default_agent: exports_external.
|
|
16918
|
+
default_agent: exports_external.string().optional().transform((v) => {
|
|
16919
|
+
if (v === undefined)
|
|
16920
|
+
return;
|
|
16921
|
+
const trimmed = v.trim();
|
|
16922
|
+
return trimmed === "" ? undefined : trimmed;
|
|
16923
|
+
}),
|
|
16919
16924
|
swarms: exports_external.record(exports_external.string(), SwarmConfigSchema).optional(),
|
|
16920
16925
|
max_iterations: exports_external.number().min(1).max(10).default(5),
|
|
16921
16926
|
pipeline: PipelineConfigSchema.optional(),
|
package/dist/config/schema.d.ts
CHANGED
|
@@ -643,26 +643,7 @@ export declare const PluginConfigSchema: z.ZodObject<{
|
|
|
643
643
|
disabled: z.ZodOptional<z.ZodBoolean>;
|
|
644
644
|
fallback_models: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
645
645
|
}, z.core.$strip>>>;
|
|
646
|
-
default_agent: z.
|
|
647
|
-
architect: "architect";
|
|
648
|
-
sme: "sme";
|
|
649
|
-
docs: "docs";
|
|
650
|
-
designer: "designer";
|
|
651
|
-
critic_sounding_board: "critic_sounding_board";
|
|
652
|
-
critic_drift_verifier: "critic_drift_verifier";
|
|
653
|
-
critic_hallucination_verifier: "critic_hallucination_verifier";
|
|
654
|
-
curator_init: "curator_init";
|
|
655
|
-
curator_phase: "curator_phase";
|
|
656
|
-
council_generalist: "council_generalist";
|
|
657
|
-
council_skeptic: "council_skeptic";
|
|
658
|
-
council_domain_expert: "council_domain_expert";
|
|
659
|
-
reviewer: "reviewer";
|
|
660
|
-
critic: "critic";
|
|
661
|
-
critic_oversight: "critic_oversight";
|
|
662
|
-
explorer: "explorer";
|
|
663
|
-
coder: "coder";
|
|
664
|
-
test_engineer: "test_engineer";
|
|
665
|
-
}>>>;
|
|
646
|
+
default_agent: z.ZodPipe<z.ZodOptional<z.ZodString>, z.ZodTransform<string | undefined, string | undefined>>;
|
|
666
647
|
swarms: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
667
648
|
name: z.ZodOptional<z.ZodString>;
|
|
668
649
|
agents: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
@@ -4,6 +4,16 @@
|
|
|
4
4
|
* were modified, or null if in-scope, no scope declared, or git unavailable.
|
|
5
5
|
* Never throws.
|
|
6
6
|
*/
|
|
7
|
+
import { bunSpawn } from '../utils/bun-compat';
|
|
8
|
+
/**
|
|
9
|
+
* Test-only dependency-injection seam — see `gitignore-warning.ts:_internals`
|
|
10
|
+
* for the rationale (`mock.module` from `bun:test` leaks across files in
|
|
11
|
+
* Bun's shared test-runner process). Mutating this local object is
|
|
12
|
+
* file-scoped and trivially restorable via `afterEach`.
|
|
13
|
+
*/
|
|
14
|
+
export declare const _internals: {
|
|
15
|
+
bunSpawn: typeof bunSpawn;
|
|
16
|
+
};
|
|
7
17
|
/**
|
|
8
18
|
* Validate that git-changed files match the declared scope for a task.
|
|
9
19
|
* Returns a warning string if undeclared files were modified, null otherwise.
|
package/dist/index.js
CHANGED
|
@@ -33,7 +33,7 @@ var package_default;
|
|
|
33
33
|
var init_package = __esm(() => {
|
|
34
34
|
package_default = {
|
|
35
35
|
name: "opencode-swarm",
|
|
36
|
-
version: "7.3.
|
|
36
|
+
version: "7.3.5",
|
|
37
37
|
description: "Architect-centric agentic swarm plugin for OpenCode - hub-and-spoke orchestration with SME consultation, code generation, and QA review",
|
|
38
38
|
main: "dist/index.js",
|
|
39
39
|
types: "dist/index.d.ts",
|
|
@@ -15352,7 +15352,12 @@ var init_schema = __esm(() => {
|
|
|
15352
15352
|
});
|
|
15353
15353
|
PluginConfigSchema = exports_external.object({
|
|
15354
15354
|
agents: exports_external.record(exports_external.string(), AgentOverrideConfigSchema).optional(),
|
|
15355
|
-
default_agent: exports_external.
|
|
15355
|
+
default_agent: exports_external.string().optional().transform((v) => {
|
|
15356
|
+
if (v === undefined)
|
|
15357
|
+
return;
|
|
15358
|
+
const trimmed = v.trim();
|
|
15359
|
+
return trimmed === "" ? undefined : trimmed;
|
|
15360
|
+
}),
|
|
15356
15361
|
swarms: exports_external.record(exports_external.string(), SwarmConfigSchema).optional(),
|
|
15357
15362
|
max_iterations: exports_external.number().min(1).max(10).default(5),
|
|
15358
15363
|
pipeline: PipelineConfigSchema.optional(),
|
|
@@ -59986,6 +59991,52 @@ function createAgents(config3) {
|
|
|
59986
59991
|
}
|
|
59987
59992
|
return allAgents;
|
|
59988
59993
|
}
|
|
59994
|
+
function resolvePrimaryAgentNames(agentNames, defaultAgent) {
|
|
59995
|
+
const collectArchitectRole = () => agentNames.filter((n) => stripKnownSwarmPrefix(n) === "architect");
|
|
59996
|
+
const trimmed = typeof defaultAgent === "string" ? defaultAgent.trim() : undefined;
|
|
59997
|
+
const value = trimmed === "" ? undefined : trimmed;
|
|
59998
|
+
if (agentNames.length === 0) {
|
|
59999
|
+
return { primaryNames: new Set, reason: "implicit-architects" };
|
|
60000
|
+
}
|
|
60001
|
+
if (value === undefined) {
|
|
60002
|
+
const architects2 = collectArchitectRole();
|
|
60003
|
+
if (architects2.length > 0) {
|
|
60004
|
+
return {
|
|
60005
|
+
primaryNames: new Set(architects2),
|
|
60006
|
+
reason: "implicit-architects"
|
|
60007
|
+
};
|
|
60008
|
+
}
|
|
60009
|
+
const first2 = agentNames[0];
|
|
60010
|
+
return {
|
|
60011
|
+
primaryNames: new Set([first2]),
|
|
60012
|
+
reason: "fallback-first",
|
|
60013
|
+
warning: `[swarm] No architect-role agents are registered and default_agent is unset; falling back to '${first2}' as primary. Re-enable an architect agent or set default_agent to silence this warning.`
|
|
60014
|
+
};
|
|
60015
|
+
}
|
|
60016
|
+
if (ALL_AGENT_NAMES.includes(value)) {
|
|
60017
|
+
const matching = agentNames.filter((n) => stripKnownSwarmPrefix(n) === value);
|
|
60018
|
+
if (matching.length > 0) {
|
|
60019
|
+
return { primaryNames: new Set(matching), reason: "base-role" };
|
|
60020
|
+
}
|
|
60021
|
+
}
|
|
60022
|
+
if (agentNames.includes(value)) {
|
|
60023
|
+
return { primaryNames: new Set([value]), reason: "exact" };
|
|
60024
|
+
}
|
|
60025
|
+
const architects = collectArchitectRole();
|
|
60026
|
+
if (architects.length > 0) {
|
|
60027
|
+
return {
|
|
60028
|
+
primaryNames: new Set(architects),
|
|
60029
|
+
reason: "fallback-architects",
|
|
60030
|
+
warning: `[swarm] default_agent '${value}' did not match any registered agent; falling back to architect-role primaries: ${architects.join(", ")}.`
|
|
60031
|
+
};
|
|
60032
|
+
}
|
|
60033
|
+
const first = agentNames[0];
|
|
60034
|
+
return {
|
|
60035
|
+
primaryNames: new Set([first]),
|
|
60036
|
+
reason: "fallback-first",
|
|
60037
|
+
warning: `[swarm] default_agent '${value}' did not match any registered agent and no architect-role agents are registered; falling back to '${first}' as primary.`
|
|
60038
|
+
};
|
|
60039
|
+
}
|
|
59989
60040
|
function getAgentConfigs(config3, directory, sessionId) {
|
|
59990
60041
|
const agents = createAgents(config3);
|
|
59991
60042
|
const toolFilterEnabled = config3?.tool_filter?.enabled ?? true;
|
|
@@ -59993,21 +60044,29 @@ function getAgentConfigs(config3, directory, sessionId) {
|
|
|
59993
60044
|
const quiet = config3?.quiet ?? true;
|
|
59994
60045
|
const warnedMissingWhitelist = new Set;
|
|
59995
60046
|
const agentToolSnapshot = {};
|
|
60047
|
+
const resolution = resolvePrimaryAgentNames(agents.map((a) => a.name), config3?.default_agent);
|
|
60048
|
+
if (resolution.warning) {
|
|
60049
|
+
if (!quiet) {
|
|
60050
|
+
console.warn(resolution.warning);
|
|
60051
|
+
} else {
|
|
60052
|
+
addDeferredWarning(resolution.warning);
|
|
60053
|
+
}
|
|
60054
|
+
}
|
|
60055
|
+
if (agents.length > 0 && resolution.primaryNames.size === 0) {
|
|
60056
|
+
const generated = agents.map((a) => a.name).join(", ");
|
|
60057
|
+
const diagnostic = `[swarm] DIAGNOSTIC: ${agents.length} generated agents but zero primaries. Likely cause: a regression in resolvePrimaryAgentNames. Generated: ${generated}.`;
|
|
60058
|
+
if (!quiet) {
|
|
60059
|
+
console.warn(diagnostic);
|
|
60060
|
+
} else {
|
|
60061
|
+
addDeferredWarning(diagnostic);
|
|
60062
|
+
}
|
|
60063
|
+
}
|
|
59996
60064
|
const result = Object.fromEntries(agents.map((agent) => {
|
|
59997
60065
|
const sdkConfig = {
|
|
59998
60066
|
...agent.config,
|
|
59999
60067
|
description: agent.description
|
|
60000
60068
|
};
|
|
60001
|
-
|
|
60002
|
-
if (defaultAgent !== "architect" && !ALL_AGENT_NAMES.includes(defaultAgent)) {
|
|
60003
|
-
if (!quiet) {
|
|
60004
|
-
console.warn(`[swarm] Invalid default_agent '${defaultAgent}' — falling back to 'architect'. Valid values: ${ALL_AGENT_NAMES.join(", ")}`);
|
|
60005
|
-
} else {
|
|
60006
|
-
addDeferredWarning(`[swarm] Invalid default_agent '${defaultAgent}' — falling back to 'architect'. Valid values: ${ALL_AGENT_NAMES.join(", ")}`);
|
|
60007
|
-
}
|
|
60008
|
-
defaultAgent = "architect";
|
|
60009
|
-
}
|
|
60010
|
-
const isPrimaryAgent = agent.name === defaultAgent;
|
|
60069
|
+
const isPrimaryAgent = resolution.primaryNames.has(agent.name);
|
|
60011
60070
|
if (isPrimaryAgent) {
|
|
60012
60071
|
sdkConfig.mode = "primary";
|
|
60013
60072
|
sdkConfig.permission = { task: "allow" };
|
|
@@ -89610,19 +89669,141 @@ init_loader();
|
|
|
89610
89669
|
init_schema();
|
|
89611
89670
|
init_qa_gate_profile();
|
|
89612
89671
|
init_gate_evidence();
|
|
89672
|
+
import * as fs86 from "node:fs";
|
|
89673
|
+
import * as path106 from "node:path";
|
|
89674
|
+
|
|
89675
|
+
// src/hooks/diff-scope.ts
|
|
89676
|
+
init_bun_compat();
|
|
89613
89677
|
import * as fs85 from "node:fs";
|
|
89614
89678
|
import * as path105 from "node:path";
|
|
89615
89679
|
|
|
89616
|
-
// src/
|
|
89680
|
+
// src/utils/gitignore-warning.ts
|
|
89617
89681
|
init_bun_compat();
|
|
89618
89682
|
import * as fs84 from "node:fs";
|
|
89619
89683
|
import * as path104 from "node:path";
|
|
89684
|
+
var _internals = { bunSpawn };
|
|
89685
|
+
var _swarmGitExcludedChecked = false;
|
|
89686
|
+
function fileCoversSwarm(content) {
|
|
89687
|
+
for (const rawLine of content.split(`
|
|
89688
|
+
`)) {
|
|
89689
|
+
const line = rawLine.trim();
|
|
89690
|
+
if (line.startsWith("#") || line.length === 0)
|
|
89691
|
+
continue;
|
|
89692
|
+
if (line === ".swarm" || line === ".swarm/")
|
|
89693
|
+
return true;
|
|
89694
|
+
}
|
|
89695
|
+
return false;
|
|
89696
|
+
}
|
|
89697
|
+
var ENSURE_SWARM_GIT_EXCLUDED_OUTER_TIMEOUT_MS = 3000;
|
|
89698
|
+
var ENSURE_SWARM_GIT_EXCLUDED_PER_CALL_TIMEOUT_MS = 1500;
|
|
89699
|
+
var GIT_SPAWN_OPTIONS = {
|
|
89700
|
+
timeout: ENSURE_SWARM_GIT_EXCLUDED_PER_CALL_TIMEOUT_MS,
|
|
89701
|
+
stdin: "ignore",
|
|
89702
|
+
stdout: "pipe",
|
|
89703
|
+
stderr: "pipe"
|
|
89704
|
+
};
|
|
89705
|
+
async function ensureSwarmGitExcluded(directory, options = {}) {
|
|
89706
|
+
if (_swarmGitExcludedChecked)
|
|
89707
|
+
return;
|
|
89708
|
+
_swarmGitExcludedChecked = true;
|
|
89709
|
+
const { quiet = false } = options;
|
|
89710
|
+
try {
|
|
89711
|
+
const gitRootProc = _internals.bunSpawn(["git", "-C", directory, "rev-parse", "--show-toplevel"], GIT_SPAWN_OPTIONS);
|
|
89712
|
+
let gitRootExitCode;
|
|
89713
|
+
let gitRootOutput;
|
|
89714
|
+
try {
|
|
89715
|
+
[gitRootExitCode, gitRootOutput] = await Promise.all([
|
|
89716
|
+
gitRootProc.exited,
|
|
89717
|
+
gitRootProc.stdout.text()
|
|
89718
|
+
]);
|
|
89719
|
+
} finally {
|
|
89720
|
+
try {
|
|
89721
|
+
gitRootProc.kill();
|
|
89722
|
+
} catch {}
|
|
89723
|
+
}
|
|
89724
|
+
if (gitRootExitCode !== 0)
|
|
89725
|
+
return;
|
|
89726
|
+
const gitRoot = gitRootOutput.trim();
|
|
89727
|
+
if (!gitRoot)
|
|
89728
|
+
return;
|
|
89729
|
+
const excludePathProc = _internals.bunSpawn(["git", "-C", directory, "rev-parse", "--git-path", "info/exclude"], GIT_SPAWN_OPTIONS);
|
|
89730
|
+
let excludePathExitCode;
|
|
89731
|
+
let excludePathRaw;
|
|
89732
|
+
try {
|
|
89733
|
+
[excludePathExitCode, excludePathRaw] = await Promise.all([
|
|
89734
|
+
excludePathProc.exited,
|
|
89735
|
+
excludePathProc.stdout.text()
|
|
89736
|
+
]);
|
|
89737
|
+
} finally {
|
|
89738
|
+
try {
|
|
89739
|
+
excludePathProc.kill();
|
|
89740
|
+
} catch {}
|
|
89741
|
+
}
|
|
89742
|
+
if (excludePathExitCode !== 0)
|
|
89743
|
+
return;
|
|
89744
|
+
const excludeRelPath = excludePathRaw.trim();
|
|
89745
|
+
if (!excludeRelPath)
|
|
89746
|
+
return;
|
|
89747
|
+
const excludePath = path104.isAbsolute(excludeRelPath) ? excludeRelPath : path104.join(directory, excludeRelPath);
|
|
89748
|
+
const checkIgnoreProc = _internals.bunSpawn(["git", "-C", directory, "check-ignore", "-q", ".swarm/.gitkeep"], GIT_SPAWN_OPTIONS);
|
|
89749
|
+
let checkIgnoreExitCode;
|
|
89750
|
+
try {
|
|
89751
|
+
checkIgnoreExitCode = await checkIgnoreProc.exited;
|
|
89752
|
+
} finally {
|
|
89753
|
+
try {
|
|
89754
|
+
checkIgnoreProc.kill();
|
|
89755
|
+
} catch {}
|
|
89756
|
+
}
|
|
89757
|
+
if (checkIgnoreExitCode !== 0) {
|
|
89758
|
+
try {
|
|
89759
|
+
fs84.mkdirSync(path104.dirname(excludePath), { recursive: true });
|
|
89760
|
+
let existing = "";
|
|
89761
|
+
try {
|
|
89762
|
+
existing = fs84.readFileSync(excludePath, "utf8");
|
|
89763
|
+
} catch {}
|
|
89764
|
+
if (!fileCoversSwarm(existing)) {
|
|
89765
|
+
fs84.appendFileSync(excludePath, `
|
|
89766
|
+
# opencode-swarm local runtime state
|
|
89767
|
+
.swarm/
|
|
89768
|
+
`, "utf8");
|
|
89769
|
+
if (!quiet) {
|
|
89770
|
+
console.warn("[opencode-swarm] Added .swarm/ to .git/info/exclude to prevent runtime state from appearing in git status.");
|
|
89771
|
+
}
|
|
89772
|
+
}
|
|
89773
|
+
} catch {}
|
|
89774
|
+
}
|
|
89775
|
+
const trackedProc = _internals.bunSpawn(["git", "-C", directory, "ls-files", "--", ".swarm"], GIT_SPAWN_OPTIONS);
|
|
89776
|
+
let trackedExitCode;
|
|
89777
|
+
let trackedOutput;
|
|
89778
|
+
try {
|
|
89779
|
+
[trackedExitCode, trackedOutput] = await Promise.all([
|
|
89780
|
+
trackedProc.exited,
|
|
89781
|
+
trackedProc.stdout.text()
|
|
89782
|
+
]);
|
|
89783
|
+
} finally {
|
|
89784
|
+
try {
|
|
89785
|
+
trackedProc.kill();
|
|
89786
|
+
} catch {}
|
|
89787
|
+
}
|
|
89788
|
+
if (trackedExitCode === 0 && trackedOutput.trim().length > 0) {
|
|
89789
|
+
console.warn(`[opencode-swarm] WARNING: .swarm/ files are tracked by Git.
|
|
89790
|
+
` + `.swarm/ contains local runtime state and may contain sensitive session data.
|
|
89791
|
+
` + `Ignoring will not affect already-tracked files. To stop tracking them, run:
|
|
89792
|
+
` + ` git rm -r --cached .swarm
|
|
89793
|
+
` + ` echo ".swarm/" >> .gitignore
|
|
89794
|
+
` + ' git commit -m "Stop tracking opencode-swarm runtime state"');
|
|
89795
|
+
}
|
|
89796
|
+
} catch {}
|
|
89797
|
+
}
|
|
89798
|
+
|
|
89799
|
+
// src/hooks/diff-scope.ts
|
|
89800
|
+
var _internals2 = { bunSpawn };
|
|
89620
89801
|
function getDeclaredScope(taskId, directory) {
|
|
89621
89802
|
try {
|
|
89622
|
-
const planPath =
|
|
89623
|
-
if (!
|
|
89803
|
+
const planPath = path105.join(directory, ".swarm", "plan.json");
|
|
89804
|
+
if (!fs85.existsSync(planPath))
|
|
89624
89805
|
return null;
|
|
89625
|
-
const raw =
|
|
89806
|
+
const raw = fs85.readFileSync(planPath, "utf-8");
|
|
89626
89807
|
const plan = JSON.parse(raw);
|
|
89627
89808
|
for (const phase of plan.phases ?? []) {
|
|
89628
89809
|
for (const task of phase.tasks ?? []) {
|
|
@@ -89643,30 +89824,47 @@ function getDeclaredScope(taskId, directory) {
|
|
|
89643
89824
|
return null;
|
|
89644
89825
|
}
|
|
89645
89826
|
}
|
|
89827
|
+
var GIT_DIFF_SPAWN_OPTIONS = {
|
|
89828
|
+
timeout: ENSURE_SWARM_GIT_EXCLUDED_PER_CALL_TIMEOUT_MS,
|
|
89829
|
+
stdin: "ignore",
|
|
89830
|
+
stdout: "pipe",
|
|
89831
|
+
stderr: "pipe"
|
|
89832
|
+
};
|
|
89646
89833
|
async function getChangedFiles(directory) {
|
|
89647
89834
|
try {
|
|
89648
|
-
const proc = bunSpawn(["git", "diff", "--name-only", "HEAD~1"], {
|
|
89835
|
+
const proc = _internals2.bunSpawn(["git", "diff", "--name-only", "HEAD~1"], {
|
|
89649
89836
|
cwd: directory,
|
|
89650
|
-
|
|
89651
|
-
stderr: "pipe"
|
|
89837
|
+
...GIT_DIFF_SPAWN_OPTIONS
|
|
89652
89838
|
});
|
|
89653
|
-
|
|
89654
|
-
|
|
89655
|
-
|
|
89656
|
-
|
|
89839
|
+
let exitCode;
|
|
89840
|
+
let stdout;
|
|
89841
|
+
try {
|
|
89842
|
+
[exitCode, stdout] = await Promise.all([proc.exited, proc.stdout.text()]);
|
|
89843
|
+
} finally {
|
|
89844
|
+
try {
|
|
89845
|
+
proc.kill();
|
|
89846
|
+
} catch {}
|
|
89847
|
+
}
|
|
89657
89848
|
if (exitCode === 0) {
|
|
89658
89849
|
return stdout.trim().split(`
|
|
89659
89850
|
`).map((f) => f.trim()).filter((f) => f.length > 0);
|
|
89660
89851
|
}
|
|
89661
|
-
const proc2 = bunSpawn(["git", "diff", "--name-only", "HEAD"], {
|
|
89852
|
+
const proc2 = _internals2.bunSpawn(["git", "diff", "--name-only", "HEAD"], {
|
|
89662
89853
|
cwd: directory,
|
|
89663
|
-
|
|
89664
|
-
stderr: "pipe"
|
|
89854
|
+
...GIT_DIFF_SPAWN_OPTIONS
|
|
89665
89855
|
});
|
|
89666
|
-
|
|
89667
|
-
|
|
89668
|
-
|
|
89669
|
-
|
|
89856
|
+
let exitCode2;
|
|
89857
|
+
let stdout2;
|
|
89858
|
+
try {
|
|
89859
|
+
[exitCode2, stdout2] = await Promise.all([
|
|
89860
|
+
proc2.exited,
|
|
89861
|
+
proc2.stdout.text()
|
|
89862
|
+
]);
|
|
89863
|
+
} finally {
|
|
89864
|
+
try {
|
|
89865
|
+
proc2.kill();
|
|
89866
|
+
} catch {}
|
|
89867
|
+
}
|
|
89670
89868
|
if (exitCode2 === 0) {
|
|
89671
89869
|
return stdout2.trim().split(`
|
|
89672
89870
|
`).map((f) => f.trim()).filter((f) => f.length > 0);
|
|
@@ -89739,7 +89937,7 @@ var TIER_3_PATTERNS = [
|
|
|
89739
89937
|
];
|
|
89740
89938
|
function matchesTier3Pattern(files) {
|
|
89741
89939
|
for (const file3 of files) {
|
|
89742
|
-
const fileName =
|
|
89940
|
+
const fileName = path106.basename(file3);
|
|
89743
89941
|
for (const pattern of TIER_3_PATTERNS) {
|
|
89744
89942
|
if (pattern.test(fileName)) {
|
|
89745
89943
|
return true;
|
|
@@ -89753,8 +89951,8 @@ function checkReviewerGate(taskId, workingDirectory, stageBParallelEnabled = fal
|
|
|
89753
89951
|
if (hasActiveTurboMode()) {
|
|
89754
89952
|
const resolvedDir2 = workingDirectory;
|
|
89755
89953
|
try {
|
|
89756
|
-
const planPath =
|
|
89757
|
-
const planRaw =
|
|
89954
|
+
const planPath = path106.join(resolvedDir2, ".swarm", "plan.json");
|
|
89955
|
+
const planRaw = fs86.readFileSync(planPath, "utf-8");
|
|
89758
89956
|
const plan = JSON.parse(planRaw);
|
|
89759
89957
|
for (const planPhase of plan.phases ?? []) {
|
|
89760
89958
|
for (const task of planPhase.tasks ?? []) {
|
|
@@ -89823,8 +90021,8 @@ function checkReviewerGate(taskId, workingDirectory, stageBParallelEnabled = fal
|
|
|
89823
90021
|
}
|
|
89824
90022
|
try {
|
|
89825
90023
|
const resolvedDir2 = workingDirectory;
|
|
89826
|
-
const planPath =
|
|
89827
|
-
const planRaw =
|
|
90024
|
+
const planPath = path106.join(resolvedDir2, ".swarm", "plan.json");
|
|
90025
|
+
const planRaw = fs86.readFileSync(planPath, "utf-8");
|
|
89828
90026
|
const plan = JSON.parse(planRaw);
|
|
89829
90027
|
for (const planPhase of plan.phases ?? []) {
|
|
89830
90028
|
for (const task of planPhase.tasks ?? []) {
|
|
@@ -90013,8 +90211,8 @@ async function executeUpdateTaskStatus(args2, fallbackDir) {
|
|
|
90013
90211
|
};
|
|
90014
90212
|
}
|
|
90015
90213
|
}
|
|
90016
|
-
normalizedDir =
|
|
90017
|
-
const pathParts = normalizedDir.split(
|
|
90214
|
+
normalizedDir = path106.normalize(args2.working_directory);
|
|
90215
|
+
const pathParts = normalizedDir.split(path106.sep);
|
|
90018
90216
|
if (pathParts.includes("..")) {
|
|
90019
90217
|
return {
|
|
90020
90218
|
success: false,
|
|
@@ -90024,11 +90222,11 @@ async function executeUpdateTaskStatus(args2, fallbackDir) {
|
|
|
90024
90222
|
]
|
|
90025
90223
|
};
|
|
90026
90224
|
}
|
|
90027
|
-
const resolvedDir =
|
|
90225
|
+
const resolvedDir = path106.resolve(normalizedDir);
|
|
90028
90226
|
try {
|
|
90029
|
-
const realPath =
|
|
90030
|
-
const planPath =
|
|
90031
|
-
if (!
|
|
90227
|
+
const realPath = fs86.realpathSync(resolvedDir);
|
|
90228
|
+
const planPath = path106.join(realPath, ".swarm", "plan.json");
|
|
90229
|
+
if (!fs86.existsSync(planPath)) {
|
|
90032
90230
|
return {
|
|
90033
90231
|
success: false,
|
|
90034
90232
|
message: `Invalid working_directory: plan not found in "${realPath}"`,
|
|
@@ -90059,22 +90257,22 @@ async function executeUpdateTaskStatus(args2, fallbackDir) {
|
|
|
90059
90257
|
}
|
|
90060
90258
|
if (args2.status === "in_progress") {
|
|
90061
90259
|
try {
|
|
90062
|
-
const evidencePath =
|
|
90063
|
-
|
|
90064
|
-
const fd =
|
|
90260
|
+
const evidencePath = path106.join(directory, ".swarm", "evidence", `${args2.task_id}.json`);
|
|
90261
|
+
fs86.mkdirSync(path106.dirname(evidencePath), { recursive: true });
|
|
90262
|
+
const fd = fs86.openSync(evidencePath, "wx");
|
|
90065
90263
|
let writeOk = false;
|
|
90066
90264
|
try {
|
|
90067
|
-
|
|
90265
|
+
fs86.writeSync(fd, JSON.stringify({
|
|
90068
90266
|
taskId: args2.task_id,
|
|
90069
90267
|
required_gates: ["reviewer", "test_engineer"],
|
|
90070
90268
|
gates: {}
|
|
90071
90269
|
}, null, 2));
|
|
90072
90270
|
writeOk = true;
|
|
90073
90271
|
} finally {
|
|
90074
|
-
|
|
90272
|
+
fs86.closeSync(fd);
|
|
90075
90273
|
if (!writeOk) {
|
|
90076
90274
|
try {
|
|
90077
|
-
|
|
90275
|
+
fs86.unlinkSync(evidencePath);
|
|
90078
90276
|
} catch {}
|
|
90079
90277
|
}
|
|
90080
90278
|
}
|
|
@@ -90084,8 +90282,8 @@ async function executeUpdateTaskStatus(args2, fallbackDir) {
|
|
|
90084
90282
|
recoverTaskStateFromDelegations(args2.task_id);
|
|
90085
90283
|
let phaseRequiresReviewer = true;
|
|
90086
90284
|
try {
|
|
90087
|
-
const planPath =
|
|
90088
|
-
const planRaw =
|
|
90285
|
+
const planPath = path106.join(directory, ".swarm", "plan.json");
|
|
90286
|
+
const planRaw = fs86.readFileSync(planPath, "utf-8");
|
|
90089
90287
|
const plan = JSON.parse(planRaw);
|
|
90090
90288
|
const taskPhase = plan.phases.find((p) => p.tasks.some((t) => t.id === args2.task_id));
|
|
90091
90289
|
if (taskPhase?.required_agents && !taskPhase.required_agents.includes("reviewer")) {
|
|
@@ -90395,8 +90593,8 @@ init_utils2();
|
|
|
90395
90593
|
init_ledger();
|
|
90396
90594
|
init_manager();
|
|
90397
90595
|
init_create_tool();
|
|
90398
|
-
import
|
|
90399
|
-
import
|
|
90596
|
+
import fs87 from "node:fs";
|
|
90597
|
+
import path107 from "node:path";
|
|
90400
90598
|
function derivePlanId5(plan) {
|
|
90401
90599
|
return `${plan.swarm}-${plan.title}`.replace(/[^a-zA-Z0-9-_]/g, "_");
|
|
90402
90600
|
}
|
|
@@ -90447,7 +90645,7 @@ async function executeWriteDriftEvidence(args2, directory) {
|
|
|
90447
90645
|
entries: [evidenceEntry]
|
|
90448
90646
|
};
|
|
90449
90647
|
const filename = "drift-verifier.json";
|
|
90450
|
-
const relativePath =
|
|
90648
|
+
const relativePath = path107.join("evidence", String(phase), filename);
|
|
90451
90649
|
let validatedPath;
|
|
90452
90650
|
try {
|
|
90453
90651
|
validatedPath = validateSwarmPath(directory, relativePath);
|
|
@@ -90458,12 +90656,12 @@ async function executeWriteDriftEvidence(args2, directory) {
|
|
|
90458
90656
|
message: error93 instanceof Error ? error93.message : "Failed to validate path"
|
|
90459
90657
|
}, null, 2);
|
|
90460
90658
|
}
|
|
90461
|
-
const evidenceDir =
|
|
90659
|
+
const evidenceDir = path107.dirname(validatedPath);
|
|
90462
90660
|
try {
|
|
90463
|
-
await
|
|
90464
|
-
const tempPath =
|
|
90465
|
-
await
|
|
90466
|
-
await
|
|
90661
|
+
await fs87.promises.mkdir(evidenceDir, { recursive: true });
|
|
90662
|
+
const tempPath = path107.join(evidenceDir, `.${filename}.tmp`);
|
|
90663
|
+
await fs87.promises.writeFile(tempPath, JSON.stringify(evidenceContent, null, 2), "utf-8");
|
|
90664
|
+
await fs87.promises.rename(tempPath, validatedPath);
|
|
90467
90665
|
let snapshotInfo;
|
|
90468
90666
|
let snapshotError;
|
|
90469
90667
|
let qaProfileLocked;
|
|
@@ -90557,8 +90755,8 @@ var write_drift_evidence = createSwarmTool({
|
|
|
90557
90755
|
init_zod();
|
|
90558
90756
|
init_utils2();
|
|
90559
90757
|
init_create_tool();
|
|
90560
|
-
import
|
|
90561
|
-
import
|
|
90758
|
+
import fs88 from "node:fs";
|
|
90759
|
+
import path108 from "node:path";
|
|
90562
90760
|
function normalizeVerdict2(verdict) {
|
|
90563
90761
|
switch (verdict) {
|
|
90564
90762
|
case "APPROVED":
|
|
@@ -90606,7 +90804,7 @@ async function executeWriteHallucinationEvidence(args2, directory) {
|
|
|
90606
90804
|
entries: [evidenceEntry]
|
|
90607
90805
|
};
|
|
90608
90806
|
const filename = "hallucination-guard.json";
|
|
90609
|
-
const relativePath =
|
|
90807
|
+
const relativePath = path108.join("evidence", String(phase), filename);
|
|
90610
90808
|
let validatedPath;
|
|
90611
90809
|
try {
|
|
90612
90810
|
validatedPath = validateSwarmPath(directory, relativePath);
|
|
@@ -90617,12 +90815,12 @@ async function executeWriteHallucinationEvidence(args2, directory) {
|
|
|
90617
90815
|
message: error93 instanceof Error ? error93.message : "Failed to validate path"
|
|
90618
90816
|
}, null, 2);
|
|
90619
90817
|
}
|
|
90620
|
-
const evidenceDir =
|
|
90818
|
+
const evidenceDir = path108.dirname(validatedPath);
|
|
90621
90819
|
try {
|
|
90622
|
-
await
|
|
90623
|
-
const tempPath =
|
|
90624
|
-
await
|
|
90625
|
-
await
|
|
90820
|
+
await fs88.promises.mkdir(evidenceDir, { recursive: true });
|
|
90821
|
+
const tempPath = path108.join(evidenceDir, `.${filename}.tmp`);
|
|
90822
|
+
await fs88.promises.writeFile(tempPath, JSON.stringify(evidenceContent, null, 2), "utf-8");
|
|
90823
|
+
await fs88.promises.rename(tempPath, validatedPath);
|
|
90626
90824
|
return JSON.stringify({
|
|
90627
90825
|
success: true,
|
|
90628
90826
|
phase,
|
|
@@ -90668,8 +90866,8 @@ var write_hallucination_evidence = createSwarmTool({
|
|
|
90668
90866
|
init_zod();
|
|
90669
90867
|
init_utils2();
|
|
90670
90868
|
init_create_tool();
|
|
90671
|
-
import
|
|
90672
|
-
import
|
|
90869
|
+
import fs89 from "node:fs";
|
|
90870
|
+
import path109 from "node:path";
|
|
90673
90871
|
function normalizeVerdict3(verdict) {
|
|
90674
90872
|
switch (verdict) {
|
|
90675
90873
|
case "PASS":
|
|
@@ -90743,7 +90941,7 @@ async function executeWriteMutationEvidence(args2, directory) {
|
|
|
90743
90941
|
entries: [evidenceEntry]
|
|
90744
90942
|
};
|
|
90745
90943
|
const filename = "mutation-gate.json";
|
|
90746
|
-
const relativePath =
|
|
90944
|
+
const relativePath = path109.join("evidence", String(phase), filename);
|
|
90747
90945
|
let validatedPath;
|
|
90748
90946
|
try {
|
|
90749
90947
|
validatedPath = validateSwarmPath(directory, relativePath);
|
|
@@ -90754,12 +90952,12 @@ async function executeWriteMutationEvidence(args2, directory) {
|
|
|
90754
90952
|
message: error93 instanceof Error ? error93.message : "Failed to validate path"
|
|
90755
90953
|
}, null, 2);
|
|
90756
90954
|
}
|
|
90757
|
-
const evidenceDir =
|
|
90955
|
+
const evidenceDir = path109.dirname(validatedPath);
|
|
90758
90956
|
try {
|
|
90759
|
-
await
|
|
90760
|
-
const tempPath =
|
|
90761
|
-
await
|
|
90762
|
-
await
|
|
90957
|
+
await fs89.promises.mkdir(evidenceDir, { recursive: true });
|
|
90958
|
+
const tempPath = path109.join(evidenceDir, `.${filename}.tmp`);
|
|
90959
|
+
await fs89.promises.writeFile(tempPath, JSON.stringify(evidenceContent, null, 2), "utf-8");
|
|
90960
|
+
await fs89.promises.rename(tempPath, validatedPath);
|
|
90763
90961
|
return JSON.stringify({
|
|
90764
90962
|
success: true,
|
|
90765
90963
|
phase,
|
|
@@ -90812,85 +91010,6 @@ init_write_retro();
|
|
|
90812
91010
|
// src/index.ts
|
|
90813
91011
|
init_utils();
|
|
90814
91012
|
|
|
90815
|
-
// src/utils/gitignore-warning.ts
|
|
90816
|
-
init_bun_compat();
|
|
90817
|
-
import * as fs89 from "node:fs";
|
|
90818
|
-
import * as path109 from "node:path";
|
|
90819
|
-
var _swarmGitExcludedChecked = false;
|
|
90820
|
-
function fileCoversSwarm(content) {
|
|
90821
|
-
for (const rawLine of content.split(`
|
|
90822
|
-
`)) {
|
|
90823
|
-
const line = rawLine.trim();
|
|
90824
|
-
if (line.startsWith("#") || line.length === 0)
|
|
90825
|
-
continue;
|
|
90826
|
-
if (line === ".swarm" || line === ".swarm/")
|
|
90827
|
-
return true;
|
|
90828
|
-
}
|
|
90829
|
-
return false;
|
|
90830
|
-
}
|
|
90831
|
-
async function ensureSwarmGitExcluded(directory, options = {}) {
|
|
90832
|
-
if (_swarmGitExcludedChecked)
|
|
90833
|
-
return;
|
|
90834
|
-
_swarmGitExcludedChecked = true;
|
|
90835
|
-
const { quiet = false } = options;
|
|
90836
|
-
try {
|
|
90837
|
-
const gitRootProc = bunSpawn(["git", "-C", directory, "rev-parse", "--show-toplevel"], { stdout: "pipe", stderr: "pipe" });
|
|
90838
|
-
const [gitRootExitCode, gitRootOutput] = await Promise.all([
|
|
90839
|
-
gitRootProc.exited,
|
|
90840
|
-
gitRootProc.stdout.text()
|
|
90841
|
-
]);
|
|
90842
|
-
if (gitRootExitCode !== 0)
|
|
90843
|
-
return;
|
|
90844
|
-
const gitRoot = gitRootOutput.trim();
|
|
90845
|
-
if (!gitRoot)
|
|
90846
|
-
return;
|
|
90847
|
-
const excludePathProc = bunSpawn(["git", "-C", directory, "rev-parse", "--git-path", "info/exclude"], { stdout: "pipe", stderr: "pipe" });
|
|
90848
|
-
const [excludePathExitCode, excludePathRaw] = await Promise.all([
|
|
90849
|
-
excludePathProc.exited,
|
|
90850
|
-
excludePathProc.stdout.text()
|
|
90851
|
-
]);
|
|
90852
|
-
if (excludePathExitCode !== 0)
|
|
90853
|
-
return;
|
|
90854
|
-
const excludeRelPath = excludePathRaw.trim();
|
|
90855
|
-
if (!excludeRelPath)
|
|
90856
|
-
return;
|
|
90857
|
-
const excludePath = path109.isAbsolute(excludeRelPath) ? excludeRelPath : path109.join(directory, excludeRelPath);
|
|
90858
|
-
const checkIgnoreProc = bunSpawn(["git", "-C", directory, "check-ignore", "-q", ".swarm/.gitkeep"], { stdout: "pipe", stderr: "pipe" });
|
|
90859
|
-
const checkIgnoreExitCode = await checkIgnoreProc.exited;
|
|
90860
|
-
if (checkIgnoreExitCode !== 0) {
|
|
90861
|
-
try {
|
|
90862
|
-
fs89.mkdirSync(path109.dirname(excludePath), { recursive: true });
|
|
90863
|
-
let existing = "";
|
|
90864
|
-
try {
|
|
90865
|
-
existing = fs89.readFileSync(excludePath, "utf8");
|
|
90866
|
-
} catch {}
|
|
90867
|
-
if (!fileCoversSwarm(existing)) {
|
|
90868
|
-
fs89.appendFileSync(excludePath, `
|
|
90869
|
-
# opencode-swarm local runtime state
|
|
90870
|
-
.swarm/
|
|
90871
|
-
`, "utf8");
|
|
90872
|
-
if (!quiet) {
|
|
90873
|
-
console.warn("[opencode-swarm] Added .swarm/ to .git/info/exclude to prevent runtime state from appearing in git status.");
|
|
90874
|
-
}
|
|
90875
|
-
}
|
|
90876
|
-
} catch {}
|
|
90877
|
-
}
|
|
90878
|
-
const trackedProc = bunSpawn(["git", "-C", directory, "ls-files", "--", ".swarm"], { stdout: "pipe", stderr: "pipe" });
|
|
90879
|
-
const [trackedExitCode, trackedOutput] = await Promise.all([
|
|
90880
|
-
trackedProc.exited,
|
|
90881
|
-
trackedProc.stdout.text()
|
|
90882
|
-
]);
|
|
90883
|
-
if (trackedExitCode === 0 && trackedOutput.trim().length > 0) {
|
|
90884
|
-
console.warn(`[opencode-swarm] WARNING: .swarm/ files are tracked by Git.
|
|
90885
|
-
` + `.swarm/ contains local runtime state and may contain sensitive session data.
|
|
90886
|
-
` + `Ignoring will not affect already-tracked files. To stop tracking them, run:
|
|
90887
|
-
` + ` git rm -r --cached .swarm
|
|
90888
|
-
` + ` echo ".swarm/" >> .gitignore
|
|
90889
|
-
` + ' git commit -m "Stop tracking opencode-swarm runtime state"');
|
|
90890
|
-
}
|
|
90891
|
-
} catch {}
|
|
90892
|
-
}
|
|
90893
|
-
|
|
90894
91013
|
// src/utils/tool-output.ts
|
|
90895
91014
|
function truncateToolOutput(output, maxLines, toolName, tailLines = 10) {
|
|
90896
91015
|
if (!output) {
|
|
@@ -90994,7 +91113,12 @@ async function initializeOpenCodeSwarm(ctx) {
|
|
|
90994
91113
|
}
|
|
90995
91114
|
repoGraphHook.init().catch(() => {}).finally(() => clearTimeout(watchdog));
|
|
90996
91115
|
});
|
|
90997
|
-
await ensureSwarmGitExcluded(ctx.directory, { quiet: config3.quiet });
|
|
91116
|
+
await withTimeout(ensureSwarmGitExcluded(ctx.directory, { quiet: config3.quiet }), ENSURE_SWARM_GIT_EXCLUDED_OUTER_TIMEOUT_MS, new Error(`ensureSwarmGitExcluded exceeded ${ENSURE_SWARM_GIT_EXCLUDED_OUTER_TIMEOUT_MS}ms budget; continuing without git-hygiene check`)).catch((err3) => {
|
|
91117
|
+
const msg = err3 instanceof Error ? err3.message : String(err3);
|
|
91118
|
+
log("ensureSwarmGitExcluded timed out or failed (non-fatal)", {
|
|
91119
|
+
error: msg
|
|
91120
|
+
});
|
|
91121
|
+
});
|
|
90998
91122
|
initTelemetry(ctx.directory);
|
|
90999
91123
|
writeSwarmConfigExampleIfNew(ctx.directory);
|
|
91000
91124
|
writeProjectConfigIfNew(ctx.directory, config3.quiet);
|
|
@@ -1,3 +1,15 @@
|
|
|
1
|
+
import { bunSpawn } from './bun-compat';
|
|
2
|
+
/**
|
|
3
|
+
* Test-only dependency-injection seam. Production code calls
|
|
4
|
+
* `_internals.bunSpawn(...)` so tests can replace the function on this object
|
|
5
|
+
* without touching the real `./bun-compat` module — `mock.module` from
|
|
6
|
+
* `bun:test` leaks across files in Bun's shared test-runner process, which
|
|
7
|
+
* would corrupt unrelated suites that import `bun-compat`. Mutating this
|
|
8
|
+
* local object is file-scoped and trivially restorable via `afterEach`.
|
|
9
|
+
*/
|
|
10
|
+
export declare const _internals: {
|
|
11
|
+
bunSpawn: typeof bunSpawn;
|
|
12
|
+
};
|
|
1
13
|
/**
|
|
2
14
|
* Module-level flag so the warning fires at most once per process.
|
|
3
15
|
* Exported for test reset purposes only — do not use in production code.
|
|
@@ -30,6 +42,31 @@ export declare function warnIfSwarmNotGitignored(directory: string, quiet?: bool
|
|
|
30
42
|
export interface EnsureSwarmGitExcludedOptions {
|
|
31
43
|
quiet?: boolean;
|
|
32
44
|
}
|
|
45
|
+
/**
|
|
46
|
+
* Hard upper bound on the entire `ensureSwarmGitExcluded` operation when
|
|
47
|
+
* called from plugin init. The plugin host (OpenCode TUI / Desktop) will
|
|
48
|
+
* silently drop a plugin whose entry never resolves (issue #704); every
|
|
49
|
+
* awaited call on the init path therefore has an obligation to be bounded.
|
|
50
|
+
*
|
|
51
|
+
* 3_000 ms is ~30× the realistic worst-case duration on a healthy host (all
|
|
52
|
+
* four `git` calls land in well under 200 ms in aggregate) and ~6× the
|
|
53
|
+
* per-call budget below. Slower-than-3 s hosts are pathological (NFS-stalled
|
|
54
|
+
* `.git`, antivirus quarantine) and we deliberately fail-open: a debug log
|
|
55
|
+
* is emitted and the plugin continues to load without the hygiene exclude.
|
|
56
|
+
*/
|
|
57
|
+
export declare const ENSURE_SWARM_GIT_EXCLUDED_OUTER_TIMEOUT_MS = 3000;
|
|
58
|
+
/**
|
|
59
|
+
* Hard upper bound on each individual `git` subprocess invoked by
|
|
60
|
+
* `ensureSwarmGitExcluded` (and reused by `validateDiffScope`). Both Bun's
|
|
61
|
+
* `Bun.spawn` and the Node fallback in `bunSpawn` honor this `timeout`
|
|
62
|
+
* option and kill the child on expiry (`bun-compat.ts` Node fallback calls
|
|
63
|
+
* `proc.kill('SIGKILL')`; Bun kills via `killSignal`).
|
|
64
|
+
*
|
|
65
|
+
* 1_500 ms gives a ~30× margin over the realistic worst case and is well
|
|
66
|
+
* below the outer wrapper budget so the inner kills fire first on a
|
|
67
|
+
* pathological host.
|
|
68
|
+
*/
|
|
69
|
+
export declare const ENSURE_SWARM_GIT_EXCLUDED_PER_CALL_TIMEOUT_MS = 1500;
|
|
33
70
|
/**
|
|
34
71
|
* Automatically protect `.swarm/` from Git pollution before any `.swarm/` write.
|
|
35
72
|
*
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opencode-swarm",
|
|
3
|
-
"version": "7.3.
|
|
3
|
+
"version": "7.3.5",
|
|
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",
|