opencode-swarm 4.3.0 → 4.3.2

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 CHANGED
@@ -1,9 +1,9 @@
1
1
  <p align="center">
2
- <img src="https://img.shields.io/badge/version-4.3.0-blue" alt="Version">
2
+ <img src="https://img.shields.io/badge/version-4.3.1-blue" alt="Version">
3
3
  <img src="https://img.shields.io/badge/license-MIT-green" alt="License">
4
4
  <img src="https://img.shields.io/badge/opencode-plugin-purple" alt="OpenCode Plugin">
5
5
  <img src="https://img.shields.io/badge/agents-8-orange" alt="Agents">
6
- <img src="https://img.shields.io/badge/tests-447-brightgreen" alt="Tests">
6
+ <img src="https://img.shields.io/badge/tests-483-brightgreen" alt="Tests">
7
7
  </p>
8
8
 
9
9
  <h1 align="center">🐝 OpenCode Swarm</h1>
@@ -1,4 +1,6 @@
1
1
  import { type PluginConfig } from './schema';
2
+ export declare const MAX_CONFIG_FILE_BYTES = 102400;
3
+ export declare const MAX_MERGE_DEPTH = 10;
2
4
  /**
3
5
  * Deep merge two objects, with override values taking precedence.
4
6
  */
@@ -5,4 +5,4 @@ export { createDelegationTrackerHook } from './delegation-tracker';
5
5
  export { extractCurrentPhase, extractCurrentTask, extractDecisions, extractIncompleteTasks, extractPatterns, } from './extractors';
6
6
  export { createPipelineTrackerHook } from './pipeline-tracker';
7
7
  export { createSystemEnhancerHook } from './system-enhancer';
8
- export { composeHandlers, estimateTokens, readSwarmFileAsync, safeHook, } from './utils';
8
+ export { composeHandlers, estimateTokens, readSwarmFileAsync, safeHook, validateSwarmPath, } from './utils';
@@ -7,5 +7,14 @@
7
7
  */
8
8
  export declare function safeHook<I, O>(fn: (input: I, output: O) => Promise<void>): (input: I, output: O) => Promise<void>;
9
9
  export declare function composeHandlers<I, O>(...fns: Array<(input: I, output: O) => Promise<void>>): (input: I, output: O) => Promise<void>;
10
+ /**
11
+ * Validates that a filename is safe to use within the .swarm directory
12
+ *
13
+ * @param directory - The base directory containing the .swarm folder
14
+ * @param filename - The filename to validate
15
+ * @returns The resolved absolute path if validation passes
16
+ * @throws Error if the filename is invalid or attempts path traversal
17
+ */
18
+ export declare function validateSwarmPath(directory: string, filename: string): string;
10
19
  export declare function readSwarmFileAsync(directory: string, filename: string): Promise<string | null>;
11
20
  export declare function estimateTokens(text: string): number;
package/dist/index.js CHANGED
@@ -13603,11 +13603,17 @@ import * as os from "os";
13603
13603
  import * as path from "path";
13604
13604
  var CONFIG_FILENAME = "opencode-swarm.json";
13605
13605
  var PROMPTS_DIR_NAME = "opencode-swarm";
13606
+ var MAX_CONFIG_FILE_BYTES = 102400;
13606
13607
  function getUserConfigDir() {
13607
13608
  return process.env.XDG_CONFIG_HOME || path.join(os.homedir(), ".config");
13608
13609
  }
