opencode-swarm 7.3.3 → 7.3.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/index.js +1 -1
- package/dist/hooks/diff-scope.d.ts +10 -0
- package/dist/index.js +207 -142
- package/dist/utils/gitignore-warning.d.ts +37 -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.3.
|
|
37
|
+
version: "7.3.4",
|
|
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",
|
|
@@ -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.4",
|
|
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",
|
|
@@ -89610,19 +89610,141 @@ init_loader();
|
|
|
89610
89610
|
init_schema();
|
|
89611
89611
|
init_qa_gate_profile();
|
|
89612
89612
|
init_gate_evidence();
|
|
89613
|
+
import * as fs86 from "node:fs";
|
|
89614
|
+
import * as path106 from "node:path";
|
|
89615
|
+
|
|
89616
|
+
// src/hooks/diff-scope.ts
|
|
89617
|
+
init_bun_compat();
|
|
89613
89618
|
import * as fs85 from "node:fs";
|
|
89614
89619
|
import * as path105 from "node:path";
|
|
89615
89620
|
|
|
89616
|
-
// src/
|
|
89621
|
+
// src/utils/gitignore-warning.ts
|
|
89617
89622
|
init_bun_compat();
|
|
89618
89623
|
import * as fs84 from "node:fs";
|
|
89619
89624
|
import * as path104 from "node:path";
|
|
89625
|
+
var _internals = { bunSpawn };
|
|
89626
|
+
var _swarmGitExcludedChecked = false;
|
|
89627
|
+
function fileCoversSwarm(content) {
|
|
89628
|
+
for (const rawLine of content.split(`
|
|
89629
|
+
`)) {
|
|
89630
|
+
const line = rawLine.trim();
|
|
89631
|
+
if (line.startsWith("#") || line.length === 0)
|
|
89632
|
+
continue;
|
|
89633
|
+
if (line === ".swarm" || line === ".swarm/")
|
|
89634
|
+
return true;
|
|
89635
|
+
}
|
|
89636
|
+
return false;
|
|
89637
|
+
}
|
|
89638
|
+
var ENSURE_SWARM_GIT_EXCLUDED_OUTER_TIMEOUT_MS = 3000;
|
|
89639
|
+
var ENSURE_SWARM_GIT_EXCLUDED_PER_CALL_TIMEOUT_MS = 1500;
|
|
89640
|
+
var GIT_SPAWN_OPTIONS = {
|
|
89641
|
+
timeout: ENSURE_SWARM_GIT_EXCLUDED_PER_CALL_TIMEOUT_MS,
|
|
89642
|
+
stdin: "ignore",
|
|
89643
|
+
stdout: "pipe",
|
|
89644
|
+
stderr: "pipe"
|
|
89645
|
+
};
|
|
89646
|
+
async function ensureSwarmGitExcluded(directory, options = {}) {
|
|
89647
|
+
if (_swarmGitExcludedChecked)
|
|
89648
|
+
return;
|
|
89649
|
+
_swarmGitExcludedChecked = true;
|
|
89650
|
+
const { quiet = false } = options;
|
|
89651
|
+
try {
|
|
89652
|
+
const gitRootProc = _internals.bunSpawn(["git", "-C", directory, "rev-parse", "--show-toplevel"], GIT_SPAWN_OPTIONS);
|
|
89653
|
+
let gitRootExitCode;
|
|
89654
|
+
let gitRootOutput;
|
|
89655
|
+
try {
|
|
89656
|
+
[gitRootExitCode, gitRootOutput] = await Promise.all([
|
|
89657
|
+
gitRootProc.exited,
|
|
89658
|
+
gitRootProc.stdout.text()
|
|
89659
|
+
]);
|
|
89660
|
+
} finally {
|
|
89661
|
+
try {
|
|
89662
|
+
gitRootProc.kill();
|
|
89663
|
+
} catch {}
|
|
89664
|
+
}
|
|
89665
|
+
if (gitRootExitCode !== 0)
|
|
89666
|
+
return;
|
|
89667
|
+
const gitRoot = gitRootOutput.trim();
|
|
89668
|
+
if (!gitRoot)
|
|
89669
|
+
return;
|
|
89670
|
+
const excludePathProc = _internals.bunSpawn(["git", "-C", directory, "rev-parse", "--git-path", "info/exclude"], GIT_SPAWN_OPTIONS);
|
|
89671
|
+
let excludePathExitCode;
|
|
89672
|
+
let excludePathRaw;
|
|
89673
|
+
try {
|
|
89674
|
+
[excludePathExitCode, excludePathRaw] = await Promise.all([
|
|
89675
|
+
excludePathProc.exited,
|
|
89676
|
+
excludePathProc.stdout.text()
|
|
89677
|
+
]);
|
|
89678
|
+
} finally {
|
|
89679
|
+
try {
|
|
89680
|
+
excludePathProc.kill();
|
|
89681
|
+
} catch {}
|
|
89682
|
+
}
|
|
89683
|
+
if (excludePathExitCode !== 0)
|
|
89684
|
+
return;
|
|
89685
|
+
const excludeRelPath = excludePathRaw.trim();
|
|
89686
|
+
if (!excludeRelPath)
|
|
89687
|
+
return;
|
|
89688
|
+
const excludePath = path104.isAbsolute(excludeRelPath) ? excludeRelPath : path104.join(directory, excludeRelPath);
|
|
89689
|
+
const checkIgnoreProc = _internals.bunSpawn(["git", "-C", directory, "check-ignore", "-q", ".swarm/.gitkeep"], GIT_SPAWN_OPTIONS);
|
|
89690
|
+
let checkIgnoreExitCode;
|
|
89691
|
+
try {
|
|
89692
|
+
checkIgnoreExitCode = await checkIgnoreProc.exited;
|
|
89693
|
+
} finally {
|
|
89694
|
+
try {
|
|
89695
|
+
checkIgnoreProc.kill();
|
|
89696
|
+
} catch {}
|
|
89697
|
+
}
|
|
89698
|
+
if (checkIgnoreExitCode !== 0) {
|
|
89699
|
+
try {
|
|
89700
|
+
fs84.mkdirSync(path104.dirname(excludePath), { recursive: true });
|
|
89701
|
+
let existing = "";
|
|
89702
|
+
try {
|
|
89703
|
+
existing = fs84.readFileSync(excludePath, "utf8");
|
|
89704
|
+
} catch {}
|
|
89705
|
+
if (!fileCoversSwarm(existing)) {
|
|
89706
|
+
fs84.appendFileSync(excludePath, `
|
|
89707
|
+
# opencode-swarm local runtime state
|
|
89708
|
+
.swarm/
|
|
89709
|
+
`, "utf8");
|
|
89710
|
+
if (!quiet) {
|
|
89711
|
+
console.warn("[opencode-swarm] Added .swarm/ to .git/info/exclude to prevent runtime state from appearing in git status.");
|
|
89712
|
+
}
|
|
89713
|
+
}
|
|
89714
|
+
} catch {}
|
|
89715
|
+
}
|
|
89716
|
+
const trackedProc = _internals.bunSpawn(["git", "-C", directory, "ls-files", "--", ".swarm"], GIT_SPAWN_OPTIONS);
|
|
89717
|
+
let trackedExitCode;
|
|
89718
|
+
let trackedOutput;
|
|
89719
|
+
try {
|
|
89720
|
+
[trackedExitCode, trackedOutput] = await Promise.all([
|
|
89721
|
+
trackedProc.exited,
|
|
89722
|
+
trackedProc.stdout.text()
|
|
89723
|
+
]);
|
|
89724
|
+
} finally {
|
|
89725
|
+
try {
|
|
89726
|
+
trackedProc.kill();
|
|
89727
|
+
} catch {}
|
|
89728
|
+
}
|
|
89729
|
+
if (trackedExitCode === 0 && trackedOutput.trim().length > 0) {
|
|
89730
|
+
console.warn(`[opencode-swarm] WARNING: .swarm/ files are tracked by Git.
|
|
89731
|
+
` + `.swarm/ contains local runtime state and may contain sensitive session data.
|
|
89732
|
+
` + `Ignoring will not affect already-tracked files. To stop tracking them, run:
|
|
89733
|
+
` + ` git rm -r --cached .swarm
|
|
89734
|
+
` + ` echo ".swarm/" >> .gitignore
|
|
89735
|
+
` + ' git commit -m "Stop tracking opencode-swarm runtime state"');
|
|
89736
|
+
}
|
|
89737
|
+
} catch {}
|
|
89738
|
+
}
|
|
89739
|
+
|
|
89740
|
+
// src/hooks/diff-scope.ts
|
|
89741
|
+
var _internals2 = { bunSpawn };
|
|
89620
89742
|
function getDeclaredScope(taskId, directory) {
|
|
89621
89743
|
try {
|
|
89622
|
-
const planPath =
|
|
89623
|
-
if (!
|
|
89744
|
+
const planPath = path105.join(directory, ".swarm", "plan.json");
|
|
89745
|
+
if (!fs85.existsSync(planPath))
|
|
89624
89746
|
return null;
|
|
89625
|
-
const raw =
|
|
89747
|
+
const raw = fs85.readFileSync(planPath, "utf-8");
|
|
89626
89748
|
const plan = JSON.parse(raw);
|
|
89627
89749
|
for (const phase of plan.phases ?? []) {
|
|
89628
89750
|
for (const task of phase.tasks ?? []) {
|
|
@@ -89643,30 +89765,47 @@ function getDeclaredScope(taskId, directory) {
|
|
|
89643
89765
|
return null;
|
|
89644
89766
|
}
|
|
89645
89767
|
}
|
|
89768
|
+
var GIT_DIFF_SPAWN_OPTIONS = {
|
|
89769
|
+
timeout: ENSURE_SWARM_GIT_EXCLUDED_PER_CALL_TIMEOUT_MS,
|
|
89770
|
+
stdin: "ignore",
|
|
89771
|
+
stdout: "pipe",
|
|
89772
|
+
stderr: "pipe"
|
|
89773
|
+
};
|
|
89646
89774
|
async function getChangedFiles(directory) {
|
|
89647
89775
|
try {
|
|
89648
|
-
const proc = bunSpawn(["git", "diff", "--name-only", "HEAD~1"], {
|
|
89776
|
+
const proc = _internals2.bunSpawn(["git", "diff", "--name-only", "HEAD~1"], {
|
|
89649
89777
|
cwd: directory,
|
|
89650
|
-
|
|
89651
|
-
stderr: "pipe"
|
|
89778
|
+
...GIT_DIFF_SPAWN_OPTIONS
|
|
89652
89779
|
});
|
|
89653
|
-
|
|
89654
|
-
|
|
89655
|
-
|
|
89656
|
-
|
|
89780
|
+
let exitCode;
|
|
89781
|
+
let stdout;
|
|
89782
|
+
try {
|
|
89783
|
+
[exitCode, stdout] = await Promise.all([proc.exited, proc.stdout.text()]);
|
|
89784
|
+
} finally {
|
|
89785
|
+
try {
|
|
89786
|
+
proc.kill();
|
|
89787
|
+
} catch {}
|
|
89788
|
+
}
|
|
89657
89789
|
if (exitCode === 0) {
|
|
89658
89790
|
return stdout.trim().split(`
|
|
89659
89791
|
`).map((f) => f.trim()).filter((f) => f.length > 0);
|
|
89660
89792
|
}
|
|
89661
|
-
const proc2 = bunSpawn(["git", "diff", "--name-only", "HEAD"], {
|
|
89793
|
+
const proc2 = _internals2.bunSpawn(["git", "diff", "--name-only", "HEAD"], {
|
|
89662
89794
|
cwd: directory,
|
|
89663
|
-
|
|
89664
|
-
stderr: "pipe"
|
|
89795
|
+
...GIT_DIFF_SPAWN_OPTIONS
|
|
89665
89796
|
});
|
|
89666
|
-
|
|
89667
|
-
|
|
89668
|
-
|
|
89669
|
-
|
|
89797
|
+
let exitCode2;
|
|
89798
|
+
let stdout2;
|
|
89799
|
+
try {
|
|
89800
|
+
[exitCode2, stdout2] = await Promise.all([
|
|
89801
|
+
proc2.exited,
|
|
89802
|
+
proc2.stdout.text()
|
|
89803
|
+
]);
|
|
89804
|
+
} finally {
|
|
89805
|
+
try {
|
|
89806
|
+
proc2.kill();
|
|
89807
|
+
} catch {}
|
|
89808
|
+
}
|
|
89670
89809
|
if (exitCode2 === 0) {
|
|
89671
89810
|
return stdout2.trim().split(`
|
|
89672
89811
|
`).map((f) => f.trim()).filter((f) => f.length > 0);
|
|
@@ -89739,7 +89878,7 @@ var TIER_3_PATTERNS = [
|
|
|
89739
89878
|
];
|
|
89740
89879
|
function matchesTier3Pattern(files) {
|
|
89741
89880
|
for (const file3 of files) {
|
|
89742
|
-
const fileName =
|
|
89881
|
+
const fileName = path106.basename(file3);
|
|
89743
89882
|
for (const pattern of TIER_3_PATTERNS) {
|
|
89744
89883
|
if (pattern.test(fileName)) {
|
|
89745
89884
|
return true;
|
|
@@ -89753,8 +89892,8 @@ function checkReviewerGate(taskId, workingDirectory, stageBParallelEnabled = fal
|
|
|
89753
89892
|
if (hasActiveTurboMode()) {
|
|
89754
89893
|
const resolvedDir2 = workingDirectory;
|
|
89755
89894
|
try {
|
|
89756
|
-
const planPath =
|
|
89757
|
-
const planRaw =
|
|
89895
|
+
const planPath = path106.join(resolvedDir2, ".swarm", "plan.json");
|
|
89896
|
+
const planRaw = fs86.readFileSync(planPath, "utf-8");
|
|
89758
89897
|
const plan = JSON.parse(planRaw);
|
|
89759
89898
|
for (const planPhase of plan.phases ?? []) {
|
|
89760
89899
|
for (const task of planPhase.tasks ?? []) {
|
|
@@ -89823,8 +89962,8 @@ function checkReviewerGate(taskId, workingDirectory, stageBParallelEnabled = fal
|
|
|
89823
89962
|
}
|
|
89824
89963
|
try {
|
|
89825
89964
|
const resolvedDir2 = workingDirectory;
|
|
89826
|
-
const planPath =
|
|
89827
|
-
const planRaw =
|
|
89965
|
+
const planPath = path106.join(resolvedDir2, ".swarm", "plan.json");
|
|
89966
|
+
const planRaw = fs86.readFileSync(planPath, "utf-8");
|
|
89828
89967
|
const plan = JSON.parse(planRaw);
|
|
89829
89968
|
for (const planPhase of plan.phases ?? []) {
|
|
89830
89969
|
for (const task of planPhase.tasks ?? []) {
|
|
@@ -90013,8 +90152,8 @@ async function executeUpdateTaskStatus(args2, fallbackDir) {
|
|
|
90013
90152
|
};
|
|
90014
90153
|
}
|
|
90015
90154
|
}
|
|
90016
|
-
normalizedDir =
|
|
90017
|
-
const pathParts = normalizedDir.split(
|
|
90155
|
+
normalizedDir = path106.normalize(args2.working_directory);
|
|
90156
|
+
const pathParts = normalizedDir.split(path106.sep);
|
|
90018
90157
|
if (pathParts.includes("..")) {
|
|
90019
90158
|
return {
|
|
90020
90159
|
success: false,
|
|
@@ -90024,11 +90163,11 @@ async function executeUpdateTaskStatus(args2, fallbackDir) {
|
|
|
90024
90163
|
]
|
|
90025
90164
|
};
|
|
90026
90165
|
}
|
|
90027
|
-
const resolvedDir =
|
|
90166
|
+
const resolvedDir = path106.resolve(normalizedDir);
|
|
90028
90167
|
try {
|
|
90029
|
-
const realPath =
|
|
90030
|
-
const planPath =
|
|
90031
|
-
if (!
|
|
90168
|
+
const realPath = fs86.realpathSync(resolvedDir);
|
|
90169
|
+
const planPath = path106.join(realPath, ".swarm", "plan.json");
|
|
90170
|
+
if (!fs86.existsSync(planPath)) {
|
|
90032
90171
|
return {
|
|
90033
90172
|
success: false,
|
|
90034
90173
|
message: `Invalid working_directory: plan not found in "${realPath}"`,
|
|
@@ -90059,22 +90198,22 @@ async function executeUpdateTaskStatus(args2, fallbackDir) {
|
|
|
90059
90198
|
}
|
|
90060
90199
|
if (args2.status === "in_progress") {
|
|
90061
90200
|
try {
|
|
90062
|
-
const evidencePath =
|
|
90063
|
-
|
|
90064
|
-
const fd =
|
|
90201
|
+
const evidencePath = path106.join(directory, ".swarm", "evidence", `${args2.task_id}.json`);
|
|
90202
|
+
fs86.mkdirSync(path106.dirname(evidencePath), { recursive: true });
|
|
90203
|
+
const fd = fs86.openSync(evidencePath, "wx");
|
|
90065
90204
|
let writeOk = false;
|
|
90066
90205
|
try {
|
|
90067
|
-
|
|
90206
|
+
fs86.writeSync(fd, JSON.stringify({
|
|
90068
90207
|
taskId: args2.task_id,
|
|
90069
90208
|
required_gates: ["reviewer", "test_engineer"],
|
|
90070
90209
|
gates: {}
|
|
90071
90210
|
}, null, 2));
|
|
90072
90211
|
writeOk = true;
|
|
90073
90212
|
} finally {
|
|
90074
|
-
|
|
90213
|
+
fs86.closeSync(fd);
|
|
90075
90214
|
if (!writeOk) {
|
|
90076
90215
|
try {
|
|
90077
|
-
|
|
90216
|
+
fs86.unlinkSync(evidencePath);
|
|
90078
90217
|
} catch {}
|
|
90079
90218
|
}
|
|
90080
90219
|
}
|
|
@@ -90084,8 +90223,8 @@ async function executeUpdateTaskStatus(args2, fallbackDir) {
|
|
|
90084
90223
|
recoverTaskStateFromDelegations(args2.task_id);
|
|
90085
90224
|
let phaseRequiresReviewer = true;
|
|
90086
90225
|
try {
|
|
90087
|
-
const planPath =
|
|
90088
|
-
const planRaw =
|
|
90226
|
+
const planPath = path106.join(directory, ".swarm", "plan.json");
|
|
90227
|
+
const planRaw = fs86.readFileSync(planPath, "utf-8");
|
|
90089
90228
|
const plan = JSON.parse(planRaw);
|
|
90090
90229
|
const taskPhase = plan.phases.find((p) => p.tasks.some((t) => t.id === args2.task_id));
|
|
90091
90230
|
if (taskPhase?.required_agents && !taskPhase.required_agents.includes("reviewer")) {
|
|
@@ -90395,8 +90534,8 @@ init_utils2();
|
|
|
90395
90534
|
init_ledger();
|
|
90396
90535
|
init_manager();
|
|
90397
90536
|
init_create_tool();
|
|
90398
|
-
import
|
|
90399
|
-
import
|
|
90537
|
+
import fs87 from "node:fs";
|
|
90538
|
+
import path107 from "node:path";
|
|
90400
90539
|
function derivePlanId5(plan) {
|
|
90401
90540
|
return `${plan.swarm}-${plan.title}`.replace(/[^a-zA-Z0-9-_]/g, "_");
|
|
90402
90541
|
}
|
|
@@ -90447,7 +90586,7 @@ async function executeWriteDriftEvidence(args2, directory) {
|
|
|
90447
90586
|
entries: [evidenceEntry]
|
|
90448
90587
|
};
|
|
90449
90588
|
const filename = "drift-verifier.json";
|
|
90450
|
-
const relativePath =
|
|
90589
|
+
const relativePath = path107.join("evidence", String(phase), filename);
|
|
90451
90590
|
let validatedPath;
|
|
90452
90591
|
try {
|
|
90453
90592
|
validatedPath = validateSwarmPath(directory, relativePath);
|
|
@@ -90458,12 +90597,12 @@ async function executeWriteDriftEvidence(args2, directory) {
|
|
|
90458
90597
|
message: error93 instanceof Error ? error93.message : "Failed to validate path"
|
|
90459
90598
|
}, null, 2);
|
|
90460
90599
|
}
|
|
90461
|
-
const evidenceDir =
|
|
90600
|
+
const evidenceDir = path107.dirname(validatedPath);
|
|
90462
90601
|
try {
|
|
90463
|
-
await
|
|
90464
|
-
const tempPath =
|
|
90465
|
-
await
|
|
90466
|
-
await
|
|
90602
|
+
await fs87.promises.mkdir(evidenceDir, { recursive: true });
|
|
90603
|
+
const tempPath = path107.join(evidenceDir, `.${filename}.tmp`);
|
|
90604
|
+
await fs87.promises.writeFile(tempPath, JSON.stringify(evidenceContent, null, 2), "utf-8");
|
|
90605
|
+
await fs87.promises.rename(tempPath, validatedPath);
|
|
90467
90606
|
let snapshotInfo;
|
|
90468
90607
|
let snapshotError;
|
|
90469
90608
|
let qaProfileLocked;
|
|
@@ -90557,8 +90696,8 @@ var write_drift_evidence = createSwarmTool({
|
|
|
90557
90696
|
init_zod();
|
|
90558
90697
|
init_utils2();
|
|
90559
90698
|
init_create_tool();
|
|
90560
|
-
import
|
|
90561
|
-
import
|
|
90699
|
+
import fs88 from "node:fs";
|
|
90700
|
+
import path108 from "node:path";
|
|
90562
90701
|
function normalizeVerdict2(verdict) {
|
|
90563
90702
|
switch (verdict) {
|
|
90564
90703
|
case "APPROVED":
|
|
@@ -90606,7 +90745,7 @@ async function executeWriteHallucinationEvidence(args2, directory) {
|
|
|
90606
90745
|
entries: [evidenceEntry]
|
|
90607
90746
|
};
|
|
90608
90747
|
const filename = "hallucination-guard.json";
|
|
90609
|
-
const relativePath =
|
|
90748
|
+
const relativePath = path108.join("evidence", String(phase), filename);
|
|
90610
90749
|
let validatedPath;
|
|
90611
90750
|
try {
|
|
90612
90751
|
validatedPath = validateSwarmPath(directory, relativePath);
|
|
@@ -90617,12 +90756,12 @@ async function executeWriteHallucinationEvidence(args2, directory) {
|
|
|
90617
90756
|
message: error93 instanceof Error ? error93.message : "Failed to validate path"
|
|
90618
90757
|
}, null, 2);
|
|
90619
90758
|
}
|
|
90620
|
-
const evidenceDir =
|
|
90759
|
+
const evidenceDir = path108.dirname(validatedPath);
|
|
90621
90760
|
try {
|
|
90622
|
-
await
|
|
90623
|
-
const tempPath =
|
|
90624
|
-
await
|
|
90625
|
-
await
|
|
90761
|
+
await fs88.promises.mkdir(evidenceDir, { recursive: true });
|
|
90762
|
+
const tempPath = path108.join(evidenceDir, `.${filename}.tmp`);
|
|
90763
|
+
await fs88.promises.writeFile(tempPath, JSON.stringify(evidenceContent, null, 2), "utf-8");
|
|
90764
|
+
await fs88.promises.rename(tempPath, validatedPath);
|
|
90626
90765
|
return JSON.stringify({
|
|
90627
90766
|
success: true,
|
|
90628
90767
|
phase,
|
|
@@ -90668,8 +90807,8 @@ var write_hallucination_evidence = createSwarmTool({
|
|
|
90668
90807
|
init_zod();
|
|
90669
90808
|
init_utils2();
|
|
90670
90809
|
init_create_tool();
|
|
90671
|
-
import
|
|
90672
|
-
import
|
|
90810
|
+
import fs89 from "node:fs";
|
|
90811
|
+
import path109 from "node:path";
|
|
90673
90812
|
function normalizeVerdict3(verdict) {
|
|
90674
90813
|
switch (verdict) {
|
|
90675
90814
|
case "PASS":
|
|
@@ -90743,7 +90882,7 @@ async function executeWriteMutationEvidence(args2, directory) {
|
|
|
90743
90882
|
entries: [evidenceEntry]
|
|
90744
90883
|
};
|
|
90745
90884
|
const filename = "mutation-gate.json";
|
|
90746
|
-
const relativePath =
|
|
90885
|
+
const relativePath = path109.join("evidence", String(phase), filename);
|
|
90747
90886
|
let validatedPath;
|
|
90748
90887
|
try {
|
|
90749
90888
|
validatedPath = validateSwarmPath(directory, relativePath);
|
|
@@ -90754,12 +90893,12 @@ async function executeWriteMutationEvidence(args2, directory) {
|
|
|
90754
90893
|
message: error93 instanceof Error ? error93.message : "Failed to validate path"
|
|
90755
90894
|
}, null, 2);
|
|
90756
90895
|
}
|
|
90757
|
-
const evidenceDir =
|
|
90896
|
+
const evidenceDir = path109.dirname(validatedPath);
|
|
90758
90897
|
try {
|
|
90759
|
-
await
|
|
90760
|
-
const tempPath =
|
|
90761
|
-
await
|
|
90762
|
-
await
|
|
90898
|
+
await fs89.promises.mkdir(evidenceDir, { recursive: true });
|
|
90899
|
+
const tempPath = path109.join(evidenceDir, `.${filename}.tmp`);
|
|
90900
|
+
await fs89.promises.writeFile(tempPath, JSON.stringify(evidenceContent, null, 2), "utf-8");
|
|
90901
|
+
await fs89.promises.rename(tempPath, validatedPath);
|
|
90763
90902
|
return JSON.stringify({
|
|
90764
90903
|
success: true,
|
|
90765
90904
|
phase,
|
|
@@ -90812,85 +90951,6 @@ init_write_retro();
|
|
|
90812
90951
|
// src/index.ts
|
|
90813
90952
|
init_utils();
|
|
90814
90953
|
|
|
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
90954
|
// src/utils/tool-output.ts
|
|
90895
90955
|
function truncateToolOutput(output, maxLines, toolName, tailLines = 10) {
|
|
90896
90956
|
if (!output) {
|
|
@@ -90994,7 +91054,12 @@ async function initializeOpenCodeSwarm(ctx) {
|
|
|
90994
91054
|
}
|
|
90995
91055
|
repoGraphHook.init().catch(() => {}).finally(() => clearTimeout(watchdog));
|
|
90996
91056
|
});
|
|
90997
|
-
await ensureSwarmGitExcluded(ctx.directory, { quiet: config3.quiet });
|
|
91057
|
+
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) => {
|
|
91058
|
+
const msg = err3 instanceof Error ? err3.message : String(err3);
|
|
91059
|
+
log("ensureSwarmGitExcluded timed out or failed (non-fatal)", {
|
|
91060
|
+
error: msg
|
|
91061
|
+
});
|
|
91062
|
+
});
|
|
90998
91063
|
initTelemetry(ctx.directory);
|
|
90999
91064
|
writeSwarmConfigExampleIfNew(ctx.directory);
|
|
91000
91065
|
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.4",
|
|
4
4
|
"description": "Architect-centric agentic swarm plugin for OpenCode - hub-and-spoke orchestration with SME consultation, code generation, and QA review",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|