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 +2 -2
- package/dist/config/loader.d.ts +2 -0
- package/dist/hooks/index.d.ts +1 -1
- package/dist/hooks/utils.d.ts +9 -0
- package/dist/index.js +166 -52
- package/dist/tools/gitingest.d.ts +4 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
<p align="center">
|
|
2
|
-
<img src="https://img.shields.io/badge/version-4.3.
|
|
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-
|
|
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>
|
package/dist/config/loader.d.ts
CHANGED
package/dist/hooks/index.d.ts
CHANGED
|
@@ -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';
|
package/dist/hooks/utils.d.ts
CHANGED
|
@@ -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
|
-
|
|
13628
|
-
|
|
13629
|
-
|
|
13630
|
-
|
|
13631
|
-
|
|
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] =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
|
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
|
|
14754
|
-
await Bun.write(
|
|
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,
|
|
15774
|
-
if (!
|
|
15840
|
+
function getElementAtPath2(obj, path3) {
|
|
15841
|
+
if (!path3)
|
|
15775
15842
|
return obj;
|
|
15776
|
-
return
|
|
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(
|
|
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(
|
|
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,
|
|
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 = [...
|
|
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
|
|
16350
|
-
for (const seg of
|
|
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
|
|
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 =
|
|
27625
|
-
const base =
|
|
27626
|
-
const ext =
|
|
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 =
|
|
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
|
-
|
|
27660
|
-
|
|
27661
|
-
|
|
27662
|
-
|
|
27663
|
-
|
|
27664
|
-
|
|
27665
|
-
|
|
27666
|
-
|
|
27667
|
-
|
|
27668
|
-
|
|
27669
|
-
|
|
27670
|
-
|
|
27671
|
-
|
|
27672
|
-
|
|
27673
|
-
|
|
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.
|
|
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",
|