opencode-swarm 7.29.0 → 7.29.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli/index.js CHANGED
@@ -34,7 +34,7 @@ var package_default;
34
34
  var init_package = __esm(() => {
35
35
  package_default = {
36
36
  name: "opencode-swarm",
37
- version: "7.29.0",
37
+ version: "7.29.2",
38
38
  description: "Architect-centric agentic swarm plugin for OpenCode - hub-and-spoke orchestration with SME consultation, code generation, and QA review",
39
39
  main: "dist/index.js",
40
40
  types: "dist/index.d.ts",
@@ -40260,19 +40260,9 @@ function validateConfigKey(path24, value, _config) {
40260
40260
  case "guardrails.profiles": {
40261
40261
  const profiles = value;
40262
40262
  if (profiles) {
40263
- const validAgents = [
40264
- "architect",
40265
- "coder",
40266
- "test_engineer",
40267
- "explorer",
40268
- "reviewer",
40269
- "critic",
40270
- "sme",
40271
- "docs",
40272
- "designer"
40273
- ];
40263
+ const validAgents = new Set(ALL_AGENT_NAMES);
40274
40264
  for (const [agentName, profile] of Object.entries(profiles)) {
40275
- if (!validAgents.includes(agentName)) {
40265
+ if (!validAgents.has(agentName)) {
40276
40266
  findings.push({
40277
40267
  id: "unknown-agent-profile",
40278
40268
  title: "Unknown agent profile",
@@ -40433,23 +40423,23 @@ function validateConfigKey(path24, value, _config) {
40433
40423
  case "swarms": {
40434
40424
  const swarms = value;
40435
40425
  if (swarms && typeof swarms === "object") {
40426
+ const validAgents = new Set(ALL_AGENT_NAMES);
40436
40427
  for (const [swarmId, swarmConfig] of Object.entries(swarms)) {
40437
40428
  const swarm = swarmConfig;
40438
40429
  if (swarm.agents && typeof swarm.agents === "object") {
40439
40430
  for (const [agentName] of Object.entries(swarm.agents)) {
40440
- const validAgents = [
40441
- "architect",
40442
- "coder",
40443
- "test_engineer",
40444
- "explorer",
40445
- "reviewer",
40446
- "critic",
40447
- "sme",
40448
- "docs",
40449
- "designer"
40450
- ];
40451
- const baseName = agentName.replace(/^[a-zA-Z0-9]+_/, "");
40452
- if (!validAgents.includes(baseName)) {
40431
+ const baseName = stripKnownSwarmPrefix(agentName);
40432
+ if (baseName !== agentName && agentName.startsWith(`${swarmId}_`) && validAgents.has(baseName)) {
40433
+ findings.push({
40434
+ id: "prefixed-swarm-agent-override",
40435
+ title: "Prefixed agent override is ignored",
40436
+ description: `Agent "${agentName}" in swarm "${swarmId}" uses a generated agent name. ` + `Per-swarm overrides must use the canonical key "${baseName}", e.g. ` + `"swarms.${swarmId}.agents.${baseName}.model". Otherwise the override is ignored and the agent falls back to its default model.`,
40437
+ severity: "warn",
40438
+ path: `swarms.${swarmId}.agents.${agentName}`,
40439
+ currentValue: swarm.agents[agentName],
40440
+ autoFixable: false
40441
+ });
40442
+ } else if (!validAgents.has(baseName)) {
40453
40443
  findings.push({
40454
40444
  id: "unknown-swarm-agent",
40455
40445
  title: "Unknown agent in swarm",
@@ -40800,6 +40790,8 @@ function removeStraySwarmDir(projectRoot, strayPath) {
40800
40790
  }
40801
40791
  var VALID_CONFIG_PATTERNS, DANGEROUS_PATH_SEGMENTS;
40802
40792
  var init_config_doctor = __esm(() => {
40793
+ init_constants();
40794
+ init_schema();
40803
40795
  init_utils();
40804
40796
  VALID_CONFIG_PATTERNS = [
40805
40797
  /^\.config[\\/]opencode[\\/]opencode-swarm\.json$/,
package/dist/index.js CHANGED
@@ -48,7 +48,7 @@ var package_default;
48
48
  var init_package = __esm(() => {
49
49
  package_default = {
50
50
  name: "opencode-swarm",
51
- version: "7.29.0",
51
+ version: "7.29.2",
52
52
  description: "Architect-centric agentic swarm plugin for OpenCode - hub-and-spoke orchestration with SME consultation, code generation, and QA review",
53
53
  main: "dist/index.js",
54
54
  types: "dist/index.d.ts",
@@ -39003,7 +39003,46 @@ async function buildParallelExecutionGuidance(directory, sessionID, session) {
39003
39003
  if (eligible.length === 0) {
39004
39004
  return `[PARALLEL EXECUTION PROFILE] parallelization_enabled=true max_concurrent_tasks=${maxConcurrent}; no dependency-ready pending tasks are available for a new coder slot. Continue the current task/gate.`;
39005
39005
  }
39006
- return `[PARALLEL EXECUTION PROFILE] parallelization_enabled=true max_concurrent_tasks=${maxConcurrent}; ${occupied.size} slot(s) occupied. Eligible now: ${eligible.join(", ")}. [NEXT] dispatch up to ${availableSlots} eligible coder task(s) before waiting; preserve ONE task per coder call and call declare_scope for each task.`;
39006
+ return `[PARALLEL EXECUTION PROFILE] parallelization_enabled=true max_concurrent_tasks=${maxConcurrent}; ${occupied.size} slot(s) occupied. Eligible now: ${eligible.join(", ")}. [NEXT] dispatch up to ${availableSlots} eligible coder task(s) before waiting; for each dispatched task, call update_task_status(in_progress), call declare_scope, then send the coder Task. Preserve ONE atomic task per coder Task call.`;
39007
+ }
39008
+ async function buildPlanContinuationGuidance(directory) {
39009
+ if (!directory)
39010
+ return null;
39011
+ const plan = await loadPlanJsonOnly(directory);
39012
+ const currentTaskId = getPlanContinuationTaskId(plan);
39013
+ if (!currentTaskId)
39014
+ return null;
39015
+ const sanitizedTaskId = sanitizeGuidanceValue(currentTaskId, 32);
39016
+ return `[NEXT] Continue plan task ${sanitizedTaskId}: if it is not already in progress, call update_task_status with task_id="${sanitizedTaskId}" and status="in_progress"; ` + `then call declare_scope for the task files and dispatch coder Task call(s) according to the execution profile. ` + `Preserve ONE atomic task per coder Task call; when parallel execution is enabled, use available coder slots instead of forcing a single coder.`;
39017
+ }
39018
+ function sanitizeGuidanceValue(value, maxLength) {
39019
+ return value.replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/\[ \]/g, "()").replace(/\[/g, "(").replace(/\]/g, ")").replace(/[\r\n]/g, " ").slice(0, maxLength);
39020
+ }
39021
+ function getPlanContinuationTaskId(plan) {
39022
+ if (!plan)
39023
+ return;
39024
+ const currentPhase = plan.current_phase ?? 1;
39025
+ const phase = plan.phases.find((p) => p.id === currentPhase);
39026
+ if (!phase)
39027
+ return;
39028
+ const sortedTasks = [...phase.tasks].sort((a, b) => comparePlanTaskIds(a.id, b.id));
39029
+ const inProgress = sortedTasks.find((task) => task.status === "in_progress");
39030
+ if (inProgress)
39031
+ return inProgress.id;
39032
+ const incomplete = sortedTasks.find((task) => task.status !== "completed" && task.status !== "closed");
39033
+ return incomplete?.id;
39034
+ }
39035
+ function comparePlanTaskIds(a, b) {
39036
+ const partsA = a.split(".").map((part) => Number.parseInt(part, 10));
39037
+ const partsB = b.split(".").map((part) => Number.parseInt(part, 10));
39038
+ const maxLength = Math.max(partsA.length, partsB.length);
39039
+ for (let i2 = 0;i2 < maxLength; i2++) {
39040
+ const numA = partsA[i2] ?? 0;
39041
+ const numB = partsB[i2] ?? 0;
39042
+ if (numA !== numB)
39043
+ return numA - numB;
39044
+ }
39045
+ return 0;
39007
39046
  }
39008
39047
  function isParallelGuidancePhaseComplete(phase) {
39009
39048
  return phase.status === "complete" || phase.status === "completed" || phase.status === "closed";
@@ -39581,6 +39620,7 @@ ${trimComment}${after}`;
39581
39620
  const deliberationSession = ensureAgentSession(deliberationSessionID);
39582
39621
  const lastGate = deliberationSession.lastGateOutcome;
39583
39622
  const parallelGuidance = await buildParallelExecutionGuidance(directory, deliberationSessionID, deliberationSession);
39623
+ const planContinuationGuidance = parallelGuidance === null ? await buildPlanContinuationGuidance(directory) : null;
39584
39624
  const taskAwaitingCompletion = await findTaskAwaitingCompletion(directory, deliberationSession);
39585
39625
  let guidance;
39586
39626
  if (taskAwaitingCompletion) {
@@ -39588,12 +39628,12 @@ ${trimComment}${after}`;
39588
39628
  [NEXT] Print the task completion checklist, then call update_task_status with task_id="${taskAwaitingCompletion}" and status="completed" before declare_scope or starting another task.`;
39589
39629
  } else if (lastGate?.taskId) {
39590
39630
  const gateResult = lastGate.passed ? "PASSED" : "FAILED";
39591
- const sanitizedGate = lastGate.gate.replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/\[ \]/g, "()").replace(/\[/g, "(").replace(/\]/g, ")").replace(/[\r\n]/g, " ").slice(0, 64);
39592
- const sanitizedTaskId = lastGate.taskId.replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/\[/g, "(").replace(/\]/g, ")").replace(/[\r\n]/g, " ").slice(0, 32);
39631
+ const sanitizedGate = sanitizeGuidanceValue(lastGate.gate, 64);
39632
+ const sanitizedTaskId = sanitizeGuidanceValue(lastGate.taskId, 32);
39593
39633
  guidance = `[Last gate: ${sanitizedGate} ${gateResult} for task ${sanitizedTaskId}]
39594
39634
  ${parallelGuidance ?? "[NEXT] Execute the next gate for the current task."}`;
39595
39635
  } else {
39596
- guidance = parallelGuidance ?? "[NEXT] Begin the first plan task and run gates sequentially.";
39636
+ guidance = parallelGuidance ?? planContinuationGuidance ?? "[NEXT] Begin the first plan task and run gates sequentially.";
39597
39637
  }
39598
39638
  const systemMsgIdx = messages.findIndex((m) => m && m.info?.role === "system");
39599
39639
  const insertIdx = systemMsgIdx >= 0 ? systemMsgIdx + 1 : 0;
@@ -61164,19 +61204,9 @@ function validateConfigKey(path31, value, _config) {
61164
61204
  case "guardrails.profiles": {
61165
61205
  const profiles = value;
61166
61206
  if (profiles) {
61167
- const validAgents = [
61168
- "architect",
61169
- "coder",
61170
- "test_engineer",
61171
- "explorer",
61172
- "reviewer",
61173
- "critic",
61174
- "sme",
61175
- "docs",
61176
- "designer"
61177
- ];
61207
+ const validAgents = new Set(ALL_AGENT_NAMES);
61178
61208
  for (const [agentName, profile] of Object.entries(profiles)) {
61179
- if (!validAgents.includes(agentName)) {
61209
+ if (!validAgents.has(agentName)) {
61180
61210
  findings.push({
61181
61211
  id: "unknown-agent-profile",
61182
61212
  title: "Unknown agent profile",
@@ -61337,23 +61367,23 @@ function validateConfigKey(path31, value, _config) {
61337
61367
  case "swarms": {
61338
61368
  const swarms = value;
61339
61369
  if (swarms && typeof swarms === "object") {
61370
+ const validAgents = new Set(ALL_AGENT_NAMES);
61340
61371
  for (const [swarmId, swarmConfig] of Object.entries(swarms)) {
61341
61372
  const swarm = swarmConfig;
61342
61373
  if (swarm.agents && typeof swarm.agents === "object") {
61343
61374
  for (const [agentName] of Object.entries(swarm.agents)) {
61344
- const validAgents = [
61345
- "architect",
61346
- "coder",
61347
- "test_engineer",
61348
- "explorer",
61349
- "reviewer",
61350
- "critic",
61351
- "sme",
61352
- "docs",
61353
- "designer"
61354
- ];
61355
- const baseName = agentName.replace(/^[a-zA-Z0-9]+_/, "");
61356
- if (!validAgents.includes(baseName)) {
61375
+ const baseName = stripKnownSwarmPrefix(agentName);
61376
+ if (baseName !== agentName && agentName.startsWith(`${swarmId}_`) && validAgents.has(baseName)) {
61377
+ findings.push({
61378
+ id: "prefixed-swarm-agent-override",
61379
+ title: "Prefixed agent override is ignored",
61380
+ description: `Agent "${agentName}" in swarm "${swarmId}" uses a generated agent name. ` + `Per-swarm overrides must use the canonical key "${baseName}", e.g. ` + `"swarms.${swarmId}.agents.${baseName}.model". Otherwise the override is ignored and the agent falls back to its default model.`,
61381
+ severity: "warn",
61382
+ path: `swarms.${swarmId}.agents.${agentName}`,
61383
+ currentValue: swarm.agents[agentName],
61384
+ autoFixable: false
61385
+ });
61386
+ } else if (!validAgents.has(baseName)) {
61357
61387
  findings.push({
61358
61388
  id: "unknown-swarm-agent",
61359
61389
  title: "Unknown agent in swarm",
@@ -61704,6 +61734,8 @@ function removeStraySwarmDir(projectRoot, strayPath) {
61704
61734
  }
61705
61735
  var VALID_CONFIG_PATTERNS, DANGEROUS_PATH_SEGMENTS;
61706
61736
  var init_config_doctor = __esm(() => {
61737
+ init_constants();
61738
+ init_schema();
61707
61739
  init_utils();
61708
61740
  VALID_CONFIG_PATTERNS = [
61709
61741
  /^\.config[\\/]opencode[\\/]opencode-swarm\.json$/,
@@ -75598,7 +75630,7 @@ ANTI-RATIONALIZATION: Context does not clarify. Models revert to CC training.
75598
75630
  ## IDENTITY
75599
75631
 
75600
75632
  Swarm: {{SWARM_ID}}
75601
- Your agents: {{AGENT_PREFIX}}explorer, {{AGENT_PREFIX}}sme, {{AGENT_PREFIX}}coder, {{AGENT_PREFIX}}reviewer, {{AGENT_PREFIX}}test_engineer, {{AGENT_PREFIX}}critic, {{AGENT_PREFIX}}critic_sounding_board, {{AGENT_PREFIX}}docs, {{AGENT_PREFIX}}designer
75633
+ Your agents: {{AGENT_PREFIX}}explorer, {{AGENT_PREFIX}}sme, {{AGENT_PREFIX}}coder, {{AGENT_PREFIX}}reviewer, {{AGENT_PREFIX}}test_engineer, {{AGENT_PREFIX}}critic, {{AGENT_PREFIX}}critic_sounding_board, {{AGENT_PREFIX}}skill_improver, {{AGENT_PREFIX}}spec_writer, {{AGENT_PREFIX}}docs, {{AGENT_PREFIX}}designer
75602
75634
 
75603
75635
  ## PROJECT CONTEXT
75604
75636
  Session-start priming block. Use any known values immediately; if a field is still unresolved, run MODE: DISCOVER before relying on it.
@@ -75939,6 +75971,8 @@ SECURITY_KEYWORDS: password, secret, token, credential, auth, login, encryption,
75939
75971
  {{AGENT_PREFIX}}test_engineer - Test generation AND execution (writes tests, runs them, reports PASS/FAIL)
75940
75972
  {{AGENT_PREFIX}}critic - Plan review gate (reviews plan BEFORE implementation)
75941
75973
  {{AGENT_PREFIX}}critic_sounding_board - Pre-escalation pushback (honest engineer review before user contact)
75974
+ {{AGENT_PREFIX}}skill_improver - Low-frequency skill / knowledge / prompt improvement adviser
75975
+ {{AGENT_PREFIX}}spec_writer - .swarm/spec.md authoring via spec_write
75942
75976
  {{AGENT_PREFIX}}docs - Documentation updates (README, API docs, guides — NOT .swarm/ files)
75943
75977
  {{AGENT_PREFIX}}designer - UI/UX design specs (scaffold generation for UI components — runs BEFORE coder on UI tasks)
75944
75978
 
@@ -75989,30 +76023,20 @@ For every applicable directive in the block:
75989
76023
 
75990
76024
  You may also call the \`knowledge_ack\` tool to record an outcome explicitly when chat-text markers would be ambiguous (e.g. inside structured tool args).
75991
76025
 
75992
- ## SKILL IMPROVER (low-frequency, expensive-model adviser)
76026
+ ## SKILL IMPROVER
75993
76027
 
75994
- The \`skill_improver\` agent and the \`skill_improve\` tool exist for rare, deep
75995
- review of accumulated knowledge / skills / spec / architect prompt. They are
75996
- quota-bounded (default 10 calls/day) and disabled by default. Suggest running
75997
- \`skill_improve\` only after one of:
75998
- - repeated reviewer rejections in a row,
75999
- - many \`KNOWLEDGE_IGNORED\` outcomes for the same cluster,
76000
- - stale skills (no updates while their target area changed),
76001
- - a fresh spec mismatch with shipped behaviour.
76028
+ \`{{AGENT_PREFIX}}skill_improver\` / \`skill_improve\`: rare, quota-bounded,
76029
+ disabled by default, proposal-only. Use for repeated rejections,
76030
+ \`KNOWLEDGE_IGNORED\`, stale skills, or spec drift.
76002
76031
 
76003
76032
  When \`skill_improver.require_user_approval\` is true (default), ASK the user
76004
76033
  before running. Default outputs are proposals only — they never modify source.
76005
76034
 
76006
76035
  ## SPEC WRITER
76007
76036
 
76008
- For substantial spec authoring or revision, prefer delegating to the
76009
- \`spec_writer\` agent (independent model from architect). It writes only via
76010
- the safe \`spec_write\` tool. Use it when:
76011
- - the user requests a new spec or major spec revision,
76012
- - requirements decomposition is non-trivial,
76013
- - you would otherwise inline-author \`.swarm/spec.md\` yourself.
76014
-
76015
- Continue handling small touch-ups (typos, cross-references) inline.
76037
+ For substantial spec authoring/revision, prefer \`{{AGENT_PREFIX}}spec_writer\`;
76038
+ it writes via \`spec_write\`. Use for major specs or non-trivial
76039
+ decomposition. Handle small touch-ups.
76016
76040
 
76017
76041
  ### ANTI-RATIONALIZATION
76018
76042
  - ✗ "The coder already knows these conventions" → Skills contain project-specific rules the model cannot know from training. Always pass.
@@ -76193,11 +76217,12 @@ MODE: BRAINSTORM runs seven phases in strict order. Do not skip phases. Do not c
76193
76217
  - Exit with a design outline the user can skim in under two minutes.
76194
76218
 
76195
76219
  **Phase 5: SPEC WRITE + SELF-REVIEW (architect + reviewer).**
76196
- - Generate \`.swarm/spec.md\` following the same SPEC CONTENT RULES that MODE: SPECIFY uses: WHAT/WHY only, no tech stack, no implementation details, FR-### / SC-### numbering, Given/When/Then scenarios, \`[NEEDS CLARIFICATION]\` markers (max 3).
76220
+ - Delegate substantial spec drafting to \`{{AGENT_PREFIX}}spec_writer\` with the chosen design, dialogue notes, SME context, and SPEC CONTENT RULES. The spec writer must persist \`.swarm/spec.md\` through \`spec_write\`.
76221
+ - The spec must follow the same SPEC CONTENT RULES that MODE: SPECIFY uses: WHAT/WHY only, no tech stack, no implementation details, FR-### / SC-### numbering, Given/When/Then scenarios, \`[NEEDS CLARIFICATION]\` markers (max 3).
76197
76222
  - Cross-reference design sections by name where relevant context helps (but keep HOW out of the spec).
76198
76223
  - Delegate to \`{{AGENT_PREFIX}}reviewer\` for an independent review of the draft spec. Reviewer must flag: requirements that encode HOW, untestable requirements, missing edge cases, silent assumptions.
76199
76224
  - Apply reviewer feedback. If reviewer rejects, iterate once and re-review. After two rounds, surface remaining disagreements to the user.
76200
- - Write the final spec to \`.swarm/spec.md\`.
76225
+ - Read back and lint the final spec after \`{{AGENT_PREFIX}}spec_writer\` writes it.
76201
76226
  - Exit when reviewer signs off (or user explicitly accepts remaining disagreements).
76202
76227
 
76203
76228
  **Phase 6: QA GATE SELECTION (architect, dialogue only).**
@@ -76269,7 +76294,7 @@ Activates when: user asks to "specify", "define requirements", "write a spec", o
76269
76294
  1b. Run CODEBASE REALITY CHECK for any codebase references mentioned by the user or implied by the feature. Skip if work is purely greenfield (no existing codebase to check). Report discrepancies before proceeding to explorer.
76270
76295
  2. Delegate to \`{{AGENT_PREFIX}}explorer\` to scan the codebase for relevant context (existing patterns, related code, affected areas).
76271
76296
  3. Delegate to \`{{AGENT_PREFIX}}sme\` for domain research on the feature area to surface known constraints, best practices, and integration concerns.
76272
- 4. Generate \`.swarm/spec.md\` capturing:
76297
+ 4. Delegate substantial spec drafting to \`{{AGENT_PREFIX}}spec_writer\`. Include the user requirements, explorer findings, SME constraints, and these required contents:
76273
76298
  - First line must be: \`# Specification: <feature-name>\`
76274
76299
  - Feature description: WHAT users need and WHY — never HOW to implement
76275
76300
  - User scenarios with acceptance criteria (Given/When/Then format)
@@ -76278,7 +76303,7 @@ Activates when: user asks to "specify", "define requirements", "write a spec", o
76278
76303
  - Key entities if data is involved (no schema or field definitions — entity names only)
76279
76304
  - Edge cases and known failure modes
76280
76305
  - \`[NEEDS CLARIFICATION]\` markers (max 3) for items where uncertainty could change scope, security, or core behavior; prefer informed defaults over asking
76281
- 5. Write the spec to \`.swarm/spec.md\`.
76306
+ 5. Require \`{{AGENT_PREFIX}}spec_writer\` to write the spec via \`spec_write\`, then read back and lint \`.swarm/spec.md\`.
76282
76307
  5b. **QA GATE SELECTION (dialogue only).**
76283
76308
  {{QA_GATE_DIALOGUE_SPECIFY}}
76284
76309
 
@@ -76433,6 +76458,7 @@ If .swarm/plan.md exists:
76433
76458
  - Update context.md Swarm field to "{{SWARM_ID}}"
76434
76459
  - Inform user: "Resuming project from [other] swarm. Cleared stale context. Ready to continue."
76435
76460
  - Resume at current task
76461
+ Resume execution rule: after identifying the current task, do not restart broad discovery. Move into EXECUTE: call \`update_task_status\` if the task is not already in progress, call \`declare_scope\` for the task files, then dispatch coder Task call(s) according to \`execution_profile\`. Preserve ONE atomic task per coder Task call; parallel profiles may dispatch up to the available coder slots.
76436
76462
  If .swarm/plan.md does not exist → New project, proceed to MODE: CLARIFY
76437
76463
  If new project: Run \`complexity_hotspots\` tool (90 days) to generate a risk map. Note modules with recommendation "security_review" or "full_gates" in context.md for stricter QA gates during Phase 5. Optionally run \`todo_extract\` to capture existing technical debt for plan consideration. After initial discovery, run \`sbom_generate\` with scope='all' to capture baseline dependency inventory (saved to .swarm/evidence/sbom/).
76438
76464
 
@@ -79481,8 +79507,24 @@ function createSwarmAgents(swarmId, swarmConfig, isDefault, pluginConfig, projec
79481
79507
  const swarmIdentity = isDefault ? "default" : swarmId;
79482
79508
  const agentPrefix = prefix;
79483
79509
  architect.config.prompt = architect.config.prompt?.replace(/\{\{SWARM_ID\}\}/g, swarmIdentity).replace(/\{\{AGENT_PREFIX\}\}/g, agentPrefix).replace(/\{\{QA_RETRY_LIMIT\}\}/g, String(qaRetryLimit)).replace(/\{\{PROJECT_LANGUAGE\}\}/g, projectContext.PROJECT_LANGUAGE).replace(/\{\{PROJECT_FRAMEWORK\}\}/g, projectContext.PROJECT_FRAMEWORK).replace(/\{\{BUILD_CMD\}\}/g, projectContext.BUILD_CMD).replace(/\{\{TEST_CMD\}\}/g, projectContext.TEST_CMD).replace(/\{\{LINT_CMD\}\}/g, projectContext.LINT_CMD).replace(/\{\{ENTRY_POINTS\}\}/g, projectContext.ENTRY_POINTS).replace(/\{\{CODER_CONSTRAINTS\}\}/g, projectContext.CODER_CONSTRAINTS).replace(/\{\{TEST_CONSTRAINTS\}\}/g, projectContext.TEST_CONSTRAINTS).replace(/\{\{REVIEWER_CHECKLIST\}\}/g, projectContext.REVIEWER_CHECKLIST).replace(/\{\{PROJECT_CONTEXT_SECONDARY_LANGUAGES\}\}/g, projectContext.PROJECT_CONTEXT_SECONDARY_LANGUAGES);
79510
+ const skillImproverEnabled = !isAgentDisabled("skill_improver", swarmAgents, swarmPrefix);
79511
+ const specWriterEnabled = !isAgentDisabled("spec_writer", swarmAgents, swarmPrefix);
79512
+ if (!skillImproverEnabled) {
79513
+ architect.config.prompt = architect.config.prompt?.replace(`, ${agentPrefix}skill_improver`, "").replace(`
79514
+ ${agentPrefix}skill_improver - Low-frequency skill / knowledge / prompt improvement adviser`, "").replace(/\n## SKILL IMPROVER[\s\S]*?(?=\n\n## SPEC WRITER)/, "");
79515
+ }
79516
+ if (!specWriterEnabled) {
79517
+ const escapedAgentPrefix = agentPrefix.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
79518
+ architect.config.prompt = architect.config.prompt?.replace(`, ${agentPrefix}spec_writer`, "").replace(`
79519
+ ${agentPrefix}spec_writer - .swarm/spec.md authoring via spec_write`, "").replace(/\n## SPEC WRITER[\s\S]*?(?=\n\n### ANTI-RATIONALIZATION)/, "").replace(new RegExp(`- Delegate substantial spec drafting to \`${escapedAgentPrefix}spec_writer\`[^\\n]*\\n`, "g"), "- spec_writer is disabled. Ask the user to enable the spec_writer agent before creating or revising `.swarm/spec.md`.\n").replace(new RegExp(`4\\. Delegate substantial spec drafting to \`${escapedAgentPrefix}spec_writer\`[^\\n]*`), "4. spec_writer is disabled. Ask the user to enable the spec_writer agent before creating or revising `.swarm/spec.md`.").replace(new RegExp(`5\\. Require \`${escapedAgentPrefix}spec_writer\` to write the spec via \`spec_write\`, then read back and lint \`\\.swarm/spec\\.md\`\\.`), "5. Do not continue SPECIFY until spec_writer is available.").replace(new RegExp(`- Read back and lint the final spec after \`${escapedAgentPrefix}spec_writer\` writes it\\.`), "- Do not continue BRAINSTORM spec writing until spec_writer is available.");
79520
+ }
79484
79521
  if (!isDefault) {
79485
79522
  architect.description = `[${swarmName}] ${architect.description}`;
79523
+ const optionalAgentLines = [
79524
+ skillImproverEnabled ? `- @${swarmId}_skill_improver (not @skill_improver)` : undefined,
79525
+ specWriterEnabled ? `- @${swarmId}_spec_writer (not @spec_writer)` : undefined
79526
+ ].filter((line) => Boolean(line)).join(`
79527
+ `);
79486
79528
  const swarmHeader = `## ⚠️ YOU ARE THE ${swarmName.toUpperCase()} SWARM ARCHITECT
79487
79529
 
79488
79530
  Your swarm ID is "${swarmId}". ALL your agents have the "${swarmId}_" prefix:
@@ -79490,7 +79532,8 @@ Your swarm ID is "${swarmId}". ALL your agents have the "${swarmId}_" prefix:
79490
79532
  - @${swarmId}_coder (not @coder)
79491
79533
  - @${swarmId}_sme (not @sme)
79492
79534
  - @${swarmId}_reviewer (not @reviewer)
79493
- - etc.
79535
+ ${optionalAgentLines ? `${optionalAgentLines}
79536
+ ` : ""}- etc.
79494
79537
 
79495
79538
  CRITICAL: Agents without the "${swarmId}_" prefix DO NOT EXIST or belong to a DIFFERENT swarm.
79496
79539
  If you call @coder instead of @${swarmId}_coder, the call will FAIL or go to the wrong swarm.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-swarm",
3
- "version": "7.29.0",
3
+ "version": "7.29.2",
4
4
  "description": "Architect-centric agentic swarm plugin for OpenCode - hub-and-spoke orchestration with SME consultation, code generation, and QA review",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",