opencode-swarm 6.1.0 → 6.1.1

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-6.0.0-blue" alt="Version">
2
+ <img src="https://img.shields.io/badge/version-6.1.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
- <img src="https://img.shields.io/badge/agents-7-orange" alt="Agents">
6
- <img src="https://img.shields.io/badge/tests-1188-brightgreen" alt="Tests">
5
+ <img src="https://img.shields.io/badge/agents-9-orange" alt="Agents">
6
+ <img src="https://img.shields.io/badge/tests-1280-brightgreen" alt="Tests">
7
7
  </p>
8
8
 
9
9
  <h1 align="center">🐝 OpenCode Swarm</h1>
@@ -343,6 +343,17 @@ bunx opencode-swarm uninstall --clean
343
343
 
344
344
  ## What's New
345
345
 
346
+ ### v6.1.1 — Security Fix & Tech Debt
347
+ - **Security hardening (`_loadedFromFile`)** — Fixed a critical vulnerability where an internal loader flag could be injected via JSON config to bypass guardrails. The flag is now purely internal and no longer part of the public schema.
348
+ - **TOCTOU protection** — Added atomic-style content checks in the config loader to prevent race conditions during file reads.
349
+ - **`retrieve_summary` tool** — Properly registered the retrieval tool, allowing agents to fetch full content from auto-summarized tool outputs.
350
+ - **92 new tests** — 1280 total tests across 57+ files (up from 1188 in v6.0.0).
351
+
352
+ ### v6.1.0 — Docs & Design Agents
353
+ - **`docs` agent** — Dedicated documentation synthesizer that automatically updates READMEs, API docs, and guides during Phase 6.
354
+ - **`designer` agent** — UI/UX specification agent that generates component scaffolds before coding begins on UI-heavy tasks.
355
+ - **Heterogeneous model defaults** — Updated default models for new agents to use optimized Gemini models for speed and cost.
356
+
346
357
  ### v6.0.0 — Core QA & Security Gates
347
358
  - **Dual-pass security reviewer** — After the general reviewer APPROVES, the architect automatically triggers a second security-only review pass when the changed file matches security-sensitive paths (`auth`, `crypto`, `session`, `token`, `middleware`, `api`, `security`) or the coder's output contains security keywords. Configurable via `review_passes` config.
348
359
  - **Adversarial testing** — After verification tests PASS, the test engineer is re-delegated with adversarial-only framing: attack vectors, boundary violations, and injection attempts. Pure prompt engineering, no new infrastructure.
@@ -411,6 +422,11 @@ All features are opt-in via configuration. See [Installation Guide](docs/install
411
422
  |-------|------|
412
423
  | `explorer` | Fast codebase scanner. Identifies structure, languages, frameworks, key files. |
413
424
 
425
+ ### 🎨 Design
426
+ | Agent | Role |
427
+ |-------|------|
428
+ | `designer` | UI/UX specification agent. Generates component scaffolds and design tokens before coding begins on UI-heavy tasks. |
429
+
414
430
  ### 🧠 Domain Expert
415
431
  | Agent | Role |
416
432
  |-------|------|
@@ -428,6 +444,11 @@ All features are opt-in via configuration. See [Installation Guide](docs/install
428
444
  | `reviewer` | Dual-pass review: correctness review first, then automatic security-only pass for security-sensitive files. The architect specifies CHECK dimensions per call. OWASP Top 10 categories built in. |
429
445
  | `critic` | Plan review gate. Reviews the architect's plan BEFORE implementation — checks completeness, feasibility, scope, dependencies, and flags AI-slop. |
430
446
 
447
+ ### 📝 Documentation
448
+ | Agent | Role |
449
+ |-------|------|
450
+ | `docs` | Documentation synthesizer. Automatically updates READMEs, API docs, and guides based on implementation changes during Phase 6. |
451
+
431
452
  ---
432
453
 
433
454
  ## Slash Commands
@@ -462,7 +483,9 @@ Create `~/.config/opencode/opencode-swarm.json`:
462
483
  "sme": { "model": "google/gemini-2.0-flash" },
463
484
  "reviewer": { "model": "openai/gpt-4o" },
464
485
  "critic": { "model": "google/gemini-2.0-flash" },
465
- "test_engineer": { "model": "google/gemini-2.0-flash" }
486
+ "test_engineer": { "model": "google/gemini-2.0-flash" },
487
+ "docs": { "model": "google/gemini-2.0-flash" },
488
+ "designer": { "model": "google/gemini-2.0-flash" }
466
489
  }
467
490
  }
468
491
  ```
@@ -630,7 +653,7 @@ bun test
630
653
  bun test tests/unit/config/schema.test.ts
631
654
  ```
