opencode-swarm 7.113.4 → 7.114.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.
Files changed (47) hide show
  1. package/.opencode/skills/brainstorm/SKILL.md +10 -8
  2. package/.opencode/skills/ci-fix-monitor/SKILL.md +2 -2
  3. package/.opencode/skills/clarify/SKILL.md +2 -2
  4. package/.opencode/skills/clarify-spec/SKILL.md +4 -4
  5. package/.opencode/skills/commit-pr/SKILL.md +32 -20
  6. package/.opencode/skills/council/SKILL.md +1 -1
  7. package/.opencode/skills/deep-dive/SKILL.md +3 -3
  8. package/.opencode/skills/design-docs/SKILL.md +2 -1
  9. package/.opencode/skills/discover/SKILL.md +2 -2
  10. package/.opencode/skills/engineering-conventions/SKILL.md +54 -1
  11. package/.opencode/skills/gate-attribution/SKILL.md +11 -8
  12. package/.opencode/skills/issue-ingest/SKILL.md +18 -14
  13. package/.opencode/skills/loop/SKILL.md +3 -4
  14. package/.opencode/skills/merge-queue-readiness/SKILL.md +6 -13
  15. package/.opencode/skills/phase-wrap/SKILL.md +30 -28
  16. package/.opencode/skills/plan/SKILL.md +8 -7
  17. package/.opencode/skills/resume/SKILL.md +5 -5
  18. package/.opencode/skills/running-tests/SKILL.md +3 -4
  19. package/.opencode/skills/specify/SKILL.md +2 -1
  20. package/.opencode/skills/swarm/SKILL.md +8 -0
  21. package/.opencode/skills/swarm-ci-monitor/SKILL.md +4 -4
  22. package/.opencode/skills/swarm-pr-feedback/SKILL.md +55 -38
  23. package/.opencode/skills/swarm-pr-subscribe/SKILL.md +2 -0
  24. package/.opencode/skills/test-file-split/SKILL.md +3 -5
  25. package/.opencode/skills/writing-tests/SKILL.md +98 -28
  26. package/README.md +2 -2
  27. package/dist/cli/{curator-llm-factory-b3g0g0cd.js → curator-llm-factory-26pyy8cq.js} +1 -1
  28. package/dist/cli/{curator-nh9raf1a.js → curator-wwyj379g.js} +1 -1
  29. package/dist/cli/{guardrail-explain-wpv5eskq.js → guardrail-explain-qzg7rr7f.js} +2 -2
  30. package/dist/cli/{hive-promoter-c0fd3j02.js → hive-promoter-kfjbydfj.js} +1 -1
  31. package/dist/cli/{index-10v7hrkj.js → index-2aszzcyk.js} +1 -1
  32. package/dist/cli/{index-ct9e0y0q.js → index-8phvk4v4.js} +58 -25
  33. package/dist/cli/{index-5yzr9fk6.js → index-gge9vh5s.js} +2 -2
  34. package/dist/cli/index.js +1 -1
  35. package/dist/commands/council.d.ts +0 -1
  36. package/dist/commands/registry.d.ts +3 -3
  37. package/dist/config/skill-mirrors.d.ts +13 -3
  38. package/dist/full-auto/state.d.ts +26 -0
  39. package/dist/index.js +20 -16
  40. package/dist/services/status-service.d.ts +16 -1
  41. package/dist/state.d.ts +9 -0
  42. package/dist/tools/create-tool.d.ts +9 -0
  43. package/dist/tools/test-runner.d.ts +0 -1
  44. package/package.json +2 -3
  45. package/dist/output/agent-writer.d.ts +0 -27
  46. package/dist/output/index.d.ts +0 -1
  47. package/dist/tools/knowledge-ack.d.ts +0 -15
@@ -10,7 +10,7 @@ description: >
10
10
 
11
11
  # Writing Tests for opencode-swarm
12
12
 
13
- > **⚠️ Do NOT use the OpenCode `test_runner` tool to validate the full repo.** It is for targeted agent validation with explicit `files: [...]` or small targeted scopes. `scope: 'all'` requires `allow_full_suite: true` and is intended for opt-in CI mirrors only. Broad scopes can stall or kill OpenCode before the `MAX_SAFE_TEST_FILES = 50` guard in `src/tools/test-runner.ts` fires. For repo validation, use the shell commands in this file — per-file isolation loops match CI behavior. `allow_full_suite` should be used only when intentional and justified in the PR description. See [`AGENTS.md`](../../../AGENTS.md) invariant 6 for the full contract.
13
+ > **⚠️ Do NOT use the OpenCode `test_runner` tool to validate the full repo.** It is for targeted agent validation with explicit `files: [...]` or small targeted scopes. `scope: 'all'` is gated behind the `SWARM_ALLOW_FULL_SUITE=1` env var (intended for opt-in CI mirrors only; there is no `allow_full_suite` arg). Broad scopes can stall or kill OpenCode before the `MAX_SAFE_TEST_FILES = 50` guard in `src/tools/test-runner.ts` fires. For repo validation, use the shell commands in this file — per-file isolation loops match CI behavior. See [`AGENTS.md`](../../../AGENTS.md) invariant 6 for the full contract.
14
14
 
15
15
  ## ⛔ STOP — Read Before Running Any Tests
16
16
 
@@ -522,22 +522,24 @@ const actual = readFileSync(path, 'utf-8').replace(/\r\n/g, '\n');
522
522
 
523
523
  ## CI Pipeline Structure
524
524
 
525
- The CI runs on three platforms (ubuntu, macos, windows). Tests are split into sequential steps within each platform's job.
525
+ The CI runs on three platforms (ubuntu, macos, windows). Tests are split into 6 logical steps within each platform's job. (CI distributes files across shards via round-robin — see TESTING.md's CI Pipeline Steps table for the authoritative directory lists.)
526
526
 
