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.
- package/.opencode/skills/brainstorm/SKILL.md +10 -8
- package/.opencode/skills/ci-fix-monitor/SKILL.md +2 -2
- package/.opencode/skills/clarify/SKILL.md +2 -2
- package/.opencode/skills/clarify-spec/SKILL.md +4 -4
- package/.opencode/skills/commit-pr/SKILL.md +32 -20
- package/.opencode/skills/council/SKILL.md +1 -1
- package/.opencode/skills/deep-dive/SKILL.md +3 -3
- package/.opencode/skills/design-docs/SKILL.md +2 -1
- package/.opencode/skills/discover/SKILL.md +2 -2
- package/.opencode/skills/engineering-conventions/SKILL.md +54 -1
- package/.opencode/skills/gate-attribution/SKILL.md +11 -8
- package/.opencode/skills/issue-ingest/SKILL.md +18 -14
- package/.opencode/skills/loop/SKILL.md +3 -4
- package/.opencode/skills/merge-queue-readiness/SKILL.md +6 -13
- package/.opencode/skills/phase-wrap/SKILL.md +30 -28
- package/.opencode/skills/plan/SKILL.md +8 -7
- package/.opencode/skills/resume/SKILL.md +5 -5
- package/.opencode/skills/running-tests/SKILL.md +3 -4
- package/.opencode/skills/specify/SKILL.md +2 -1
- package/.opencode/skills/swarm/SKILL.md +8 -0
- package/.opencode/skills/swarm-ci-monitor/SKILL.md +4 -4
- package/.opencode/skills/swarm-pr-feedback/SKILL.md +55 -38
- package/.opencode/skills/swarm-pr-subscribe/SKILL.md +2 -0
- package/.opencode/skills/test-file-split/SKILL.md +3 -5
- package/.opencode/skills/writing-tests/SKILL.md +98 -28
- package/README.md +2 -2
- package/dist/cli/{curator-llm-factory-b3g0g0cd.js → curator-llm-factory-26pyy8cq.js} +1 -1
- package/dist/cli/{curator-nh9raf1a.js → curator-wwyj379g.js} +1 -1
- package/dist/cli/{guardrail-explain-wpv5eskq.js → guardrail-explain-qzg7rr7f.js} +2 -2
- package/dist/cli/{hive-promoter-c0fd3j02.js → hive-promoter-kfjbydfj.js} +1 -1
- package/dist/cli/{index-10v7hrkj.js → index-2aszzcyk.js} +1 -1
- package/dist/cli/{index-ct9e0y0q.js → index-8phvk4v4.js} +58 -25
- package/dist/cli/{index-5yzr9fk6.js → index-gge9vh5s.js} +2 -2
- package/dist/cli/index.js +1 -1
- package/dist/commands/council.d.ts +0 -1
- package/dist/commands/registry.d.ts +3 -3
- package/dist/config/skill-mirrors.d.ts +13 -3
- package/dist/full-auto/state.d.ts +26 -0
- package/dist/index.js +20 -16
- package/dist/services/status-service.d.ts +16 -1
- package/dist/state.d.ts +9 -0
- package/dist/tools/create-tool.d.ts +9 -0
- package/dist/tools/test-runner.d.ts +0 -1
- package/package.json +2 -3
- package/dist/output/agent-writer.d.ts +0 -27
- package/dist/output/index.d.ts +0 -1
- 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'`
|
|
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
|
|
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
|
|
529
|
-
Step
|
|
530
|
-
Step
|
|
531
|
-
Step
|
|
532
|
-
Step
|
|
533
|
-
Step
|
|
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
|
-
**
|
|
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:
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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> [--
|
|
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,8 +1,8 @@
|
|
|
1
1
|
// @bun
|
|
2
2
|
import {
|
|
3
3
|
handleGuardrailExplain
|
|
4
|
-
} from "./index-
|
|
5
|
-
import"./index-
|
|
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";
|
|
@@ -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-
|
|
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-
|
|
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> [--
|
|
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-
|
|
17590
|
-
import("./curator-llm-factory-
|
|
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.
|
|
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
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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] [--
|
|
33443
|
-
args: "<question> [--
|
|
33444
|
-
details: "Triggers the architect to convene a three-agent General Council: Generalist (reviewer model), Skeptic (critic model), and Domain Expert (SME model).
|
|
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
|
|
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
|
|
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-
|
|
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-
|
|
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
|
@@ -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] [--
|
|
494
|
-
readonly args: "<question> [--
|
|
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
|
|
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.
|
|
18
|
-
*
|
|
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:
|
|
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;
|