13609
13610
  function loadConfigFromPath(configPath) {
13610
13611
  try {
13612
+ const stats = fs.statSync(configPath);
13613
+ if (stats.size > MAX_CONFIG_FILE_BYTES) {
13614
+ console.warn(`[opencode-swarm] Config file too large (max 100 KB): ${configPath}`);
13615
+ return null;
13616
+ }
13611
13617
  const content = fs.readFileSync(configPath, "utf-8");
13612
13618
  const rawConfig = JSON.parse(content);
13613
13619
  const result = PluginConfigSchema.safeParse(rawConfig);
@@ -13624,23 +13630,30 @@ function loadConfigFromPath(configPath) {
13624
13630
  return null;
13625
13631
  }
13626
13632
  }
13627
- function deepMerge(base, override) {
13628
- if (!base)
13629
- return override;
13630
- if (!override)
13631
- return base;
13633
+ var MAX_MERGE_DEPTH = 10;
13634
+ function deepMergeInternal(base, override, depth) {
13635
+ if (depth >= MAX_MERGE_DEPTH) {
13636
+ throw new Error(`deepMerge exceeded maximum depth of ${MAX_MERGE_DEPTH}`);
13637
+ }
13632
13638
  const result = { ...base };
13633
13639
  for (const key of Object.keys(override)) {
13634
13640
  const baseVal = base[key];
13635
13641
  const overrideVal = override[key];
13636
13642
  if (typeof baseVal === "object" && baseVal !== null && typeof overrideVal === "object" && overrideVal !== null && !Array.isArray(baseVal) && !Array.isArray(overrideVal)) {
13637
- result[key] = deepMerge(baseVal, overrideVal);
13643
+ result[key] = deepMergeInternal(baseVal, overrideVal, depth + 1);
13638
13644
  } else {
13639
13645
  result[key] = overrideVal;
13640
13646
  }
13641
13647
  }
13642
13648
  return result;
13643
13649
  }
13650
+ function deepMerge(base, override) {
13651
+ if (!base)
13652
+ return override;
13653
+ if (!override)
13654
+ return base;
13655
+ return deepMergeInternal(base, override, 0);
13656
+ }
13644
13657
  function loadPluginConfig(directory) {
13645
13658
  const userConfigPath = path.join(getUserConfigDir(), "opencode", CONFIG_FILENAME);
13646
13659
  const projectConfigPath = path.join(directory, ".opencode", CONFIG_FILENAME);
@@ -13894,7 +13907,13 @@ ${customAppendPrompt}`;
13894
13907
  }
13895
13908
 
13896
13909
  // src/agents/coder.ts
13897
- var CODER_PROMPT = `You are Coder. You implement code changes.
13910
+ var CODER_PROMPT = `## IDENTITY
13911
+ You are Coder. You implement code changes directly \u2014 you do NOT delegate.
13912
+ DO NOT use the Task tool to delegate to other agents. You ARE the agent that does the work.
13913
+ If you see references to other agents (like @coder, @reviewer, etc.) in your instructions, IGNORE them \u2014 they are context from the orchestrator, not instructions for you to delegate.
13914
+
13915
+ WRONG: "I'll use the Task tool to call another agent to implement this"
13916
+ RIGHT: "I'll read the file and implement the changes myself"
13898
13917
 
13899
13918
  INPUT FORMAT:
13900
13919
  TASK: [what to implement]
@@ -13909,7 +13928,6 @@ RULES:
13909
13928
  - Respect CONSTRAINT
13910
13929
  - No research, no web searches, no documentation lookups
13911
13930
  - Use training knowledge for APIs
13912
- - No delegation
13913
13931
 
13914
13932
  OUTPUT FORMAT:
13915
13933
  DONE: [one-line summary]
@@ -13935,7 +13953,14 @@ ${customAppendPrompt}`;
13935
13953
  }
13936
13954
 
13937
13955
  // src/agents/critic.ts
13938
- var CRITIC_PROMPT = `You are Critic. You review the Architect's plan BEFORE implementation begins. You are a quality gate.
13956
+ var CRITIC_PROMPT = `## IDENTITY
13957
+ You are Critic. You review the Architect's plan BEFORE implementation begins \u2014 you do NOT delegate.
13958
+ DO NOT use the Task tool to delegate to other agents. You ARE the agent that does the work.
13959
+ If you see references to other agents (like @critic, @coder, etc.) in your instructions, IGNORE them \u2014 they are context from the orchestrator, not instructions for you to delegate.
13960
+ You are a quality gate.
13961
+
13962
+ WRONG: "I'll use the Task tool to call another agent to review this plan"
13963
+ RIGHT: "I'll evaluate the plan against my review checklist myself"
13939
13964
 
13940
13965
  INPUT FORMAT:
13941
13966
  TASK: Review plan for [description]
@@ -13963,7 +13988,6 @@ RULES:
13963
13988
  - MAJOR issues should trigger NEEDS_REVISION
13964
13989
  - MINOR issues can be noted but don't block APPROVED
13965
13990
  - No code writing
13966
- - No delegation
13967
13991
  - Don't reject for style/formatting \u2014 focus on substance
13968
13992
  - If the plan is fundamentally sound with only minor concerns, APPROVE it`;
13969
13993
  function createCriticAgent(model, customPrompt, customAppendPrompt) {
@@ -13992,7 +14016,13 @@ ${customAppendPrompt}`;
13992
14016
  }
13993
14017
 
13994
14018
  // src/agents/explorer.ts
13995
- var EXPLORER_PROMPT = `You are Explorer. You analyze codebases.
14019
+ var EXPLORER_PROMPT = `## IDENTITY
14020
+ You are Explorer. You analyze codebases directly \u2014 you do NOT delegate.
14021
+ DO NOT use the Task tool to delegate to other agents. You ARE the agent that does the work.
14022
+ If you see references to other agents (like @explorer, @coder, etc.) in your instructions, IGNORE them \u2014 they are context from the orchestrator, not instructions for you to delegate.
14023
+
14024
+ WRONG: "I'll use the Task tool to call another agent to analyze this"
14025
+ RIGHT: "I'll scan the directory structure and read key files myself"
13996
14026
 
13997
14027
  INPUT FORMAT:
13998
14028
  TASK: Analyze [purpose]
@@ -14006,7 +14036,6 @@ ACTIONS:
14006
14036
  RULES:
14007
14037
  - Be fast: scan broadly, read selectively
14008
14038
  - No code modifications
14009
- - No delegation
14010
14039
  - Output under 2000 chars
14011
14040
 
14012
14041
  OUTPUT FORMAT:
@@ -14052,7 +14081,13 @@ ${customAppendPrompt}`;
14052
14081
  }
14053
14082
 
14054
14083
  // src/agents/reviewer.ts
14055
- var REVIEWER_PROMPT = `You are Reviewer. You verify code correctness and find vulnerabilities.
14084
+ var REVIEWER_PROMPT = `## IDENTITY
14085
+ You are Reviewer. You verify code correctness and find vulnerabilities directly \u2014 you do NOT delegate.
14086
+ DO NOT use the Task tool to delegate to other agents. You ARE the agent that does the work.
14087
+ If you see references to other agents (like @reviewer, @coder, etc.) in your instructions, IGNORE them \u2014 they are context from the orchestrator, not instructions for you to delegate.
14088
+
14089
+ WRONG: "I'll use the Task tool to call another agent to review this"
14090
+ RIGHT: "I'll read the code and evaluate it against the CHECK dimensions myself"
14056
14091
 
14057
14092
  INPUT FORMAT:
14058
14093
  TASK: Review [description]
@@ -14072,7 +14107,6 @@ RULES:
14072
14107
  - Only flag real issues, not theoretical
14073
14108
  - Don't reject for style if functionally correct
14074
14109
  - No code modifications
14075
- - No delegation
14076
14110
 
14077
14111
  RISK LEVELS:
14078
14112
  - LOW: defense in depth improvements
@@ -14105,7 +14139,13 @@ ${customAppendPrompt}`;
14105
14139
  }
