opencode-swarm 7.28.2 → 7.29.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +42 -442
- package/dist/cli/index.js +55 -13
- package/dist/hooks/knowledge-application.d.ts +12 -0
- package/dist/hooks/skill-propagation-gate.d.ts +16 -4
- package/dist/index.js +351 -117
- package/dist/tools/test-runner.d.ts +0 -1
- package/dist/utils/bun-compat.d.ts +10 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -538,469 +538,69 @@ All tools run locally. No Docker, no network calls.
|
|
|
538
538
|
|
|
539
539
|
### Context Budget Guard
|
|
540
540
|
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
- **Warning threshold (70%)** — Advisory when context reaches ~2800 tokens
|
|
544
|
-
- **Critical threshold (90%)** — Alert at ~3600 tokens with `/swarm handoff` recommendation
|
|
545
|
-
- **Non-nagging** — One-time alerts per session
|
|
546
|
-
|
|
547
|
-
Disable entirely with `context_budget.enabled: false`.
|
|
548
|
-
|
|
549
|
-
### File Locking for Concurrent Safety
|
|
550
|
-
|
|
551
|
-
Hard lock on `plan.json` (serialized writes), advisory lock on `events.jsonl` (append-only log). Stale locks auto-expire via `proper-lockfile`.
|
|
552
|
-
|
|
553
|
-
### Agent Categories
|
|
541
|
+
The Context Budget Guard monitors how much context Swarm is injecting into the conversation. It helps prevent context overflow before it becomes a problem.
|
|
554
542
|
|
|
555
|
-
|
|
543
|
+
### Default Behavior
|
|
556
544
|
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
545
|
+
- **Enabled automatically** — No setup required. Swarm starts tracking context usage right away.
|
|
546
|
+
- **What it measures** — Only the context that Swarm injects (plan, context, evidence, retrospectives). It does **not** count your chat history or the model's responses.
|
|
547
|
+
- **Warning threshold (0.7 ratio)** — When swarm-injected context reaches ~2800 tokens (70% of 4000), the architect receives a one-time advisory warning. This is informational — execution continues normally.
|
|
548
|
+
- **Critical threshold (0.9 ratio)** — When context reaches ~3600 tokens (90% of 4000), the architect receives a critical alert with a recommendation to run `/swarm handoff`. This is also one-time only.
|
|
549
|
+
- **Non-nagging** — Alerts fire once per session, not repeatedly. You won't be pestered every turn.
|
|
550
|
+
- **Who sees warnings** — Only the architect receives these warnings. Other agents are unaware of the budget.
|
|
563
551
|
|
|
564
|
-
|
|
552
|
+
To disable entirely, set `context_budget.enabled: false` in your swarm config.
|
|
565
553
|
|
|
566
554
|
---
|
|
567
555
|
|
|
568
|
-
|
|
569
|
-
<summary><strong>Full Execution Pipeline (Technical Detail)</strong></summary>
|
|
570
|
-
|
|
571
|
-
### The Pipeline
|
|
572
|
-
|
|
573
|
-
Every task goes through this sequence. No exceptions, no overrides.
|
|
574
|
-
|
|
575
|
-
```
|
|
576
|
-
MODE: EXECUTE (per task)
|
|
577
|
-
│
|
|
578
|
-
├── 5a. @coder implements (ONE task only)
|
|
579
|
-
├── 5b. diff + imports (contract + dependency analysis + semantic diff context)
|
|
580
|
-
│ └── @system-enhancer injects AST-based semantic diff summary with blast radius
|
|
581
|
-
│ into @reviewer context (up to 10 files, conditional on declared scope)
|
|
582
|
-
├── 5c. syntax_check (parse validation)
|
|
583
|
-
├── 5d. placeholder_scan (catches TODOs, stubs, incomplete code)
|
|
584
|
-
├── 5e. lint fix → lint check
|
|
585
|
-
├── 5f. build_check (does it compile?)
|
|
586
|
-
├── 5g. pre_check_batch (4 parallel: lint, secretscan, SAST, quality budget)
|
|
587
|
-
├── 5h. @reviewer (correctness pass)
|
|
588
|
-
├── 5i. @reviewer (security pass, if security-sensitive files changed)
|
|
589
|
-
├── 5j. @test_engineer (verification tests + coverage ≥70%)
|
|
590
|
-
├── 5k. @test_engineer (adversarial tests)
|
|
591
|
-
├── 5l. architect regression sweep (scope:"graph" to find cross-task test regressions)
|
|
592
|
-
├── 5l-ter. test drift detection (conditional — fires when changes involve command behaviour,
|
|
593
|
-
│ parsing/routing logic, user-visible output, public contracts, assertion-heavy areas,
|
|
594
|
-
│ or helper lifecycle changes; validates tests still align with current behaviour)
|
|
595
|
-
├── 5m. ⛔ Pre-commit checklist (all 4 items required, no override)
|
|
596
|
-
└── 5n. Task marked complete, evidence written
|
|
597
|
-
```
|
|
598
|
-
|
|
599
|
-
If any step fails, the coder gets structured feedback and retries. After 5 failures on the same task, it escalates to you.
|
|
600
|
-
|
|
601
|
-
### Architect Workflow Modes
|
|
602
|
-
|
|
603
|
-
The architect moves through these modes automatically:
|
|
604
|
-
|
|
605
|
-
| Mode | What It Means |
|
|
606
|
-
|---|---|
|
|
607
|
-
| `RESUME` | Existing `.swarm/` state was found, so Swarm continues where it left off |
|
|
608
|
-
| `CLARIFY` | Swarm asks for missing information it cannot infer |
|
|
609
|
-
| `DISCOVER` | Explorer scans the codebase; co-change dark matter analysis runs automatically to detect hidden file couplings (v6.41) |
|
|
610
|
-
| `CONSULT` | SME agents provide domain guidance |
|
|
611
|
-
| `PLAN` | Architect writes or updates the phased plan (includes CODEBASE REALITY CHECK on brownfield projects) |
|
|
612
|
-
| `CRITIC-GATE` | Critic reviews the plan before execution |
|
|
613
|
-
| `EXECUTE` | Tasks are implemented one at a time through the QA pipeline |
|
|
614
|
-
| `PHASE-WRAP` | A phase closes out, including: explorer rescan, docs update, `context.md` update, `write_retro`, evidence check, `sbom_generate`, **`@critic_drift_verifier` delegation** (drift check — blocking gate), `write_drift_evidence` call with verdict, mandatory gate evidence verification (`completion-verify.json` + `drift-verifier.json` both required), then `phase_complete` |
|
|
615
|
-
|
|
616
|
-
> **CODEBASE REALITY CHECK (v6.29.2):** Before any planning, the Architect dispatches Explorer to verify the current state of every referenced item. Produces a CODEBASE REALITY REPORT with statuses: NOT STARTED, PARTIALLY DONE, ALREADY COMPLETE, or ASSUMPTION INCORRECT. This prevents planning against stale assumptions. Skipped for greenfield projects with no existing codebase references.
|
|
617
|
-
|
|
618
|
-
> **Phase Completion Gates (v6.33.4):** Before a phase can be marked complete, two mandatory gates are enforced: (1) completion-verify — deterministic check that plan task identifiers exist in source files, and (2) critic_drift_verifier evidence — verification that the drift verifier approved the implementation. Both gates are automatically bypassed when turbo mode is active.
|
|
619
|
-
|
|
620
|
-
### Important
|
|
621
|
-
|
|
622
|
-
A second or later run does **not** necessarily look like a first run.
|
|
623
|
-
|
|
624
|
-
If `.swarm/plan.md` already exists, the architect may enter `RESUME` and then go directly into `EXECUTE`. That is expected and does **not** mean Swarm stopped using agents.
|
|
625
|
-
|
|
626
|
-
Use `/swarm status` if you are unsure what Swarm is doing.
|
|
627
|
-
|
|
628
|
-
Release automation uses release-please and requires conventional commit prefixes such as `fix:` or `feat:` on changes merged to `main`.
|
|
629
|
-
|
|
630
|
-
</details>
|
|
631
|
-
|
|
632
|
-
<details>
|
|
633
|
-
<summary><strong>Persistent Memory (What's in .swarm/)</strong></summary>
|
|
634
|
-
|
|
635
|
-
### plan.md: Your Project Roadmap
|
|
636
|
-
|
|
637
|
-
```markdown
|
|
638
|
-
# Project: Auth System
|
|
639
|
-
Current Phase: 2
|
|
640
|
-
|
|
641
|
-
## Phase 1: Foundation [COMPLETE]
|
|
642
|
-
- [x] Task 1.1: Create user model [SMALL]
|
|
643
|
-
- [x] Task 1.2: Add password hashing [SMALL]
|
|
644
|
-
- [x] Task 1.3: Database migrations [MEDIUM]
|
|
645
|
-
|
|
646
|
-
## Phase 2: Core Auth [IN PROGRESS]
|
|
647
|
-
- [x] Task 2.1: Login endpoint [MEDIUM]
|
|
648
|
-
- [ ] Task 2.2: JWT generation [MEDIUM] (depends: 2.1) ← CURRENT
|
|
649
|
-
- Acceptance: Returns valid JWT with user claims, 15-minute expiry
|
|
650
|
-
- Attempt 1: REJECTED — missing expiration claim
|
|
651
|
-
- [ ] Task 2.3: Token validation middleware [MEDIUM]
|
|
652
|
-
```
|
|
653
|
-
|
|
654
|
-
### context.md: What's Been Decided
|
|
655
|
-
|
|
656
|
-
```markdown
|
|
657
|
-
## Technical Decisions
|
|
658
|
-
- bcrypt cost factor: 12
|
|
659
|
-
- JWT TTL: 15 minutes; refresh TTL: 7 days
|
|
660
|
-
|
|
661
|
-
## SME Guidance (cached, never re-asked)
|
|
662
|
-
### security (Phase 1)
|
|
663
|
-
- Never log tokens or passwords
|
|
664
|
-
- Rate-limit login: 5 attempts / 15 min per IP
|
|
665
|
-
|
|
666
|
-
### api (Phase 1)
|
|
667
|
-
- Return 401 for invalid credentials (not 404)
|
|
668
|
-
```
|
|
669
|
-
|
|
670
|
-
### Evidence Bundles
|
|
671
|
-
|
|
672
|
-
Every completed task writes structured evidence to `.swarm/evidence/`:
|
|
673
|
-
|
|
674
|
-
| Type | What It Captures |
|
|
675
|
-
|------|--------------------|
|
|
676
|
-
| review | Verdict, risk level, specific issues |
|
|
677
|
-
| test | Pass/fail counts, coverage %, failure messages |
|
|
678
|
-
| diff | Files changed, additions/deletions |
|
|
679
|
-
| retrospective | Phase metrics, lessons learned, error taxonomy classification (injected into next phase) |
|
|
680
|
-
| secretscan | Secret scan results: findings count, files scanned, skipped files (v6.33) |
|
|
681
|
-
| completion-verify | Deterministic gate: verifies plan task identifiers exist in source files (written automatically by `completion-verify` tool; required before `phase_complete`) |
|
|
682
|
-
| drift-verifier | Phase-close drift gate: `critic_drift_verifier` verdict (APPROVED/NEEDS_REVISION) and summary (written by architect via `write_drift_evidence`; required before `phase_complete`) |
|
|
683
|
-
|
|
684
|
-
### telemetry.jsonl: Session Observability
|
|
685
|
-
|
|
686
|
-
Swarm emits structured JSONL events to `.swarm/telemetry.jsonl` for observability tooling (dashboards, alerting, audit logs). Events are fire-and-forget — failures never affect execution.
|
|
687
|
-
|
|
688
|
-
```json
|
|
689
|
-
{"timestamp":"2026-03-25T14:30:00.000Z","event":"session_started","sessionId":"abc123","agentName":"architect"}
|
|
690
|
-
{"timestamp":"2026-03-25T14:30:05.000Z","event":"delegation_begin","sessionId":"abc123","agentName":"coder","taskId":"1.1"}
|
|
691
|
-
{"timestamp":"2026-03-25T14:31:00.000Z","event":"delegation_end","sessionId":"abc123","agentName":"coder","taskId":"1.1","result":"success"}
|
|
692
|
-
{"timestamp":"2026-03-25T14:31:10.000Z","event":"gate_passed","sessionId":"abc123","gate":"reviewer","taskId":"1.1"}
|
|
693
|
-
{"timestamp":"2026-03-25T14:32:00.000Z","event":"phase_changed","sessionId":"abc123","oldPhase":1,"newPhase":2}
|
|
694
|
-
```
|
|
695
|
-
|
|
696
|
-
| Event | When Emitted |
|
|
697
|
-
|-------|-------------|
|
|
698
|
-
| `session_started` | New agent session created |
|
|
699
|
-
| `session_ended` | Session ends (reason: normal, timeout, error) |
|
|
700
|
-
| `agent_activated` | Agent identity confirmed via chat.message |
|
|
701
|
-
| `delegation_begin` | Task dispatched to a sub-agent |
|
|
702
|
-
| `delegation_end` | Sub-agent returns (success, rejected, error) |
|
|
703
|
-
| `task_state_changed` | Task workflow state transitions |
|
|
704
|
-
| `gate_passed` | Evidence written to `.swarm/evidence/{taskId}.json` |
|
|
705
|
-
| `gate_failed` | Gate check blocked task completion |
|
|
706
|
-
| `phase_changed` | Phase completed and new phase started |
|
|
707
|
-
| `budget_updated` | Context budget crossed warning/critical threshold |
|
|
708
|
-
| `hard_limit_hit` | Tool call/duration/repetition limit reached |
|
|
709
|
-
| `revision_limit_hit` | Coder revision limit exceeded |
|
|
710
|
-
| `loop_detected` | Repetitive tool call pattern detected |
|
|
711
|
-
| `scope_violation` | Architect wrote outside declared scope |
|
|
712
|
-
| `qa_skip_violation` | QA gate skipped without valid reason |
|
|
713
|
-
| `model_fallback` | Transient error triggered model fallback |
|
|
714
|
-
| `heartbeat` | 30-second throttled keep-alive signal |
|
|
715
|
-
|
|
716
|
-
File rotates automatically at 10MB to `.swarm/telemetry.jsonl.1`.
|
|
717
|
-
|
|
718
|
-
</details>
|
|
719
|
-
|
|
720
|
-
<details>
|
|
721
|
-
<summary><strong>Working Directory Requirement: No process.cwd() Fallback</strong></summary>
|
|
722
|
-
|
|
723
|
-
All Swarm tools that accept a `working_directory` parameter **require an explicit path**. They do **not** fall back to `process.cwd()`. This prevents `.swarm` state from being created in project subdirectories when the host process's working directory differs from the actual project root (issue [#922](https://github.com/zaxbysauce/opencode-swarm/issues/922)).
|
|
724
|
-
|
|
725
|
-
### Defense-in-Depth
|
|
726
|
-
|
|
727
|
-
This safety guarantee is implemented in two layers:
|
|
728
|
-
|
|
729
|
-
1. **Fast-path filter** (`resolveWorkingDirectory` in `src/tools/resolve-working-directory.ts`) — validates all incoming `working_directory` values for null-byte injection, path traversal, Windows device paths, and subdirectory containment before any file system access
|
|
730
|
-
2. **Canonical write-time guard** (`validateProjectRoot` in `src/evidence/manager.ts`) — uses `realpathSync` to canonicalize paths at evidence-write time, catching any symlink-based subdirectory bypasses that slip past the fast-path filter
|
|
731
|
-
|
|
732
|
-
### Tools That Require Explicit working_directory
|
|
733
|
-
|
|
734
|
-
The following tools require an explicit `working_directory` and reject subdirectory paths:
|
|
735
|
-
|
|
736
|
-
- `save_plan`
|
|
737
|
-
- `update_task_status`
|
|
738
|
-
- `declare_scope`
|
|
739
|
-
- `pre_check_batch`
|
|
740
|
-
- `test_impact`
|
|
741
|
-
- `mutation_test`
|
|
742
|
-
- `diff_summary`
|
|
743
|
-
|
|
744
|
-
### Failure Conditions
|
|
745
|
-
|
|
746
|
-
| Condition | Behavior |
|
|
747
|
-
|-----------|----------|
|
|
748
|
-
| Missing (`undefined` / `null`) | Fails with: "Target workspace is required" |
|
|
749
|
-
| Empty or whitespace-only | Fails with: "Target workspace cannot be empty or whitespace" |
|
|
750
|
-
| Path traversal (`..`) | Fails with: "Target workspace cannot contain path traversal" |
|
|
751
|
-
| Subdirectory of project root | Fails with: "...is a subdirectory of fallback..." |
|
|
752
|
-
| Windows device path | Fails with: "Windows device paths are not allowed" |
|
|
753
|
-
|
|
754
|
-
### Usage Contract
|
|
755
|
-
|
|
756
|
-
When using any affected tool, always pass a valid `working_directory`:
|
|
757
|
-
|
|
758
|
-
```typescript
|
|
759
|
-
// save_plan example
|
|
760
|
-
save_plan({
|
|
761
|
-
title: "My Project",
|
|
762
|
-
swarm_id: "mega",
|
|
763
|
-
phases: [{ id: 1, name: "Setup", tasks: [{ id: "1.1", description: "Initialize project" }] }],
|
|
764
|
-
working_directory: "/path/to/project" // Required - no process.cwd() fallback
|
|
765
|
-
})
|
|
766
|
-
|
|
767
|
-
// update_task_status example
|
|
768
|
-
update_task_status({
|
|
769
|
-
task_id: "1.1",
|
|
770
|
-
status: "completed",
|
|
771
|
-
working_directory: "/path/to/project" // Required - no fallback
|
|
772
|
-
})
|
|
773
|
-
```
|
|
774
|
-
|
|
775
|
-
### Stray .swarm Detection
|
|
776
|
-
|
|
777
|
-
`/swarm doctor` now detects and reports stray `.swarm` directories found in project subdirectories (created by older versions or misconfigured tools). It offers cleanup guidance to prevent state pollution.
|
|
778
|
-
|
|
779
|
-
</details>
|
|
780
|
-
|
|
781
|
-
<details>
|
|
782
|
-
<summary><strong>Guardrails and Circuit Breakers</strong></summary>
|
|
783
|
-
|
|
784
|
-
Every agent runs inside a circuit breaker that kills runaway behavior before it burns your credits.
|
|
785
|
-
|
|
786
|
-
| Signal | Default Limit | What Happens |
|
|
787
|
-
|--------|:---:|-------------|
|
|
788
|
-
| Tool calls | 200 | Agent is stopped |
|
|
789
|
-
| Duration | 30 min | Agent is stopped |
|
|
790
|
-
| Same tool repeated | 10x | Agent is warned, then stopped |
|
|
791
|
-
| Consecutive errors | 5 | Agent is stopped |
|
|
792
|
-
|
|
793
|
-
Limits reset per task. A coder working on Task 2.3 is not penalized for tool calls made during Task 2.2.
|
|
794
|
-
|
|
795
|
-
#### Architect Self-Coding Block
|
|
796
|
-
|
|
797
|
-
If the architect writes files directly instead of delegating to the coder, a hard block fires:
|
|
798
|
-
|
|
799
|
-
| Write count | Behavior |
|
|
800
|
-
|:-----------:|----------|
|
|
801
|
-
| 1–2 | Warning injected into next architect message |
|
|
802
|
-
| ≥ 3 | `Error` thrown with `SELF_CODING_BLOCK` — identifies file paths written and count |
|
|
803
|
-
|
|
804
|
-
The counter resets only when a coder delegation is dispatched. This is a hard enforcement — not advisory.
|
|
805
|
-
|
|
806
|
-
Per-agent overrides:
|
|
807
|
-
|
|
808
|
-
```json
|
|
809
|
-
{
|
|
810
|
-
"guardrails": {
|
|
811
|
-
"profiles": {
|
|
812
|
-
"coder": { "max_tool_calls": 500, "max_duration_minutes": 60 },
|
|
813
|
-
"explorer": { "max_tool_calls": 50 }
|
|
814
|
-
}
|
|
815
|
-
}
|
|
816
|
-
}
|
|
817
|
-
```
|
|
818
|
-
|
|
819
|
-
</details>
|
|
820
|
-
|
|
821
|
-
<details>
|
|
822
|
-
<summary><strong>File Authority (Per-Agent Write Permissions)</strong></summary>
|
|
823
|
-
|
|
824
|
-
Swarm enforces per-agent file write authority — each agent can only write to specific paths. By default, these rules are hardcoded, but you can override them via config.
|
|
825
|
-
|
|
826
|
-
### Default Rules
|
|
827
|
-
|
|
828
|
-
| Agent | Can Write | Blocked | Zones |
|
|
829
|
-
|-------|-----------|---------|-------|
|
|
830
|
-
| `architect` | Everything (except plan files) | `.swarm/plan.md`, `.swarm/plan.json` | `generated` |
|
|
831
|
-
| `coder` | `src/`, `tests/`, `docs/`, `scripts/` | `.swarm/` (entire directory) | `generated`, `config` |
|
|
832
|
-
| `reviewer` | `.swarm/evidence/`, `.swarm/outputs/` | `src/`, `.swarm/plan.md`, `.swarm/plan.json` | `generated` |
|
|
833
|
-
| `test_engineer` | `tests/`, `.swarm/evidence/` | `src/`, `.swarm/plan.md`, `.swarm/plan.json` | `generated` |
|
|
834
|
-
| `explorer` | Read-only | Everything | — |
|
|
835
|
-
| `sme` | Read-only | Everything | — |
|
|
836
|
-
| `docs` | `docs/`, `.swarm/outputs/` | — | `generated` |
|
|
837
|
-
| `designer` | `docs/`, `.swarm/outputs/` | — | `generated` |
|
|
838
|
-
| `critic` | `.swarm/evidence/` | — | `generated` |
|
|
839
|
-
|
|
840
|
-
### Prefixed Agents
|
|
841
|
-
|
|
842
|
-
Prefixed agents (e.g., `paid_coder`, `mega_reviewer`, `local_architect`) inherit defaults from their canonical base agent via `stripKnownSwarmPrefix`. The lookup order is:
|
|
843
|
-
|
|
844
|
-
1. Exact match for the prefixed name (if explicitly defined in user config)
|
|
845
|
-
2. Fall back to the canonical agent's defaults (e.g., `paid_coder` → `coder`)
|
|
846
|
-
|
|
847
|
-
```json
|
|
848
|
-
{
|
|
849
|
-
"authority": {
|
|
850
|
-
"rules": {
|
|
851
|
-
"coder": { "allowedPrefix": ["src/", "lib/"] },
|
|
852
|
-
"paid_coder": { "allowedPrefix": ["vendor/", "plugins/"] }
|
|
853
|
-
}
|
|
854
|
-
}
|
|
855
|
-
}
|
|
856
|
-
```
|
|
857
|
-
|
|
858
|
-
In this example, `paid_coder` gets its own explicit rule, while other prefixed coders (e.g., `mega_coder`) fall back to `coder`.
|
|
556
|
+
### Skill Propagation
|
|
859
557
|
|
|
860
|
-
|
|
558
|
+
Swarm includes an intelligent skill propagation system that tracks, validates, and scores skill usage across agent delegations. When the architect delegates to subagents (coder, reviewer, test_engineer, etc.), the system:
|
|
861
559
|
|
|
862
|
-
|
|
560
|
+
- **Logs skill usage** to `.swarm/skill-usage.jsonl` with session-scoped entries (agent, task ID, skill paths, timestamp)
|
|
561
|
+
- **Scores skill relevance** based on frequency, compliance, recency, task ID diversity, and context matching
|
|
562
|
+
- **Provides recommendations** — when delegating without a `SKILLS:` field, the system suggests relevant skills with relevance scores
|
|
563
|
+
- **Enforces compliance** — optional enforcement mode can block delegations missing the `SKILLS:` field
|
|
564
|
+
- **Auto-populates skill index** — maintains a `## Available Skills` section in `.swarm/context.md` with usage counts and compliance rates
|
|
565
|
+
- **Supports explicit routing** — `.opencode/skill-routing.yaml` maps agent types to specific skill paths with optional keyword descriptions
|
|
863
566
|
|
|
864
|
-
|
|
865
|
-
-
|
|
866
|
-
-
|
|
867
|
-
-
|
|
567
|
+
**Guardrails:**
|
|
568
|
+
- Relevance scoring threshold: 0.5 (skills below this are not recommended)
|
|
569
|
+
- Maximum recommendations per delegation: 5
|
|
570
|
+
- Scoring budget safeguard: Skipped when session exceeds 500 skill-usage entries to prevent unbounded file reads
|
|
571
|
+
- Graceful degradation: Zero installed skills = zero friction — no warnings, no blocks, no auto-population
|
|
868
572
|
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
### Runtime Enforcement
|
|
872
|
-
|
|
873
|
-
Architect direct writes are enforced at runtime via `toolBefore` hook. This tracks writes to source code paths outside `.swarm/` and protects `.swarm/plan.md` and `.swarm/plan.json` from direct modification.
|
|
874
|
-
|
|
875
|
-
### Configuration
|
|
876
|
-
|
|
877
|
-
Override default rules in `.opencode/opencode-swarm.json`:
|
|
573
|
+
**Configuration:**
|
|
878
574
|
|
|
879
575
|
```json
|
|
880
576
|
{
|
|
881
|
-
"
|
|
577
|
+
"skill_propagation": {
|
|
882
578
|
"enabled": true,
|
|
883
|
-
"
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
"blockedZones": ["generated"]
|
|
888
|
-
},
|
|
889
|
-
"explorer": {
|
|
890
|
-
"readOnly": false,
|
|
891
|
-
"allowedPrefix": ["notes/", "scratch/"]
|
|
892
|
-
}
|
|
893
|
-
}
|
|
894
|
-
}
|
|
895
|
-
}
|
|
896
|
-
```
|
|
897
|
-
|
|
898
|
-
### Rule Fields
|
|
899
|
-
|
|
900
|
-
| Field | Type | Description |
|
|
901
|
-
|-------|------|-------------|
|
|
902
|
-
| `readOnly` | boolean | If `true`, agent cannot write anywhere |
|
|
903
|
-
| `blockedExact` | string[] | Exact file paths that are blocked |
|
|
904
|
-
| `allowedExact` | string[] | Exact file paths that are allowed (overrides prefix/glob restrictions) |
|
|
905
|
-
| `blockedPrefix` | string[] | Path prefixes that are blocked (e.g., `.swarm/`) |
|
|
906
|
-
| `allowedPrefix` | string[] | Only these path prefixes are allowed. Omit to remove restriction; set `[]` to deny all |
|
|
907
|
-
| `blockedGlobs` | string[] | Glob patterns that are blocked (uses picomatch: `**`, `*`, `?`) |
|
|
908
|
-
| `allowedGlobs` | string[] | Glob patterns that are allowed (uses picomatch: `**`, `*`, `?`) |
|
|
909
|
-
| `blockedZones` | string[] | File zones to block: `production`, `test`, `config`, `generated`, `docs`, `build` |
|
|
910
|
-
|
|
911
|
-
### Merge Behavior
|
|
912
|
-
|
|
913
|
-
- User rules **override** hardcoded defaults for the specified agent
|
|
914
|
-
- Scalar fields (`readOnly`) — user value replaces default
|
|
915
|
-
- Array fields (`blockedPrefix`, `allowedPrefix`, etc.) — user array **replaces** entirely (not merged)
|
|
916
|
-
- If a field is omitted in the user rule for a **known agent** (one with hardcoded defaults), the default value for that field is preserved
|
|
917
|
-
- If a field is omitted in the user rule for a **custom agent** (not in the defaults list), that field is `undefined` — there are no defaults to inherit
|
|
918
|
-
- `allowedPrefix: []` explicitly denies all writes; omitting `allowedPrefix` entirely means no allowlist restriction is applied (all paths are evaluated against blocklist rules only)
|
|
919
|
-
- Setting `enabled: false` ignores all custom rules and uses hardcoded defaults
|
|
920
|
-
|
|
921
|
-
### Custom Agents
|
|
922
|
-
|
|
923
|
-
Custom agents (not in the defaults list) start with no rules. Their write authority depends entirely on what you configure:
|
|
924
|
-
|
|
925
|
-
- **Not in config at all** — agent is denied with `Unknown agent` (no rule exists; this is not the same as "blocked from all writes")
|
|
926
|
-
- **In config without `allowedPrefix`** — no allowlist restriction applies; only any `blockedPrefix`, `blockedZones`, or `readOnly` rules you explicitly set will enforce limits
|
|
927
|
-
- **In config with `allowedPrefix: []`** — all writes are denied
|
|
928
|
-
|
|
929
|
-
To safely restrict a custom agent, always set `allowedPrefix` explicitly:
|
|
930
|
-
|
|
931
|
-
```json
|
|
932
|
-
{
|
|
933
|
-
"authority": {
|
|
934
|
-
"rules": {
|
|
935
|
-
"my_custom_agent": {
|
|
936
|
-
"allowedPrefix": ["plugins/", "extensions/"],
|
|
937
|
-
"blockedZones": ["generated"]
|
|
938
|
-
}
|
|
579
|
+
"enforce": false,
|
|
580
|
+
"scoring": {
|
|
581
|
+
"threshold": 0.5,
|
|
582
|
+
"max_recommendations": 5
|
|
939
583
|
}
|
|
940
584
|
}
|
|
941
585
|
}
|
|
942
586
|
```
|
|
943
587
|
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
"allowedExact": ["src/index.ts", "package.json"]
|
|
958
|
-
},
|
|
959
|
-
"docs_agent": {
|
|
960
|
-
"allowedGlobs": ["docs/**/*.md", "*.md"],
|
|
961
|
-
"blockedExact": [".swarm/plan.md"]
|
|
962
|
-
}
|
|
963
|
-
}
|
|
964
|
-
}
|
|
965
|
-
}
|
|
588
|
+
**Skill routing file format** (`.opencode/skill-routing.yaml`):
|
|
589
|
+
|
|
590
|
+
```yaml
|
|
591
|
+
version: 1
|
|
592
|
+
routing:
|
|
593
|
+
coder:
|
|
594
|
+
- path: .claude/skills/writing-tests/SKILL.md
|
|
595
|
+
keywords: ["test", "testing", "writing tests"]
|
|
596
|
+
- path: .claude/skills/engineering-conventions/SKILL.md
|
|
597
|
+
keywords: ["engineering", "conventions", "invariants"]
|
|
598
|
+
reviewer:
|
|
599
|
+
- path: .claude/skills/swarm-pr-review/SKILL.md
|
|
600
|
+
keywords: ["review", "security", "audit"]
|
|
966
601
|
```
|
|
967
602
|
|
|
968
|
-
|
|
969
|
-
- `**` — Match any number of directories: `src/**/*.ts` matches all TypeScript files in src/ and subdirectories
|
|
970
|
-
- `*` — Match any characters except path separators: `*.md` matches all Markdown files in current directory
|
|
971
|
-
- `?` — Match single character: `test?.js` matches `test1.js`, `testa.js`
|
|
972
|
-
- Uses [picomatch](https://github.com/micromatch/picomatch) for cross-platform compatibility
|
|
973
|
-
|
|
974
|
-
**Path Normalization and Symlinks:**
|
|
975
|
-
Paths are resolved via `realpathSync` before matching, which resolves symlinks and prevents path-traversal escapes. However, if a symlink's target does not exist, `realpathSync` throws and the fallback returns the symlink's own path (unresolved). A dangling symlink inside an `allowedPrefix` directory will therefore pass prefix-based checks even if its intended target is outside the project. Use `blockedExact` or `blockedGlobs` to deny known dangling-symlink paths explicitly.
|
|
976
|
-
|
|
977
|
-
**Evaluation Order:**
|
|
978
|
-
1. `readOnly` check (if true, deny all writes)
|
|
979
|
-
2. `blockedExact` (exact path matches, highest priority)
|
|
980
|
-
3. `blockedGlobs` (glob pattern matches)
|
|
981
|
-
4. `allowedExact` (exact path matches, overrides prefix/glob restrictions)
|
|
982
|
-
5. `allowedGlobs` (glob pattern matches)
|
|
983
|
-
6. `blockedPrefix` (prefix matches)
|
|
984
|
-
7. `allowedPrefix` (prefix matches)
|
|
985
|
-
8. `blockedZones` (zone classification)
|
|
986
|
-
|
|
987
|
-
</details>
|
|
988
|
-
|
|
989
|
-
<details>
|
|
990
|
-
<summary><strong>Context Budget Guard</strong></summary>
|
|
991
|
-
|
|
992
|
-
The Context Budget Guard monitors how much context Swarm is injecting into the conversation. It helps prevent context overflow before it becomes a problem.
|
|
993
|
-
|
|
994
|
-
### Default Behavior
|
|
995
|
-
|
|
996
|
-
- **Enabled automatically** — No setup required. Swarm starts tracking context usage right away.
|
|
997
|
-
- **What it measures** — Only the context that Swarm injects (plan, context, evidence, retrospectives). It does **not** count your chat history or the model's responses.
|
|
998
|
-
- **Warning threshold (0.7 ratio)** — When swarm-injected context reaches ~2800 tokens (70% of 4000), the architect receives a one-time advisory warning. This is informational — execution continues normally.
|
|
999
|
-
- **Critical threshold (0.9 ratio)** — When context reaches ~3600 tokens (90% of 4000), the architect receives a critical alert with a recommendation to run `/swarm handoff`. This is also one-time only.
|
|
1000
|
-
- **Non-nagging** — Alerts fire once per session, not repeatedly. You won't be pestered every turn.
|
|
1001
|
-
- **Who sees warnings** — Only the architect receives these warnings. Other agents are unaware of the budget.
|
|
1002
|
-
|
|
1003
|
-
To disable entirely, set `context_budget.enabled: false` in your swarm config.
|
|
603
|
+
Routing skills are merged with scored recommendations, with explicitly routed skills receiving a boosted score (0.9) to prioritize them.
|
|
1004
604
|
|
|
1005
605
|
### Configuration Reference
|
|
1006
606
|
|
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.
|
|
37
|
+
version: "7.29.0",
|
|
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",
|
|
@@ -308,6 +308,27 @@ function bunHash(input) {
|
|
|
308
308
|
}
|
|
309
309
|
return hash;
|
|
310
310
|
}
|
|
311
|
+
function killProcessTreeImpl(pid, signal, directKill, wasDetached) {
|
|
312
|
+
if (typeof pid !== "number" || pid <= 0) {
|
|
313
|
+
directKill();
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
316
|
+
if (process.platform === "win32") {
|
|
317
|
+
try {
|
|
318
|
+
nodeSpawnSync("taskkill", ["/PID", String(pid), "/T", "/F"]);
|
|
319
|
+
} catch {
|
|
320
|
+
directKill();
|
|
321
|
+
}
|
|
322
|
+
return;
|
|
323
|
+
}
|
|
324
|
+
if (wasDetached) {
|
|
325
|
+
try {
|
|
326
|
+
process.kill(-pid, signal ?? "SIGKILL");
|
|
327
|
+
return;
|
|
328
|
+
} catch {}
|
|
329
|
+
}
|
|
330
|
+
directKill();
|
|
331
|
+
}
|
|
311
332
|
function streamFromNode(pipe) {
|
|
312
333
|
const collected = new Promise((resolve) => {
|
|
313
334
|
if (!pipe) {
|
|
@@ -430,20 +451,34 @@ function bunSpawn(cmd, options) {
|
|
|
430
451
|
return proc2.exitCode;
|
|
431
452
|
},
|
|
432
453
|
kill(sig) {
|
|
433
|
-
|
|
454
|
+
if (options?.killProcessTree) {
|
|
455
|
+
killProcessTreeImpl(proc2.pid, sig, () => proc2.kill(sig), false);
|
|
456
|
+
} else {
|
|
457
|
+
proc2.kill(sig);
|
|
458
|
+
}
|
|
434
459
|
}
|
|
435
460
|
};
|
|
436
461
|
}
|
|
437
462
|
const [file, ...args] = cmd;
|
|
463
|
+
const detached = options?.killProcessTree === true;
|
|
438
464
|
const proc = nodeSpawn(file, args, {
|
|
439
465
|
cwd: options?.cwd,
|
|
440
466
|
env: options?.env,
|
|
467
|
+
detached,
|
|
468
|
+
windowsHide: true,
|
|
441
469
|
stdio: [
|
|
442
470
|
mapStdio(options?.stdin),
|
|
443
471
|
mapStdio(options?.stdout),
|
|
444
472
|
mapStdio(options?.stderr)
|
|
445
473
|
]
|
|
446
474
|
});
|
|
475
|
+
const killChild = (signal) => {
|
|
476
|
+
if (detached) {
|
|
477
|
+
killProcessTreeImpl(proc.pid, signal, () => proc.kill(signal), true);
|
|
478
|
+
} else {
|
|
479
|
+
proc.kill(signal);
|
|
480
|
+
}
|
|
481
|
+
};
|
|
447
482
|
let timeoutHandle;
|
|
448
483
|
const exited = new Promise((resolve) => {
|
|
449
484
|
proc.on("exit", (code) => resolve(code ?? 0));
|
|
@@ -451,7 +486,7 @@ function bunSpawn(cmd, options) {
|
|
|
451
486
|
if (options?.timeout && options.timeout > 0) {
|
|
452
487
|
timeoutHandle = setTimeout(() => {
|
|
453
488
|
try {
|
|
454
|
-
|
|
489
|
+
killChild("SIGKILL");
|
|
455
490
|
} catch {}
|
|
456
491
|
}, options.timeout);
|
|
457
492
|
if (typeof timeoutHandle.unref === "function") {
|
|
@@ -471,7 +506,7 @@ function bunSpawn(cmd, options) {
|
|
|
471
506
|
},
|
|
472
507
|
kill(signal) {
|
|
473
508
|
try {
|
|
474
|
-
|
|
509
|
+
killChild(signal);
|
|
475
510
|
} catch {}
|
|
476
511
|
}
|
|
477
512
|
};
|
|
@@ -46255,7 +46290,7 @@ function defaultBuildTestCommand(profile, framework, files, dir = ".", opts = {}
|
|
|
46255
46290
|
const coverage = opts.coverage ?? false;
|
|
46256
46291
|
switch (framework) {
|
|
46257
46292
|
case "bun": {
|
|
46258
|
-
const args = ["bun", "test"];
|
|
46293
|
+
const args = ["bun", "--smol", "test"];
|
|
46259
46294
|
if (coverage)
|
|
46260
46295
|
args.push("--coverage");
|
|
46261
46296
|
if (scope !== "all" && files.length > 0)
|
|
@@ -48688,7 +48723,7 @@ function getTargetedExecutionUnsupportedReason(framework) {
|
|
|
48688
48723
|
function buildTestCommand2(framework, scope, files, coverage, baseDir) {
|
|
48689
48724
|
switch (framework) {
|
|
48690
48725
|
case "bun": {
|
|
48691
|
-
const args = ["bun", "test"];
|
|
48726
|
+
const args = ["bun", "--smol", "test"];
|
|
48692
48727
|
if (coverage)
|
|
48693
48728
|
args.push("--coverage");
|
|
48694
48729
|
if (scope !== "all" && files.length > 0) {
|
|
@@ -49225,17 +49260,24 @@ async function runTests(framework, scope, files, coverage, timeout_ms, cwd) {
|
|
|
49225
49260
|
const proc = bunSpawn(command, {
|
|
49226
49261
|
stdout: "pipe",
|
|
49227
49262
|
stderr: "pipe",
|
|
49228
|
-
|
|
49263
|
+
stdin: "ignore",
|
|
49264
|
+
cwd,
|
|
49265
|
+
killProcessTree: true
|
|
49266
|
+
});
|
|
49267
|
+
let timeoutHandle;
|
|
49268
|
+
const timeoutPromise = new Promise((resolve14) => {
|
|
49269
|
+
timeoutHandle = setTimeout(() => {
|
|
49270
|
+
proc.kill();
|
|
49271
|
+
resolve14(-1);
|
|
49272
|
+
}, timeout_ms);
|
|
49229
49273
|
});
|
|
49230
|
-
const timeoutPromise = new Promise((resolve14) => setTimeout(() => {
|
|
49231
|
-
proc.kill();
|
|
49232
|
-
resolve14(-1);
|
|
49233
|
-
}, timeout_ms));
|
|
49234
49274
|
const [exitCode, stdoutResult, stderrResult] = await Promise.all([
|
|
49235
49275
|
Promise.race([proc.exited, timeoutPromise]),
|
|
49236
49276
|
readBoundedStream(proc.stdout, MAX_OUTPUT_BYTES3),
|
|
49237
49277
|
readBoundedStream(proc.stderr, MAX_OUTPUT_BYTES3)
|
|
49238
49278
|
]);
|
|
49279
|
+
if (timeoutHandle !== undefined)
|
|
49280
|
+
clearTimeout(timeoutHandle);
|
|
49239
49281
|
const duration_ms = Date.now() - startTime;
|
|
49240
49282
|
let output = stdoutResult.text;
|
|
49241
49283
|
if (stderrResult.text) {
|
|
@@ -49538,7 +49580,6 @@ var init_test_runner = __esm(() => {
|
|
|
49538
49580
|
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.'),
|
|
49539
49581
|
coverage: exports_external.boolean().optional().describe("Enable coverage reporting if supported"),
|
|
49540
49582
|
timeout_ms: exports_external.number().optional().describe("Timeout in milliseconds (default 60000, max 300000)"),
|
|
49541
|
-
allow_full_suite: exports_external.boolean().optional().describe('Explicit opt-in for scope "all". Required because full-suite output can destabilize SSE streaming.'),
|
|
49542
49583
|
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.")
|
|
49543
49584
|
},
|
|
49544
49585
|
async execute(args, directory) {
|
|
@@ -49612,7 +49653,8 @@ var init_test_runner = __esm(() => {
|
|
|
49612
49653
|
}
|
|
49613
49654
|
const scope = args.scope || "all";
|
|
49614
49655
|
if (scope === "all") {
|
|
49615
|
-
|
|
49656
|
+
const fullSuiteAllowed = process.env.SWARM_ALLOW_FULL_SUITE === "1" || process.env.SWARM_ALLOW_FULL_SUITE === "true";
|
|
49657
|
+
if (!fullSuiteAllowed) {
|
|
49616
49658
|
const errorResult = {
|
|
49617
49659
|
success: false,
|
|
49618
49660
|
framework: "none",
|