opencode-swarm 7.29.2 → 7.29.3

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.2",
37
+ version: "7.29.3",
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",
@@ -505,9 +505,7 @@ function bunSpawn(cmd, options) {
505
505
  return proc.exitCode;
506
506
  },
507
507
  kill(signal) {
508
- try {
509
- killChild(signal);
510
- } catch {}
508
+ killChild(signal);
511
509
  }
512
510
  };
513
511
  }
@@ -46282,7 +46280,7 @@ function defaultBuildTestCommand(profile, framework, files, dir = ".", opts = {}
46282
46280
  const coverage = opts.coverage ?? false;
46283
46281
  switch (framework) {
46284
46282
  case "bun": {
46285
- const args = ["bun", "--smol", "test"];
46283
+ const args = ["bun", "test"];
46286
46284
  if (coverage)
46287
46285
  args.push("--coverage");
46288
46286
  if (scope !== "all" && files.length > 0)
@@ -48715,7 +48713,7 @@ function getTargetedExecutionUnsupportedReason(framework) {
48715
48713
  function buildTestCommand2(framework, scope, files, coverage, baseDir) {
48716
48714
  switch (framework) {
48717
48715
  case "bun": {
48718
- const args = ["bun", "--smol", "test"];
48716
+ const args = ["bun", "test"];
48719
48717
  if (coverage)
48720
48718
  args.push("--coverage");
48721
48719
  if (scope !== "all" && files.length > 0) {
@@ -49252,24 +49250,17 @@ async function runTests(framework, scope, files, coverage, timeout_ms, cwd) {
49252
49250
  const proc = bunSpawn(command, {
49253
49251
  stdout: "pipe",
49254
49252
  stderr: "pipe",
49255
- stdin: "ignore",
49256
- cwd,
49257
- killProcessTree: true
49258
- });
49259
- let timeoutHandle;
49260
- const timeoutPromise = new Promise((resolve14) => {
49261
- timeoutHandle = setTimeout(() => {
49262
- proc.kill();
49263
- resolve14(-1);
49264
- }, timeout_ms);
49253
+ cwd
49265
49254
  });
49255
+ const timeoutPromise = new Promise((resolve14) => setTimeout(() => {
49256
+ proc.kill();
49257
+ resolve14(-1);
49258
+ }, timeout_ms));
49266
49259
  const [exitCode, stdoutResult, stderrResult] = await Promise.all([
49267
49260
  Promise.race([proc.exited, timeoutPromise]),
49268
49261
  readBoundedStream(proc.stdout, MAX_OUTPUT_BYTES3),
49269
49262
  readBoundedStream(proc.stderr, MAX_OUTPUT_BYTES3)
49270
49263
  ]);
49271
- if (timeoutHandle !== undefined)
49272
- clearTimeout(timeoutHandle);
49273
49264
  const duration_ms = Date.now() - startTime;
49274
49265
  let output = stdoutResult.text;
49275
49266
  if (stderrResult.text) {
@@ -49572,6 +49563,7 @@ var init_test_runner = __esm(() => {
49572
49563
  files: exports_external.array(exports_external.string()).optional().describe('Specific files to test. For "convention", pass source files or direct test files. For "graph" and "impact", pass source files only.'),
49573
49564
  coverage: exports_external.boolean().optional().describe("Enable coverage reporting if supported"),
49574
49565
  timeout_ms: exports_external.number().optional().describe("Timeout in milliseconds (default 60000, max 300000)"),
49566
+ allow_full_suite: exports_external.boolean().optional().describe('Explicit opt-in for scope "all". Required because full-suite output can destabilize SSE streaming.'),
49575
49567
  working_directory: exports_external.string().optional().describe("Explicit project root directory. When provided, tests run relative to this path instead of the plugin context directory. Use this when CWD differs from the actual project root.")
49576
49568
  },
49577
49569
  async execute(args, directory) {
@@ -49645,8 +49637,7 @@ var init_test_runner = __esm(() => {
49645
49637
  }
49646
49638
  const scope = args.scope || "all";
49647
49639
  if (scope === "all") {
49648
- const fullSuiteAllowed = process.env.SWARM_ALLOW_FULL_SUITE === "1" || process.env.SWARM_ALLOW_FULL_SUITE === "true";
49649
- if (!fullSuiteAllowed) {
49640
+ if (!process.env.SWARM_ALLOW_FULL_SUITE) {
49650
49641
  const errorResult = {
49651
49642
  success: false,
49652
49643
  framework: "none",
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.2",
51
+ version: "7.29.3",
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",
@@ -16715,9 +16715,7 @@ function bunSpawn(cmd, options) {
16715
16715
  return proc.exitCode;
16716
16716
  },
16717
16717
  kill(signal) {
16718
- try {
16719
- killChild(signal);
16720
- } catch {}
16718
+ killChild(signal);
16721
16719
  }
16722
16720
  };
16723
16721
  }
@@ -16969,6 +16967,13 @@ var init_telemetry = __esm(() => {
16969
16967
  gatePassed(sessionId, gate, taskId) {
16970
16968
  _internals4.emit("gate_passed", { sessionId, gate, taskId });
16971
16969
  },
16970
+ gateParseError(taskId, error49) {
16971
+ _internals4.emit("gate_parse_error", {
16972
+ taskId,
16973
+ errorName: error49.name,
16974
+ errorMessage: error49.message.slice(0, 200)
16975
+ });
16976
+ },
16972
16977
  gateFailed(sessionId, gate, taskId, reason) {
16973
16978
  _internals4.emit("gate_failed", { sessionId, gate, taskId, reason });
16974
16979
  },
@@ -38741,12 +38746,15 @@ function getEvidencePath(directory, taskId) {
38741
38746
  assertValidTaskId(taskId);
38742
38747
  return path12.join(getEvidenceDir(directory), `${taskId}.json`);
38743
38748
  }
38744
- function readExisting(evidencePath) {
38749
+ function readExisting(evidencePath, taskId) {
38745
38750
  try {
38746
38751
  const raw = readFileSync5(evidencePath, "utf-8");
38747
38752
  return TaskEvidenceSchema.parse(JSON.parse(raw));
38748
- } catch {
38749
- return null;
38753
+ } catch (error49) {
38754
+ if (error49.code === "ENOENT")
38755
+ return null;
38756
+ telemetry.gateParseError(taskId, error49);
38757
+ throw error49;
38750
38758
  }
38751
38759
  }
38752
38760
  async function atomicWrite2(targetPath, content) {
@@ -38767,7 +38775,13 @@ async function recordGateEvidence(directory, taskId, gate, sessionId, turbo) {
38767
38775
  const lockRelPath = path12.join("evidence", `${taskId}.json`);
38768
38776
  await withEvidenceLock(directory, lockRelPath, gate, taskId, async () => {
38769
38777
  const evidencePath = getEvidencePath(directory, taskId);
38770
- const existing = readExisting(evidencePath);
38778
+ let existing = null;
38779
+ try {
38780
+ existing = readExisting(evidencePath, taskId);
38781
+ } catch (error49) {
38782
+ telemetry.gateParseError(taskId, error49);
38783
+ throw error49;
38784
+ }
38771
38785
  const requiredGates = existing ? expandRequiredGates(existing.required_gates, gate) : deriveRequiredGates(gate);
38772
38786
  const updated = {
38773
38787
  taskId,
@@ -38793,7 +38807,13 @@ async function recordAgentDispatch(directory, taskId, agentType, turbo) {
38793
38807
  const lockRelPath = path12.join("evidence", `${taskId}.json`);
38794
38808
  await withEvidenceLock(directory, lockRelPath, agentType, taskId, async () => {
38795
38809
  const evidencePath = getEvidencePath(directory, taskId);
38796
- const existing = readExisting(evidencePath);
38810
+ let existing = null;
38811
+ try {
38812
+ existing = readExisting(evidencePath, taskId);
38813
+ } catch (error49) {
38814
+ telemetry.gateParseError(taskId, error49);
38815
+ throw error49;
38816
+ }
38797
38817
  const requiredGates = existing ? expandRequiredGates(existing.required_gates, agentType) : deriveRequiredGates(agentType);
38798
38818
  const updated = {
38799
38819
  taskId,
@@ -38807,7 +38827,7 @@ async function recordAgentDispatch(directory, taskId, agentType, turbo) {
38807
38827
  async function readTaskEvidence(directory, taskId) {
38808
38828
  try {
38809
38829
  assertValidTaskId(taskId);
38810
- return readExisting(getEvidencePath(directory, taskId));
38830
+ return readExisting(getEvidencePath(directory, taskId), taskId);
38811
38831
  } catch {
38812
38832
  return null;
38813
38833
  }
@@ -39003,46 +39023,7 @@ async function buildParallelExecutionGuidance(directory, sessionID, session) {
39003
39023
  if (eligible.length === 0) {
39004
39024
  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
39025
  }
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;
39026
+ 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.`;
39046
39027
  }
39047
39028
  function isParallelGuidancePhaseComplete(phase) {
39048
39029
  return phase.status === "complete" || phase.status === "completed" || phase.status === "closed";
@@ -39620,7 +39601,6 @@ ${trimComment}${after}`;
39620
39601
  const deliberationSession = ensureAgentSession(deliberationSessionID);
39621
39602
  const lastGate = deliberationSession.lastGateOutcome;
39622
39603
  const parallelGuidance = await buildParallelExecutionGuidance(directory, deliberationSessionID, deliberationSession);
39623
- const planContinuationGuidance = parallelGuidance === null ? await buildPlanContinuationGuidance(directory) : null;
39624
39604
  const taskAwaitingCompletion = await findTaskAwaitingCompletion(directory, deliberationSession);
39625
39605
  let guidance;
39626
39606
  if (taskAwaitingCompletion) {
@@ -39628,12 +39608,12 @@ ${trimComment}${after}`;
39628
39608
  [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.`;
39629
39609
  } else if (lastGate?.taskId) {
39630
39610
  const gateResult = lastGate.passed ? "PASSED" : "FAILED";
39631
- const sanitizedGate = sanitizeGuidanceValue(lastGate.gate, 64);
39632
- const sanitizedTaskId = sanitizeGuidanceValue(lastGate.taskId, 32);
39611
+ const sanitizedGate = lastGate.gate.replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/\[ \]/g, "()").replace(/\[/g, "(").replace(/\]/g, ")").replace(/[\r\n]/g, " ").slice(0, 64);
39612
+ const sanitizedTaskId = lastGate.taskId.replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/\[/g, "(").replace(/\]/g, ")").replace(/[\r\n]/g, " ").slice(0, 32);
39633
39613
  guidance = `[Last gate: ${sanitizedGate} ${gateResult} for task ${sanitizedTaskId}]
39634
39614
  ${parallelGuidance ?? "[NEXT] Execute the next gate for the current task."}`;
39635
39615
  } else {
39636
- guidance = parallelGuidance ?? planContinuationGuidance ?? "[NEXT] Begin the first plan task and run gates sequentially.";
39616
+ guidance = parallelGuidance ?? "[NEXT] Begin the first plan task and run gates sequentially.";
39637
39617
  }
39638
39618
  const systemMsgIdx = messages.findIndex((m) => m && m.info?.role === "system");
39639
39619
  const insertIdx = systemMsgIdx >= 0 ? systemMsgIdx + 1 : 0;
@@ -67415,7 +67395,7 @@ function defaultBuildTestCommand(profile, framework, files, dir = ".", opts = {}
67415
67395
  const coverage = opts.coverage ?? false;
67416
67396
  switch (framework) {
67417
67397
  case "bun": {
67418
- const args2 = ["bun", "--smol", "test"];
67398
+ const args2 = ["bun", "test"];
67419
67399
  if (coverage)
67420
67400
  args2.push("--coverage");
67421
67401
  if (scope !== "all" && files.length > 0)
@@ -69848,7 +69828,7 @@ function getTargetedExecutionUnsupportedReason(framework) {
69848
69828
  function buildTestCommand2(framework, scope, files, coverage, baseDir) {
69849
69829
  switch (framework) {
69850
69830
  case "bun": {
69851
- const args2 = ["bun", "--smol", "test"];
69831
+ const args2 = ["bun", "test"];
69852
69832
  if (coverage)
69853
69833
  args2.push("--coverage");
69854
69834
  if (scope !== "all" && files.length > 0) {
@@ -70385,24 +70365,17 @@ async function runTests(framework, scope, files, coverage, timeout_ms, cwd) {
70385
70365
  const proc = bunSpawn(command, {
70386
70366
  stdout: "pipe",
70387
70367
  stderr: "pipe",
70388
- stdin: "ignore",
70389
- cwd,
70390
- killProcessTree: true
70391
- });
70392
- let timeoutHandle;
70393
- const timeoutPromise = new Promise((resolve16) => {
70394
- timeoutHandle = setTimeout(() => {
70395
- proc.kill();
70396
- resolve16(-1);
70397
- }, timeout_ms);
70368
+ cwd
70398
70369
  });
70370
+ const timeoutPromise = new Promise((resolve16) => setTimeout(() => {
70371
+ proc.kill();
70372
+ resolve16(-1);
70373
+ }, timeout_ms));
70399
70374
  const [exitCode, stdoutResult, stderrResult] = await Promise.all([
70400
70375
  Promise.race([proc.exited, timeoutPromise]),
70401
70376
  readBoundedStream(proc.stdout, MAX_OUTPUT_BYTES3),
70402
70377
  readBoundedStream(proc.stderr, MAX_OUTPUT_BYTES3)
70403
70378
  ]);
70404
- if (timeoutHandle !== undefined)
70405
- clearTimeout(timeoutHandle);
70406
70379
  const duration_ms = Date.now() - startTime;
70407
70380
  let output = stdoutResult.text;
70408
70381
  if (stderrResult.text) {
@@ -70705,6 +70678,7 @@ var init_test_runner = __esm(() => {
70705
70678
  files: exports_external.array(exports_external.string()).optional().describe('Specific files to test. For "convention", pass source files or direct test files. For "graph" and "impact", pass source files only.'),
70706
70679
  coverage: exports_external.boolean().optional().describe("Enable coverage reporting if supported"),
70707
70680
  timeout_ms: exports_external.number().optional().describe("Timeout in milliseconds (default 60000, max 300000)"),
70681
+ allow_full_suite: exports_external.boolean().optional().describe('Explicit opt-in for scope "all". Required because full-suite output can destabilize SSE streaming.'),
70708
70682
  working_directory: exports_external.string().optional().describe("Explicit project root directory. When provided, tests run relative to this path instead of the plugin context directory. Use this when CWD differs from the actual project root.")
70709
70683
  },
70710
70684
  async execute(args2, directory) {
@@ -70778,8 +70752,7 @@ var init_test_runner = __esm(() => {
70778
70752
  }
70779
70753
  const scope = args2.scope || "all";
70780
70754
  if (scope === "all") {
70781
- const fullSuiteAllowed = process.env.SWARM_ALLOW_FULL_SUITE === "1" || process.env.SWARM_ALLOW_FULL_SUITE === "true";
70782
- if (!fullSuiteAllowed) {
70755
+ if (!process.env.SWARM_ALLOW_FULL_SUITE) {
70783
70756
  const errorResult = {
70784
70757
  success: false,
70785
70758
  framework: "none",
@@ -75971,8 +75944,6 @@ SECURITY_KEYWORDS: password, secret, token, credential, auth, login, encryption,
75971
75944
  {{AGENT_PREFIX}}test_engineer - Test generation AND execution (writes tests, runs them, reports PASS/FAIL)
75972
75945
  {{AGENT_PREFIX}}critic - Plan review gate (reviews plan BEFORE implementation)
75973
75946
  {{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
75976
75947
  {{AGENT_PREFIX}}docs - Documentation updates (README, API docs, guides — NOT .swarm/ files)
75977
75948
  {{AGENT_PREFIX}}designer - UI/UX design specs (scaffold generation for UI components — runs BEFORE coder on UI tasks)
75978
75949
 
@@ -76023,20 +75994,30 @@ For every applicable directive in the block:
76023
75994
 
76024
75995
  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).
76025
75996
 
76026
- ## SKILL IMPROVER
75997
+ ## SKILL IMPROVER (low-frequency, expensive-model adviser)
76027
75998
 
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.
75999
+ The \`skill_improver\` agent and the \`skill_improve\` tool exist for rare, deep
76000
+ review of accumulated knowledge / skills / spec / architect prompt. They are
76001
+ quota-bounded (default 10 calls/day) and disabled by default. Suggest running
76002
+ \`skill_improve\` only after one of:
76003
+ - repeated reviewer rejections in a row,
76004
+ - many \`KNOWLEDGE_IGNORED\` outcomes for the same cluster,
76005
+ - stale skills (no updates while their target area changed),
76006
+ - a fresh spec mismatch with shipped behaviour.
76031
76007
 
76032
76008
  When \`skill_improver.require_user_approval\` is true (default), ASK the user
76033
76009
  before running. Default outputs are proposals only — they never modify source.
76034
76010
 
76035
76011
  ## SPEC WRITER
76036
76012
 
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.
76013
+ For substantial spec authoring or revision, prefer delegating to the
76014
+ \`spec_writer\` agent (independent model from architect). It writes only via
76015
+ the safe \`spec_write\` tool. Use it when:
76016
+ - the user requests a new spec or major spec revision,
76017
+ - requirements decomposition is non-trivial,
76018
+ - you would otherwise inline-author \`.swarm/spec.md\` yourself.
76019
+
76020
+ Continue handling small touch-ups (typos, cross-references) inline.
76040
76021
 
76041
76022
  ### ANTI-RATIONALIZATION
76042
76023
  - ✗ "The coder already knows these conventions" → Skills contain project-specific rules the model cannot know from training. Always pass.
@@ -76217,12 +76198,11 @@ MODE: BRAINSTORM runs seven phases in strict order. Do not skip phases. Do not c
76217
76198
  - Exit with a design outline the user can skim in under two minutes.
76218
76199
 
76219
76200
  **Phase 5: SPEC WRITE + SELF-REVIEW (architect + reviewer).**
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).
76201
+ - 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).
76222
76202
  - Cross-reference design sections by name where relevant context helps (but keep HOW out of the spec).
76223
76203
  - 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.
76224
76204
  - Apply reviewer feedback. If reviewer rejects, iterate once and re-review. After two rounds, surface remaining disagreements to the user.
76225
- - Read back and lint the final spec after \`{{AGENT_PREFIX}}spec_writer\` writes it.
76205
+ - Write the final spec to \`.swarm/spec.md\`.
76226
76206
  - Exit when reviewer signs off (or user explicitly accepts remaining disagreements).
76227
76207
 
76228
76208
  **Phase 6: QA GATE SELECTION (architect, dialogue only).**
@@ -76294,7 +76274,7 @@ Activates when: user asks to "specify", "define requirements", "write a spec", o
76294
76274
  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.
76295
76275
  2. Delegate to \`{{AGENT_PREFIX}}explorer\` to scan the codebase for relevant context (existing patterns, related code, affected areas).
76296
76276
  3. Delegate to \`{{AGENT_PREFIX}}sme\` for domain research on the feature area to surface known constraints, best practices, and integration concerns.
76297
- 4. Delegate substantial spec drafting to \`{{AGENT_PREFIX}}spec_writer\`. Include the user requirements, explorer findings, SME constraints, and these required contents:
76277
+ 4. Generate \`.swarm/spec.md\` capturing:
76298
76278
  - First line must be: \`# Specification: <feature-name>\`
76299
76279
  - Feature description: WHAT users need and WHY — never HOW to implement
76300
76280
  - User scenarios with acceptance criteria (Given/When/Then format)
@@ -76303,7 +76283,7 @@ Activates when: user asks to "specify", "define requirements", "write a spec", o
76303
76283
  - Key entities if data is involved (no schema or field definitions — entity names only)
76304
76284
  - Edge cases and known failure modes
76305
76285
  - \`[NEEDS CLARIFICATION]\` markers (max 3) for items where uncertainty could change scope, security, or core behavior; prefer informed defaults over asking
76306
- 5. Require \`{{AGENT_PREFIX}}spec_writer\` to write the spec via \`spec_write\`, then read back and lint \`.swarm/spec.md\`.
76286
+ 5. Write the spec to \`.swarm/spec.md\`.
76307
76287
  5b. **QA GATE SELECTION (dialogue only).**
76308
76288
  {{QA_GATE_DIALOGUE_SPECIFY}}
76309
76289
 
@@ -76458,7 +76438,6 @@ If .swarm/plan.md exists:
76458
76438
  - Update context.md Swarm field to "{{SWARM_ID}}"
76459
76439
  - Inform user: "Resuming project from [other] swarm. Cleared stale context. Ready to continue."
76460
76440
  - 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.
76462
76441
  If .swarm/plan.md does not exist → New project, proceed to MODE: CLARIFY
76463
76442
  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/).
76464
76443
 
@@ -79507,24 +79486,8 @@ function createSwarmAgents(swarmId, swarmConfig, isDefault, pluginConfig, projec
79507
79486
  const swarmIdentity = isDefault ? "default" : swarmId;
79508
79487
  const agentPrefix = prefix;
79509
79488
  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
- }
79521
79489
  if (!isDefault) {
79522
79490
  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
- `);
79528
79491
  const swarmHeader = `## ⚠️ YOU ARE THE ${swarmName.toUpperCase()} SWARM ARCHITECT
79529
79492
 
79530
79493
  Your swarm ID is "${swarmId}". ALL your agents have the "${swarmId}_" prefix:
@@ -79532,8 +79495,8 @@ Your swarm ID is "${swarmId}". ALL your agents have the "${swarmId}_" prefix:
79532
79495
  - @${swarmId}_coder (not @coder)
79533
79496
  - @${swarmId}_sme (not @sme)
79534
79497
  - @${swarmId}_reviewer (not @reviewer)
79535
- ${optionalAgentLines ? `${optionalAgentLines}
79536
- ` : ""}- etc.
79498
+ - @${swarmId}_spec_writer (not @spec_writer)
79499
+ - etc.
79537
79500
 
79538
79501
  CRITICAL: Agents without the "${swarmId}_" prefix DO NOT EXIST or belong to a DIFFERENT swarm.
79539
79502
  If you call @coder instead of @${swarmId}_coder, the call will FAIL or go to the wrong swarm.
@@ -1,4 +1,4 @@
1
- export type TelemetryEvent = 'session_started' | 'session_ended' | 'agent_activated' | 'delegation_begin' | 'delegation_end' | 'task_state_changed' | 'gate_passed' | 'gate_failed' | 'phase_changed' | 'budget_updated' | 'model_fallback' | 'hard_limit_hit' | 'revision_limit_hit' | 'loop_detected' | 'scope_violation' | 'qa_skip_violation' | 'heartbeat' | 'turbo_mode_changed' | 'auto_oversight_escalation' | 'environment_detected' | 'evidence_lock_acquired' | 'evidence_lock_contended' | 'evidence_lock_stale_recovered' | 'plan_ledger_cas_retry' | 'plan_md_write_failed' | 'prm_pattern_detected' | 'prm_course_correction_injected' | 'prm_escalation_triggered' | 'prm_hard_stop';
1
+ export type TelemetryEvent = 'session_started' | 'session_ended' | 'agent_activated' | 'delegation_begin' | 'delegation_end' | 'task_state_changed' | 'gate_passed' | 'gate_failed' | 'gate_parse_error' | 'phase_changed' | 'budget_updated' | 'model_fallback' | 'hard_limit_hit' | 'revision_limit_hit' | 'loop_detected' | 'scope_violation' | 'qa_skip_violation' | 'heartbeat' | 'turbo_mode_changed' | 'auto_oversight_escalation' | 'environment_detected' | 'evidence_lock_acquired' | 'evidence_lock_contended' | 'evidence_lock_stale_recovered' | 'plan_ledger_cas_retry' | 'plan_md_write_failed' | 'prm_pattern_detected' | 'prm_course_correction_injected' | 'prm_escalation_triggered' | 'prm_hard_stop';
2
2
  export type TelemetryListener = (event: TelemetryEvent, data: Record<string, unknown>) => void;
3
3
  /** @internal - For testing only */
4
4
  export declare function resetTelemetryForTesting(): void;
@@ -39,6 +39,7 @@ export declare const telemetry: {
39
39
  delegationEnd(sessionId: string, agentName: string, taskId: string, result: string): void;
40
40
  taskStateChanged(sessionId: string, taskId: string, newState: string, oldState?: string): void;
41
41
  gatePassed(sessionId: string, gate: string, taskId: string): void;
42
+ gateParseError(taskId: string, error: Error): void;
42
43
  gateFailed(sessionId: string, gate: string, taskId: string, reason: string): void;
43
44
  phaseChanged(sessionId: string, oldPhase: number, newPhase: number): void;
44
45
  budgetUpdated(sessionId: string, budgetPct: number, agentName: string): void;
@@ -23,6 +23,7 @@ export interface TestRunnerArgs {
23
23
  files?: string[];
24
24
  coverage?: boolean;
25
25
  timeout_ms?: number;
26
+ allow_full_suite?: boolean;
26
27
  }
27
28
  export type RegressionOutcome = 'pass' | 'skip' | 'regression' | 'scope_exceeded' | 'error';
28
29
  export interface TestTotals {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-swarm",
3
- "version": "7.29.2",
3
+ "version": "7.29.3",
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",