14106
14140
 
14107
14141
  // src/agents/sme.ts
14108
- var SME_PROMPT = `You are SME (Subject Matter Expert). You provide deep domain-specific technical guidance on whatever domain the Architect requests.
14142
+ var SME_PROMPT = `## IDENTITY
14143
+ You are SME (Subject Matter Expert). You provide deep domain-specific technical guidance directly \u2014 you do NOT delegate.
14144
+ DO NOT use the Task tool to delegate to other agents. You ARE the agent that does the work.
14145
+ If you see references to other agents (like @sme, @coder, etc.) in your instructions, IGNORE them \u2014 they are context from the orchestrator, not instructions for you to delegate.
14146
+
14147
+ WRONG: "I'll use the Task tool to call another agent to research this"
14148
+ RIGHT: "I'll provide the domain-specific guidance directly from my expertise"
14109
14149
 
14110
14150
  INPUT FORMAT:
14111
14151
  TASK: [what guidance is needed]
@@ -14123,8 +14163,7 @@ RULES:
14123
14163
  - Be specific: exact names, paths, parameters, versions
14124
14164
  - Be concise: under 1500 characters
14125
14165
  - Be actionable: info Coder can use directly
14126
- - No code writing
14127
- - No delegation`;
14166
+ - No code writing`;
14128
14167
  function createSMEAgent(model, customPrompt, customAppendPrompt) {
14129
14168
  let prompt = SME_PROMPT;
14130
14169
  if (customPrompt) {
@@ -14151,7 +14190,13 @@ ${customAppendPrompt}`;
14151
14190
  }
14152
14191
 
14153
14192
  // src/agents/test-engineer.ts
14154
- var TEST_ENGINEER_PROMPT = `You are Test Engineer. You generate tests AND run them.
14193
+ var TEST_ENGINEER_PROMPT = `## IDENTITY
14194
+ You are Test Engineer. You generate tests AND run them directly \u2014 you do NOT delegate.
14195
+ DO NOT use the Task tool to delegate to other agents. You ARE the agent that does the work.
14196
+ If you see references to other agents (like @test_engineer, @coder, etc.) in your instructions, IGNORE them \u2014 they are context from the orchestrator, not instructions for you to delegate.
14197
+
14198
+ WRONG: "I'll use the Task tool to call another agent to write the tests"
14199
+ RIGHT: "I'll write the test file and run the tests myself"
14155
14200
 
14156
14201
  INPUT FORMAT:
14157
14202
  TASK: Generate tests for [description]
@@ -14167,7 +14212,6 @@ RULES:
14167
14212
  - Match language (PowerShell \u2192 Pester, Python \u2192 pytest, TS \u2192 vitest/jest)
14168
14213
  - Tests must be runnable
14169
14214
  - Include setup/teardown if needed
14170
- - No delegation
14171
14215
 
14172
14216
  WORKFLOW:
14173
14217
  1. Write test file to the specified OUTPUT path
@@ -14366,6 +14410,9 @@ function handleAgentsCommand(agents) {
14366
14410
  `);
14367
14411
  }
