opencode-swarm 6.0.1 → 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
 
@@ -0,0 +1,2 @@
1
+ import type { AgentDefinition } from './architect';
2
+ export declare function createDesignerAgent(model: string, customPrompt?: string, customAppendPrompt?: string): AgentDefinition;
@@ -0,0 +1,2 @@
1
+ import type { AgentDefinition } from './architect';
2
+ export declare function createDocsAgent(model: string, customPrompt?: string, customAppendPrompt?: string): AgentDefinition;
@@ -19,6 +19,8 @@ export declare function getAgentConfigs(config?: PluginConfig): Record<string, S
19
19
  export { createArchitectAgent } from './architect';
20
20
  export { createCoderAgent } from './coder';
21
21
  export { createCriticAgent } from './critic';
22
+ export { createDesignerAgent } from './designer';
23
+ export { createDocsAgent } from './docs';
22
24
  export { createExplorerAgent } from './explorer';
23
25
  export { createReviewerAgent, SECURITY_CATEGORIES, type SecurityCategory, } from './reviewer';
24
26
  export { createSMEAgent } from './sme';
@@ -1,8 +1,8 @@
1
1
  export declare const QA_AGENTS: readonly ["reviewer", "critic"];
2
2
  export declare const PIPELINE_AGENTS: readonly ["explorer", "coder", "test_engineer"];
3
3
  export declare const ORCHESTRATOR_NAME: "architect";
4
- export declare const ALL_SUBAGENT_NAMES: readonly ["sme", "reviewer", "critic", "explorer", "coder", "test_engineer"];
5
- export declare const ALL_AGENT_NAMES: readonly ["architect", "sme", "reviewer", "critic", "explorer", "coder", "test_engineer"];
4
+ export declare const ALL_SUBAGENT_NAMES: readonly ["sme", "docs", "designer", "reviewer", "critic", "explorer", "coder", "test_engineer"];
5
+ export declare const ALL_AGENT_NAMES: readonly ["architect", "sme", "docs", "designer", "reviewer", "critic", "explorer", "coder", "test_engineer"];
6
6
  export type QAAgentName = (typeof QA_AGENTS)[number];
7
7
  export type PipelineAgentName = (typeof PIPELINE_AGENTS)[number];
8
8
  export type AgentName = (typeof ALL_AGENT_NAMES)[number];
@@ -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;
@@ -137,6 +137,17 @@ export declare const IntegrationAnalysisConfigSchema: z.ZodObject<{
137
137
  enabled: z.ZodDefault<z.ZodBoolean>;
138
138
  }, z.core.$strip>;
139
139
  export type IntegrationAnalysisConfig = z.infer<typeof IntegrationAnalysisConfigSchema>;