632
655
 
633
- 1188 tests across 53+ files covering config, tools, agents, hooks, commands, state, guardrails, evidence, plan schemas, circuit breaker race conditions, invocation windows, multi-invocation isolation, security categories, review/integration schemas, and diff tool. Uses Bun's built-in test runner — zero additional test dependencies.
656
+ 1280 tests across 57+ files covering config, tools, agents, hooks, commands, state, guardrails, evidence, plan schemas, circuit breaker race conditions, invocation windows, multi-invocation isolation, security categories, review/integration schemas, and diff tool. Uses Bun's built-in test runner — zero additional test dependencies.
634
657
 
635
658
  ## Troubleshooting
636
659
 
@@ -55,8 +55,8 @@ export declare const ReviewEvidenceSchema: z.ZodObject<{
55
55
  metadata: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
56
56
  type: z.ZodLiteral<"review">;
57
57
  risk: z.ZodEnum<{
58
- medium: "medium";
59
58
  low: "low";
59
+ medium: "medium";
60
60
  high: "high";
61
61
  critical: "critical";
62
62
  }>;
@@ -162,8 +162,8 @@ export declare const EvidenceSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
162
162
  metadata: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
163
163
  type: z.ZodLiteral<"review">;
164
164
  risk: z.ZodEnum<{
165
- medium: "medium";
166
165
  low: "low";
166
+ medium: "medium";
167
167
  high: "high";
168
168
  critical: "critical";
169
169
  }>;
@@ -264,8 +264,8 @@ export declare const EvidenceBundleSchema: z.ZodObject<{
264
264
  metadata: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
265
265
  type: z.ZodLiteral<"review">;
266
266
  risk: z.ZodEnum<{
267
- medium: "medium";
268
267
  low: "low";
268
+ medium: "medium";
269
269
  high: "high";
270
270
  critical: "critical";
271
271
  }>;
@@ -1,9 +1,9 @@
1
1
  export type { AgentName, PipelineAgentName, QAAgentName, } from './constants';
2
2
  export { ALL_AGENT_NAMES, ALL_SUBAGENT_NAMES, DEFAULT_MODELS, isQAAgent, isSubagent, ORCHESTRATOR_NAME, PIPELINE_AGENTS, QA_AGENTS, } from './constants';
3
- export { loadAgentPrompt, loadPluginConfig, } from './loader';
4
- export type { MigrationStatus, Phase, PhaseStatus, Plan, Task, TaskSize, TaskStatus, } from './plan-schema';
5
- export { MigrationStatusSchema, PhaseSchema, PhaseStatusSchema, PlanSchema, TaskSchema, TaskSizeSchema, TaskStatusSchema, } from './plan-schema';
6
3
  export type { ApprovalEvidence, BaseEvidence, DiffEvidence, Evidence, EvidenceBundle, EvidenceType, EvidenceVerdict, NoteEvidence, ReviewEvidence, TestEvidence, } from './evidence-schema';
7
4
  export { ApprovalEvidenceSchema, BaseEvidenceSchema, DiffEvidenceSchema, EVIDENCE_MAX_JSON_BYTES, EVIDENCE_MAX_PATCH_BYTES, EVIDENCE_MAX_TASK_BYTES, EvidenceBundleSchema, EvidenceSchema, EvidenceTypeSchema, EvidenceVerdictSchema, NoteEvidenceSchema, ReviewEvidenceSchema, TestEvidenceSchema, } from './evidence-schema';
5
+ export { loadAgentPrompt, loadPluginConfig, loadPluginConfigWithMeta, } from './loader';
6
+ export type { MigrationStatus, Phase, PhaseStatus, Plan, Task, TaskSize, TaskStatus, } from './plan-schema';
7
+ export { MigrationStatusSchema, PhaseSchema, PhaseStatusSchema, PlanSchema, TaskSchema, TaskSizeSchema, TaskStatusSchema, } from './plan-schema';
8
8
  export type { AgentOverrideConfig, PluginConfig, SwarmConfig, } from './schema';
9
9
  export { AgentOverrideConfigSchema, PluginConfigSchema, SwarmConfigSchema, } from './schema';
@@ -1,10 +1,6 @@
1
1
  import { type PluginConfig } from './schema';
2
2
  export declare const MAX_CONFIG_FILE_BYTES = 102400;
3
- export declare const MAX_MERGE_DEPTH = 10;
4
- /**
5
- * Deep merge two objects, with override values taking precedence.
6
- */
7
- export declare function deepMerge<T extends Record<string, unknown>>(base?: T, override?: T): T | undefined;
3
+ export { deepMerge, MAX_MERGE_DEPTH } from '../utils/merge';
8
4
  /**
9
5
  * Load plugin configuration from user and project config files.
10
6
  *
@@ -17,6 +13,15 @@ export declare function deepMerge<T extends Record<string, unknown>>(base?: T, o
17
13
  * Zod defaults don't override explicit user values.
18
14
  */
19
15
  export declare function loadPluginConfig(directory: string): PluginConfig;
16
+ /**
17
+ * Internal variant of loadPluginConfig that also returns loader metadata.
18
+ * Used only by src/index.ts to determine guardrails fallback behavior.
19
+ * NOT part of the public API — use loadPluginConfig() for all other callers.
20
+ */
21
+ export declare function loadPluginConfigWithMeta(directory: string): {
22
+ config: PluginConfig;
23
+ loadedFromFile: boolean;
24
+ };
20
25
  /**
21
26
  * Load custom prompt for an agent from the prompts directory.
22
27
  * Checks for {agent}.md (replaces default) and {agent}_append.md (appends).
@@ -7,8 +7,8 @@ export declare const TaskStatusSchema: z.ZodEnum<{
7
7
  }>;
8
8
  export type TaskStatus = z.infer<typeof TaskStatusSchema>;
9
9
  export declare const TaskSizeSchema: z.ZodEnum<{
10
- small: "small";
11
10
  medium: "medium";
11
+ small: "small";
12
12
  large: "large";
13
13
  }>;
14
14
  export type TaskSize = z.infer<typeof TaskSizeSchema>;
@@ -35,8 +35,8 @@ export declare const TaskSchema: z.ZodObject<{
35
35
  blocked: "blocked";
36
36
  }>>;
37
37
  size: z.ZodDefault<z.ZodEnum<{
38
- small: "small";
39
38
  medium: "medium";
39
+ small: "small";
40
40
  large: "large";
41
41
  }>>;
42
42
  description: z.ZodString;
@@ -66,8 +66,8 @@ export declare const PhaseSchema: z.ZodObject<{
66
66
  blocked: "blocked";
67
67
  }>>;
68
68
  size: z.ZodDefault<z.ZodEnum<{
69
- small: "small";
70
69
  medium: "medium";
70
+ small: "small";
71
71
  large: "large";
72
72
  }>>;
73
73
  description: z.ZodString;
@@ -103,8 +103,8 @@ export declare const PlanSchema: z.ZodObject<{
103
103
  blocked: "blocked";
104
104
  }>>;
105
105
  size: z.ZodDefault<z.ZodEnum<{
106
- small: "small";
107
106
  medium: "medium";
107
+ small: "small";
108
108
  large: "large";
109
109
  }>>;
110
110
  description: z.ZodString;
@@ -318,7 +318,6 @@ export declare const PluginConfigSchema: z.ZodObject<{
318
318
  trigger_paths: z.ZodDefault<z.ZodArray<z.ZodString>>;
319
319
  trigger_keywords: z.ZodDefault<z.ZodArray<z.ZodString>>;
320
320
  }, z.core.$strip>>;
321
- _loadedFromFile: z.ZodDefault<z.ZodBoolean>;
322
321
  }, z.core.$strip>;
323
322
  export type PluginConfig = z.infer<typeof PluginConfigSchema>;
324
323
  export type { AgentName, PipelineAgentName, QAAgentName, } from './constants';
package/dist/index.js CHANGED
@@ -26,11 +26,83 @@ var __export = (target, all) => {
26
26
  };
27
27
  var __require = import.meta.require;
28
28
 
29
- // src/config/loader.ts
30
- import * as fs from "fs";
31
- import * as os from "os";
32
- import * as path from "path";
29
+ // src/utils/merge.ts
30
+ var MAX_MERGE_DEPTH = 10;
31
+ function deepMergeInternal(base, override, depth) {
32
+ if (depth >= MAX_MERGE_DEPTH) {
33
+ throw new Error(`deepMerge exceeded maximum depth of ${MAX_MERGE_DEPTH}`);
34
+ }
35
+ const result = { ...base };
36
+ for (const key of Object.keys(override)) {
37
+ const baseVal = base[key];
38
+ const overrideVal = override[key];
39
+ if (typeof baseVal === "object" && baseVal !== null && typeof overrideVal === "object" && overrideVal !== null && !Array.isArray(baseVal) && !Array.isArray(overrideVal)) {
40
+ result[key] = deepMergeInternal(baseVal, overrideVal, depth + 1);
41
+ } else {
42
+ result[key] = overrideVal;
43
+ }
44
+ }
45
+ return result;
46
+ }
47
+ function deepMerge(base, override) {
48
+ if (!base)
49
+ return override;
50
+ if (!override)
51
+ return base;
52
+ return deepMergeInternal(base, override, 0);
53
+ }
33
54
 
55
+ // src/config/constants.ts
56
+ var QA_AGENTS = ["reviewer", "critic"];
57
+ var PIPELINE_AGENTS = ["explorer", "coder", "test_engineer"];
58
+ var ORCHESTRATOR_NAME = "architect";
59
+ var ALL_SUBAGENT_NAMES = [
60
+ "sme",
61
+ "docs",
62
+ "designer",
63
+ ...QA_AGENTS,
64
+ ...PIPELINE_AGENTS
65
+ ];
66
+ var ALL_AGENT_NAMES = [
67
+ ORCHESTRATOR_NAME,
68
+ ...ALL_SUBAGENT_NAMES
69
+ ];
70
+ var DEFAULT_MODELS = {
71
+ architect: "anthropic/claude-sonnet-4-5",
72
+ explorer: "google/gemini-2.0-flash",
73
+ coder: "anthropic/claude-sonnet-4-5",
74
+ test_engineer: "google/gemini-2.0-flash",
75
+ sme: "google/gemini-2.0-flash",
76
+ reviewer: "google/gemini-2.0-flash",
77
+ critic: "google/gemini-2.0-flash",
78
+ docs: "google/gemini-2.0-flash",
79
+ designer: "google/gemini-2.0-flash",
80
+ default: "google/gemini-2.0-flash"
81
+ };
82
+ var DEFAULT_SCORING_CONFIG = {
83
+ enabled: false,
84
+ max_candidates: 100,
85
+ weights: {
86
+ phase: 1,
87
+ current_task: 2,
88
+ blocked_task: 1.5,
89
+ recent_failure: 2.5,
90
+ recent_success: 0.5,
91
+ evidence_presence: 1,
92
+ decision_recency: 1.5,
93
+ dependency_proximity: 1
94
+ },
95
+ decision_decay: {
96
+ mode: "exponential",
97
+ half_life_hours: 24
98
+ },
99
+ token_ratios: {
100
+ prose: 0.25,
101
+ code: 0.4,
102
+ markdown: 0.3,
103
+ json: 0.35
104
+ }
105
+ };
34
106
  // node_modules/zod/v4/classic/external.js
35
107
  var exports_external = {};
36
108
  __export(exports_external, {
@@ -13563,6 +13635,85 @@ function date4(params) {
13563
13635
 
13564
13636
  // node_modules/zod/v4/classic/external.js
13565
13637
  config(en_default());
13638
+ // src/config/evidence-schema.ts
13639
+ var EVIDENCE_MAX_JSON_BYTES = 500 * 1024;
13640
+ var EVIDENCE_MAX_PATCH_BYTES = 5 * 1024 * 1024;
13641
+ var EVIDENCE_MAX_TASK_BYTES = 20 * 1024 * 1024;
13642
+ var EvidenceTypeSchema = exports_external.enum([
13643
+ "review",
13644
+ "test",
13645
+ "diff",
13646
+ "approval",
13647
+ "note"
13648
+ ]);
13649
+ var EvidenceVerdictSchema = exports_external.enum([
13650
+ "pass",
13651
+ "fail",
13652
+ "approved",
13653
+ "rejected",
13654
+ "info"
13655
+ ]);
13656
+ var BaseEvidenceSchema = exports_external.object({
13657
+ task_id: exports_external.string().min(1),
13658
+ type: EvidenceTypeSchema,
13659
+ timestamp: exports_external.string().datetime(),
13660
+ agent: exports_external.string().min(1),
13661
+ verdict: EvidenceVerdictSchema,
13662
+ summary: exports_external.string().min(1),
13663
+ metadata: exports_external.record(exports_external.string(), exports_external.unknown()).optional()
13664
+ });
13665
+ var ReviewEvidenceSchema = BaseEvidenceSchema.extend({
13666
+ type: exports_external.literal("review"),
13667
+ risk: exports_external.enum(["low", "medium", "high", "critical"]),
13668
+ issues: exports_external.array(exports_external.object({
13669
+ severity: exports_external.enum(["error", "warning", "info"]),
13670
+ message: exports_external.string().min(1),
13671
+ file: exports_external.string().optional(),
13672
+ line: exports_external.number().int().optional()
13673
+ })).default([])
13674
+ });
13675
+ var TestEvidenceSchema = BaseEvidenceSchema.extend({
13676
+ type: exports_external.literal("test"),
13677
+ tests_passed: exports_external.number().int().min(0),
13678
+ tests_failed: exports_external.number().int().min(0),
13679
+ test_file: exports_external.string().optional(),
13680
+ failures: exports_external.array(exports_external.object({
13681
+ name: exports_external.string().min(1),
13682
+ message: exports_external.string().min(1)
13683
+ })).default([])
13684
+ });
13685
+ var DiffEvidenceSchema = BaseEvidenceSchema.extend({
13686
+ type: exports_external.literal("diff"),
13687
+ files_changed: exports_external.array(exports_external.string()).default([]),
13688
+ additions: exports_external.number().int().min(0).default(0),
13689
+ deletions: exports_external.number().int().min(0).default(0),
13690
+ patch_path: exports_external.string().optional()
13691
+ });
13692
+ var ApprovalEvidenceSchema = BaseEvidenceSchema.extend({
13693
+ type: exports_external.literal("approval")
13694
+ });
13695
+ var NoteEvidenceSchema = BaseEvidenceSchema.extend({
13696
+ type: exports_external.literal("note")
13697
+ });
13698
+ var EvidenceSchema = exports_external.discriminatedUnion("type", [
13699
+ ReviewEvidenceSchema,
13700
+ TestEvidenceSchema,
13701
+ DiffEvidenceSchema,
13702
+ ApprovalEvidenceSchema,
13703
+ NoteEvidenceSchema
13704
+ ]);
13705
+ var EvidenceBundleSchema = exports_external.object({
13706
+ schema_version: exports_external.literal("1.0.0"),
13707
+ task_id: exports_external.string().min(1),
13708
+ entries: exports_external.array(EvidenceSchema).default([]),
13709
+ created_at: exports_external.string().datetime(),
13710
+ updated_at: exports_external.string().datetime()
13711
+ });
13712
+ // src/config/loader.ts
13713
+ import * as fs from "fs";
13714
+ import * as os from "os";
13715
+ import * as path from "path";
13716
+
13566
13717
  // src/config/schema.ts
13567
13718
  var AgentOverrideConfigSchema = exports_external.object({
13568
13719
  model: exports_external.string().optional(),
@@ -13798,8 +13949,7 @@ var PluginConfigSchema = exports_external.object({
13798
13949
  review_passes: ReviewPassesConfigSchema.optional(),
13799
13950
  integration_analysis: IntegrationAnalysisConfigSchema.optional(),
13800
13951
  docs: DocsConfigSchema.optional(),
13801
- ui_review: UIReviewConfigSchema.optional(),
13802
- _loadedFromFile: exports_external.boolean().default(false)
13952
+ ui_review: UIReviewConfigSchema.optional()
13803
13953
  });
13804
13954
 
13805
13955
  // src/config/loader.ts
@@ -13818,6 +13968,11 @@ function loadRawConfigFromPath(configPath) {
13818
13968
  return null;
13819
13969
  }
13820
13970
  const content = fs.readFileSync(configPath, "utf-8");
13971
+ if (content.length > MAX_CONFIG_FILE_BYTES) {
13972
+ console.warn(`[opencode-swarm] Config file too large after read (max 100 KB): ${configPath}`);
13973
+ console.warn("[opencode-swarm] \u26A0\uFE0F Guardrails will be DISABLED as a safety precaution. Fix the config file to restore normal operation.");
13974
+ return null;
13975
+ }
13821
13976
  const rawConfig = JSON.parse(content);
13822
13977
  if (typeof rawConfig !== "object" || rawConfig === null || Array.isArray(rawConfig)) {
13823
13978
  console.warn(`[opencode-swarm] Invalid config at ${configPath}: expected an object`);
@@ -13833,23 +13988,6 @@ function loadRawConfigFromPath(configPath) {
13833
13988
  return null;
13834
13989
  }
13835
13990
  }
13836
- var MAX_MERGE_DEPTH = 10;
13837
- function deepMergeInternal(base, override, depth) {
13838
- if (depth >= MAX_MERGE_DEPTH) {
13839
- throw new Error(`deepMerge exceeded maximum depth of ${MAX_MERGE_DEPTH}`);
13840
- }
13841
- const result = { ...base };
13842
- for (const key of Object.keys(override)) {
13843
- const baseVal = base[key];
13844
- const overrideVal = override[key];
13845
- if (typeof baseVal === "object" && baseVal !== null && typeof overrideVal === "object" && overrideVal !== null && !Array.isArray(baseVal) && !Array.isArray(overrideVal)) {
13846
- result[key] = deepMergeInternal(baseVal, overrideVal, depth + 1);
13847
- } else {
13848
- result[key] = overrideVal;
13849
- }
13850
- }
13851
- return result;
13852
- }
13853
13991
  function loadPluginConfig(directory) {
13854
13992
  const userConfigPath = path.join(getUserConfigDir(), "opencode", CONFIG_FILENAME);
13855
13993
  const projectConfigPath = path.join(directory, ".opencode", CONFIG_FILENAME);
@@ -13858,7 +13996,7 @@ function loadPluginConfig(directory) {
13858
13996
  const loadedFromFile = rawUserConfig !== null || rawProjectConfig !== null;
13859
13997
  let mergedRaw = rawUserConfig ?? {};
13860
13998
  if (rawProjectConfig) {
13861
- mergedRaw = deepMergeInternal(mergedRaw, rawProjectConfig, 0);
13999
+ mergedRaw = deepMerge(mergedRaw, rawProjectConfig);
13862
14000
  }
13863
14001
  const result = PluginConfigSchema.safeParse(mergedRaw);
13864
14002
  if (!result.success) {
@@ -13866,20 +14004,24 @@ function loadPluginConfig(directory) {
13866
14004
  const userResult = PluginConfigSchema.safeParse(rawUserConfig);
13867
14005
  if (userResult.success) {
13868
14006
  console.warn("[opencode-swarm] Project config ignored due to validation errors. Using user config.");
13869
- return { ...userResult.data, _loadedFromFile: true };
14007
+ return userResult.data;
13870
14008
  }
13871
14009
  }
13872
14010
  console.warn("[opencode-swarm] Merged config validation failed:");
13873
14011
  console.warn(result.error.format());
13874
14012
  console.warn("[opencode-swarm] \u26A0\uFE0F Guardrails will be DISABLED as a safety precaution. Fix the config file to restore normal operation.");
13875
- return {
13876
- max_iterations: 5,
13877
- qa_retry_limit: 3,
13878
- inject_phase_reminders: true,
13879
- _loadedFromFile: false
13880
- };
14013
+ return PluginConfigSchema.parse({});
13881
14014
  }
13882
- return { ...result.data, _loadedFromFile: loadedFromFile };
14015
+ return result.data;
14016
+ }
14017
+ function loadPluginConfigWithMeta(directory) {
14018
+ const userConfigPath = path.join(getUserConfigDir(), "opencode", CONFIG_FILENAME);
14019
+ const projectConfigPath = path.join(directory, ".opencode", CONFIG_FILENAME);
14020
+ const rawUserConfig = loadRawConfigFromPath(userConfigPath);
14021
+ const rawProjectConfig = loadRawConfigFromPath(projectConfigPath);
14022
+ const loadedFromFile = rawUserConfig !== null || rawProjectConfig !== null;
14023
+ const config2 = loadPluginConfig(directory);
14024
+ return { config: config2, loadedFromFile };
13883
14025
  }
13884
14026
  function loadAgentPrompt(agentName) {
13885
14027
  const promptsDir = path.join(getUserConfigDir(), "opencode", PROMPTS_DIR_NAME);
@@ -13902,58 +14044,6 @@ function loadAgentPrompt(agentName) {
13902
14044
  }
13903
14045
  return result;
13904
14046
  }
13905
-
13906
- // src/config/constants.ts
13907
- var QA_AGENTS = ["reviewer", "critic"];
13908
- var PIPELINE_AGENTS = ["explorer", "coder", "test_engineer"];
13909
- var ORCHESTRATOR_NAME = "architect";
13910
- var ALL_SUBAGENT_NAMES = [
13911
- "sme",
13912
- "docs",
13913
- "designer",
13914
- ...QA_AGENTS,
13915
- ...PIPELINE_AGENTS
13916
- ];
13917
- var ALL_AGENT_NAMES = [
13918
- ORCHESTRATOR_NAME,
13919
- ...ALL_SUBAGENT_NAMES
13920
- ];
13921
- var DEFAULT_MODELS = {
13922
- architect: "anthropic/claude-sonnet-4-5",
13923
- explorer: "google/gemini-2.0-flash",
13924
- coder: "anthropic/claude-sonnet-4-5",
13925
- test_engineer: "google/gemini-2.0-flash",
13926
- sme: "google/gemini-2.0-flash",
13927
- reviewer: "google/gemini-2.0-flash",
13928
- critic: "google/gemini-2.0-flash",
13929
- docs: "google/gemini-2.0-flash",
13930
- designer: "google/gemini-2.0-flash",
13931
- default: "google/gemini-2.0-flash"
13932
- };
13933
- var DEFAULT_SCORING_CONFIG = {
13934
- enabled: false,
13935
- max_candidates: 100,
13936
- weights: {
13937
- phase: 1,
13938
- current_task: 2,
13939
- blocked_task: 1.5,
13940
- recent_failure: 2.5,
13941
- recent_success: 0.5,
13942
- evidence_presence: 1,
13943
- decision_recency: 1.5,
13944
- dependency_proximity: 1
13945
- },
13946
- decision_decay: {
13947
- mode: "exponential",
13948
- half_life_hours: 24
13949
- },
13950
- token_ratios: {
13951
- prose: 0.25,
13952
- code: 0.4,
13953
- markdown: 0.3,
13954
- json: 0.35
13955
- }
13956
- };
13957
14047
  // src/config/plan-schema.ts
13958
14048
  var TaskStatusSchema = exports_external.enum([
13959
14049
  "pending",
@@ -13999,80 +14089,6 @@ var PlanSchema = exports_external.object({
13999
14089
  phases: exports_external.array(PhaseSchema).min(1),
14000
14090
  migration_status: MigrationStatusSchema.optional()
14001
14091
  });
14002
- // src/config/evidence-schema.ts
14003
- var EVIDENCE_MAX_JSON_BYTES = 500 * 1024;
14004
- var EVIDENCE_MAX_PATCH_BYTES = 5 * 1024 * 1024;
14005
- var EVIDENCE_MAX_TASK_BYTES = 20 * 1024 * 1024;
14006
- var EvidenceTypeSchema = exports_external.enum([
14007
- "review",
14008
- "test",
14009
- "diff",
14010
- "approval",
14011
- "note"
14012
- ]);
14013
- var EvidenceVerdictSchema = exports_external.enum([
14014
- "pass",
14015
- "fail",
14016
- "approved",
14017
- "rejected",
14018
- "info"
14019
- ]);
14020
- var BaseEvidenceSchema = exports_external.object({
14021
- task_id: exports_external.string().min(1),
14022
- type: EvidenceTypeSchema,
14023
- timestamp: exports_external.string().datetime(),
14024
- agent: exports_external.string().min(1),
14025
- verdict: EvidenceVerdictSchema,
14026
- summary: exports_external.string().min(1),
14027
- metadata: exports_external.record(exports_external.string(), exports_external.unknown()).optional()
14028
- });
14029
- var ReviewEvidenceSchema = BaseEvidenceSchema.extend({
14030
- type: exports_external.literal("review"),
14031
- risk: exports_external.enum(["low", "medium", "high", "critical"]),
14032
- issues: exports_external.array(exports_external.object({
14033
- severity: exports_external.enum(["error", "warning", "info"]),
14034
- message: exports_external.string().min(1),
14035
- file: exports_external.string().optional(),
14036
- line: exports_external.number().int().optional()
14037
- })).default([])
14038
- });
14039
- var TestEvidenceSchema = BaseEvidenceSchema.extend({
14040
- type: exports_external.literal("test"),
14041
- tests_passed: exports_external.number().int().min(0),
14042
- tests_failed: exports_external.number().int().min(0),
14043
- test_file: exports_external.string().optional(),
14044
- failures: exports_external.array(exports_external.object({
14045
- name: exports_external.string().min(1),
14046
- message: exports_external.string().min(1)
14047
- })).default([])
14048
- });
14049
- var DiffEvidenceSchema = BaseEvidenceSchema.extend({
14050
- type: exports_external.literal("diff"),
14051
- files_changed: exports_external.array(exports_external.string()).default([]),
14052
- additions: exports_external.number().int().min(0).default(0),
14053
- deletions: exports_external.number().int().min(0).default(0),
14054
- patch_path: exports_external.string().optional()
14055
- });
14056
- var ApprovalEvidenceSchema = BaseEvidenceSchema.extend({
14057
- type: exports_external.literal("approval")
14058
- });
14059
- var NoteEvidenceSchema = BaseEvidenceSchema.extend({
14060
- type: exports_external.literal("note")
14061
- });
14062
- var EvidenceSchema = exports_external.discriminatedUnion("type", [
14063
- ReviewEvidenceSchema,
14064
- TestEvidenceSchema,
14065
- DiffEvidenceSchema,
14066
- ApprovalEvidenceSchema,
14067
- NoteEvidenceSchema
14068
- ]);
14069
- var EvidenceBundleSchema = exports_external.object({
14070
- schema_version: exports_external.literal("1.0.0"),
14071
- task_id: exports_external.string().min(1),
14072
- entries: exports_external.array(EvidenceSchema).default([]),
14073
- created_at: exports_external.string().datetime(),
14074
- updated_at: exports_external.string().datetime()
14075
- });
14076
14092
  // src/agents/architect.ts
14077
14093
  var ARCHITECT_PROMPT = `You are Architect - orchestrator of a multi-agent swarm.
14078
14094
 
@@ -30766,9 +30782,39 @@ var gitingest = tool({
30766
30782
  return fetchGitingest(args);
30767
30783
  }
30768
30784
  });
30785
+ // src/tools/retrieve-summary.ts
30786
+ var RETRIEVE_MAX_BYTES = 10 * 1024 * 1024;
30787
+ var retrieve_summary = tool({
30788
+ description: "Retrieve the full content of a stored tool output summary by its ID (e.g. S1, S2). Use this when a prior tool output was summarized and you need the full content.",
30789
+ args: {
30790
+ id: tool.schema.string().describe("The summary ID to retrieve (e.g. S1, S2, S99). Must match pattern S followed by digits.")
30791
+ },
30792
+ async execute(args, context) {
30793
+ const directory = context.directory;
30794
+ let sanitizedId;
30795
+ try {
30796
+ sanitizedId = sanitizeSummaryId(args.id);
30797
+ } catch {
30798
+ return "Error: invalid summary ID format. Expected format: S followed by digits (e.g. S1, S2, S99).";
30799
+ }
30800
+ let fullOutput;
30801
+ try {
30802
+ fullOutput = await loadFullOutput(directory, sanitizedId);
30803
+ } catch {
30804
+ return "Error: failed to retrieve summary.";
30805
+ }
30806
+ if (fullOutput === null) {
30807
+ return `Summary \`${sanitizedId}\` not found. Use a valid summary ID (e.g. S1, S2).`;
30808
+ }
30809
+ if (fullOutput.length > RETRIEVE_MAX_BYTES) {
30810
+ return `Error: summary content exceeds maximum size limit (10 MB).`;
30811
+ }
30812
+ return fullOutput;
30813
+ }
30814
+ });
30769
30815
  // src/index.ts
30770
30816
  var OpenCodeSwarm = async (ctx) => {
30771
- const config3 = loadPluginConfig(ctx.directory);
30817
+ const { config: config3, loadedFromFile } = loadPluginConfigWithMeta(ctx.directory);
30772
30818
  const agents = getAgentConfigs(config3);
30773
30819
  const agentDefinitions = createAgents(config3);
30774
30820
  const pipelineHook = createPipelineTrackerHook(config3);
@@ -30778,7 +30824,7 @@ var OpenCodeSwarm = async (ctx) => {
30778
30824
  const commandHandler = createSwarmCommandHandler(ctx.directory, Object.fromEntries(agentDefinitions.map((agent) => [agent.name, agent])));
30779
30825
  const activityHooks = createAgentActivityHooks(config3, ctx.directory);
30780
30826
  const delegationGateHandler = createDelegationGateHook(config3);
30781
- const guardrailsFallback = config3._loadedFromFile ? config3.guardrails ?? {} : { ...config3.guardrails, enabled: false };
30827
+ const guardrailsFallback = loadedFromFile ? config3.guardrails ?? {} : { ...config3.guardrails, enabled: false };
30782
30828
  const guardrailsConfig = GuardrailsConfigSchema.parse(guardrailsFallback);
30783
30829
  const delegationHandler = createDelegationTrackerHook(config3, guardrailsConfig.enabled);
30784
30830
  const guardrailsHooks = createGuardrailsHooks(guardrailsConfig);
@@ -30808,7 +30854,8 @@ var OpenCodeSwarm = async (ctx) => {
30808
30854
  detect_domains,
30809
30855
  extract_code_blocks,
30810
30856
  gitingest,
30811
- diff
30857
+ diff,
30858
+ retrieve_summary
30812
30859
  },
30813
30860
  config: async (opencodeConfig) => {
30814
30861
  if (!opencodeConfig.agent) {
@@ -2,3 +2,4 @@ export { type DiffErrorResult, type DiffResult, diff } from './diff';
2
2
  export { detect_domains } from './domain-detector';
3
3
  export { extract_code_blocks } from './file-extractor';
4
4
  export { fetchGitingest, type GitingestArgs, gitingest } from './gitingest';
5
+ export { retrieve_summary } from './retrieve-summary';
@@ -0,0 +1,2 @@
1
+ import { tool } from '@opencode-ai/plugin';
2
+ export declare const retrieve_summary: ReturnType<typeof tool>;
@@ -1,2 +1,3 @@
1
1
  export { CLIError, ConfigError, HookError, SwarmError, ToolError, } from './errors';
2
2
  export { error, log, warn } from './logger';
3
+ export { deepMerge, MAX_MERGE_DEPTH } from './merge';
@@ -0,0 +1,5 @@
1
+ export declare const MAX_MERGE_DEPTH = 10;
2
+ /**
3
+ * Deep merge two objects, with override values taking precedence.
4
+ */
5
+ export declare function deepMerge<T extends Record<string, unknown>>(base?: T, override?: T): T | undefined;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-swarm",
3
- "version": "6.1.0",
3
+ "version": "6.1.1",
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",