opencode-swarm 7.3.2 → 7.3.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli/index.js CHANGED
@@ -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.2",
37
+ version: "7.3.3",
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",
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.2",
36
+ version: "7.3.3",
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",
@@ -65374,7 +65374,7 @@ function createAgentActivityHooks(config3, directory) {
65374
65374
  const duration5 = Date.now() - entry.startTime;
65375
65375
  const explicitSuccess = typeof output.success === "boolean" ? output.success : undefined;
65376
65376
  const explicitFailure = explicitSuccess === false || !!output.error;
65377
- const success3 = explicitFailure ? false : true;
65377
+ const success3 = !explicitFailure;
65378
65378
  const key = entry.tool;
65379
65379
  const existing = swarmState.toolAggregates.get(key) ?? {
65380
65380
  tool: key,
@@ -89684,9 +89684,10 @@ async function validateDiffScope(taskId, directory) {
89684
89684
  const changedFiles = await getChangedFiles(directory);
89685
89685
  if (!changedFiles)
89686
89686
  return null;
89687
+ const nonSwarmFiles = changedFiles.filter((f) => !f.replace(/\\/g, "/").startsWith(".swarm/"));
89687
89688
  const normalise = (p) => p.replace(/\\/g, "/").replace(/^\.\//, "");
89688
89689
  const normScope = new Set(declaredScope.map(normalise));
89689
- const undeclared = changedFiles.map(normalise).filter((f) => !normScope.has(f));
89690
+ const undeclared = nonSwarmFiles.map(normalise).filter((f) => !normScope.has(f));
89690
89691
  if (undeclared.length === 0)
89691
89692
  return null;
89692
89693
  const scopeStr = declaredScope.join(", ");
@@ -90812,26 +90813,10 @@ init_write_retro();
90812
90813
  init_utils();
90813
90814
 
90814
90815
  // src/utils/gitignore-warning.ts
90816
+ init_bun_compat();
90815
90817
  import * as fs89 from "node:fs";
90816
90818
  import * as path109 from "node:path";
90817
- var _gitignoreWarningEmitted = false;
90818
- function findGitRoot(startDir) {
90819
- let current = startDir;
90820
- while (true) {
90821
- try {
90822
- const gitPath = path109.join(current, ".git");
90823
- const stat6 = fs89.statSync(gitPath);
90824
- if (stat6.isDirectory()) {
90825
- return current;
90826
- }
90827
- } catch {}
90828
- const parent = path109.dirname(current);
90829
- if (parent === current) {
90830
- return null;
90831
- }
90832
- current = parent;
90833
- }
90834
- }
90819
+ var _swarmGitExcludedChecked = false;
90835
90820
  function fileCoversSwarm(content) {
90836
90821
  for (const rawLine of content.split(`
90837
90822
  `)) {
@@ -90843,33 +90828,65 @@ function fileCoversSwarm(content) {
90843
90828
  }
90844
90829
  return false;
90845
90830
  }
90846
- function readFileSafe(filePath) {
90847
- try {
90848
- return fs89.readFileSync(filePath, "utf8");
90849
- } catch {
90850
- return null;
90851
- }
90852
- }
90853
- function warnIfSwarmNotGitignored(directory, quiet = false) {
90854
- if (_gitignoreWarningEmitted)
90831
+ async function ensureSwarmGitExcluded(directory, options = {}) {
90832
+ if (_swarmGitExcludedChecked)
90855
90833
  return;
90834
+ _swarmGitExcludedChecked = true;
90835
+ const { quiet = false } = options;
90856
90836
  try {
90857
- const gitRoot = findGitRoot(directory);
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();
90858
90845
  if (!gitRoot)
90859
90846
  return;
90860
- const gitignoreContent = readFileSafe(path109.join(gitRoot, ".gitignore"));
90861
- if (gitignoreContent !== null && fileCoversSwarm(gitignoreContent)) {
90862
- _gitignoreWarningEmitted = true;
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)
90863
90853
  return;
90864
- }
90865
- const excludeContent = readFileSafe(path109.join(gitRoot, ".git", "info", "exclude"));
90866
- if (excludeContent !== null && fileCoversSwarm(excludeContent)) {
90867
- _gitignoreWarningEmitted = true;
90854
+ const excludeRelPath = excludePathRaw.trim();
90855
+ if (!excludeRelPath)
90868
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 {}
90869
90877
  }
90870
- _gitignoreWarningEmitted = true;
90871
- if (!quiet) {
90872
- console.warn('[opencode-swarm] WARNING: .swarm/ is not in your .gitignore. Shell audit logs may contain API keys. Add ".swarm/" to your .gitignore to prevent accidental commits.');
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"');
90873
90890
  }
90874
90891
  } catch {}
90875
90892
  }
@@ -90977,10 +90994,10 @@ async function initializeOpenCodeSwarm(ctx) {
90977
90994
  }
90978
90995
  repoGraphHook.init().catch(() => {}).finally(() => clearTimeout(watchdog));
90979
90996
  });
90997
+ await ensureSwarmGitExcluded(ctx.directory, { quiet: config3.quiet });
90980
90998
  initTelemetry(ctx.directory);
90981
90999
  writeSwarmConfigExampleIfNew(ctx.directory);
90982
91000
  writeProjectConfigIfNew(ctx.directory, config3.quiet);
90983
- warnIfSwarmNotGitignored(ctx.directory, config3.quiet);
90984
91001
  if (config3.version_check !== false) {
90985
91002
  scheduleVersionCheck(package_default.version, (msg) => {
90986
91003
  if (config3.quiet) {
@@ -7,11 +7,46 @@ export declare let _gitignoreWarningEmitted: boolean;
7
7
  * Reset the deduplication flag. Exposed for test isolation only.
8
8
  */
9
9
  export declare function resetGitignoreWarningState(): void;
10
+ /**
11
+ * Module-level flag for ensureSwarmGitExcluded deduplication.
12
+ * Exported for test reset purposes only.
13
+ */
14
+ export declare let _swarmGitExcludedChecked: boolean;
15
+ /**
16
+ * Reset the ensureSwarmGitExcluded deduplication flag. Exposed for test isolation only.
17
+ */
18
+ export declare function resetSwarmGitExcludedState(): void;
10
19
  /**
11
20
  * Checks whether `.swarm/` is covered by `.gitignore` or `.git/info/exclude`
12
21
  * in the git repo rooted at or above `directory`. If not covered, emits a
13
22
  * single `console.warn` (unless `quiet` is true). Fires at most once per process.
14
23
  *
15
24
  * Never throws — any file-system error silently skips the check.
25
+ *
26
+ * @deprecated Use `ensureSwarmGitExcluded` instead. This function only recognises
27
+ * `.git` as a directory and does NOT handle Git worktrees or submodules.
16
28
  */
17
29
  export declare function warnIfSwarmNotGitignored(directory: string, quiet?: boolean): void;
30
+ export interface EnsureSwarmGitExcludedOptions {
31
+ quiet?: boolean;
32
+ }
33
+ /**
34
+ * Automatically protect `.swarm/` from Git pollution before any `.swarm/` write.
35
+ *
36
+ * Uses git CLI (not filesystem walks) so it correctly handles Git worktrees
37
+ * and submodules where `.git` is a file rather than a directory.
38
+ *
39
+ * Steps:
40
+ * 1. Resolve git root via `git rev-parse --show-toplevel`
41
+ * 2. Resolve local exclude path via `git rev-parse --git-path info/exclude`
42
+ * 3. Check if `.swarm/` is already ignored via `git check-ignore -q`
43
+ * 4. If not ignored: append `.swarm/` to the local exclude file (idempotent)
44
+ * 5. Detect tracked `.swarm/` files via `git ls-files -- .swarm`
45
+ * 6. If tracked: emit an unsuppressed remediation warning
46
+ *
47
+ * Never throws. Fires at most once per process.
48
+ *
49
+ * quiet option: only suppresses cosmetic logs. The exclude write and tracked-file
50
+ * warning are never suppressed regardless of quiet mode.
51
+ */
52
+ export declare function ensureSwarmGitExcluded(directory: string, options?: EnsureSwarmGitExcludedOptions): Promise<void>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-swarm",
3
- "version": "7.3.2",
3
+ "version": "7.3.3",
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",