140
+ export declare const DocsConfigSchema: z.ZodObject<{
141
+ enabled: z.ZodDefault<z.ZodBoolean>;
142
+ doc_patterns: z.ZodDefault<z.ZodArray<z.ZodString>>;
143
+ }, z.core.$strip>;
144
+ export type DocsConfig = z.infer<typeof DocsConfigSchema>;
145
+ export declare const UIReviewConfigSchema: z.ZodObject<{
146
+ enabled: z.ZodDefault<z.ZodBoolean>;
147
+ trigger_paths: z.ZodDefault<z.ZodArray<z.ZodString>>;
148
+ trigger_keywords: z.ZodDefault<z.ZodArray<z.ZodString>>;
149
+ }, z.core.$strip>;
150
+ export type UIReviewConfig = z.infer<typeof UIReviewConfigSchema>;
140
151
  export declare const GuardrailsProfileSchema: z.ZodObject<{
141
152
  max_tool_calls: z.ZodOptional<z.ZodNumber>;
142
153
  max_duration_minutes: z.ZodOptional<z.ZodNumber>;
@@ -298,7 +309,15 @@ export declare const PluginConfigSchema: z.ZodObject<{
298
309
  integration_analysis: z.ZodOptional<z.ZodObject<{
299
310
  enabled: z.ZodDefault<z.ZodBoolean>;
300
311
  }, z.core.$strip>>;
301
- _loadedFromFile: z.ZodDefault<z.ZodBoolean>;
312
+ docs: z.ZodOptional<z.ZodObject<{
313
+ enabled: z.ZodDefault<z.ZodBoolean>;
314
+ doc_patterns: z.ZodDefault<z.ZodArray<z.ZodString>>;
315
+ }, z.core.$strip>>;
316
+ ui_review: z.ZodOptional<z.ZodObject<{
317
+ enabled: z.ZodDefault<z.ZodBoolean>;
318
+ trigger_paths: z.ZodDefault<z.ZodArray<z.ZodString>>;
319
+ trigger_keywords: z.ZodDefault<z.ZodArray<z.ZodString>>;
320
+ }, z.core.$strip>>;
302
321
  }, z.core.$strip>;
303
322
  export type PluginConfig = z.infer<typeof PluginConfigSchema>;
304
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(),
@@ -13645,6 +13796,46 @@ var ReviewPassesConfigSchema = exports_external.object({
13645
13796
  var IntegrationAnalysisConfigSchema = exports_external.object({
13646
13797
  enabled: exports_external.boolean().default(true)
13647
13798
  });
13799
+ var DocsConfigSchema = exports_external.object({
13800
+ enabled: exports_external.boolean().default(true),
13801
+ doc_patterns: exports_external.array(exports_external.string()).default([
13802
+ "README.md",
13803
+ "CONTRIBUTING.md",
13804
+ "docs/**/*.md",
13805
+ "docs/**/*.rst",
13806
+ "**/CHANGELOG.md"
13807
+ ])
13808
+ });
13809
+ var UIReviewConfigSchema = exports_external.object({
13810
+ enabled: exports_external.boolean().default(false),
13811
+ trigger_paths: exports_external.array(exports_external.string()).default([
13812
+ "**/pages/**",
13813
+ "**/components/**",
13814
+ "**/views/**",
13815
+ "**/screens/**",
13816
+ "**/ui/**",
13817
+ "**/layouts/**"
13818
+ ]),
13819
+ trigger_keywords: exports_external.array(exports_external.string()).default([
13820
+ "new page",
13821
+ "new screen",
13822
+ "new component",
13823
+ "redesign",
13824
+ "layout change",
13825
+ "form",
13826
+ "modal",
13827
+ "dialog",
13828
+ "dropdown",
13829
+ "sidebar",
13830
+ "navbar",
13831
+ "dashboard",
13832
+ "landing page",
13833
+ "signup",
13834
+ "login form",
13835
+ "settings page",
13836
+ "profile page"
13837
+ ])
13838
+ });
13648
13839
  var GuardrailsProfileSchema = exports_external.object({
13649
13840
  max_tool_calls: exports_external.number().min(0).max(1000).optional(),
13650
13841
  max_duration_minutes: exports_external.number().min(0).max(480).optional(),
@@ -13689,6 +13880,16 @@ var DEFAULT_AGENT_PROFILES = {
13689
13880
  max_tool_calls: 200,
13690
13881
  max_duration_minutes: 30,
13691
13882
  warning_threshold: 0.65
13883
+ },
13884
+ docs: {
13885
+ max_tool_calls: 200,
13886
+ max_duration_minutes: 30,
13887
+ warning_threshold: 0.75
13888
+ },
13889
+ designer: {
13890
+ max_tool_calls: 150,
13891
+ max_duration_minutes: 20,
13892
+ warning_threshold: 0.75
13692
13893
  }
13693
13894
  };
13694
13895
  var DEFAULT_ARCHITECT_PROFILE = DEFAULT_AGENT_PROFILES.architect;
@@ -13747,7 +13948,8 @@ var PluginConfigSchema = exports_external.object({
13747
13948
  summaries: SummaryConfigSchema.optional(),
13748
13949
  review_passes: ReviewPassesConfigSchema.optional(),
13749
13950
  integration_analysis: IntegrationAnalysisConfigSchema.optional(),
13750
- _loadedFromFile: exports_external.boolean().default(false)
13951
+ docs: DocsConfigSchema.optional(),
13952
+ ui_review: UIReviewConfigSchema.optional()
13751
13953
  });
13752
13954
 
13753
13955
  // src/config/loader.ts
@@ -13766,6 +13968,11 @@ function loadRawConfigFromPath(configPath) {
13766
13968
  return null;
13767
13969
  }
13768
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
+ }
13769
13976
  const rawConfig = JSON.parse(content);
13770
13977
  if (typeof rawConfig !== "object" || rawConfig === null || Array.isArray(rawConfig)) {
13771
13978
  console.warn(`[opencode-swarm] Invalid config at ${configPath}: expected an object`);
@@ -13781,23 +13988,6 @@ function loadRawConfigFromPath(configPath) {
13781
13988
  return null;
13782
13989
  }
13783
13990
  }
13784
- var MAX_MERGE_DEPTH = 10;
13785
- function deepMergeInternal(base, override, depth) {
13786
- if (depth >= MAX_MERGE_DEPTH) {
13787
- throw new Error(`deepMerge exceeded maximum depth of ${MAX_MERGE_DEPTH}`);
13788
- }
13789
- const result = { ...base };
13790
- for (const key of Object.keys(override)) {
13791
- const baseVal = base[key];
13792
- const overrideVal = override[key];
13793
- if (typeof baseVal === "object" && baseVal !== null && typeof overrideVal === "object" && overrideVal !== null && !Array.isArray(baseVal) && !Array.isArray(overrideVal)) {
13794
- result[key] = deepMergeInternal(baseVal, overrideVal, depth + 1);
13795
- } else {
13796
- result[key] = overrideVal;
13797
- }
13798
- }
13799
- return result;
13800
- }
13801
13991
  function loadPluginConfig(directory) {
13802
13992
  const userConfigPath = path.join(getUserConfigDir(), "opencode", CONFIG_FILENAME);
13803
13993
  const projectConfigPath = path.join(directory, ".opencode", CONFIG_FILENAME);
@@ -13806,7 +13996,7 @@ function loadPluginConfig(directory) {
13806
13996
  const loadedFromFile = rawUserConfig !== null || rawProjectConfig !== null;
13807
13997
  let mergedRaw = rawUserConfig ?? {};
13808
13998
  if (rawProjectConfig) {
13809
- mergedRaw = deepMergeInternal(mergedRaw, rawProjectConfig, 0);
13999
+ mergedRaw = deepMerge(mergedRaw, rawProjectConfig);
13810
14000
  }
13811
14001
  const result = PluginConfigSchema.safeParse(mergedRaw);
13812
14002
  if (!result.success) {
@@ -13814,20 +14004,24 @@ function loadPluginConfig(directory) {
13814
14004
  const userResult = PluginConfigSchema.safeParse(rawUserConfig);
13815
14005
  if (userResult.success) {
13816
14006
  console.warn("[opencode-swarm] Project config ignored due to validation errors. Using user config.");
13817
- return { ...userResult.data, _loadedFromFile: true };
14007
+ return userResult.data;
13818
14008
  }
13819
14009
  }
13820
14010
  console.warn("[opencode-swarm] Merged config validation failed:");
13821
14011
  console.warn(result.error.format());
13822
14012
  console.warn("[opencode-swarm] \u26A0\uFE0F Guardrails will be DISABLED as a safety precaution. Fix the config file to restore normal operation.");
13823
- return {
13824
- max_iterations: 5,
13825
- qa_retry_limit: 3,
13826
- inject_phase_reminders: true,
13827
- _loadedFromFile: false
13828
- };
14013
+ return PluginConfigSchema.parse({});
13829
14014
  }
13830
- 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 };
13831
14025
  }
13832
14026
  function loadAgentPrompt(agentName) {
13833
14027
  const promptsDir = path.join(getUserConfigDir(), "opencode", PROMPTS_DIR_NAME);
@@ -13850,54 +14044,6 @@ function loadAgentPrompt(agentName) {
13850
14044
  }
13851
14045
  return result;
13852
14046
  }
13853
-
13854
- // src/config/constants.ts
13855
- var QA_AGENTS = ["reviewer", "critic"];
13856
- var PIPELINE_AGENTS = ["explorer", "coder", "test_engineer"];
13857
- var ORCHESTRATOR_NAME = "architect";
13858
- var ALL_SUBAGENT_NAMES = [
13859
- "sme",
13860
- ...QA_AGENTS,
13861
- ...PIPELINE_AGENTS
13862
- ];
13863
- var ALL_AGENT_NAMES = [
13864
- ORCHESTRATOR_NAME,
13865
- ...ALL_SUBAGENT_NAMES
13866
- ];
13867
- var DEFAULT_MODELS = {
13868
- architect: "anthropic/claude-sonnet-4-5",
13869
- explorer: "google/gemini-2.0-flash",
13870
- coder: "anthropic/claude-sonnet-4-5",
13871
- test_engineer: "google/gemini-2.0-flash",
13872
- sme: "google/gemini-2.0-flash",
13873
- reviewer: "google/gemini-2.0-flash",
13874
- critic: "google/gemini-2.0-flash",
13875
- default: "google/gemini-2.0-flash"
13876
- };
13877
- var DEFAULT_SCORING_CONFIG = {
13878
- enabled: false,
13879
- max_candidates: 100,
13880
- weights: {
13881
- phase: 1,
13882
- current_task: 2,
13883
- blocked_task: 1.5,
13884
- recent_failure: 2.5,
13885
- recent_success: 0.5,
13886
- evidence_presence: 1,
13887
- decision_recency: 1.5,
13888
- dependency_proximity: 1
13889
- },
13890
- decision_decay: {
13891
- mode: "exponential",
13892
- half_life_hours: 24
13893
- },
13894
- token_ratios: {
13895
- prose: 0.25,
13896
- code: 0.4,
13897
- markdown: 0.3,
13898
- json: 0.35
13899
- }
13900
- };
13901
14047
  // src/config/plan-schema.ts
13902
14048
  var TaskStatusSchema = exports_external.enum([
13903
14049
  "pending",
@@ -13943,87 +14089,13 @@ var PlanSchema = exports_external.object({
13943
14089
  phases: exports_external.array(PhaseSchema).min(1),
13944
14090
  migration_status: MigrationStatusSchema.optional()
13945
14091
  });
13946
- // src/config/evidence-schema.ts
13947
- var EVIDENCE_MAX_JSON_BYTES = 500 * 1024;
13948
- var EVIDENCE_MAX_PATCH_BYTES = 5 * 1024 * 1024;
13949
- var EVIDENCE_MAX_TASK_BYTES = 20 * 1024 * 1024;
13950
- var EvidenceTypeSchema = exports_external.enum([
13951
- "review",
13952
- "test",
13953
- "diff",
13954
- "approval",
13955
- "note"
13956
- ]);
13957
- var EvidenceVerdictSchema = exports_external.enum([
13958
- "pass",
13959
- "fail",
13960
- "approved",
13961
- "rejected",
13962
- "info"
13963
- ]);
13964
- var BaseEvidenceSchema = exports_external.object({
13965
- task_id: exports_external.string().min(1),
13966
- type: EvidenceTypeSchema,
13967
- timestamp: exports_external.string().datetime(),
13968
- agent: exports_external.string().min(1),
13969
- verdict: EvidenceVerdictSchema,
13970
- summary: exports_external.string().min(1),
13971
- metadata: exports_external.record(exports_external.string(), exports_external.unknown()).optional()
13972
- });
13973
- var ReviewEvidenceSchema = BaseEvidenceSchema.extend({
13974
- type: exports_external.literal("review"),
13975
- risk: exports_external.enum(["low", "medium", "high", "critical"]),
13976
- issues: exports_external.array(exports_external.object({
13977
- severity: exports_external.enum(["error", "warning", "info"]),
13978
- message: exports_external.string().min(1),
13979
- file: exports_external.string().optional(),
13980
- line: exports_external.number().int().optional()
13981
- })).default([])
13982
- });
13983
- var TestEvidenceSchema = BaseEvidenceSchema.extend({
13984
- type: exports_external.literal("test"),
13985
- tests_passed: exports_external.number().int().min(0),
13986
- tests_failed: exports_external.number().int().min(0),
13987
- test_file: exports_external.string().optional(),
13988
- failures: exports_external.array(exports_external.object({
13989
- name: exports_external.string().min(1),
13990
- message: exports_external.string().min(1)
13991
- })).default([])
13992
- });
13993
- var DiffEvidenceSchema = BaseEvidenceSchema.extend({
13994
- type: exports_external.literal("diff"),
13995
- files_changed: exports_external.array(exports_external.string()).default([]),
13996
- additions: exports_external.number().int().min(0).default(0),
13997
- deletions: exports_external.number().int().min(0).default(0),
13998
- patch_path: exports_external.string().optional()
13999
- });
14000
- var ApprovalEvidenceSchema = BaseEvidenceSchema.extend({
14001
- type: exports_external.literal("approval")
14002
- });
14003
- var NoteEvidenceSchema = BaseEvidenceSchema.extend({
14004
- type: exports_external.literal("note")
14005
- });
14006
- var EvidenceSchema = exports_external.discriminatedUnion("type", [
14007
- ReviewEvidenceSchema,
14008
- TestEvidenceSchema,
14009
- DiffEvidenceSchema,
14010
- ApprovalEvidenceSchema,
14011
- NoteEvidenceSchema
14012
- ]);
14013
- var EvidenceBundleSchema = exports_external.object({
14014
- schema_version: exports_external.literal("1.0.0"),
14015
- task_id: exports_external.string().min(1),
14016
- entries: exports_external.array(EvidenceSchema).default([]),
14017
- created_at: exports_external.string().datetime(),
14018
- updated_at: exports_external.string().datetime()
14019
- });
14020
14092
  // src/agents/architect.ts
14021
14093
  var ARCHITECT_PROMPT = `You are Architect - orchestrator of a multi-agent swarm.
14022
14094
 
14023
14095
  ## IDENTITY
14024
14096
 
14025
14097
  Swarm: {{SWARM_ID}}
14026
- Your agents: {{AGENT_PREFIX}}explorer, {{AGENT_PREFIX}}sme, {{AGENT_PREFIX}}coder, {{AGENT_PREFIX}}reviewer, {{AGENT_PREFIX}}critic, {{AGENT_PREFIX}}test_engineer
14098
+ Your agents: {{AGENT_PREFIX}}explorer, {{AGENT_PREFIX}}sme, {{AGENT_PREFIX}}coder, {{AGENT_PREFIX}}reviewer, {{AGENT_PREFIX}}critic, {{AGENT_PREFIX}}test_engineer, {{AGENT_PREFIX}}docs, {{AGENT_PREFIX}}designer
14027
14099
 
14028
14100
  ## ROLE
14029
14101
 
@@ -14053,6 +14125,11 @@ You THINK. Subagents DO. You have the largest context window and strongest reaso
14053
14125
  - Delegate {{AGENT_PREFIX}}test_engineer for verification tests. FAIL \u2192 return to coder.
14054
14126
  - Delegate {{AGENT_PREFIX}}test_engineer for adversarial tests (attack vectors only). FAIL \u2192 return to coder.
14055
14127
  - All pass \u2192 mark task complete, proceed to next task.
14128
+ 9. **UI/UX DESIGN GATE**: Before delegating UI tasks to {{AGENT_PREFIX}}coder, check if the task involves UI components. Trigger conditions (ANY match):
14129
+ - Task description contains UI keywords: new page, new screen, new component, redesign, layout change, form, modal, dialog, dropdown, sidebar, navbar, dashboard, landing page, signup, login form, settings page, profile page
14130
+ - Target file is in: pages/, components/, views/, screens/, ui/, layouts/
14131
+ If triggered: delegate to {{AGENT_PREFIX}}designer FIRST to produce a code scaffold. Then pass the scaffold to {{AGENT_PREFIX}}coder as INPUT alongside the task. The coder implements the TODOs in the scaffold without changing component structure or accessibility attributes.
14132
+ If not triggered: delegate directly to {{AGENT_PREFIX}}coder as normal.
14056
14133
 
14057
14134
  ## AGENTS
14058
14135
 
@@ -14062,6 +14139,8 @@ You THINK. Subagents DO. You have the largest context window and strongest reaso
14062
14139
  {{AGENT_PREFIX}}reviewer - Code review (correctness, security, and any other dimensions you specify)
14063
14140
  {{AGENT_PREFIX}}test_engineer - Test generation AND execution (writes tests, runs them, reports PASS/FAIL)
14064
14141
  {{AGENT_PREFIX}}critic - Plan review gate (reviews plan BEFORE implementation)
14142
+ {{AGENT_PREFIX}}docs - Documentation updates (README, API docs, guides \u2014 NOT .swarm/ files)
14143
+ {{AGENT_PREFIX}}designer - UI/UX design specs (scaffold generation for UI components \u2014 runs BEFORE coder on UI tasks)
14065
14144
 
14066
14145
  SMEs advise only. Reviewer and critic review only. None of them write code.
14067
14146
 
@@ -14140,6 +14219,23 @@ INPUT: Contract changes detected: [list from diff tool]
14140
14219
  OUTPUT: BREAKING CHANGES + CONSUMERS AFFECTED + VERDICT: BREAKING/COMPATIBLE
14141
14220
  CONSTRAINT: Read-only. grep for imports/usages of changed exports.
14142
14221
 
14222
+ {{AGENT_PREFIX}}docs
14223
+ TASK: Update documentation for Phase 2 changes
14224
+ FILES CHANGED: src/auth/login.ts, src/auth/session.ts, src/types/user.ts
14225
+ CHANGES SUMMARY:
14226
+ - Added login() function with email/password authentication
14227
+ - Added SessionManager class with create/revoke/refresh methods
14228
+ - Added UserSession interface with refreshToken field
14229
+ DOC FILES: README.md, docs/api.md, docs/installation.md
14230
+ OUTPUT: Updated doc files + SUMMARY
14231
+
14232
+ {{AGENT_PREFIX}}designer
14233
+ TASK: Design specification for user settings page
14234
+ CONTEXT: Users need to update profile info, change password, manage notification preferences. App uses React + Tailwind + shadcn/ui.
14235
+ FRAMEWORK: React (TSX)
14236
+ EXISTING PATTERNS: All forms use react-hook-form, validation with zod, toast notifications for success/error
14237
+ OUTPUT: Code scaffold for src/pages/Settings.tsx with component tree, typed props, layout, and accessibility
14238
+
14143
14239
  ## WORKFLOW
14144
14240
 
14145
14241
  ### Phase 0: Resume Check
@@ -14191,19 +14287,24 @@ Delegate plan to {{AGENT_PREFIX}}critic for review BEFORE any implementation beg
14191
14287
  ### Phase 5: Execute
14192
14288
  For each task (respecting dependencies):
14193
14289
 
14194
- 5a. {{AGENT_PREFIX}}coder - Implement
14195
- 5b. Run \`diff\` tool. If \`hasContractChanges\` \u2192 {{AGENT_PREFIX}}explorer integration analysis. BREAKING \u2192 coder retry.
14196
- 5c. {{AGENT_PREFIX}}reviewer - General review. REJECTED (< {{QA_RETRY_LIMIT}}) \u2192 coder retry. REJECTED ({{QA_RETRY_LIMIT}}) \u2192 escalate.
14197
- 5d. Security gate: if file matches security globs or content has security keywords \u2192 {{AGENT_PREFIX}}reviewer security-only. REJECTED \u2192 coder retry.
14198
- 5e. {{AGENT_PREFIX}}test_engineer - Verification tests. FAIL \u2192 coder retry from 5c.
14199
- 5f. {{AGENT_PREFIX}}test_engineer - Adversarial tests. FAIL \u2192 coder retry from 5c.
14200
- 5g. Update plan.md [x], proceed to next task.
14290
+ 5a. **UI DESIGN GATE** (conditional \u2014 Rule 9): If task matches UI trigger \u2192 {{AGENT_PREFIX}}designer produces scaffold \u2192 pass scaffold to coder as INPUT. If no match \u2192 skip.
14291
+ 5b. {{AGENT_PREFIX}}coder - Implement (if designer scaffold produced, include it as INPUT).
14292
+ 5c. Run \`diff\` tool. If \`hasContractChanges\` \u2192 {{AGENT_PREFIX}}explorer integration analysis. BREAKING \u2192 coder retry.
14293
+ 5d. {{AGENT_PREFIX}}reviewer - General review. REJECTED (< {{QA_RETRY_LIMIT}}) \u2192 coder retry. REJECTED ({{QA_RETRY_LIMIT}}) \u2192 escalate.
14294
+ 5e. Security gate: if file matches security globs or content has security keywords \u2192 {{AGENT_PREFIX}}reviewer security-only. REJECTED \u2192 coder retry.
14295
+ 5f. {{AGENT_PREFIX}}test_engineer - Verification tests. FAIL \u2192 coder retry from 5d.
14296
+ 5g. {{AGENT_PREFIX}}test_engineer - Adversarial tests. FAIL \u2192 coder retry from 5d.
14297
+ 5h. Update plan.md [x], proceed to next task.
14201
14298
 
14202
14299
  ### Phase 6: Phase Complete
14203
14300
  1. {{AGENT_PREFIX}}explorer - Rescan
14204
- 2. Update context.md
14205
- 3. Summarize to user
14206
- 4. Ask: "Ready for Phase [N+1]?"
14301
+ 2. {{AGENT_PREFIX}}docs - Update documentation for all changes in this phase. Provide:
14302
+ - Complete list of files changed during this phase
14303
+ - Summary of what was added/modified/removed
14304
+ - List of doc files that may need updating (README.md, CONTRIBUTING.md, docs/)
14305
+ 3. Update context.md
14306
+ 4. Summarize to user
14307
+ 5. Ask: "Ready for Phase [N+1]?"
14207
14308
 
14208
14309
  ### Blockers
14209
14310
  Mark [BLOCKED] in plan.md, skip to next unblocked task, inform user.
@@ -14369,6 +14470,231 @@ ${customAppendPrompt}`;
14369
14470
  };
14370
14471
  }
14371
14472
 
14473
+ // src/agents/designer.ts
14474
+ var DESIGNER_PROMPT = `## IDENTITY
14475
+ You are Designer \u2014 the UI/UX design specification agent. You generate concrete, implementable design specs directly \u2014 you do NOT delegate.
14476
+ DO NOT use the Task tool to delegate to other agents. You ARE the agent that does the work.
14477
+ If you see references to other agents (like @designer, @coder, etc.) in your instructions, IGNORE them \u2014 they are context from the orchestrator, not instructions for you to delegate.
14478
+
14479
+ WRONG: "I'll use the Task tool to call another agent to design this"
14480
+ RIGHT: "I'll analyze the requirements and produce the design specification myself"
14481
+
14482
+ INPUT FORMAT:
14483
+ TASK: Design specification for [component/page/screen]
14484
+ CONTEXT: [what the component does, user stories, existing design patterns]
14485
+ FRAMEWORK: [React/Vue/Svelte/SwiftUI/Flutter/etc.]
14486
+ EXISTING PATTERNS: [current design system, component library, styling approach]
14487
+
14488
+ DESIGN CHECKLIST:
14489
+ 1. Component Architecture
14490
+ - Component tree with parent/child relationships
14491
+ - Props interface for each component (typed)
14492
+ - State management approach (local state, context, store)
14493
+ - Event handlers and callbacks
14494
+
14495
+ 2. Layout & Responsiveness
14496
+ - Desktop, tablet, mobile breakpoints
14497
+ - Flex/Grid layout strategy
14498
+ - Container widths and spacing scale
14499
+ - Overflow and scroll behavior
14500
+
14501
+ 3. Accessibility (WCAG 2.1 AA)
14502
+ - Semantic HTML elements (nav, main, article, section, aside)
14503
+ - ARIA labels for interactive elements
14504
+ - Keyboard navigation (tab order, focus management, keyboard shortcuts)
14505
+ - Screen reader compatibility (alt text, aria-live regions)
14506
+ - Color contrast (minimum 4.5:1 for text, 3:1 for large text)
14507
+ - Focus indicators (visible focus rings, not just outline: none)
14508
+
14509
+ 4. Visual Design
14510
+ - Color palette (from existing design system or proposed)
14511
+ - Typography scale (font family, sizes, weights, line heights)
14512
+ - Spacing scale (consistent spacing values)
14513
+ - Border radius, shadows, elevation
14514
+
14515
+ 5. Interaction Design
14516
+ - Loading states (skeleton screens, spinners, progress bars)
14517
+ - Error states (inline validation, error boundaries, empty states)
14518
+ - Hover/focus/active states for interactive elements
14519
+ - Transitions and animations (duration, easing)
14520
+ - Optimistic updates where applicable
14521
+
14522
+ OUTPUT FORMAT:
14523
+ Produce a CODE SCAFFOLD in the target framework. This is a skeleton file with:
14524
+ - Component structure with typed props and proper imports
14525
+ - Layout structure using the project's CSS framework (Tailwind classes, CSS modules, styled-components, etc.)
14526
+ - Placeholder TODO comments for business logic
14527
+ - Accessibility attributes (aria-*, role, tabIndex)
14528
+ - Responsive breakpoint classes/media queries
14529
+ - Named event handler stubs
14530
+
14531
+ Example output structure:
14532
+ \`\`\`tsx
14533
+ // src/components/LoginForm.tsx
14534
+ // DESIGN SPEC \u2014 generated by Designer agent
14535
+ // Coder: implement TODO items, do not change component structure or accessibility attributes
14536
+
14537
+ import { useState } from 'react';
14538
+
14539
+ interface LoginFormProps {
14540
+ onSubmit: (email: string, password: string) => Promise<void>;
14541
+ onForgotPassword?: () => void;
14542
+ isLoading?: boolean;
14543
+ error?: string;
14544
+ }
14545
+
14546
+ export function LoginForm({ onSubmit, onForgotPassword, isLoading, error }: LoginFormProps) {
14547
+ const [email, setEmail] = useState('');
14548
+ const [password, setPassword] = useState('');
14549
+
14550
+ return (
14551
+ <div className="flex min-h-screen items-center justify-center bg-gray-50 px-4 sm:px-6 lg:px-8"
14552
+ role="main">
14553
+ <div className="w-full max-w-md space-y-8">
14554
+ <div>
14555
+ <h2 className="mt-6 text-center text-3xl font-bold tracking-tight text-gray-900">
14556
+ {/* TODO: Use app name from config */}
14557
+ Sign in to your account
14558
+ </h2>
14559
+ </div>
14560
+ {error && (
14561
+ <div role="alert" aria-live="polite"
14562
+ className="rounded-md bg-red-50 p-4 text-sm text-red-800">
14563
+ {error}
14564
+ </div>
14565
+ )}
14566
+ <div className="mt-8 space-y-6">
14567
+ <div className="space-y-4 rounded-md">
14568
+ <div>
14569
+ <label htmlFor="email" className="sr-only">Email address</label>
14570
+ <input id="email" name="email" type="email" autoComplete="email" required
14571
+ aria-label="Email address"
14572
+ className="relative block w-full rounded-t-md border-0 py-1.5 text-gray-900 ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:z-10 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"
14573
+ placeholder="Email address"
14574
+ value={email}
14575
+ onChange={(e) => setEmail(e.target.value)} />
14576
+ </div>
14577
+ {/* TODO: Password field with show/hide toggle */}
14578
+ {/* TODO: Remember me checkbox */}
14579
+ </div>
14580
+ <div className="flex items-center justify-between">
14581
+ {/* TODO: Forgot password link */}
14582
+ </div>
14583
+ <button type="submit" disabled={isLoading}
14584
+ aria-busy={isLoading}
14585
+ className="group relative flex w-full justify-center rounded-md bg-indigo-600 px-3 py-2 text-sm font-semibold text-white hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600 disabled:opacity-50">
14586
+ {isLoading ? 'Signing in...' : 'Sign in'}
14587
+ </button>
14588
+ </div>
14589
+ </div>
14590
+ </div>
14591
+ );
14592
+ }
14593
+ \`\`\`
14594
+
14595
+ RULES:
14596
+ - Produce REAL, syntactically valid code \u2014 not pseudocode
14597
+ - Match the project's existing framework, styling approach, and conventions
14598
+ - All interactive elements MUST have keyboard accessibility
14599
+ - All images/icons MUST have alt text or aria-label
14600
+ - Form inputs MUST have associated labels (visible or sr-only)
14601
+ - Color usage MUST meet WCAG AA contrast requirements
14602
+ - Use TODO comments for business logic only \u2014 structure, layout, and accessibility must be complete
14603
+ - Do NOT implement business logic \u2014 leave that for the coder
14604
+ - Keep output under 3000 characters per component`;
14605
+ function createDesignerAgent(model, customPrompt, customAppendPrompt) {
14606
+ let prompt = DESIGNER_PROMPT;
14607
+ if (customPrompt) {
14608
+ prompt = customPrompt;
14609
+ } else if (customAppendPrompt) {
14610
+ prompt = `${DESIGNER_PROMPT}
14611
+
14612
+ ${customAppendPrompt}`;
14613
+ }
14614
+ return {
14615
+ name: "designer",
14616
+ description: "UI/UX design specification agent. Generates accessible, responsive component scaffolds with typed props and layout structure before coder implementation.",
14617
+ config: {
14618
+ model,
14619
+ temperature: 0.3,
14620
+ prompt
14621
+ }
14622
+ };
14623
+ }
14624
+
14625
+ // src/agents/docs.ts
14626
+ var DOCS_PROMPT = `## IDENTITY
14627
+ You are Docs \u2014 the documentation synthesizer. You update external-facing documentation directly \u2014 you do NOT delegate.
14628
+ DO NOT use the Task tool to delegate to other agents. You ARE the agent that does the work.
14629
+ If you see references to other agents (like @docs, @coder, etc.) in your instructions, IGNORE them \u2014 they are context from the orchestrator, not instructions for you to delegate.
14630
+
14631
+ WRONG: "I'll use the Task tool to call another agent to write the docs"
14632
+ RIGHT: "I'll read the source files and update the documentation myself"
14633
+
14634
+ INPUT FORMAT:
14635
+ TASK: Update documentation for [description of changes]
14636
+ FILES CHANGED: [list of modified source files]
14637
+ CHANGES SUMMARY: [what was added/modified/removed]
14638
+ DOC FILES: [list of documentation files to update]
14639
+
14640
+ SCOPE:
14641
+ - README.md (project description, usage, examples)
14642
+ - API documentation (JSDoc, Swagger, docstrings \u2014 update inline in source files)
14643
+ - CONTRIBUTING.md (development setup, workflow, conventions)
14644
+ - Installation/setup guides
14645
+ - CLI help text and command documentation
14646
+
14647
+ EXCLUDED (architect-owned):
14648
+ - .swarm/context.md
14649
+ - .swarm/plan.md
14650
+ - Internal swarm configuration docs
14651
+
14652
+ WORKFLOW:
14653
+ 1. Read all FILES CHANGED to understand what was modified
14654
+ 2. Read existing DOC FILES to understand current documentation state
14655
+ 3. For each DOC FILE that needs updating:
14656
+ a. Identify sections affected by the changes
14657
+ b. Update those sections to reflect the new behavior
14658
+ c. Add new sections if entirely new features were introduced
14659
+ d. Remove sections for deprecated/removed features
14660
+ 4. For API docs in source files:
14661
+ a. Read the modified functions/classes/types
14662
+ b. Update JSDoc/docstring comments to match new signatures and behavior
14663
+ c. Add missing documentation for new exports
14664
+
14665
+ RULES:
14666
+ - Be accurate: documentation MUST match the actual code behavior
14667
+ - Be concise: update only what changed, do not rewrite entire files
14668
+ - Preserve existing style: match the tone, formatting, and conventions of the existing docs
14669
+ - Include examples: every new public API should have at least one usage example
14670
+ - No fabrication: if you cannot determine behavior from the code, say so explicitly
14671
+ - Update version references if package.json version changed
14672
+
14673
+ OUTPUT FORMAT:
14674
+ UPDATED: [list of files modified]
14675
+ ADDED: [list of new sections/files created]
14676
+ REMOVED: [list of deprecated sections removed]
14677
+ SUMMARY: [one-line description of doc changes]`;
14678
+ function createDocsAgent(model, customPrompt, customAppendPrompt) {
14679
+ let prompt = DOCS_PROMPT;
14680
+ if (customPrompt) {
14681
+ prompt = customPrompt;
14682
+ } else if (customAppendPrompt) {
14683
+ prompt = `${DOCS_PROMPT}
14684
+
14685
+ ${customAppendPrompt}`;
14686
+ }
14687
+ return {
14688
+ name: "docs",
14689
+ description: "Documentation synthesizer. Updates README, API docs, and guides to reflect code changes after each phase.",
14690
+ config: {
14691
+ model,
14692
+ temperature: 0.2,
14693
+ prompt
14694
+ }
14695
+ };
14696
+ }
14697
+
14372
14698
  // src/agents/explorer.ts
14373
14699
  var EXPLORER_PROMPT = `## IDENTITY
14374
14700
  You are Explorer. You analyze codebases directly \u2014 you do NOT delegate.
@@ -14703,6 +15029,18 @@ If you call @coder instead of @${swarmId}_coder, the call will FAIL or go to the
14703
15029
  testEngineer.name = prefixName("test_engineer");
14704
15030
  agents.push(applyOverrides(testEngineer, swarmAgents, swarmPrefix));
14705
15031
  }
15032
+ if (!isAgentDisabled("docs", swarmAgents, swarmPrefix)) {
15033
+ const docsPrompts = getPrompts("docs");
15034
+ const docs = createDocsAgent(getModel("docs"), docsPrompts.prompt, docsPrompts.appendPrompt);
15035
+ docs.name = prefixName("docs");
15036
+ agents.push(applyOverrides(docs, swarmAgents, swarmPrefix));
15037
+ }
15038
+ if (pluginConfig?.ui_review?.enabled === true && !isAgentDisabled("designer", swarmAgents, swarmPrefix)) {
15039
+ const designerPrompts = getPrompts("designer");
15040
+ const designer = createDesignerAgent(getModel("designer"), designerPrompts.prompt, designerPrompts.appendPrompt);
15041
+ designer.name = prefixName("designer");
15042
+ agents.push(applyOverrides(designer, swarmAgents, swarmPrefix));
15043
+ }
14706
15044
  return agents;
14707
15045
  }
14708
15046
  function createAgents(config2) {
@@ -17282,6 +17620,12 @@ function createSystemEnhancerHook(config2, directory) {
17282
17620
  if (config2.integration_analysis?.enabled === false) {
17283
17621
  tryInject("[SWARM CONFIG] Integration analysis is DISABLED. Skip diff tool and integration impact analysis after coder tasks.");
17284
17622
  }
17623
+ if (config2.ui_review?.enabled) {
17624
+ tryInject("[SWARM CONFIG] UI/UX Designer agent is ENABLED. For tasks matching UI trigger keywords or file paths, delegate to designer BEFORE coder (Rule 9).");
17625
+ }
17626
+ if (config2.docs?.enabled === false) {
17627
+ tryInject("[SWARM CONFIG] Docs agent is DISABLED. Skip docs delegation in Phase 6.");
17628
+ }
17285
17629
  return;
17286
17630
  }
17287
17631
  const userScoringConfig = config2.context_budget?.scoring;
@@ -17383,6 +17727,28 @@ function createSystemEnhancerHook(config2, directory) {
17383
17727
  metadata: { contentType: "prose" }
17384
17728
  });
17385
17729
  }
17730
+ if (config2.ui_review?.enabled) {
17731
+ const text = "[SWARM CONFIG] UI/UX Designer agent is ENABLED. For tasks matching UI trigger keywords or file paths, delegate to designer BEFORE coder (Rule 9).";
17732
+ candidates.push({
17733
+ id: `candidate-${idCounter++}`,
17734
+ kind: "phase",
17735
+ text,
17736
+ tokens: estimateTokens(text),
17737
+ priority: 1,
17738
+ metadata: { contentType: "prose" }
17739
+ });
17740
+ }
17741
+ if (config2.docs?.enabled === false) {
17742
+ const text = "[SWARM CONFIG] Docs agent is DISABLED. Skip docs delegation in Phase 6.";
17743
+ candidates.push({
17744
+ id: `candidate-${idCounter++}`,
17745
+ kind: "phase",
17746
+ text,
17747
+ tokens: estimateTokens(text),
17748
+ priority: 1,
17749
+ metadata: { contentType: "prose" }
17750
+ });
17751
+ }
17386
17752
  const ranked = rankCandidates(candidates, effectiveConfig);
17387
17753
  for (const candidate of ranked) {
17388
17754
  if (injectedTokens + candidate.tokens > maxInjectionTokens) {
@@ -30416,9 +30782,39 @@ var gitingest = tool({
30416
30782
  return fetchGitingest(args);
30417
30783
  }
30418
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
+ });
30419
30815
  // src/index.ts
30420
30816
  var OpenCodeSwarm = async (ctx) => {
30421
- const config3 = loadPluginConfig(ctx.directory);
30817
+ const { config: config3, loadedFromFile } = loadPluginConfigWithMeta(ctx.directory);
30422
30818
  const agents = getAgentConfigs(config3);
30423
30819
  const agentDefinitions = createAgents(config3);
30424
30820
  const pipelineHook = createPipelineTrackerHook(config3);
@@ -30428,7 +30824,7 @@ var OpenCodeSwarm = async (ctx) => {
30428
30824
  const commandHandler = createSwarmCommandHandler(ctx.directory, Object.fromEntries(agentDefinitions.map((agent) => [agent.name, agent])));
30429
30825
  const activityHooks = createAgentActivityHooks(config3, ctx.directory);
30430
30826
  const delegationGateHandler = createDelegationGateHook(config3);
30431
- const guardrailsFallback = config3._loadedFromFile ? config3.guardrails ?? {} : { ...config3.guardrails, enabled: false };
30827
+ const guardrailsFallback = loadedFromFile ? config3.guardrails ?? {} : { ...config3.guardrails, enabled: false };
30432
30828
  const guardrailsConfig = GuardrailsConfigSchema.parse(guardrailsFallback);
30433
30829
  const delegationHandler = createDelegationTrackerHook(config3, guardrailsConfig.enabled);
30434
30830
  const guardrailsHooks = createGuardrailsHooks(guardrailsConfig);
@@ -30458,7 +30854,8 @@ var OpenCodeSwarm = async (ctx) => {
30458
30854
  detect_domains,
30459
30855
  extract_code_blocks,
30460
30856
  gitingest,
30461
- diff
30857
+ diff,
30858
+ retrieve_summary
30462
30859
  },
30463
30860
  config: async (opencodeConfig) => {
30464
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.0.1",
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",