opencode-swarm 4.3.1 → 4.4.0
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 +67 -0
- package/dist/commands/config.d.ts +5 -0
- package/dist/commands/history.d.ts +5 -0
- package/dist/commands/index.d.ts +2 -0
- 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 +225 -41
- package/dist/tools/gitingest.d.ts +4 -1
- package/dist/utils/errors.d.ts +33 -0
- package/dist/utils/index.d.ts +2 -1
- package/package.json +4 -4
package/dist/cli/index.js
CHANGED
|
@@ -96,6 +96,66 @@ Next steps:`);
|
|
|
96
96
|
console.log(" what expertise is needed and requests it dynamically.");
|
|
97
97
|
return 0;
|
|
98
98
|
}
|
|
99
|
+
async function uninstall() {
|
|
100
|
+
try {
|
|
101
|
+
console.log(`\uD83D\uDC1D Uninstalling OpenCode Swarm...
|
|
102
|
+
`);
|
|
103
|
+
const opencodeConfig = loadJson(OPENCODE_CONFIG_PATH);
|
|
104
|
+
if (!opencodeConfig) {
|
|
105
|
+
if (fs.existsSync(OPENCODE_CONFIG_PATH)) {
|
|
106
|
+
console.log(`\u2717 Could not parse opencode config at: ${OPENCODE_CONFIG_PATH}`);
|
|
107
|
+
return 1;
|
|
108
|
+
} else {
|
|
109
|
+
console.log(`\u26A0 No opencode config found at: ${OPENCODE_CONFIG_PATH}`);
|
|
110
|
+
console.log("Nothing to uninstall.");
|
|
111
|
+
return 0;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
if (!opencodeConfig.plugin || opencodeConfig.plugin.length === 0) {
|
|
115
|
+
console.log("\u26A0 opencode-swarm is not installed (no plugins configured).");
|
|
116
|
+
return 0;
|
|
117
|
+
}
|
|
118
|
+
const pluginName = "opencode-swarm";
|
|
119
|
+
const filteredPlugins = opencodeConfig.plugin.filter((p) => p !== pluginName && !p.startsWith(`${pluginName}@`));
|
|
120
|
+
if (filteredPlugins.length === opencodeConfig.plugin.length) {
|
|
121
|
+
console.log("\u26A0 opencode-swarm is not installed.");
|
|
122
|
+
return 0;
|
|
123
|
+
}
|
|
124
|
+
opencodeConfig.plugin = filteredPlugins;
|
|
125
|
+
if (opencodeConfig.agent) {
|
|
126
|
+
delete opencodeConfig.agent.explore;
|
|
127
|
+
delete opencodeConfig.agent.general;
|
|
128
|
+
if (Object.keys(opencodeConfig.agent).length === 0) {
|
|
129
|
+
delete opencodeConfig.agent;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
saveJson(OPENCODE_CONFIG_PATH, opencodeConfig);
|
|
133
|
+
console.log("\u2713 Removed opencode-swarm from OpenCode plugins");
|
|
134
|
+
console.log("\u2713 Re-enabled default OpenCode agents (explore, general)");
|
|
135
|
+
if (process.argv.includes("--clean")) {
|
|
136
|
+
let cleaned = false;
|
|
137
|
+
if (fs.existsSync(PLUGIN_CONFIG_PATH)) {
|
|
138
|
+
fs.unlinkSync(PLUGIN_CONFIG_PATH);
|
|
139
|
+
console.log(`\u2713 Removed plugin config: ${PLUGIN_CONFIG_PATH}`);
|
|
140
|
+
cleaned = true;
|
|
141
|
+
}
|
|
142
|
+
if (fs.existsSync(PROMPTS_DIR)) {
|
|
143
|
+
fs.rmSync(PROMPTS_DIR, { recursive: true });
|
|
144
|
+
console.log(`\u2713 Removed custom prompts: ${PROMPTS_DIR}`);
|
|
145
|
+
cleaned = true;
|
|
146
|
+
}
|
|
147
|
+
if (!cleaned) {
|
|
148
|
+
console.log("\u2713 No config files to clean up");
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
console.log(`
|
|
152
|
+
\u2705 Uninstall complete!`);
|
|
153
|
+
return 0;
|
|
154
|
+
} catch (error) {
|
|
155
|
+
console.log("\u2717 Uninstall failed: " + (error instanceof Error ? error.message : String(error)));
|
|
156
|
+
return 1;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
99
159
|
function printHelp() {
|
|
100
160
|
console.log(`
|
|
101
161
|
opencode-swarm - Architect-centric agentic swarm plugin for OpenCode
|
|
@@ -104,8 +164,10 @@ Usage: bunx opencode-swarm [command] [OPTIONS]
|
|
|
104
164
|
|
|
105
165
|
Commands:
|
|
106
166
|
install Install and configure the plugin (default)
|
|
167
|
+
uninstall Remove the plugin from OpenCode config
|
|
107
168
|
|
|
108
169
|
Options:
|
|
170
|
+
--clean Also remove config files and custom prompts (with uninstall)
|
|
109
171
|
-h, --help Show this help message
|
|
110
172
|
|
|
111
173
|
Configuration:
|
|
@@ -122,6 +184,8 @@ Custom Prompts:
|
|
|
122
184
|
|
|
123
185
|
Examples:
|
|
124
186
|
bunx opencode-swarm install
|
|
187
|
+
bunx opencode-swarm uninstall
|
|
188
|
+
bunx opencode-swarm uninstall --clean
|
|
125
189
|
bunx opencode-swarm --help
|
|
126
190
|
`);
|
|
127
191
|
}
|
|
@@ -135,6 +199,9 @@ async function main() {
|
|
|
135
199
|
if (command === "install") {
|
|
136
200
|
const exitCode = await install();
|
|
137
201
|
process.exit(exitCode);
|
|
202
|
+
} else if (command === "uninstall") {
|
|
203
|
+
const exitCode = await uninstall();
|
|
204
|
+
process.exit(exitCode);
|
|
138
205
|
} else {
|
|
139
206
|
console.error(`Unknown command: ${command}`);
|
|
140
207
|
console.error("Run with --help for usage information");
|
package/dist/commands/index.d.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import type { AgentDefinition } from '../agents';
|
|
2
2
|
export { handleAgentsCommand } from './agents';
|
|
3
|
+
export { handleConfigCommand } from './config';
|
|
4
|
+
export { handleHistoryCommand } from './history';
|
|
3
5
|
export { handlePlanCommand } from './plan';
|
|
4
6
|
export { handleStatusCommand } from './status';
|
|
5
7
|
/**
|
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);
|
|
@@ -14397,6 +14410,46 @@ function handleAgentsCommand(agents) {
|
|
|
14397
14410
|
`);
|
|
14398
14411
|
}
|
|
14399
14412
|
|
|
14413
|
+
// src/commands/config.ts
|
|
14414
|
+
import * as os2 from "os";
|
|
14415
|
+
import * as path2 from "path";
|
|
14416
|
+
function getUserConfigDir2() {
|
|
14417
|
+
return process.env.XDG_CONFIG_HOME || path2.join(os2.homedir(), ".config");
|
|
14418
|
+
}
|
|
14419
|
+
async function handleConfigCommand(directory, _args) {
|
|
14420
|
+
const config2 = loadPluginConfig(directory);
|
|
14421
|
+
const userConfigPath = path2.join(getUserConfigDir2(), "opencode", "opencode-swarm.json");
|
|
14422
|
+
const projectConfigPath = path2.join(directory, ".opencode", "opencode-swarm.json");
|
|
14423
|
+
const lines = [
|
|
14424
|
+
"## Swarm Configuration",
|
|
14425
|
+
"",
|
|
14426
|
+
"### Config Files",
|
|
14427
|
+
`- User: \`${userConfigPath}\``,
|
|
14428
|
+
`- Project: \`${projectConfigPath}\``,
|
|
14429
|
+
"",
|
|
14430
|
+
"### Resolved Config",
|
|
14431
|
+
"```json",
|
|
14432
|
+
JSON.stringify(config2, null, 2),
|
|
14433
|
+
"```"
|
|
14434
|
+
];
|
|
14435
|
+
return lines.join(`
|
|
14436
|
+
`);
|
|
14437
|
+
}
|
|
14438
|
+
|
|
14439
|
+
// src/hooks/utils.ts
|
|
14440
|
+
import * as path3 from "path";
|
|
14441
|
+
|
|
14442
|
+
// src/utils/errors.ts
|
|
14443
|
+
class SwarmError extends Error {
|
|
14444
|
+
code;
|
|
14445
|
+
guidance;
|
|
14446
|
+
constructor(message, code, guidance) {
|
|
14447
|
+
super(message);
|
|
14448
|
+
this.name = "SwarmError";
|
|
14449
|
+
this.code = code;
|
|
14450
|
+
this.guidance = guidance;
|
|
14451
|
+
}
|
|
14452
|
+
}
|
|
14400
14453
|
// src/utils/logger.ts
|
|
14401
14454
|
var DEBUG = process.env.OPENCODE_SWARM_DEBUG === "1";
|
|
14402
14455
|
function log(message, data) {
|
|
@@ -14424,7 +14477,12 @@ function safeHook(fn) {
|
|
|
14424
14477
|
await fn(input, output);
|
|
14425
14478
|
} catch (_error) {
|
|
14426
14479
|
const functionName = fn.name || "unknown";
|
|
14427
|
-
|
|
14480
|
+
if (_error instanceof SwarmError) {
|
|
14481
|
+
warn(`Hook '${functionName}' failed: ${_error.message}
|
|
14482
|
+
\u2192 ${_error.guidance}`);
|
|
14483
|
+
} else {
|
|
14484
|
+
warn(`Hook function '${functionName}' failed:`, _error);
|
|
14485
|
+
}
|
|
14428
14486
|
}
|
|
14429
14487
|
};
|
|
14430
14488
|
}
|
|
@@ -14439,10 +14497,30 @@ function composeHandlers(...fns) {
|
|
|
14439
14497
|
}
|
|
14440
14498
|
};
|
|
14441
14499
|
}
|
|
14500
|
+
function validateSwarmPath(directory, filename) {
|
|
14501
|
+
if (/[\0]/.test(filename)) {
|
|
14502
|
+
throw new Error("Invalid filename: contains null bytes");
|
|
14503
|
+
}
|
|
14504
|
+
if (/\.\.[/\\]/.test(filename)) {
|
|
14505
|
+
throw new Error("Invalid filename: path traversal detected");
|
|
14506
|
+
}
|
|
14507
|
+
const baseDir = path3.normalize(path3.resolve(directory, ".swarm"));
|
|
14508
|
+
const resolved = path3.normalize(path3.resolve(baseDir, filename));
|
|
14509
|
+
if (process.platform === "win32") {
|
|
14510
|
+
if (!resolved.toLowerCase().startsWith((baseDir + path3.sep).toLowerCase())) {
|
|
14511
|
+
throw new Error("Invalid filename: path escapes .swarm directory");
|
|
14512
|
+
}
|
|
14513
|
+
} else {
|
|
14514
|
+
if (!resolved.startsWith(baseDir + path3.sep)) {
|
|
14515
|
+
throw new Error("Invalid filename: path escapes .swarm directory");
|
|
14516
|
+
}
|
|
14517
|
+
}
|
|
14518
|
+
return resolved;
|
|
14519
|
+
}
|
|
14442
14520
|
async function readSwarmFileAsync(directory, filename) {
|
|
14443
|
-
const path2 = `${directory}/.swarm/${filename}`;
|
|
14444
14521
|
try {
|
|
14445
|
-
const
|
|
14522
|
+
const resolvedPath = validateSwarmPath(directory, filename);
|
|
14523
|
+
const file2 = Bun.file(resolvedPath);
|
|
14446
14524
|
const content = await file2.text();
|
|
14447
14525
|
return content;
|
|
14448
14526
|
} catch {
|
|
@@ -14456,6 +14534,57 @@ function estimateTokens(text) {
|
|
|
14456
14534
|
return Math.ceil(text.length * 0.33);
|
|
14457
14535
|
}
|
|
14458
14536
|
|
|
14537
|
+
// src/commands/history.ts
|
|
14538
|
+
async function handleHistoryCommand(directory, _args) {
|
|
14539
|
+
const planContent = await readSwarmFileAsync(directory, "plan.md");
|
|
14540
|
+
if (!planContent) {
|
|
14541
|
+
return "No history available.";
|
|
14542
|
+
}
|
|
14543
|
+
const phaseRegex = /^## Phase (\d+):?\s*(.+?)(?:\s*\[(COMPLETE|IN PROGRESS|PENDING)\])?\s*$/gm;
|
|
14544
|
+
const phases = [];
|
|
14545
|
+
const lines = planContent.split(`
|
|
14546
|
+
`);
|
|
14547
|
+
for (let match = phaseRegex.exec(planContent);match !== null; match = phaseRegex.exec(planContent)) {
|
|
14548
|
+
const num = parseInt(match[1], 10);
|
|
14549
|
+
const name = match[2].trim();
|
|
14550
|
+
const status = match[3] || "PENDING";
|
|
14551
|
+
const headerLineIndex = lines.indexOf(match[0]);
|
|
14552
|
+
let completed = 0;
|
|
14553
|
+
let total = 0;
|
|
14554
|
+
if (headerLineIndex !== -1) {
|
|
14555
|
+
for (let i = headerLineIndex + 1;i < lines.length; i++) {
|
|
14556
|
+
const line = lines[i];
|
|
14557
|
+
if (/^## Phase \d+/.test(line) || line.trim() === "---" && total > 0) {
|
|
14558
|
+
break;
|
|
14559
|
+
}
|
|
14560
|
+
if (/^- \[x\]/.test(line)) {
|
|
14561
|
+
completed++;
|
|
14562
|
+
total++;
|
|
14563
|
+
} else if (/^- \[ \]/.test(line)) {
|
|
14564
|
+
total++;
|
|
14565
|
+
}
|
|
14566
|
+
}
|
|
14567
|
+
}
|
|
14568
|
+
phases.push({ num, name, status, completed, total });
|
|
14569
|
+
}
|
|
14570
|
+
if (phases.length === 0) {
|
|
14571
|
+
return "No history available.";
|
|
14572
|
+
}
|
|
14573
|
+
const tableLines = [
|
|
14574
|
+
"## Swarm History",
|
|
14575
|
+
"",
|
|
14576
|
+
"| Phase | Name | Status | Tasks |",
|
|
14577
|
+
"|-------|------|--------|-------|"
|
|
14578
|
+
];
|
|
14579
|
+
for (const phase of phases) {
|
|
14580
|
+
const statusIcon = phase.status === "COMPLETE" ? "\u2705" : phase.status === "IN PROGRESS" ? "\uD83D\uDD04" : "\u23F3";
|
|
14581
|
+
const tasks = phase.total > 0 ? `${phase.completed}/${phase.total}` : "-";
|
|
14582
|
+
tableLines.push(`| ${phase.num} | ${phase.name} | ${statusIcon} ${phase.status} | ${tasks} |`);
|
|
14583
|
+
}
|
|
14584
|
+
return tableLines.join(`
|
|
14585
|
+
`);
|
|
14586
|
+
}
|
|
14587
|
+
|
|
14459
14588
|
// src/commands/plan.ts
|
|
14460
14589
|
async function handlePlanCommand(directory, args) {
|
|
14461
14590
|
const planContent = await readSwarmFileAsync(directory, "plan.md");
|
|
@@ -14673,7 +14802,9 @@ var HELP_TEXT = [
|
|
|
14673
14802
|
"",
|
|
14674
14803
|
"- `/swarm status` \u2014 Show current swarm state",
|
|
14675
14804
|
"- `/swarm plan [phase]` \u2014 Show plan (optionally filter by phase number)",
|
|
14676
|
-
"- `/swarm agents` \u2014 List registered agents"
|
|
14805
|
+
"- `/swarm agents` \u2014 List registered agents",
|
|
14806
|
+
"- `/swarm history` \u2014 Show completed phases summary",
|
|
14807
|
+
"- `/swarm config` \u2014 Show current resolved configuration"
|
|
14677
14808
|
].join(`
|
|
14678
14809
|
`);
|
|
14679
14810
|
function createSwarmCommandHandler(directory, agents) {
|
|
@@ -14694,6 +14825,12 @@ function createSwarmCommandHandler(directory, agents) {
|
|
|
14694
14825
|
case "agents":
|
|
14695
14826
|
text = handleAgentsCommand(agents);
|
|
14696
14827
|
break;
|
|
14828
|
+
case "history":
|
|
14829
|
+
text = await handleHistoryCommand(directory, args);
|
|
14830
|
+
break;
|
|
14831
|
+
case "config":
|
|
14832
|
+
text = await handleConfigCommand(directory, args);
|
|
14833
|
+
break;
|
|
14697
14834
|
default:
|
|
14698
14835
|
text = HELP_TEXT;
|
|
14699
14836
|
break;
|
|
@@ -14781,8 +14918,8 @@ async function doFlush(directory) {
|
|
|
14781
14918
|
const activitySection = renderActivitySection();
|
|
14782
14919
|
const updated = replaceOrAppendSection(existing, "## Agent Activity", activitySection);
|
|
14783
14920
|
const flushedCount = swarmState.pendingEvents;
|
|
14784
|
-
const
|
|
14785
|
-
await Bun.write(
|
|
14921
|
+
const path4 = `${directory}/.swarm/context.md`;
|
|
14922
|
+
await Bun.write(path4, updated);
|
|
14786
14923
|
swarmState.pendingEvents = Math.max(0, swarmState.pendingEvents - flushedCount);
|
|
14787
14924
|
} catch (error49) {
|
|
14788
14925
|
warn("Agent activity flush failed:", error49);
|
|
@@ -15801,10 +15938,10 @@ function mergeDefs2(...defs) {
|
|
|
15801
15938
|
function cloneDef2(schema) {
|
|
15802
15939
|
return mergeDefs2(schema._zod.def);
|
|
15803
15940
|
}
|
|
15804
|
-
function getElementAtPath2(obj,
|
|
15805
|
-
if (!
|
|
15941
|
+
function getElementAtPath2(obj, path4) {
|
|
15942
|
+
if (!path4)
|
|
15806
15943
|
return obj;
|
|
15807
|
-
return
|
|
15944
|
+
return path4.reduce((acc, key) => acc?.[key], obj);
|
|
15808
15945
|
}
|
|
15809
15946
|
function promiseAllObject2(promisesObj) {
|
|
15810
15947
|
const keys = Object.keys(promisesObj);
|
|
@@ -16163,11 +16300,11 @@ function aborted2(x, startIndex = 0) {
|
|
|
16163
16300
|
}
|
|
16164
16301
|
return false;
|
|
16165
16302
|
}
|
|
16166
|
-
function prefixIssues2(
|
|
16303
|
+
function prefixIssues2(path4, issues) {
|
|
16167
16304
|
return issues.map((iss) => {
|
|
16168
16305
|
var _a2;
|
|
16169
16306
|
(_a2 = iss).path ?? (_a2.path = []);
|
|
16170
|
-
iss.path.unshift(
|
|
16307
|
+
iss.path.unshift(path4);
|
|
16171
16308
|
return iss;
|
|
16172
16309
|
});
|
|
16173
16310
|
}
|
|
@@ -16335,7 +16472,7 @@ function treeifyError2(error49, _mapper) {
|
|
|
16335
16472
|
return issue3.message;
|
|
16336
16473
|
};
|
|
16337
16474
|
const result = { errors: [] };
|
|
16338
|
-
const processError = (error50,
|
|
16475
|
+
const processError = (error50, path4 = []) => {
|
|
16339
16476
|
var _a2, _b;
|
|
16340
16477
|
for (const issue3 of error50.issues) {
|
|
16341
16478
|
if (issue3.code === "invalid_union" && issue3.errors.length) {
|
|
@@ -16345,7 +16482,7 @@ function treeifyError2(error49, _mapper) {
|
|
|
16345
16482
|
} else if (issue3.code === "invalid_element") {
|
|
16346
16483
|
processError({ issues: issue3.issues }, issue3.path);
|
|
16347
16484
|
} else {
|
|
16348
|
-
const fullpath = [...
|
|
16485
|
+
const fullpath = [...path4, ...issue3.path];
|
|
16349
16486
|
if (fullpath.length === 0) {
|
|
16350
16487
|
result.errors.push(mapper(issue3));
|
|
16351
16488
|
continue;
|
|
@@ -16377,8 +16514,8 @@ function treeifyError2(error49, _mapper) {
|
|
|
16377
16514
|
}
|
|
16378
16515
|
function toDotPath2(_path) {
|
|
16379
16516
|
const segs = [];
|
|
16380
|
-
const
|
|
16381
|
-
for (const seg of
|
|
16517
|
+
const path4 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
|
|
16518
|
+
for (const seg of path4) {
|
|
16382
16519
|
if (typeof seg === "number")
|
|
16383
16520
|
segs.push(`[${seg}]`);
|
|
16384
16521
|
else if (typeof seg === "symbol")
|
|
@@ -27574,7 +27711,7 @@ Use these as DOMAIN values when delegating to @sme.`;
|
|
|
27574
27711
|
});
|
|
27575
27712
|
// src/tools/file-extractor.ts
|
|
27576
27713
|
import * as fs2 from "fs";
|
|
27577
|
-
import * as
|
|
27714
|
+
import * as path4 from "path";
|
|
27578
27715
|
var EXT_MAP = {
|
|
27579
27716
|
python: ".py",
|
|
27580
27717
|
py: ".py",
|
|
@@ -27652,12 +27789,12 @@ var extract_code_blocks = tool({
|
|
|
27652
27789
|
if (prefix) {
|
|
27653
27790
|
filename = `${prefix}_${filename}`;
|
|
27654
27791
|
}
|
|
27655
|
-
let filepath =
|
|
27656
|
-
const base =
|
|
27657
|
-
const ext =
|
|
27792
|
+
let filepath = path4.join(targetDir, filename);
|
|
27793
|
+
const base = path4.basename(filepath, path4.extname(filepath));
|
|
27794
|
+
const ext = path4.extname(filepath);
|
|
27658
27795
|
let counter = 1;
|
|
27659
27796
|
while (fs2.existsSync(filepath)) {
|
|
27660
|
-
filepath =
|
|
27797
|
+
filepath = path4.join(targetDir, `${base}_${counter}${ext}`);
|
|
27661
27798
|
counter++;
|
|
27662
27799
|
}
|
|
27663
27800
|
try {
|
|
@@ -27686,26 +27823,73 @@ Errors:
|
|
|
27686
27823
|
}
|
|
27687
27824
|
});
|
|
27688
27825
|
// src/tools/gitingest.ts
|
|
27826
|
+
var GITINGEST_TIMEOUT_MS = 1e4;
|
|
27827
|
+
var GITINGEST_MAX_RESPONSE_BYTES = 5242880;
|
|
27828
|
+
var GITINGEST_MAX_RETRIES = 2;
|
|
27829
|
+
var delay = (ms) => new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
27689
27830
|
async function fetchGitingest(args) {
|
|
27690
|
-
|
|
27691
|
-
|
|
27692
|
-
|
|
27693
|
-
|
|
27694
|
-
|
|
27695
|
-
|
|
27696
|
-
|
|
27697
|
-
|
|
27698
|
-
|
|
27699
|
-
|
|
27700
|
-
|
|
27701
|
-
|
|
27702
|
-
|
|
27703
|
-
|
|
27704
|
-
|
|
27831
|
+
for (let attempt = 0;attempt <= GITINGEST_MAX_RETRIES; attempt++) {
|
|
27832
|
+
try {
|
|
27833
|
+
const controller = new AbortController;
|
|
27834
|
+
const timeoutId = setTimeout(() => controller.abort(), GITINGEST_TIMEOUT_MS);
|
|
27835
|
+
const response = await fetch("https://gitingest.com/api/ingest", {
|
|
27836
|
+
method: "POST",
|
|
27837
|
+
headers: { "Content-Type": "application/json" },
|
|
27838
|
+
body: JSON.stringify({
|
|
27839
|
+
input_text: args.url,
|
|
27840
|
+
max_file_size: args.maxFileSize ?? 50000,
|
|
27841
|
+
pattern: args.pattern ?? "",
|
|
27842
|
+
pattern_type: args.patternType ?? "exclude"
|
|
27843
|
+
}),
|
|
27844
|
+
signal: controller.signal
|
|
27845
|
+
});
|
|
27846
|
+
clearTimeout(timeoutId);
|
|
27847
|
+
if (response.status >= 500 && attempt < GITINGEST_MAX_RETRIES) {
|
|
27848
|
+
const backoff = 200 * 2 ** attempt;
|
|
27849
|
+
await delay(backoff);
|
|
27850
|
+
continue;
|
|
27851
|
+
}
|
|
27852
|
+
if (response.status >= 400 && response.status < 500) {
|
|
27853
|
+
throw new Error(`gitingest API error: ${response.status} ${response.statusText}`);
|
|
27854
|
+
}
|
|
27855
|
+
if (!response.ok) {
|
|
27856
|
+
throw new Error(`gitingest API error: ${response.status} ${response.statusText}`);
|
|
27857
|
+
}
|
|
27858
|
+
const contentLength = Number(response.headers.get("content-length"));
|
|
27859
|
+
if (Number.isFinite(contentLength) && contentLength > GITINGEST_MAX_RESPONSE_BYTES) {
|
|
27860
|
+
throw new Error("gitingest response too large");
|
|
27861
|
+
}
|
|
27862
|
+
const text = await response.text();
|
|
27863
|
+
if (Buffer.byteLength(text) > GITINGEST_MAX_RESPONSE_BYTES) {
|
|
27864
|
+
throw new Error("gitingest response too large");
|
|
27865
|
+
}
|
|
27866
|
+
const data = JSON.parse(text);
|
|
27867
|
+
return `${data.summary}
|
|
27705
27868
|
|
|
27706
27869
|
${data.tree}
|
|
27707
27870
|
|
|
27708
27871
|
${data.content}`;
|
|
27872
|
+
} catch (error93) {
|
|
27873
|
+
if (error93 instanceof DOMException && (error93.name === "TimeoutError" || error93.name === "AbortError")) {
|
|
27874
|
+
if (attempt >= GITINGEST_MAX_RETRIES) {
|
|
27875
|
+
throw new Error("gitingest request timed out");
|
|
27876
|
+
}
|
|
27877
|
+
const backoff = 200 * 2 ** attempt;
|
|
27878
|
+
await delay(backoff);
|
|
27879
|
+
continue;
|
|
27880
|
+
}
|
|
27881
|
+
if (error93 instanceof Error && error93.message.startsWith("gitingest ")) {
|
|
27882
|
+
throw error93;
|
|
27883
|
+
}
|
|
27884
|
+
if (attempt < GITINGEST_MAX_RETRIES) {
|
|
27885
|
+
const backoff = 200 * 2 ** attempt;
|
|
27886
|
+
await delay(backoff);
|
|
27887
|
+
continue;
|
|
27888
|
+
}
|
|
27889
|
+
throw error93;
|
|
27890
|
+
}
|
|
27891
|
+
}
|
|
27892
|
+
throw new Error("gitingest request failed after retries");
|
|
27709
27893
|
}
|
|
27710
27894
|
var gitingest = tool({
|
|
27711
27895
|
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
|
/**
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Base error class for all swarm errors.
|
|
3
|
+
* Includes a machine-readable `code` and a user-facing `guidance` string.
|
|
4
|
+
*/
|
|
5
|
+
export declare class SwarmError extends Error {
|
|
6
|
+
readonly code: string;
|
|
7
|
+
readonly guidance: string;
|
|
8
|
+
constructor(message: string, code: string, guidance: string);
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Error thrown when configuration loading or validation fails.
|
|
12
|
+
*/
|
|
13
|
+
export declare class ConfigError extends SwarmError {
|
|
14
|
+
constructor(message: string, guidance: string);
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Error thrown when a hook execution fails.
|
|
18
|
+
*/
|
|
19
|
+
export declare class HookError extends SwarmError {
|
|
20
|
+
constructor(message: string, guidance: string);
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Error thrown when a tool execution fails.
|
|
24
|
+
*/
|
|
25
|
+
export declare class ToolError extends SwarmError {
|
|
26
|
+
constructor(message: string, guidance: string);
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Error thrown when CLI operations fail.
|
|
30
|
+
*/
|
|
31
|
+
export declare class CLIError extends SwarmError {
|
|
32
|
+
constructor(message: string, guidance: string);
|
|
33
|
+
}
|
package/dist/utils/index.d.ts
CHANGED
|
@@ -1 +1,2 @@
|
|
|
1
|
-
export {
|
|
1
|
+
export { CLIError, ConfigError, HookError, SwarmError, ToolError, } from './errors';
|
|
2
|
+
export { error, log, warn } from './logger';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opencode-swarm",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.4.0",
|
|
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",
|
|
@@ -35,12 +35,12 @@
|
|
|
35
35
|
"prepublishOnly": "bun run build"
|
|
36
36
|
},
|
|
37
37
|
"dependencies": {
|
|
38
|
-
"@opencode-ai/plugin": "^1.1.
|
|
39
|
-
"@opencode-ai/sdk": "^1.1.
|
|
38
|
+
"@opencode-ai/plugin": "^1.1.53",
|
|
39
|
+
"@opencode-ai/sdk": "^1.1.53",
|
|
40
40
|
"zod": "^4.1.8"
|
|
41
41
|
},
|
|
42
42
|
"devDependencies": {
|
|
43
|
-
"@biomejs/biome": "2.3.
|
|
43
|
+
"@biomejs/biome": "2.3.14",
|
|
44
44
|
"bun-types": "latest",
|
|
45
45
|
"typescript": "^5.7.3"
|
|
46
46
|
}
|