527
527
  ```text
528
- Step 1: hooks — per-file isolation loop on every platform
529
- Step 2: clibatch
530
- Step 3: commands + config — batch
531
- Step 4: tools per-file isolation loop
532
- Step 5: services + build + quality + sast + sbom + scripts — per-file isolation loop
533
- Step 6: state + agents + knowledge + evidence + plan + misc — per-file isolation loop
528
+ Step 1a: hooks (mock.module files 15 files) — per-file isolation (dedicated step)
529
+ Step 1b: hooks (remaining groups) per-file loop per group
530
+ Step 2: cli — batch
531
+ Step 3: commands, config batch
532
+ Step 4: tools — per-file loop
533
+ Step 5: services, build, quality, sast, sbom, scripts — per-file loop
534
+ Step 6: adversarial, agents, background, context, diff, evidence, git, helpers,
535
+ knowledge, lang, output, parallel, plan, session, skills, types, utils — per-file loop
534
536
  ```
535
537
 
536
- **Steps 1 and 4-6 use per-file isolation:** each `.test.ts` file runs in its own `bun --smol` process to prevent `mock.module()` cache poisoning (#330). Steps 2-3 run files in batch (one process per step) because they have fewer mock conflicts.
538
+ **Per-file isolation (steps 1a, 1b, 4-6):** each `.test.ts` file runs in its own `bun --smol` process to prevent `mock.module()` cache poisoning (#330). Steps 2-3 run files in batch because they have fewer mock conflicts. CI partitions the gated test set into **6 shards** round-robin per platform (no hardcoded file lists), with a per-file **retry budget** (two retries / three attempts before a failure is treated as real) and **quarantine** filters (`scripts/ci/quarantined-tests.txt`, plus `-macos`/`-windows` overrides) that drop known pre-existing failures.
537
539
 
538
540
  When writing a test, know which step your file will run in. In batch steps, do not assume isolation from other files in the same step.
539
541
 
540
- **Job timeout: 15 minutes.** A single hanging test will kill the entire platform's test run.
542
+ **Job timeout: 40 minutes.** A hanging shard will kill the entire platform's test run; the per-file `run-test-with-timeout.ts` wrapper caps each file at 120 s (180 s kill-timeout).
541
543
 
542
544
  ## File Placement
543
545
 
@@ -613,7 +615,7 @@ authority checks:
613
615
 
614
616
  ## FR-006: Test File Size Limit (500 lines)
615
617
 
616
- CI enforces a **hard 500-line limit** per test file (FR-006). Files exceeding this limit fail the quality gate and block PR merge.
618
+ `scripts/check-test-file-cap.sh` enforces the **500-line cap** per test file (FR-006) as a **diff-scoped ratchet**: new test files over 500 lines and existing over-cap files that grew fail the quality gate and block PR merge. Pre-existing over-cap files not touched by the PR are non-blocking. Escape hatch: `TEST_CAP_ENFORCE=0` soft-warns (use only for a deliberate growth PR).
617
619
 
618
620
  ### Checking file length
619
621
 
@@ -695,6 +697,9 @@ When CI reports a `unit (ubuntu|macos|windows)` failure:
695
697
  - **Do not test framework behavior.** "Zod schema parses valid input" tests Zod, not your schema.
696
698
  - **Do not test test utilities.** If it only exists to support other tests, it doesn't need its own test.
697
699
  - **Do not mock everything.** If every dependency is mocked, you're testing the mock setup. Prefer real dependencies for pure functions and only mock I/O boundaries (filesystem, network, timers).
700
+ - **Do not hardcode version numbers.** Version bumps are automated — a test asserting `version === '6.31.3'` breaks on every release.
701
+ - **Do not use `sleep` or `setTimeout` for synchronization.** Use explicit signals, resolved promises, or `Bun.sleep()` with tight bounds.
702
+ - **Do not spawn `cat /dev/zero`, `yes`, or other infinite-output commands.** Use `sleep 30` for "blocking command" tests.
698
703
 
699
704
  ### Anchored Content Assertions
700
705
 
@@ -718,9 +723,6 @@ Use this pattern for:
718
723
  - Critic outcome mappings in skill files (DROP, ASK_USER, RESOLVE, REPHRASE)
719
724
  - Classification category lists (self_resolved, user_decision, etc.)
720
725
  - Any structured section where word presence is necessary but position-dependent
721
- - **Do not hardcode version numbers.** Version bumps are automated — a test asserting `version === '6.31.3'` breaks on every release.
722
- - **Do not use `sleep` or `setTimeout` for synchronization.** Use explicit signals, resolved promises, or `Bun.sleep()` with tight bounds.
723
- - **Do not spawn `cat /dev/zero`, `yes`, or other infinite-output commands.** Use `sleep 30` for "blocking command" tests.
724
726
 
725
727
  ## Documented-Example Regression Tests
726
728
 
@@ -763,6 +765,84 @@ if (isWindows) test.skip('reason', () => {});
763
765
  - Use `.cmd` extension on Windows for npm/bun binaries: `process.platform === 'win32' ? 'bun.cmd' : 'bun'`.
764
766
  - Use array-form `spawn`/`spawnSync`, never shell string commands.
765
767
 
768
+ ### macOS rename-visibility race (write-then-read atomic files)
769
+
770
+ On macOS/APFS, `fs.renameSync` can complete before the data is visible to
771
+ subsequent reads. Tests that write-then-read atomic files may fail on
772
+ `macos-latest` but pass on `ubuntu-latest` and `windows-latest`.
773
+
774
+ **Symptom:** Test fails only on macOS with `result === null` or
775
+ `result === undefined` immediately after a write that should have made the
776
+ file visible.
777
+
778
+ **Root cause:** macOS filesystem updates the directory entry asynchronously
779
+ after `fs.renameSync`. Immediately-following reads may see ENOENT or stale
780
+ data.
781
+
782
+ **Fix — three layers (use all three for production code):**
783
+
784
+ **Layer 1: Use `bunWrite` for atomic writes.** The `bunWrite` function in
785
+ `src/utils/bun-compat.ts` already handles temp file creation, fsync,
786
+ atomic rename, and parent directory fsync correctly across platforms.
787
+ Do NOT reimplement this pattern.
788
+
789
+ **Layer 2: Add ENOENT retry in the read path.** Wrap `validateSwarmPath` and
790
+ the file read in a try/catch with a bounded retry loop for ENOENT:
791
+
792
+ ```typescript
793
+ // CORRECT — retry on ENOENT only (not other errors), bounded
794
+ const maxAttempts = 5;
795
+ const retryDelayMs = 10;
796
+ for (let attempt = 0; attempt < maxAttempts; attempt++) {
797
+ try {
798
+ const resolvedPath = _internals.validateSwarmPath(directory, filename);
799
+ const file = bunFile(resolvedPath);
800
+ const content = await file.text();
801
+ return content;
802
+ } catch (err) {
803
+ const isNotFound = (err as NodeJS.ErrnoException)?.code === 'ENOENT';
804
+ if (!isNotFound || attempt === maxAttempts - 1) {
805
+ return null;
806
+ }
807
+ await new Promise((resolve) => setTimeout(resolve, retryDelayMs));
808
+ }
809
+ }
810
+ return null;
811
+ ```
812
+
813
+ CRITICAL: `validateSwarmPath` must be INSIDE the try block so that throws
814
+ (for path traversal attempts) are caught and return null. Security tests
815
+ expect `readSwarmFileAsync` to return null for traversal attempts.
816
+
817
+ **Layer 3: Don't add arbitrary delays.** `setTimeout` should be bounded
818
+ (5-10ms, max 5 attempts). Do not add `await new Promise(r => setTimeout(r, 100))`
819
+ "just in case" — that's a code smell. The retry loop handles it.
820
+
821
+ See [`.opencode/skills/engineering-conventions/SKILL.md`](../engineering-conventions/SKILL.md)
822
+ for the evidence file flow that triggers this retry pattern in QA gates.
823
+
824
+ ### Node FileHandle API
825
+
826
+ Node's `FileHandle` uses `.sync()`, NOT `.fsync()`:
827
+
828
+ ```typescript
829
+ // CORRECT
830
+ const fd = await fsPromises.open(dir, 'r');
831
+ try {
832
+ await fd.sync();
833
+ } finally {
834
+ await fd.close();
835
+ }
836
+
837
+ // WRONG — TypeScript error: Property 'fsync' does not exist on type 'FileHandle'
838
+ const fd = await fsPromises.open(dir, 'r');
839
+ try {
840
+ await fd.fsync();
841
+ } finally {
842
+ await fd.close();
843
+ }
844
+ ```
845
+
766
846
  ## Running Tests
767
847
 
768
848
  ### bash (Linux / macOS)
@@ -777,7 +857,7 @@ bun --smol test tests/unit/hooks --timeout 30000
777
857
  # Per-file loop (required for tools/services/agents — prevents mock poisoning)
778
858
  for f in tests/unit/tools/*.test.ts; do bun --smol test "$f" --timeout 30000; done
779
859
 
780
- # CI-equivalent run for batch steps
860
+ # CI-equivalent run for batch steps (step 2: cli; step 3: commands + config)
781
861
  bun --smol test tests/unit/cli --timeout 120000
782
862
  bun --smol test tests/unit/commands tests/unit/config --timeout 120000
783
863
  ```
@@ -794,7 +874,7 @@ bun --smol test tests/unit/hooks --timeout 30000
794
874
  # Per-file loop (required for tools/services/agents — prevents mock poisoning)
795
875
  Get-ChildItem tests/unit/tools/*.test.ts | ForEach-Object { bun --smol test $_.FullName --timeout 30000 }
796
876
 
797
- # CI-equivalent run for batch steps
877
+ # CI-equivalent run for batch steps (step 2: cli; step 3: commands + config)
798
878
  bun --smol test tests/unit/cli --timeout 120000
799
879
  bun --smol test tests/unit/commands tests/unit/config --timeout 120000
800
880
 
@@ -804,7 +884,7 @@ bun --smol test tests/unit/agents --timeout 60000 | Out-File "$env:TEMP\test_out
804
884
 
805
885
  **Note:** `for f in ...; do` bash syntax is invalid in PowerShell. Use `Get-ChildItem | ForEach-Object` instead. `Select-String -Last N` is also invalid — use `Select-Object -Last N`.
806
886
 
807
- **Warning:** Running `bun --smol test tests/unit/tools` as a single batch will cause mock poisoning failures. Always use the per-file loop for directories in CI steps 4-6 (tools, services, agents, etc.).
887
+ **Warning:** Running `bun --smol test tests/unit/tools` as a single batch will cause mock poisoning failures. Always use the per-file loop for the per-file-isolation steps (1a, 1b, 4-6: tools, services, agents, etc.).
808
888
 
809
889
  The `--smol` flag reduces Bun's memory footprint. Use it when running large directories (50+ files).
810
890
 
@@ -821,17 +901,7 @@ The `--timeout 120000` flag sets per-test timeout to 120 seconds. Individual tes
821
901
 
822
902
  ## Known Pre-existing Test Failures
823
903
 
824
- The following test failures are pre-existing and unrelated to mock isolation:
825
-
826
- | Test file | Failures | Cause | Status |
827
- |-----------|----------|-------|--------|
828
- | `tests/unit/hooks/full-auto-intercept.test.ts` | 21/37 | `logger.log` returns early without `OPENCODE_SWARM_DEBUG=1` | Pre-existing |
829
- | `tests/unit/hooks/full-auto-intercept.dispatch.test.ts` | 2/46 | Same logger issue | Pre-existing |
830
- | `tests/unit/commands/help-compound-commands.test.ts` | Multiple | Command routing issues | Pre-existing |
831
- | `tests/unit/commands/index.test.ts` | Multiple | Command routing issues | Pre-existing |
832
- | `tests/unit/commands/issue-command.test.ts` | Multiple | Command routing issues | Pre-existing |
833
- | `src/__tests__/preflight-phase.test.ts` | 3/3 | `loadPlan` called twice per invocation (lines 930 + 545) | Bug exposed by cleanup |
834
- | `tests/unit/agents/architect-sounding-board-protocol.adversarial.test.ts` | 1 | Token budget threshold `35000` exceeded by prompt growth; soft regression indicator that prompt size needs attention | Pre-existing |
904
+ Pre-existing and flaky failures are tracked in the per-platform quarantine ledgers (`scripts/ci/quarantined-tests.txt`, `quarantined-tests-macos.txt`, `quarantined-tests-windows.txt`), not in this skill. To confirm a failure is pre-existing, reproduce it in a clean worktree on `origin/main` (see the worktree verify protocol in `running-tests`).
835
905
 
836
906
  ## Known Cross-module mock.module Locations
837
907
 
package/README.md CHANGED
@@ -1109,8 +1109,8 @@ Control how tool outputs are summarized for LLM context.
1109
1109
  | `/swarm analyze` | Analyze spec.md vs plan.md for requirement coverage gaps |
1110
1110
  | `/swarm sdd ...` | Inspect, validate, or project OpenSpec-compatible SDD artifacts into `.swarm/spec.md` |
1111
1111
  | `/swarm brainstorm [topic]` | Enter BRAINSTORM mode for structured requirement discovery before a spec |
1112
- | `/swarm council <question> [--preset <name>] [--spec-review]` | Convene a multi-model General Council for advisory deliberation |
1113
- | `/swarm issue <issue-url\|owner/repo#N\|N> [--plan] [--trace]` | Ingest a GitHub issue for localization and resolution |
1112
+ | `/swarm council <question> [--spec-review]` | Convene a multi-model General Council for advisory deliberation |
1113
+ | `/swarm issue <issue-url\|owner/repo#N\|N> [--plan] [--trace] [--no-repro]` | Ingest a GitHub issue for localization and resolution |
1114
1114
  | `/swarm pr-review <pr-url\|owner/repo#N\|N> [--council] [instructions...]` | Structured deep PR review with parallel lanes, reviewer confirmation, and critic challenge |
1115
1115
  | `/swarm pr-feedback [<pr-url\|owner/repo#N\|N>] [instructions...]` | Ingest and close known PR feedback (review comments, CI failures, conflicts) without a fresh review |
1116
1116
  | `/swarm pr subscribe <pr-url\|owner/repo#N\|N>` | Subscribe current session to PR monitoring (session-scoped); requires `pr_monitor.enabled: true` |
@@ -1,7 +1,7 @@
1
1
  // @bun
2
2
  import {
3
3
  createCuratorLLMDelegate
4
- } from "./index-ct9e0y0q.js";
4
+ } from "./index-8phvk4v4.js";
5
5
  import"./index-g6va6n3t.js";
6
6
  import"./index-c8s9a3zh.js";
7
7
  import"./index-2148358e.js";
@@ -14,7 +14,7 @@ import {
14
14
  runCuratorInit,
15
15
  runCuratorPhase,
16
16
  writeCuratorSummary
17
- } from "./index-ct9e0y0q.js";
17
+ } from "./index-8phvk4v4.js";
18
18
  import"./index-g6va6n3t.js";
19
19
  import"./index-c8s9a3zh.js";
20
20
  import"./index-2148358e.js";
@@ -1,8 +1,8 @@
1
1
  // @bun
2
2
  import {
3
3
  handleGuardrailExplain
4
- } from "./index-10v7hrkj.js";
5
- import"./index-ct9e0y0q.js";
4
+ } from "./index-2aszzcyk.js";
5
+ import"./index-8phvk4v4.js";
6
6
  import"./index-g6va6n3t.js";
7
7
  import"./index-c8s9a3zh.js";
8
8
  import"./index-2148358e.js";
@@ -6,7 +6,7 @@ import {
6
6
  isHiveEligible,
7
7
  promoteFromSwarm,
8
8
  promoteToHive
9
- } from "./index-ct9e0y0q.js";
9
+ } from "./index-8phvk4v4.js";
10
10
  import"./index-g6va6n3t.js";
11
11
  import"./index-c8s9a3zh.js";
12
12
  import"./index-2148358e.js";
@@ -12,7 +12,7 @@ import {
12
12
  detectPosixWrites,
13
13
  detectWindowsWrites,
14
14
  resolveWriteTargets
15
- } from "./index-ct9e0y0q.js";
15
+ } from "./index-8phvk4v4.js";
16
16
  import {
17
17
  checkFileAuthority,
18
18
  classifyFile,
@@ -10432,11 +10432,11 @@ var _internals12 = {
10432
10432
  return KnowledgeConfigSchema2.parse({});
10433
10433
  },
10434
10434
  applyCuratorKnowledgeUpdates: async (directory, recommendations, knowledgeConfig) => {
10435
- const { applyCuratorKnowledgeUpdates: applyCuratorKnowledgeUpdates2 } = await import("./curator-nh9raf1a.js");
10435
+ const { applyCuratorKnowledgeUpdates: applyCuratorKnowledgeUpdates2 } = await import("./curator-wwyj379g.js");
10436
10436
  return applyCuratorKnowledgeUpdates2(directory, recommendations, knowledgeConfig);
10437
10437
  },
10438
10438
  checkHivePromotions: async (entries, knowledgeConfig) => {
10439
- const { checkHivePromotions } = await import("./hive-promoter-c0fd3j02.js");
10439
+ const { checkHivePromotions } = await import("./hive-promoter-kfjbydfj.js");
10440
10440
  return checkHivePromotions(entries, knowledgeConfig);
10441
10441
  },
10442
10442
  applyProposalTriage: async (directory, triage) => {
@@ -16774,10 +16774,9 @@ function parseArgs2(args) {
16774
16774
  return out;
16775
16775
  }
16776
16776
  var USAGE2 = [
16777
- "Usage: /swarm council <question> [--preset <name>] [--spec-review]",
16777
+ "Usage: /swarm council <question> [--spec-review]",
16778
16778
  "",
16779
16779
  " question The question to put to the council",
16780
- " --preset <name> Use a named member preset from council.general.presets",
16781
16780
  " --spec-review Use spec_review mode (single advisory pass on a draft spec)",
16782
16781
  "",
16783
16782
  "Requires council.general.enabled: true and a configured search API key in the resolved config: global ~/.config/opencode/opencode-swarm.json, then project .opencode/opencode-swarm.json overrides."
@@ -17586,8 +17585,8 @@ var _internals24 = {
17586
17585
  loadCuratorDeps: async () => {
17587
17586
  const [{ CuratorConfigSchema }, curator, { createCuratorLLMDelegate: createCuratorLLMDelegate2 }] = await Promise.all([
17588
17587
  import("./schema-e5kd993s.js"),
17589
- import("./curator-nh9raf1a.js"),
17590
- import("./curator-llm-factory-b3g0g0cd.js")
17588
+ import("./curator-wwyj379g.js"),
17589
+ import("./curator-llm-factory-26pyy8cq.js")
17591
17590
  ]);
17592
17591
  return { CuratorConfigSchema, curator, createCuratorLLMDelegate: createCuratorLLMDelegate2 };
17593
17592
  }
@@ -18083,7 +18082,7 @@ import { fileURLToPath } from "url";
18083
18082
  // package.json
18084
18083
  var package_default = {
18085
18084
  name: "opencode-swarm",
18086
- version: "7.113.4",
18085
+ version: "7.114.1",
18087
18086
  description: "Architect-centric agentic swarm plugin for OpenCode - hub-and-spoke orchestration with SME consultation, code generation, and QA review",
18088
18087
  main: "dist/index.js",
18089
18088
  types: "dist/index.d.ts",
@@ -18157,8 +18156,6 @@ var package_default = {
18157
18156
  ".opencode/skills/gate-attribution",
18158
18157
  ".opencode/skills/merge-queue-readiness",
18159
18158
  ".opencode/skills/skill-edit-validation",
18160
- ".opencode/skills/swarm",
18161
- ".opencode/skills/swarm-ci-monitor",
18162
18159
  ".opencode/skills/worktree-retry-cleanup",
18163
18160
  ".opencode/skills/test-file-split",
18164
18161
  ".opencode/skills/fork-pr-operations",
@@ -18177,6 +18174,7 @@ var package_default = {
18177
18174
  "lint:ci": "biome ci .",
18178
18175
  "test:unit:ci": "bun scripts/ci/run-unit-tests-local.ts",
18179
18176
  "drift:check": "bun run scripts/drift-check.ts",
18177
+ "drift:fix": "bun run scripts/drift-check.ts --fix --confirm",
18180
18178
  format: "biome format . --write",
18181
18179
  check: "biome check --write .",
18182
18180
  dev: "bun run build && opencode",
@@ -21784,7 +21782,7 @@ var USAGE6 = [
21784
21782
  "",
21785
21783
  "Flags:",
21786
21784
  " --plan Transition to plan creation after spec generation",
21787
- " --trace Run full fix-and-PR workflow (implies --plan)",
21785
+ " --trace Run the fix workflow end-to-end (implies --plan); compose commit-pr to publish.",
21788
21786
  " --no-repro Skip reproduction step"
21789
21787
  ].join(`
21790
21788
  `);
@@ -28448,7 +28446,6 @@ var test_runner = createSwarmTool({
28448
28446
  coverage: exports_external.boolean().optional().describe("Enable coverage reporting if supported"),
28449
28447
  bail: exports_external.boolean().optional().describe("Stop running tests after the first failure. Default false. Note: coverage may be incomplete when bail=true with coverage=true."),
28450
28448
  timeout_ms: exports_external.number().optional().describe("Timeout in milliseconds (default 60000, max 300000)"),
28451
- allow_full_suite: exports_external.boolean().optional().describe('Explicit opt-in for scope "all". Required because full-suite output can destabilize SSE streaming.'),
28452
28449
  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.")
28453
28450
  },
28454
28451
  async execute(args, directory) {
@@ -32211,7 +32208,9 @@ var DEFAULT_CONTEXT_BUDGET_CONFIG = {
32211
32208
  var _internals47 = {
32212
32209
  loadLeanTurboRunState,
32213
32210
  hasActiveLeanTurbo,
32214
- hasActiveFullAuto
32211
+ hasActiveFullAuto,
32212
+ getActiveFullAutoSessionID,
32213
+ loadFullAutoRunState
32215
32214
  };
32216
32215
  function readSpecStalenessSnapshot(directory) {
32217
32216
  try {
@@ -32309,6 +32308,21 @@ async function getStatusData(directory, agents) {
32309
32308
  status.pendingProposals = await countProposals(directory);
32310
32309
  status.unactionableQueueDepth = await safeLineCount(resolveUnactionablePath(directory));
32311
32310
  status.insightCandidatesPending = await safeLineCount(validateSwarmPath(directory, "insight-candidates.jsonl"));
32311
+ status.fullAutoActive = _internals47.hasActiveFullAuto();
32312
+ if (status.fullAutoActive) {
32313
+ const sid = _internals47.getActiveFullAutoSessionID();
32314
+ if (sid) {
32315
+ const runState = _internals47.loadFullAutoRunState(directory, sid);
32316
+ if (runState?.lastEscalation) {
32317
+ status.fullAutoEscalation = {
32318
+ reason: runState.lastEscalation.reason,
32319
+ interactionCount: runState.lastEscalation.interactionCount,
32320
+ deadlockCount: runState.lastEscalation.deadlockCount,
32321
+ phase: runState.lastEscalation.phase
32322
+ };
32323
+ }
32324
+ }
32325
+ }
32312
32326
  return enrichWithLeanTurbo(status, directory);
32313
32327
  }
32314
32328
  function enrichWithLeanTurbo(status, directory) {
@@ -32364,7 +32378,6 @@ function enrichWithLeanTurbo(status, directory) {
32364
32378
  }
32365
32379
  }
32366
32380
  }
32367
- status.fullAutoActive = _internals47.hasActiveFullAuto();
32368
32381
  return status;
32369
32382
  }
32370
32383
  function formatStatusMarkdown(status) {
@@ -32405,13 +32418,19 @@ function formatStatusMarkdown(status) {
32405
32418
  } else {
32406
32419
  lines.push(`**Turbo**: standard`);
32407
32420
  }
32408
- if (status.fullAutoActive) {
32409
- lines.push(`**Full-Auto**: active`);
32410
- }
32411
32421
  } else if (status.turboStrategy === undefined && status.turboMode === true) {
32412
32422
  lines.push("");
32413
32423
  lines.push("**TURBO MODE**: active");
32414
32424
  }
32425
+ if (status.fullAutoActive) {
32426
+ lines.push("");
32427
+ lines.push("**Full-Auto**: active");
32428
+ if (status.fullAutoEscalation) {
32429
+ const e = status.fullAutoEscalation;
32430
+ const phaseStr = e.phase !== undefined ? ` | Phase ${e.phase}` : "";
32431
+ lines.push(` - Escalation: ${e.reason} (interactions=${e.interactionCount}, deadlocks=${e.deadlockCount}${phaseStr})`);
32432
+ }
32433
+ }
32415
32434
  if (status.contextBudgetPct !== null && status.contextBudgetPct > 0) {
32416
32435
  const pct = status.contextBudgetPct.toFixed(1);
32417
32436
  const budgetTokens = DEFAULT_CONTEXT_BUDGET_CONFIG.budgetTokens;
@@ -32949,7 +32968,7 @@ function buildDetailedHelp(commandName, entry) {
32949
32968
  async function handleHelpCommand(ctx) {
32950
32969
  const targetCommand = ctx.args.join(" ");
32951
32970
  if (!targetCommand) {
32952
- const { buildHelpText } = await import("./index-5yzr9fk6.js");
32971
+ const { buildHelpText } = await import("./index-gge9vh5s.js");
32953
32972
  return buildHelpText();
32954
32973
  }
32955
32974
  const tokens = targetCommand.split(/\s+/);
@@ -32958,7 +32977,7 @@ async function handleHelpCommand(ctx) {
32958
32977
  return _internals49.buildDetailedHelp(resolved.key, resolved.entry);
32959
32978
  }
32960
32979
  const similar = _internals49.findSimilarCommands(targetCommand);
32961
- const { buildHelpText: fullHelp } = await import("./index-5yzr9fk6.js");
32980
+ const { buildHelpText: fullHelp } = await import("./index-gge9vh5s.js");
32962
32981
  if (similar.length > 0) {
32963
32982
  return `Command '/swarm ${targetCommand}' not found.
32964
32983
 
@@ -33091,7 +33110,7 @@ var COMMAND_REGISTRY = {
33091
33110
  },
33092
33111
  "guardrail explain": {
33093
33112
  handler: async (ctx) => {
33094
- const { handleGuardrailExplain } = await import("./guardrail-explain-wpv5eskq.js");
33113
+ const { handleGuardrailExplain } = await import("./guardrail-explain-qzg7rr7f.js");
33095
33114
  return handleGuardrailExplain(ctx.directory, ctx.args);
33096
33115
  },
33097
33116
  description: "Dry-run: show what the guardrails would do to a command or write target (executes nothing)",
@@ -33101,7 +33120,7 @@ var COMMAND_REGISTRY = {
33101
33120
  },
33102
33121
  "guardrail-explain": {
33103
33122
  handler: async (ctx) => {
33104
- const { handleGuardrailExplain } = await import("./guardrail-explain-wpv5eskq.js");
33123
+ const { handleGuardrailExplain } = await import("./guardrail-explain-qzg7rr7f.js");
33105
33124
  return handleGuardrailExplain(ctx.directory, ctx.args);
33106
33125
  },
33107
33126
  description: "Dry-run: show what the guardrails would do to a command or write target (executes nothing)",
@@ -33439,9 +33458,9 @@ Subcommands:
33439
33458
  },
33440
33459
  council: {
33441
33460
  handler: (ctx) => handleModeCommandWithBundledSkills(ctx, handleCouncilCommand),
33442
- description: "Enter architect MODE: COUNCIL \u2014 multi-model deliberation [question] [--preset <name>] [--spec-review]",
33443
- args: "<question> [--preset <name>] [--spec-review]",
33444
- details: "Triggers the architect to convene a three-agent General Council: Generalist (reviewer model), Skeptic (critic model), and Domain Expert (SME model). Use --preset <name> to choose a named member preset from council.general.presets. " + "The architect first runs 1\u20133 targeted web searches and passes a compiled RESEARCH CONTEXT " + "to all three agents before dispatching them in parallel. Agents deliberate using the NSED peer-review protocol (Round 1 independent analysis, Round 2 MAINTAIN/CONCEDE/NUANCE for disagreements). The architect synthesizes the final answer directly from convene_general_council output. --spec-review switches to single-pass advisory mode for spec review. Requires council.general.enabled: true and a search API key in the resolved config: global ~/.config/opencode/opencode-swarm.json, then project .opencode/opencode-swarm.json overrides.",
33461
+ description: "Enter architect MODE: COUNCIL \u2014 multi-model deliberation [question] [--spec-review]",
33462
+ args: "<question> [--spec-review]",
33463
+ details: "Triggers the architect to convene a three-agent General Council: Generalist (reviewer model), Skeptic (critic model), and Domain Expert (SME model). " + "The architect first runs 1\u20133 targeted web searches and passes a compiled RESEARCH CONTEXT " + "to all three agents before dispatching them in parallel. Agents deliberate using the NSED peer-review protocol (Round 1 independent analysis, Round 2 MAINTAIN/CONCEDE/NUANCE for disagreements). The architect synthesizes the final answer directly from convene_general_council output. --spec-review switches to single-pass advisory mode for spec review. Requires council.general.enabled: true and a search API key in the resolved config: global ~/.config/opencode/opencode-swarm.json, then project .opencode/opencode-swarm.json overrides.",
33445
33464
  category: "agent",
33446
33465
  toolPolicy: "none"
33447
33466
  },
@@ -33576,7 +33595,7 @@ Subcommands:
33576
33595
  handler: (ctx) => handleModeCommandWithBundledSkills(ctx, handleIssueCommand),
33577
33596
  description: "Ingest a GitHub issue into the swarm workflow [url] [--plan] [--trace] [--no-repro]",
33578
33597
  args: "<issue-url|owner/repo#N|N> [--plan] [--trace] [--no-repro]",
33579
- details: "Triggers the architect to enter MODE: ISSUE_INGEST \u2014 ingests a GitHub issue, restructures it into a normalized intake note, localizes root cause through hypothesis-driven tracing, and outputs a resolution spec. --plan transitions to plan creation after spec generation. --trace runs the full fix-and-PR workflow (implies --plan). --no-repro skips the reproduction step. Supports full GitHub URL, owner/repo#N shorthand, or bare issue number (resolves against origin remote).",
33598
+ details: "Triggers the architect to enter MODE: ISSUE_INGEST \u2014 ingests a GitHub issue, restructures it into a normalized intake note, localizes root cause through hypothesis-driven tracing, and outputs a resolution spec. --plan transitions to plan creation after spec generation. --trace runs the fix workflow end-to-end (implies --plan); compose commit-pr to publish. --no-repro skips the reproduction step. Supports full GitHub URL, owner/repo#N shorthand, or bare issue number (resolves against origin remote).",
33580
33599
  category: "agent",
33581
33600
  toolPolicy: "none"
33582
33601
  },
@@ -34849,7 +34868,7 @@ HARD CONSTRAINTS (apply regardless of skill load success):
34849
34868
  ### MODE: ISSUE_INGEST
34850
34869
  Activates when the user invokes /swarm issue <url> or the architect receives an ISSUE_INGEST signal.
34851
34870
 
34852
- Purpose: Ingest issue evidence, trace impact, and transition to planning or tracing.
34871
+ Purpose: Ingest issue evidence, trace impact, and transition to the full fix workflow.
34853
34872
 
34854
34873
  ACTION: Load skill ${bundledProjectSkillFileReference("issue-ingest")} immediately. Follow the protocol defined there.
34855
34874
 
@@ -39053,6 +39072,20 @@ function hasActiveFullAuto(sessionID) {
39053
39072
  }
39054
39073
  return false;
39055
39074
  }
39075
+ function getActiveFullAutoSessionID() {
39076
+ let activeId;
39077
+ let activeLastToolCall = -1;
39078
+ for (const [id, session] of swarmState.agentSessions) {
39079
+ if (session.fullAutoMode !== true)
39080
+ continue;
39081
+ const lastToolCall = session.lastToolCallTime ?? 0;
39082
+ if (activeId === undefined || lastToolCall > activeLastToolCall) {
39083
+ activeId = id;
39084
+ activeLastToolCall = lastToolCall;
39085
+ }
39086
+ }
39087
+ return activeId;
39088
+ }
39056
39089
  function hasActiveLeanTurbo(sessionID) {
39057
39090
  if (sessionID) {
39058
39091
  const session = swarmState.agentSessions.get(sessionID);
@@ -1,7 +1,7 @@
1
1
  // @bun
2
2
  import {
3
3
  handleGuardrailExplain
4
- } from "./index-10v7hrkj.js";
4
+ } from "./index-2aszzcyk.js";
5
5
  import {
6
6
  handleGuardrailLog
7
7
  } from "./index-xbe1wtme.js";
@@ -81,7 +81,7 @@ import {
81
81
  handleWriteRetroCommand,
82
82
  normalizeSwarmCommandInput,
83
83
  resolveCommand
84
- } from "./index-ct9e0y0q.js";
84
+ } from "./index-8phvk4v4.js";
85
85
  import"./index-g6va6n3t.js";
86
86
  import"./index-c8s9a3zh.js";
87
87
  import"./index-2148358e.js";
package/dist/cli/index.js CHANGED
@@ -7,7 +7,7 @@ import {
7
7
  getPluginLockFilePaths,
8
8
  package_default,
9
9
  resolveCommand
10
- } from "./index-ct9e0y0q.js";
10
+ } from "./index-8phvk4v4.js";
11
11
  import"./index-g6va6n3t.js";
12
12
  import"./index-c8s9a3zh.js";
13
13
  import"./index-2148358e.js";
@@ -6,7 +6,6 @@
6
6
  * Deliberation → Moderator Pass → Output).
7
7
  *
8
8
  * Flag parsing:
9
- * --preset <name> → emits "[MODE: COUNCIL preset=<name>] <question>"
10
9
  * --spec-review → emits "[MODE: COUNCIL spec_review] <question>"
11
10
  * default → emits "[MODE: COUNCIL] <question>"
12
11
  * no args → returns usage string (no throw)
@@ -490,8 +490,8 @@ export declare const COMMAND_REGISTRY: {
490
490
  };
491
491
  readonly council: {
492
492
  readonly handler: (ctx: CommandContext) => CommandResult;
493
- readonly description: "Enter architect MODE: COUNCIL — multi-model deliberation [question] [--preset <name>] [--spec-review]";
494
- readonly args: "<question> [--preset <name>] [--spec-review]";
493
+ readonly description: "Enter architect MODE: COUNCIL — multi-model deliberation [question] [--spec-review]";
494
+ readonly args: "<question> [--spec-review]";
495
495
  readonly details: string;
496
496
  readonly category: "agent";
497
497
  readonly toolPolicy: "none";
@@ -627,7 +627,7 @@ export declare const COMMAND_REGISTRY: {
627
627
  readonly handler: (ctx: CommandContext) => CommandResult;
628
628
  readonly description: "Ingest a GitHub issue into the swarm workflow [url] [--plan] [--trace] [--no-repro]";
629
629
  readonly args: "<issue-url|owner/repo#N|N> [--plan] [--trace] [--no-repro]";
630
- readonly details: "Triggers the architect to enter MODE: ISSUE_INGEST — ingests a GitHub issue, restructures it into a normalized intake note, localizes root cause through hypothesis-driven tracing, and outputs a resolution spec. --plan transitions to plan creation after spec generation. --trace runs the full fix-and-PR workflow (implies --plan). --no-repro skips the reproduction step. Supports full GitHub URL, owner/repo#N shorthand, or bare issue number (resolves against origin remote).";
630
+ readonly details: "Triggers the architect to enter MODE: ISSUE_INGEST — ingests a GitHub issue, restructures it into a normalized intake note, localizes root cause through hypothesis-driven tracing, and outputs a resolution spec. --plan transitions to plan creation after spec generation. --trace runs the fix workflow end-to-end (implies --plan); compose commit-pr to publish. --no-repro skips the reproduction step. Supports full GitHub URL, owner/repo#N shorthand, or bare issue number (resolves against origin remote).";
631
631
  readonly category: "agent";
632
632
  readonly toolPolicy: "none";
633
633
  };
@@ -14,10 +14,20 @@
14
14
  * Architect MODE skills whose `.opencode` and `.claude` mirrors must be
15
15
  * byte-identical. `.opencode` is the operative protocol loaded by
16
16
  * `src/agents/architect.ts` MODE stubs; the `.claude` mirror must match so
17
- * Claude Code and OpenCode sessions cannot diverge silently. Tuple shape is
18
- * `[slug, opencodePath, claudePath]`.
17
+ * Claude Code and OpenCode sessions cannot diverge silently.
18
+ *
19
+ * `canonical` records which side wins when they drift (fix direction only;
20
+ * detection is symmetric). For architect MODE skills `.opencode` is always
21
+ * canonical because it is the operative protocol the MODE stubs load. Issue
22
+ * #1781 E3 added this field so `drift:fix` can copy the canonical side to the
23
+ * mirror without guessing.
19
24
  */
20
- export declare const MIRRORED_ARCHITECT_MODE_SKILLS: readonly [readonly ["brainstorm", ".opencode/skills/brainstorm/SKILL.md", ".claude/skills/brainstorm/SKILL.md"], readonly ["specify", ".opencode/skills/specify/SKILL.md", ".claude/skills/specify/SKILL.md"], readonly ["clarify-spec", ".opencode/skills/clarify-spec/SKILL.md", ".claude/skills/clarify-spec/SKILL.md"], readonly ["resume", ".opencode/skills/resume/SKILL.md", ".claude/skills/resume/SKILL.md"], readonly ["clarify", ".opencode/skills/clarify/SKILL.md", ".claude/skills/clarify/SKILL.md"], readonly ["discover", ".opencode/skills/discover/SKILL.md", ".claude/skills/discover/SKILL.md"], readonly ["consult", ".opencode/skills/consult/SKILL.md", ".claude/skills/consult/SKILL.md"], readonly ["pre-phase-briefing", ".opencode/skills/pre-phase-briefing/SKILL.md", ".claude/skills/pre-phase-briefing/SKILL.md"], readonly ["council", ".opencode/skills/council/SKILL.md", ".claude/skills/council/SKILL.md"], readonly ["deep-dive", ".opencode/skills/deep-dive/SKILL.md", ".claude/skills/deep-dive/SKILL.md"], readonly ["deep-research", ".opencode/skills/deep-research/SKILL.md", ".claude/skills/deep-research/SKILL.md"], readonly ["issue-ingest", ".opencode/skills/issue-ingest/SKILL.md", ".claude/skills/issue-ingest/SKILL.md"], readonly ["plan", ".opencode/skills/plan/SKILL.md", ".claude/skills/plan/SKILL.md"], readonly ["critic-gate", ".opencode/skills/critic-gate/SKILL.md", ".claude/skills/critic-gate/SKILL.md"], readonly ["design-docs", ".opencode/skills/design-docs/SKILL.md", ".claude/skills/design-docs/SKILL.md"]];
25
+ export declare const MIRRORED_ARCHITECT_MODE_SKILLS: Array<{
26
+ slug: string;
27
+ opencodePath: string;
28
+ claudePath: string;
29
+ canonical: '.opencode' | '.claude';
30
+ }>;
21
31
  /**
22
32
  * Architect MODE skills where `.opencode` is the full operative protocol and
23
33
  * `.claude` is an intentionally different surface. Both must exist; byte
@@ -37,6 +37,19 @@ export interface FullAutoRunState {
37
37
  terminateReason?: string;
38
38
  /** Consecutive oversight dispatch failures for auto-degrade. */
39
39
  consecutiveOversightFailures?: number;
40
+ /**
41
+ * Issue #1781 E2: the most recent oversight-escalation detail, persisted so
42
+ * `/swarm status` can surface the reason + interaction/deadlock counts +
43
+ * phase without parsing the human-readable `.swarm/escalation-report.md`.
44
+ * Updated atomically by `recordFullAutoEscalation` at escalation time.
45
+ */
46
+ lastEscalation?: {
47
+ reason: string;
48
+ interactionCount: number;
49
+ deadlockCount: number;
50
+ phase?: number;
51
+ escalatedAt: string;
52
+ };
40
53
  }
41
54
  export interface FullAutoPersistedState {
42
55
  version: 2;
@@ -129,6 +142,19 @@ export declare function resetFullAutoDenials(directory: string, sessionID: strin
129
142
  */
130
143
  export declare function nextFullAutoOversightSequence(directory: string): number;
131
144
  export declare function recordFullAutoOversight(directory: string, sessionID: string, verdict: string, reason: string): FullAutoRunState | undefined;
145
+ /**
146
+ * Issue #1781 E2: persist the most recent oversight-escalation detail
147
+ * (reason, interaction/deadlock counts, phase) to the durable run state so
148
+ * `/swarm status` can surface it. Called from `handleEscalation` in
149
+ * `src/hooks/full-auto-intercept.ts` alongside the existing pause/terminate
150
+ * state writes — those writes spread `...state`, so this field survives them.
151
+ */
152
+ export declare function recordFullAutoEscalation(directory: string, sessionID: string, detail: {
153
+ reason: string;
154
+ interactionCount: number;
155
+ deadlockCount: number;
156
+ phase?: number;
157
+ }): FullAutoRunState | undefined;
132
158
  export interface DenialLimitDecision {
133
159
  pause: boolean;
134
160
  reason?: string;