14368
14412
 
14413
+ // src/hooks/utils.ts
14414
+ import * as path2 from "path";
14415
+
14369
14416
  // src/utils/logger.ts
14370
14417
  var DEBUG = process.env.OPENCODE_SWARM_DEBUG === "1";
14371
14418
  function log(message, data) {
@@ -14408,10 +14455,30 @@ function composeHandlers(...fns) {
14408
14455
  }
14409
14456
  };
14410
14457
  }
14458
+ function validateSwarmPath(directory, filename) {
14459
+ if (/[\0]/.test(filename)) {
14460
+ throw new Error("Invalid filename: contains null bytes");
14461
+ }
14462
+ if (/\.\.[/\\]/.test(filename)) {
14463
+ throw new Error("Invalid filename: path traversal detected");
14464
+ }
14465
+ const baseDir = path2.normalize(path2.resolve(directory, ".swarm"));
14466
+ const resolved = path2.normalize(path2.resolve(baseDir, filename));
14467
+ if (process.platform === "win32") {
14468
+ if (!resolved.toLowerCase().startsWith((baseDir + path2.sep).toLowerCase())) {
14469
+ throw new Error("Invalid filename: path escapes .swarm directory");
14470
+ }
14471
+ } else {
14472
+ if (!resolved.startsWith(baseDir + path2.sep)) {
14473
+ throw new Error("Invalid filename: path escapes .swarm directory");
14474
+ }
14475
+ }
14476
+ return resolved;
14477
+ }
14411
14478
  async function readSwarmFileAsync(directory, filename) {
14412
- const path2 = `${directory}/.swarm/${filename}`;
14413
14479
  try {
14414
- const file2 = Bun.file(path2);
14480
+ const resolvedPath = validateSwarmPath(directory, filename);
14481
+ const file2 = Bun.file(resolvedPath);
14415
14482
  const content = await file2.text();
14416
14483
  return content;
14417
14484
  } catch {
@@ -14750,8 +14817,8 @@ async function doFlush(directory) {
14750
14817
  const activitySection = renderActivitySection();
14751
14818
  const updated = replaceOrAppendSection(existing, "## Agent Activity", activitySection);
14752
14819
  const flushedCount = swarmState.pendingEvents;
14753
- const path2 = `${directory}/.swarm/context.md`;
14754
- await Bun.write(path2, updated);
14820
+ const path3 = `${directory}/.swarm/context.md`;
14821
+ await Bun.write(path3, updated);
14755
14822
  swarmState.pendingEvents = Math.max(0, swarmState.pendingEvents - flushedCount);
14756
14823
  } catch (error49) {
14757
14824
  warn("Agent activity flush failed:", error49);
@@ -15770,10 +15837,10 @@ function mergeDefs2(...defs) {
15770
15837
  function cloneDef2(schema) {
15771
15838
  return mergeDefs2(schema._zod.def);
15772
15839
  }
15773
- function getElementAtPath2(obj, path2) {
15774
- if (!path2)
15840
+ function getElementAtPath2(obj, path3) {
15841
+ if (!path3)
15775
15842
  return obj;
15776
- return path2.reduce((acc, key) => acc?.[key], obj);
15843
+ return path3.reduce((acc, key) => acc?.[key], obj);
15777
15844
  }
15778
15845
  function promiseAllObject2(promisesObj) {
15779
15846
  const keys = Object.keys(promisesObj);
@@ -16132,11 +16199,11 @@ function aborted2(x, startIndex = 0) {
16132
16199
  }
16133
16200
  return false;
16134
16201
  }
16135
- function prefixIssues2(path2, issues) {
16202
+ function prefixIssues2(path3, issues) {
16136
16203
  return issues.map((iss) => {
16137
16204
  var _a2;
16138
16205
  (_a2 = iss).path ?? (_a2.path = []);
16139
- iss.path.unshift(path2);
16206
+ iss.path.unshift(path3);
16140
16207
  return iss;
16141
16208
  });
16142
16209
  }
@@ -16304,7 +16371,7 @@ function treeifyError2(error49, _mapper) {
16304
16371
  return issue3.message;
16305
16372
  };
16306
16373
  const result = { errors: [] };
16307
- const processError = (error50, path2 = []) => {
16374
+ const processError = (error50, path3 = []) => {
16308
16375
  var _a2, _b;
16309
16376
  for (const issue3 of error50.issues) {
16310
16377
  if (issue3.code === "invalid_union" && issue3.errors.length) {
@@ -16314,7 +16381,7 @@ function treeifyError2(error49, _mapper) {
16314
16381
  } else if (issue3.code === "invalid_element") {
16315
16382
  processError({ issues: issue3.issues }, issue3.path);
16316
16383
  } else {
16317
- const fullpath = [...path2, ...issue3.path];
16384
+ const fullpath = [...path3, ...issue3.path];
16318
16385
  if (fullpath.length === 0) {
16319
16386
  result.errors.push(mapper(issue3));
16320
16387
  continue;
@@ -16346,8 +16413,8 @@ function treeifyError2(error49, _mapper) {
16346
16413
  }
16347
16414
  function toDotPath2(_path) {
16348
16415
  const segs = [];
16349
- const path2 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
16350
- for (const seg of path2) {
16416
+ const path3 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
16417
+ for (const seg of path3) {
16351
16418
  if (typeof seg === "number")
16352
16419
  segs.push(`[${seg}]`);
16353
16420
  else if (typeof seg === "symbol")
@@ -27543,7 +27610,7 @@ Use these as DOMAIN values when delegating to @sme.`;
27543
27610
  });
27544
27611
  // src/tools/file-extractor.ts
27545
27612
  import * as fs2 from "fs";
27546
- import * as path2 from "path";
27613
+ import * as path3 from "path";
27547
27614
  var EXT_MAP = {
27548
27615
  python: ".py",
27549
27616
  py: ".py",
@@ -27621,12 +27688,12 @@ var extract_code_blocks = tool({
27621
27688
  if (prefix) {
27622
27689
  filename = `${prefix}_${filename}`;
27623
27690
  }
27624
- let filepath = path2.join(targetDir, filename);
27625
- const base = path2.basename(filepath, path2.extname(filepath));
27626
- const ext = path2.extname(filepath);
27691
+ let filepath = path3.join(targetDir, filename);
27692
+ const base = path3.basename(filepath, path3.extname(filepath));
27693
+ const ext = path3.extname(filepath);
27627
27694
  let counter = 1;
27628
27695
  while (fs2.existsSync(filepath)) {
27629
- filepath = path2.join(targetDir, `${base}_${counter}${ext}`);
27696
+ filepath = path3.join(targetDir, `${base}_${counter}${ext}`);
27630
27697
  counter++;
27631
27698
  }
27632
27699
  try {
@@ -27655,26 +27722,73 @@ Errors:
27655
27722
  }
27656
27723
  });
27657
27724
  // src/tools/gitingest.ts
27725
+ var GITINGEST_TIMEOUT_MS = 1e4;
27726
+ var GITINGEST_MAX_RESPONSE_BYTES = 5242880;
27727
+ var GITINGEST_MAX_RETRIES = 2;
27728
+ var delay = (ms) => new Promise((resolve2) => setTimeout(resolve2, ms));
27658
27729
  async function fetchGitingest(args) {
27659
- const response = await fetch("https://gitingest.com/api/ingest", {
27660
- method: "POST",
27661
- headers: { "Content-Type": "application/json" },
27662
- body: JSON.stringify({
27663
- input_text: args.url,
27664
- max_file_size: args.maxFileSize ?? 50000,
27665
- pattern: args.pattern ?? "",
27666
- pattern_type: args.patternType ?? "exclude"
27667
- })
27668
- });
27669
- if (!response.ok) {
27670
- throw new Error(`gitingest API error: ${response.status} ${response.statusText}`);
27671
- }
27672
- const data = await response.json();
27673
- return `${data.summary}
27730
+ for (let attempt = 0;attempt <= GITINGEST_MAX_RETRIES; attempt++) {
27731
+ try {
27732
+ const controller = new AbortController;
27733
+ const timeoutId = setTimeout(() => controller.abort(), GITINGEST_TIMEOUT_MS);
27734
+ const response = await fetch("https://gitingest.com/api/ingest", {
27735
+ method: "POST",
27736
+ headers: { "Content-Type": "application/json" },
27737
+ body: JSON.stringify({
27738
+ input_text: args.url,
27739
+ max_file_size: args.maxFileSize ?? 50000,
27740
+ pattern: args.pattern ?? "",
27741
+ pattern_type: args.patternType ?? "exclude"
27742
+ }),
27743
+ signal: controller.signal
27744
+ });
27745
+ clearTimeout(timeoutId);
27746
+ if (response.status >= 500 && attempt < GITINGEST_MAX_RETRIES) {
27747
+ const backoff = 200 * 2 ** attempt;
27748
+ await delay(backoff);
27749
+ continue;
27750
+ }
27751
+ if (response.status >= 400 && response.status < 500) {
27752
+ throw new Error(`gitingest API error: ${response.status} ${response.statusText}`);
27753
+ }
27754
+ if (!response.ok) {
27755
+ throw new Error(`gitingest API error: ${response.status} ${response.statusText}`);
27756
+ }
27757
+ const contentLength = Number(response.headers.get("content-length"));
27758
+ if (Number.isFinite(contentLength) && contentLength > GITINGEST_MAX_RESPONSE_BYTES) {
27759
+ throw new Error("gitingest response too large");
27760
+ }
27761
+ const text = await response.text();
27762
+ if (Buffer.byteLength(text) > GITINGEST_MAX_RESPONSE_BYTES) {
27763
+ throw new Error("gitingest response too large");
27764
+ }
27765
+ const data = JSON.parse(text);
27766
+ return `${data.summary}
27674
27767
 
27675
27768
  ${data.tree}
27676
27769
 
27677
27770
  ${data.content}`;
27771
+ } catch (error93) {
27772
+ if (error93 instanceof DOMException && (error93.name === "TimeoutError" || error93.name === "AbortError")) {
27773
+ if (attempt >= GITINGEST_MAX_RETRIES) {
27774
+ throw new Error("gitingest request timed out");
27775
+ }
27776
+ const backoff = 200 * 2 ** attempt;
27777
+ await delay(backoff);
27778
+ continue;
27779
+ }
27780
+ if (error93 instanceof Error && error93.message.startsWith("gitingest ")) {
27781
+ throw error93;
27782
+ }
27783
+ if (attempt < GITINGEST_MAX_RETRIES) {
27784
+ const backoff = 200 * 2 ** attempt;
27785
+ await delay(backoff);
27786
+ continue;
27787
+ }
27788
+ throw error93;
27789
+ }
27790
+ }
27791
+ throw new Error("gitingest request failed after retries");
27678
27792
  }
27679
27793
  var gitingest = tool({
27680
27794
  description: "Fetch a GitHub repository's full content via gitingest.com. Returns summary, directory tree, and file contents optimized for LLM analysis. Use when you need to understand an external repository's structure or code.",
@@ -5,8 +5,11 @@ export interface GitingestArgs {
5
5
  pattern?: string;
6
6
  patternType?: 'include' | 'exclude';
7
7
  }
8
+ export declare const GITINGEST_TIMEOUT_MS = 10000;
9
+ export declare const GITINGEST_MAX_RESPONSE_BYTES = 5242880;
10
+ export declare const GITINGEST_MAX_RETRIES = 2;
8
11
  /**
9
- * Fetch repository content via gitingest.com API
12
+ * Fetch repository content via gitingest.com API with timeout, size guard, and retry logic
10
13
  */
11
14
  export declare function fetchGitingest(args: GitingestArgs): Promise<string>;
12
15
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-swarm",
3
- "version": "4.3.0",
3
+ "version": "4.3.2",
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",