opencode-swarm 7.28.3 → 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 +1 -1
- package/dist/hooks/knowledge-application.d.ts +12 -0
- package/dist/hooks/skill-propagation-gate.d.ts +16 -4
- package/dist/index.js +297 -105
- 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",
|
|
@@ -90,6 +90,18 @@ export declare function gateKnowledgeApplication(args: {
|
|
|
90
90
|
recentArchitectText: string;
|
|
91
91
|
config: KnowledgeApplicationConfig;
|
|
92
92
|
}): GateResult;
|
|
93
|
+
/**
|
|
94
|
+
* Filter knowledge entries by minimum confidence threshold.
|
|
95
|
+
* Used by the knowledge reinjection hook to surface high-confidence
|
|
96
|
+
* knowledge after context compression events.
|
|
97
|
+
*
|
|
98
|
+
* @param entries - Array of knowledge entries (swarm or hive)
|
|
99
|
+
* @param threshold - Minimum confidence score (default 0.8)
|
|
100
|
+
* @returns Filtered entries with confidence >= threshold
|
|
101
|
+
*/
|
|
102
|
+
export declare function filterHighConfidenceKnowledge<T extends {
|
|
103
|
+
confidence: number;
|
|
104
|
+
}>(entries: T[], threshold?: number): T[];
|
|
93
105
|
export declare const _internals: {
|
|
94
106
|
parseAcknowledgments: typeof parseAcknowledgments;
|
|
95
107
|
recordKnowledgeShown: typeof recordKnowledgeShown;
|
|
@@ -20,6 +20,12 @@ import * as fs from 'node:fs';
|
|
|
20
20
|
import type { MessageWithParts } from './knowledge-types.js';
|
|
21
21
|
import { computeSkillRelevanceScore, formatSkillIndexWithContext } from './skill-scoring.js';
|
|
22
22
|
import { appendSkillUsageEntry, readSkillUsageEntries, readSkillUsageEntriesTail } from './skill-usage-log.js';
|
|
23
|
+
/**
|
|
24
|
+
* Load routing skills from .opencode/skill-routing.yaml for a target agent.
|
|
25
|
+
* Returns array of skill paths that are explicitly routed for the agent.
|
|
26
|
+
* Best-effort: returns empty array on any error or if file doesn't exist.
|
|
27
|
+
*/
|
|
28
|
+
export declare function loadRoutingSkills(directory: string, targetAgent: string): string[];
|
|
23
29
|
/** Agents that should receive skill context in delegations. */
|
|
24
30
|
export declare const SKILL_CAPABLE_AGENTS: Set<string>;
|
|
25
31
|
/**
|
|
@@ -58,11 +64,12 @@ export declare const _internals: {
|
|
|
58
64
|
appendSkillUsageEntry: typeof appendSkillUsageEntry;
|
|
59
65
|
readSkillUsageEntries: typeof readSkillUsageEntries;
|
|
60
66
|
readSkillUsageEntriesTail: typeof readSkillUsageEntriesTail;
|
|
61
|
-
extractSkillsFieldFromPrompt: typeof extractSkillsFieldFromPrompt;
|
|
62
67
|
parseSkillPaths: typeof parseSkillPaths;
|
|
63
68
|
extractTaskIdFromPrompt: typeof extractTaskIdFromPrompt;
|
|
69
|
+
extractSkillsFieldFromPrompt: typeof extractSkillsFieldFromPrompt;
|
|
64
70
|
computeSkillRelevanceScore: typeof computeSkillRelevanceScore;
|
|
65
71
|
formatSkillIndexWithContext: typeof formatSkillIndexWithContext;
|
|
72
|
+
loadRoutingSkills: typeof loadRoutingSkills;
|
|
66
73
|
};
|
|
67
74
|
/**
|
|
68
75
|
* Scans project for available skill SKILL.md files.
|
|
@@ -117,13 +124,18 @@ export declare function extractTaskIdFromPrompt(prompt: string): string;
|
|
|
117
124
|
* the architect delegates to a skill-capable agent with a non-empty, non-"none"
|
|
118
125
|
* SKILLS field.
|
|
119
126
|
*
|
|
120
|
-
* @returns { blocked:
|
|
121
|
-
*
|
|
122
|
-
*
|
|
127
|
+
* @returns { blocked: boolean; reason: string | null; recommendedSkills?: Array<{ skillPath: string; score: number; usageCount: number }> }
|
|
128
|
+
* When scoring has computed results, includes `recommendedSkills` with ranked skill recommendations.
|
|
129
|
+
* When scoring was skipped or errored, `recommendedSkills` is undefined.
|
|
123
130
|
*/
|
|
124
131
|
export declare function skillPropagationGateBefore(directory: string, input: SkillGateInput, config: SkillPropagationConfig): Promise<{
|
|
125
132
|
blocked: boolean;
|
|
126
133
|
reason: string | null;
|
|
134
|
+
recommendedSkills?: Array<{
|
|
135
|
+
skillPath: string;
|
|
136
|
+
score: number;
|
|
137
|
+
usageCount: number;
|
|
138
|
+
}>;
|
|
127
139
|
}>;
|
|
128
140
|
/**
|
|
129
141
|
* Chat messages transform hook. Scans reviewer output for SKILL_COMPLIANCE
|
package/dist/index.js
CHANGED
|
@@ -48,7 +48,7 @@ var package_default;
|
|
|
48
48
|
var init_package = __esm(() => {
|
|
49
49
|
package_default = {
|
|
50
50
|
name: "opencode-swarm",
|
|
51
|
-
version: "7.
|
|
51
|
+
version: "7.29.0",
|
|
52
52
|
description: "Architect-centric agentic swarm plugin for OpenCode - hub-and-spoke orchestration with SME consultation, code generation, and QA review",
|
|
53
53
|
main: "dist/index.js",
|
|
54
54
|
types: "dist/index.d.ts",
|
|
@@ -75944,58 +75944,31 @@ SECURITY_KEYWORDS: password, secret, token, credential, auth, login, encryption,
|
|
|
75944
75944
|
|
|
75945
75945
|
## SKILLS PROPAGATION
|
|
75946
75946
|
|
|
75947
|
-
Subagents run in isolated contexts.
|
|
75947
|
+
Subagents run in isolated contexts. Any project-specific skill constraints loaded into your session (e.g. \`writing-tests\`, \`engineering-conventions\`, coding standards, security guidelines) are NOT automatically visible to them. The hook system auto-injects relevant skills into delegation prompts.
|
|
75948
75948
|
|
|
75949
|
-
### Step 1 —
|
|
75949
|
+
### Step 1 — Skills are auto-discovered and scored
|
|
75950
75950
|
|
|
75951
|
-
|
|
75952
|
-
1. Read contract files first: \`AGENTS.md\`, \`CLAUDE.md\`, \`CONTRIBUTING.md\`, \`TESTING.md\` when present. Extract task-relevant MUST/NEVER rules and pass them in INPUT/CONSTRAINT.
|
|
75953
|
-
2. Reuse \`<skill-context>\` skills when present.
|
|
75954
|
-
3. Otherwise use \`search\` with \`include\` patterns like \`.opencode/skills/*/SKILL.md,.claude/skills/*/SKILL.md\` and frontmatter queries \`^name:\` / \`^description:\`.
|
|
75955
|
-
4. Cache a short \`.swarm/context.md\` \`## Available Skills\` index. Include the repo-relative \`file:\` path and description so subagents can load on demand:
|
|
75956
|
-
- writing-tests: Guidelines for writing tests (used: 12, compliance: 95%) → test_engineer, coder
|
|
75957
|
-
- engineering-conventions: Engineering invariants (used: 8, compliance: 100%) → coder, reviewer, test_engineer
|
|
75958
|
-
- [name]: [description] (used: N, compliance: N%) → [applicable agents]
|
|
75951
|
+
The hook system discovers available skills and scores them by relevance to the task. The hook auto-injects them into the delegation prompt.
|
|
75959
75952
|
|
|
75960
|
-
|
|
75953
|
+
### Step 2 — SKILLS: field is auto-populated
|
|
75961
75954
|
|
|
75962
|
-
|
|
75955
|
+
The hook auto-populates the \`SKILLS:\` field with top recommended skills (max 5, threshold 0.5). Explicit \`SKILLS: none\` is preserved.
|
|
75963
75956
|
|
|
75957
|
+
### Step 3 — Skill references with context descriptions
|
|
75964
75958
|
|
|
75965
|
-
|
|
75959
|
+
When passing skill references, you may add brief context descriptions. The hook injects \`file:path (-- description)\` format.
|
|
75966
75960
|
|
|
75967
|
-
|
|
75961
|
+
### Step 4 — Forward SKILLS_USED_BY_CODER to reviewer
|
|
75968
75962
|
|
|
75969
|
-
|
|
75970
|
-
Route tests to test_engineer/coder; conventions to coder/reviewer/test_engineer; implementation to coder/reviewer; review/security to reviewer; docs to docs; architecture/ui/domain topics to designer or sme.
|
|
75963
|
+
When delegating to the reviewer after a coder task, include a \`SKILLS_USED_BY_CODER: [comma-separated list of skill paths from the coder delegation]\` field. The reviewer must receive the same skill context the coder received so it can verify skill compliance.
|
|
75971
75964
|
|
|
75972
|
-
|
|
75965
|
+
Example: If the coder received \`SKILLS: file:.claude/skills/writing-tests/SKILL.md\`, the reviewer delegation must include \`SKILLS_USED_BY_CODER: file:.claude/skills/writing-tests/SKILL.md\` in addition to the reviewer's own \`SKILLS:\` field.
|
|
75973
75966
|
|
|
75974
|
-
|
|
75967
|
+
**Skill-to-agent routing:** Managed via \`.opencode/skill-routing.yaml\`. The hook reads this file at delegation time.
|
|
75975
75968
|
|
|
75976
|
-
|
|
75977
|
-
|
|
75978
|
-
- \`SKILLS: none\` — only when no project-specific skill applies to that delegation
|
|
75979
|
-
- \`SKILLS: file:.claude/skills/writing-tests/SKILL.md\` — preferred for skills that exist on disk; use repo-relative \`file:\` references, comma-separated when multiple skills apply
|
|
75980
|
-
- Descriptive multi-line catalog:
|
|
75981
|
-
SKILLS:
|
|
75982
|
-
- file:.claude/skills/writing-tests/SKILL.md - Guidelines for writing tests
|
|
75983
|
-
- file:.claude/skills/engineering-conventions/SKILL.md - Engineering invariants for safe changes
|
|
75984
|
-
- Inline fallback:
|
|
75985
|
-
SKILLS:
|
|
75986
|
-
--- [skill-name] ---
|
|
75987
|
-
[full SKILL.md body content pasted here]
|
|
75988
|
-
|
|
75989
|
-
Default to repo-relative \`file:\` references. Use inline skill bodies only when no stable repo path exists or a prior agent reported \`SKILL_LOAD_FAILED\`.
|
|
75990
|
-
|
|
75991
|
-
**SKILL_LOAD_FAILED recovery:** do NOT retry with the same reference. Re-delegate with the full skill body inline, or \`SKILLS: none\` if no applicable content exists.
|
|
75992
|
-
|
|
75993
|
-
**Mandatory for coding tasks:** provide \`writing-tests\` to test_engineer and \`engineering-conventions\` to coder + reviewer when present.
|
|
75994
|
-
|
|
75995
|
-
### Step 4 — Forward skills to reviewer
|
|
75996
|
-
|
|
75997
|
-
When delegating to reviewer after coder, include \`SKILLS_USED_BY_CODER: [comma-separated skill paths from coder]\` plus reviewer \`SKILLS:\` so compliance can be checked.
|
|
75969
|
+
**SKILL_LOAD_FAILED recovery:** If a subagent reports SKILL_LOAD_FAILED for a \`file:\` reference, do NOT retry with the same reference. Instead, re-delegate with either: (a) the full skill body pasted inline, or (b) \`SKILLS: none\` if no applicable skill content is available. Never re-use a file: reference that has already failed.
|
|
75998
75970
|
|
|
75971
|
+
**Mandatory for coding tasks:** Always provide \`writing-tests\` to test_engineer and \`engineering-conventions\` to coder + reviewer when those skills are present in the project. Prefer \`file:\` references when the files exist.
|
|
75999
75972
|
|
|
76000
75973
|
## SWARM KNOWLEDGE DIRECTIVES (v2 acknowledgment contract)
|
|
76001
75974
|
|
|
@@ -76018,14 +75991,28 @@ You may also call the \`knowledge_ack\` tool to record an outcome explicitly whe
|
|
|
76018
75991
|
|
|
76019
75992
|
## SKILL IMPROVER (low-frequency, expensive-model adviser)
|
|
76020
75993
|
|
|
76021
|
-
|
|
75994
|
+
The \`skill_improver\` agent and the \`skill_improve\` tool exist for rare, deep
|
|
75995
|
+
review of accumulated knowledge / skills / spec / architect prompt. They are
|
|
75996
|
+
quota-bounded (default 10 calls/day) and disabled by default. Suggest running
|
|
75997
|
+
\`skill_improve\` only after one of:
|
|
75998
|
+
- repeated reviewer rejections in a row,
|
|
75999
|
+
- many \`KNOWLEDGE_IGNORED\` outcomes for the same cluster,
|
|
76000
|
+
- stale skills (no updates while their target area changed),
|
|
76001
|
+
- a fresh spec mismatch with shipped behaviour.
|
|
76022
76002
|
|
|
76023
76003
|
When \`skill_improver.require_user_approval\` is true (default), ASK the user
|
|
76024
76004
|
before running. Default outputs are proposals only — they never modify source.
|
|
76025
76005
|
|
|
76026
76006
|
## SPEC WRITER
|
|
76027
76007
|
|
|
76028
|
-
For substantial spec authoring
|
|
76008
|
+
For substantial spec authoring or revision, prefer delegating to the
|
|
76009
|
+
\`spec_writer\` agent (independent model from architect). It writes only via
|
|
76010
|
+
the safe \`spec_write\` tool. Use it when:
|
|
76011
|
+
- the user requests a new spec or major spec revision,
|
|
76012
|
+
- requirements decomposition is non-trivial,
|
|
76013
|
+
- you would otherwise inline-author \`.swarm/spec.md\` yourself.
|
|
76014
|
+
|
|
76015
|
+
Continue handling small touch-ups (typos, cross-references) inline.
|
|
76029
76016
|
|
|
76030
76017
|
### ANTI-RATIONALIZATION
|
|
76031
76018
|
- ✗ "The coder already knows these conventions" → Skills contain project-specific rules the model cannot know from training. Always pass.
|
|
@@ -76038,7 +76025,7 @@ For substantial spec authoring/revision, prefer \`spec_writer\`; it writes only
|
|
|
76038
76025
|
## SLASH COMMANDS
|
|
76039
76026
|
{{SLASH_COMMANDS}}
|
|
76040
76027
|
Commands above are documented with args and behavioral details. Run commands via /swarm <command> [args].
|
|
76041
|
-
Outside OpenCode,
|
|
76028
|
+
Outside OpenCode, invoke any plugin command via: \`bunx opencode-swarm run <command> [args]\` (e.g. \`bunx opencode-swarm run knowledge migrate\`). Do not use \`bun -e\` or look for \`src/commands/\` — those paths are internal to the plugin source and do not exist in user project directories. EXCEPTION — human-only commands (including but not limited to \`acknowledge-spec-drift\`, \`reset\`, \`reset-session\`, \`rollback\`, \`checkpoint\`, and any command that releases a runtime safety gate or destroys plan state): you MUST present these to the user and ask them to run the command themselves. Never invoke a human-only command via Bash, swarm_command, or chat fallback. The runtime guardrail will block such attempts; if a Bash call returns \`BLOCKED\` with a "human-only" message, do not retry under a different shell form — present the situation to the user instead.
|
|
76042
76029
|
|
|
76043
76030
|
SMEs advise only. Reviewer and critic review only. None of them write code.
|
|
76044
76031
|
|
|
@@ -76046,15 +76033,15 @@ Available Tools: {{AVAILABLE_TOOLS}}
|
|
|
76046
76033
|
|
|
76047
76034
|
## DELEGATION FORMAT
|
|
76048
76035
|
|
|
76049
|
-
Delegations
|
|
76036
|
+
Delegations are performed ONLY by calling the **Task** tool. Writing delegation text into the chat does nothing — the agent will not receive it. Every delegation below is the content you pass to the Task tool, not text you output to the conversation.
|
|
76050
76037
|
|
|
76051
|
-
follow the receiving agent's INPUT FORMAT exactly
|
|
76038
|
+
All delegations MUST follow the receiving agent's INPUT FORMAT exactly. Do NOT invent fields, omit required fields, or force one agent's schema onto another. Every delegation MUST begin with the agent name, include \`TASK:\`, and include \`SKILLS:\` when that agent prompt supports skills.
|
|
76052
76039
|
Do NOT add conversational preamble before the agent prefix. Begin directly with the agent name.
|
|
76053
76040
|
|
|
76054
76041
|
{{AGENT_PREFIX}}[agent]
|
|
76055
76042
|
TASK: [single objective]
|
|
76056
76043
|
[agent-specific fields required by that agent's INPUT FORMAT]
|
|
76057
|
-
SKILLS: [either "none", repo-relative file references
|
|
76044
|
+
SKILLS: [either "none", repo-relative file: references, or inline skill bodies — see SKILLS PROPAGATION; use "none" only when no project-specific skill applies]
|
|
76058
76045
|
|
|
76059
76046
|
Examples:
|
|
76060
76047
|
|
|
@@ -76087,22 +76074,22 @@ FILE: src/auth/login.ts
|
|
|
76087
76074
|
INPUT: Validate email format, password >= 8 chars
|
|
76088
76075
|
OUTPUT: Modified file
|
|
76089
76076
|
CONSTRAINT: Do not modify other functions
|
|
76090
|
-
SKILLS: file:.claude/skills/engineering-conventions/SKILL.md
|
|
76077
|
+
SKILLS: file:.claude/skills/engineering-conventions/SKILL.md
|
|
76091
76078
|
|
|
76092
76079
|
{{AGENT_PREFIX}}reviewer
|
|
76093
76080
|
TASK: Review login validation
|
|
76094
76081
|
FILE: src/auth/login.ts
|
|
76095
76082
|
CHECK: [security, correctness, edge-cases]
|
|
76096
76083
|
GATES: lint=PASS, sast_scan=PASS, secretscan=PASS
|
|
76097
|
-
SKILLS_USED_BY_CODER: file:.claude/skills/engineering-conventions/SKILL.md
|
|
76084
|
+
SKILLS_USED_BY_CODER: file:.claude/skills/engineering-conventions/SKILL.md
|
|
76098
76085
|
OUTPUT: VERDICT + RISK + ISSUES
|
|
76099
|
-
SKILLS: file:.claude/skills/engineering-conventions/SKILL.md
|
|
76086
|
+
SKILLS: file:.claude/skills/engineering-conventions/SKILL.md
|
|
76100
76087
|
|
|
76101
76088
|
{{AGENT_PREFIX}}test_engineer
|
|
76102
76089
|
TASK: Generate and run login validation tests
|
|
76103
76090
|
FILE: src/auth/login.ts
|
|
76104
76091
|
OUTPUT: Test file at src/auth/login.test.ts + VERDICT: PASS/FAIL with failure details
|
|
76105
|
-
SKILLS: file:.claude/skills/writing-tests/SKILL.md
|
|
76092
|
+
SKILLS: file:.claude/skills/writing-tests/SKILL.md
|
|
76106
76093
|
|
|
76107
76094
|
{{AGENT_PREFIX}}critic
|
|
76108
76095
|
TASK: Review plan for user authentication feature
|
|
@@ -76117,14 +76104,14 @@ FILE: src/auth/login.ts
|
|
|
76117
76104
|
CHECK: [security-only] — evaluate against OWASP Top 10, scan for hardcoded secrets, injection vectors, insecure crypto, missing input validation
|
|
76118
76105
|
GATES: lint=PASS, sast_scan=PASS, secretscan=PASS
|
|
76119
76106
|
OUTPUT: VERDICT + RISK + SECURITY ISSUES ONLY
|
|
76120
|
-
SKILLS: file:.claude/skills/engineering-conventions/SKILL.md
|
|
76107
|
+
SKILLS: file:.claude/skills/engineering-conventions/SKILL.md
|
|
76121
76108
|
|
|
76122
76109
|
{{AGENT_PREFIX}}test_engineer
|
|
76123
76110
|
TASK: Adversarial security testing
|
|
76124
76111
|
FILE: src/auth/login.ts
|
|
76125
76112
|
CONSTRAINT: ONLY attack vectors — malformed inputs, oversized payloads, injection attempts, auth bypass, boundary violations
|
|
76126
76113
|
OUTPUT: Test file + VERDICT: PASS/FAIL
|
|
76127
|
-
SKILLS: file:.claude/skills/writing-tests/SKILL.md
|
|
76114
|
+
SKILLS: file:.claude/skills/writing-tests/SKILL.md
|
|
76128
76115
|
|
|
76129
76116
|
{{AGENT_PREFIX}}explorer
|
|
76130
76117
|
TASK: Integration impact analysis
|
|
@@ -95117,6 +95104,9 @@ function utcDayKey(d = new Date) {
|
|
|
95117
95104
|
function buildAckDedupKey(sessionId, id, result, now = new Date) {
|
|
95118
95105
|
return `${sessionId}|${id}|${result}|${utcDayKey(now)}`;
|
|
95119
95106
|
}
|
|
95107
|
+
function filterHighConfidenceKnowledge(entries, threshold = 0.8) {
|
|
95108
|
+
return entries.filter((entry) => entry.confidence >= threshold);
|
|
95109
|
+
}
|
|
95120
95110
|
|
|
95121
95111
|
// src/hooks/knowledge-application-gate.ts
|
|
95122
95112
|
var HIGH_RISK_TOOLS = new Set([
|
|
@@ -95569,7 +95559,8 @@ function createKnowledgeInjectorHook(directory, config3) {
|
|
|
95569
95559
|
currentPhase: phaseDescription
|
|
95570
95560
|
};
|
|
95571
95561
|
const entries = await readContextualKnowledge(directory, config3, retrievalCtx);
|
|
95572
|
-
|
|
95562
|
+
const filteredEntries = filterHighConfidenceKnowledge(entries);
|
|
95563
|
+
cachedShownIds = filteredEntries.map((e) => e.id);
|
|
95573
95564
|
let freshPreamble = null;
|
|
95574
95565
|
try {
|
|
95575
95566
|
const driftReports = await readPriorDriftReports(directory);
|
|
@@ -95590,7 +95581,7 @@ function createKnowledgeInjectorHook(directory, config3) {
|
|
|
95590
95581
|
${freshPreamble}` : `<curator_briefing>${truncatedBriefing}</curator_briefing>`;
|
|
95591
95582
|
}
|
|
95592
95583
|
} catch {}
|
|
95593
|
-
if (
|
|
95584
|
+
if (filteredEntries.length === 0) {
|
|
95594
95585
|
if (freshPreamble === null)
|
|
95595
95586
|
return;
|
|
95596
95587
|
cachedInjectionText = freshPreamble;
|
|
@@ -95601,9 +95592,9 @@ ${freshPreamble}` : `<curator_briefing>${truncatedBriefing}</curator_briefing>`;
|
|
|
95601
95592
|
const isFullBudget = effectiveBudget === maxInjectChars;
|
|
95602
95593
|
const directiveBudget = Math.floor(effectiveBudget * 0.45);
|
|
95603
95594
|
const lessonBudget = Math.floor(effectiveBudget * 0.3);
|
|
95604
|
-
const directiveEntries =
|
|
95595
|
+
const directiveEntries = filteredEntries.filter((e) => e.triggers && e.triggers.length > 0 || e.required_actions && e.required_actions.length > 0 || e.forbidden_actions && e.forbidden_actions.length > 0 || e.directive_priority === "critical" || e.directive_priority === "high" || e.generated_skill_path);
|
|
95605
95596
|
const directiveBlock = buildDirectiveBlock(directiveEntries, directiveBudget, config3);
|
|
95606
|
-
const lessonBlock = buildKnowledgeBlock(
|
|
95597
|
+
const lessonBlock = buildKnowledgeBlock(filteredEntries, lessonBudget, config3, projectName);
|
|
95607
95598
|
const parts2 = [];
|
|
95608
95599
|
let remaining = effectiveBudget;
|
|
95609
95600
|
if (directiveBlock) {
|
|
@@ -95645,7 +95636,7 @@ ${freshPreamble}` : `<curator_briefing>${truncatedBriefing}</curator_briefing>`;
|
|
|
95645
95636
|
injectKnowledgeMessage(output, cachedInjectionText);
|
|
95646
95637
|
const sessionID = systemMsg?.info?.sessionID;
|
|
95647
95638
|
if (sessionID) {
|
|
95648
|
-
const criticalIds =
|
|
95639
|
+
const criticalIds = filteredEntries.filter((e) => e.directive_priority === "critical" && e.status !== "archived").map((e) => e.id);
|
|
95649
95640
|
if (criticalIds.length > 0) {
|
|
95650
95641
|
setCriticalShownIds(sessionID, {
|
|
95651
95642
|
ids: criticalIds,
|
|
@@ -96179,8 +96170,12 @@ function getSkillStats(skillPath, directory) {
|
|
|
96179
96170
|
};
|
|
96180
96171
|
}
|
|
96181
96172
|
function formatSkillIndexWithContext(skills, directory) {
|
|
96182
|
-
const
|
|
96183
|
-
|
|
96173
|
+
const usageLogPath = path89.join(directory, ".swarm", "skill-usage.jsonl");
|
|
96174
|
+
let hasHistory = false;
|
|
96175
|
+
try {
|
|
96176
|
+
const stat7 = fs62.statSync(usageLogPath);
|
|
96177
|
+
hasHistory = stat7.size > 0;
|
|
96178
|
+
} catch {}
|
|
96184
96179
|
if (!hasHistory) {
|
|
96185
96180
|
return skills.map((sp) => {
|
|
96186
96181
|
const meta3 = _internals42.readSkillMetadata(sp, directory);
|
|
@@ -96210,6 +96205,121 @@ _internals42.computeRecencyScore = computeRecencyScore;
|
|
|
96210
96205
|
_internals42.computeContextMatchScore = computeContextMatchScore;
|
|
96211
96206
|
|
|
96212
96207
|
// src/hooks/skill-propagation-gate.ts
|
|
96208
|
+
function parseSimpleYaml(content) {
|
|
96209
|
+
const lines = content.split(`
|
|
96210
|
+
`);
|
|
96211
|
+
const result = {};
|
|
96212
|
+
let _currentSection = null;
|
|
96213
|
+
let currentSubSection = null;
|
|
96214
|
+
let currentList = [];
|
|
96215
|
+
let currentListItem = null;
|
|
96216
|
+
for (const line of lines) {
|
|
96217
|
+
if (!line.trim() || line.trim().startsWith("#"))
|
|
96218
|
+
continue;
|
|
96219
|
+
const indent = line.search(/\S/);
|
|
96220
|
+
const trimmed = line.trim();
|
|
96221
|
+
if (indent === 0 && trimmed.endsWith(":")) {
|
|
96222
|
+
if (_currentSection && currentSubSection && currentList.length > 0) {
|
|
96223
|
+
if (!result[_currentSection]) {
|
|
96224
|
+
result[_currentSection] = {};
|
|
96225
|
+
}
|
|
96226
|
+
result[_currentSection][currentSubSection] = currentList;
|
|
96227
|
+
} else if (currentSubSection && currentList.length > 0) {
|
|
96228
|
+
result[currentSubSection] = currentList;
|
|
96229
|
+
}
|
|
96230
|
+
_currentSection = trimmed.slice(0, -1);
|
|
96231
|
+
currentSubSection = null;
|
|
96232
|
+
currentList = [];
|
|
96233
|
+
currentListItem = null;
|
|
96234
|
+
continue;
|
|
96235
|
+
}
|
|
96236
|
+
if (indent === 2 && trimmed.endsWith(":")) {
|
|
96237
|
+
if (_currentSection && currentSubSection && currentList.length > 0) {
|
|
96238
|
+
if (!result[_currentSection]) {
|
|
96239
|
+
result[_currentSection] = {};
|
|
96240
|
+
}
|
|
96241
|
+
result[_currentSection][currentSubSection] = currentList;
|
|
96242
|
+
} else if (currentSubSection && currentList.length > 0) {
|
|
96243
|
+
result[currentSubSection] = currentList;
|
|
96244
|
+
}
|
|
96245
|
+
currentSubSection = trimmed.slice(0, -1);
|
|
96246
|
+
currentList = [];
|
|
96247
|
+
currentListItem = null;
|
|
96248
|
+
continue;
|
|
96249
|
+
}
|
|
96250
|
+
if (trimmed.startsWith("- ")) {
|
|
96251
|
+
currentListItem = null;
|
|
96252
|
+
const rest = trimmed.slice(2);
|
|
96253
|
+
if (rest.includes(":")) {
|
|
96254
|
+
const colonIndex = rest.indexOf(":");
|
|
96255
|
+
const key = rest.slice(0, colonIndex).trim();
|
|
96256
|
+
const value = rest.slice(colonIndex + 1).trim();
|
|
96257
|
+
currentListItem = { [key]: parseYamlValue(value) };
|
|
96258
|
+
} else {
|
|
96259
|
+
currentListItem = { path: trimmed.slice(2) };
|
|
96260
|
+
}
|
|
96261
|
+
currentList.push(currentListItem);
|
|
96262
|
+
continue;
|
|
96263
|
+
}
|
|
96264
|
+
if (indent >= 4 && currentListItem) {
|
|
96265
|
+
if (trimmed.includes(":")) {
|
|
96266
|
+
const colonIndex = trimmed.indexOf(":");
|
|
96267
|
+
const key = trimmed.slice(0, colonIndex).trim();
|
|
96268
|
+
const value = trimmed.slice(colonIndex + 1).trim();
|
|
96269
|
+
currentListItem[key] = parseYamlValue(value);
|
|
96270
|
+
}
|
|
96271
|
+
}
|
|
96272
|
+
}
|
|
96273
|
+
if (currentSubSection && currentList.length > 0) {
|
|
96274
|
+
if (_currentSection) {
|
|
96275
|
+
if (!result[_currentSection]) {
|
|
96276
|
+
result[_currentSection] = {};
|
|
96277
|
+
}
|
|
96278
|
+
result[_currentSection][currentSubSection] = currentList;
|
|
96279
|
+
} else {
|
|
96280
|
+
result[currentSubSection] = currentList;
|
|
96281
|
+
}
|
|
96282
|
+
}
|
|
96283
|
+
return result;
|
|
96284
|
+
}
|
|
96285
|
+
function parseYamlValue(value) {
|
|
96286
|
+
const trimmed = value.trim();
|
|
96287
|
+
if (trimmed.startsWith('"') && trimmed.endsWith('"') || trimmed.startsWith("'") && trimmed.endsWith("'")) {
|
|
96288
|
+
return trimmed.slice(1, -1);
|
|
96289
|
+
}
|
|
96290
|
+
if (trimmed.startsWith("[") && trimmed.endsWith("]")) {
|
|
96291
|
+
const inner = trimmed.slice(1, -1);
|
|
96292
|
+
if (!inner.trim())
|
|
96293
|
+
return [];
|
|
96294
|
+
return inner.split(",").map((s) => s.trim().replace(/^["']|["']$/g, ""));
|
|
96295
|
+
}
|
|
96296
|
+
if (trimmed.toLowerCase() === "true")
|
|
96297
|
+
return true;
|
|
96298
|
+
if (trimmed.toLowerCase() === "false")
|
|
96299
|
+
return false;
|
|
96300
|
+
if (/^-?\d+(\.\d+)?$/.test(trimmed)) {
|
|
96301
|
+
return trimmed.includes(".") ? parseFloat(trimmed) : parseInt(trimmed, 10);
|
|
96302
|
+
}
|
|
96303
|
+
return trimmed;
|
|
96304
|
+
}
|
|
96305
|
+
function loadRoutingSkills(directory, targetAgent) {
|
|
96306
|
+
const routingPath = path90.join(directory, ".opencode", "skill-routing.yaml");
|
|
96307
|
+
if (!_internals43.existsSync(routingPath))
|
|
96308
|
+
return [];
|
|
96309
|
+
try {
|
|
96310
|
+
const content = _internals43.readFileSync(routingPath, "utf-8");
|
|
96311
|
+
const config3 = parseSimpleYaml(content);
|
|
96312
|
+
if (!config3?.routing)
|
|
96313
|
+
return [];
|
|
96314
|
+
const routing = config3.routing;
|
|
96315
|
+
const routingEntries = routing[targetAgent];
|
|
96316
|
+
if (!routingEntries || routingEntries.length === 0)
|
|
96317
|
+
return [];
|
|
96318
|
+
return routingEntries.map((entry) => entry.path);
|
|
96319
|
+
} catch {
|
|
96320
|
+
return [];
|
|
96321
|
+
}
|
|
96322
|
+
}
|
|
96213
96323
|
var SKILL_CAPABLE_AGENTS = new Set([
|
|
96214
96324
|
"coder",
|
|
96215
96325
|
"reviewer",
|
|
@@ -96242,11 +96352,12 @@ var _internals43 = {
|
|
|
96242
96352
|
appendSkillUsageEntry,
|
|
96243
96353
|
readSkillUsageEntries,
|
|
96244
96354
|
readSkillUsageEntriesTail,
|
|
96245
|
-
extractSkillsFieldFromPrompt: null,
|
|
96246
96355
|
parseSkillPaths: null,
|
|
96247
96356
|
extractTaskIdFromPrompt: null,
|
|
96357
|
+
extractSkillsFieldFromPrompt: null,
|
|
96248
96358
|
computeSkillRelevanceScore,
|
|
96249
|
-
formatSkillIndexWithContext
|
|
96359
|
+
formatSkillIndexWithContext,
|
|
96360
|
+
loadRoutingSkills: null
|
|
96250
96361
|
};
|
|
96251
96362
|
function discoverAvailableSkills(directory) {
|
|
96252
96363
|
const results = [];
|
|
@@ -96354,22 +96465,21 @@ function parseSkillPaths(fieldValue) {
|
|
|
96354
96465
|
const trimmed = fieldValue.trim();
|
|
96355
96466
|
if (trimmed.toLowerCase() === "none" || trimmed === "")
|
|
96356
96467
|
return [];
|
|
96357
|
-
const
|
|
96358
|
-
|
|
96359
|
-
|
|
96360
|
-
|
|
96361
|
-
|
|
96362
|
-
|
|
96363
|
-
|
|
96364
|
-
|
|
96365
|
-
|
|
96366
|
-
|
|
96367
|
-
|
|
96368
|
-
|
|
96369
|
-
|
|
96370
|
-
|
|
96371
|
-
}
|
|
96372
|
-
return [...new Set(paths)];
|
|
96468
|
+
const commaParts = trimmed.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
|
|
96469
|
+
if (commaParts.length === 1 && commaParts[0].startsWith("- ")) {
|
|
96470
|
+
const newlineParts = trimmed.split(`
|
|
96471
|
+
`).map((s) => s.trim()).filter((s) => s.startsWith("- ")).map((s) => s.slice(2).trim());
|
|
96472
|
+
return newlineParts.map((s) => {
|
|
96473
|
+
const parenIndex = s.indexOf("(--");
|
|
96474
|
+
const pathOnly = parenIndex !== -1 ? s.slice(0, parenIndex).trim() : s;
|
|
96475
|
+
const dashIndex = pathOnly.search(/\s+[-–—]\s+/);
|
|
96476
|
+
return dashIndex !== -1 ? pathOnly.slice(0, dashIndex).trim() : pathOnly;
|
|
96477
|
+
}).filter((s) => s.length > 0);
|
|
96478
|
+
}
|
|
96479
|
+
return commaParts.map((s) => {
|
|
96480
|
+
const parenIndex = s.indexOf("(--");
|
|
96481
|
+
return parenIndex !== -1 ? s.slice(0, parenIndex).trim() : s;
|
|
96482
|
+
}).filter((s) => s.length > 0);
|
|
96373
96483
|
}
|
|
96374
96484
|
function extractTaskIdFromPrompt(prompt) {
|
|
96375
96485
|
if (!prompt || typeof prompt !== "string")
|
|
@@ -96384,22 +96494,22 @@ function extractTaskIdFromPrompt(prompt) {
|
|
|
96384
96494
|
}
|
|
96385
96495
|
async function skillPropagationGateBefore(directory, input, config3) {
|
|
96386
96496
|
if (!config3.enabled)
|
|
96387
|
-
return { blocked: false, reason: null };
|
|
96497
|
+
return { blocked: false, reason: null, recommendedSkills: undefined };
|
|
96388
96498
|
const toolName = typeof input.tool === "string" ? input.tool : "";
|
|
96389
96499
|
if (toolName !== "task" && toolName !== "Task")
|
|
96390
|
-
return { blocked: false, reason: null };
|
|
96500
|
+
return { blocked: false, reason: null, recommendedSkills: undefined };
|
|
96391
96501
|
const agentRaw = typeof input.agent === "string" ? input.agent : "";
|
|
96392
96502
|
if (!agentRaw)
|
|
96393
|
-
return { blocked: false, reason: null };
|
|
96503
|
+
return { blocked: false, reason: null, recommendedSkills: undefined };
|
|
96394
96504
|
const baseAgent = stripKnownSwarmPrefix(agentRaw);
|
|
96395
96505
|
if (baseAgent !== "architect")
|
|
96396
|
-
return { blocked: false, reason: null };
|
|
96506
|
+
return { blocked: false, reason: null, recommendedSkills: undefined };
|
|
96397
96507
|
const parsed = _internals43.parseDelegationArgs(input.args);
|
|
96398
96508
|
if (!parsed)
|
|
96399
|
-
return { blocked: false, reason: null };
|
|
96509
|
+
return { blocked: false, reason: null, recommendedSkills: undefined };
|
|
96400
96510
|
const targetBase = stripKnownSwarmPrefix(parsed.targetAgent);
|
|
96401
96511
|
if (!_internals43.SKILL_CAPABLE_AGENTS.has(targetBase))
|
|
96402
|
-
return { blocked: false, reason: null };
|
|
96512
|
+
return { blocked: false, reason: null, recommendedSkills: undefined };
|
|
96403
96513
|
const sessionID = typeof input.sessionID === "string" ? input.sessionID : "unknown";
|
|
96404
96514
|
const availableSkills = _internals43.discoverAvailableSkills(directory);
|
|
96405
96515
|
const skillsValue = parsed.skillsField.trim();
|
|
@@ -96462,6 +96572,23 @@ async function skillPropagationGateBefore(directory, input, config3) {
|
|
|
96462
96572
|
warn(`[skill-propagation-gate] skill scoring failed (non-blocking): ${err2 instanceof Error ? err2.message : String(err2)}`);
|
|
96463
96573
|
}
|
|
96464
96574
|
}
|
|
96575
|
+
try {
|
|
96576
|
+
const routingPaths = _internals43.loadRoutingSkills(directory, targetBase);
|
|
96577
|
+
if (routingPaths.length > 0) {
|
|
96578
|
+
const existingPaths = new Set(scored.map((s) => s.skillPath));
|
|
96579
|
+
for (const routingPath of routingPaths) {
|
|
96580
|
+
if (!existingPaths.has(routingPath)) {
|
|
96581
|
+
scored.push({
|
|
96582
|
+
skillPath: routingPath,
|
|
96583
|
+
score: 0.9,
|
|
96584
|
+
usageCount: 0
|
|
96585
|
+
});
|
|
96586
|
+
existingPaths.add(routingPath);
|
|
96587
|
+
}
|
|
96588
|
+
}
|
|
96589
|
+
scored.sort((a, b) => b.score - a.score || b.usageCount - a.usageCount);
|
|
96590
|
+
}
|
|
96591
|
+
} catch {}
|
|
96465
96592
|
if (availableSkills.length > 0) {
|
|
96466
96593
|
try {
|
|
96467
96594
|
let skillsForIndex = availableSkills;
|
|
@@ -96520,14 +96647,14 @@ ${newSection}`;
|
|
|
96520
96647
|
const coderHadSkills = skillsValue.length > 0 && skillsValue.toLowerCase() !== "none";
|
|
96521
96648
|
if (!hasSkillsUsedByCoder && coderHadSkills) {
|
|
96522
96649
|
const message = `SKILLS_USED_BY_CODER warning: Delegating to reviewer without SKILLS_USED_BY_CODER field. ` + `Add SKILLS_USED_BY_CODER with the skills the coder received for this task.`;
|
|
96523
|
-
return { blocked: false, reason: message };
|
|
96650
|
+
return { blocked: false, reason: message, recommendedSkills: undefined };
|
|
96524
96651
|
}
|
|
96525
96652
|
}
|
|
96526
96653
|
if (availableSkills.length === 0)
|
|
96527
|
-
return { blocked: false, reason: null };
|
|
96654
|
+
return { blocked: false, reason: null, recommendedSkills: undefined };
|
|
96528
96655
|
const skillsLower = skillsValue.toLowerCase();
|
|
96529
96656
|
if (skillsValue && skillsLower !== "none")
|
|
96530
|
-
return { blocked: false, reason: null };
|
|
96657
|
+
return { blocked: false, reason: null, recommendedSkills: scored };
|
|
96531
96658
|
const skillNames = availableSkills.map((p) => {
|
|
96532
96659
|
const parts2 = p.split("/");
|
|
96533
96660
|
return parts2[parts2.length - 2] ?? p;
|
|
@@ -96547,9 +96674,9 @@ ${newSection}`;
|
|
|
96547
96674
|
} catch {}
|
|
96548
96675
|
if (config3.enforce) {
|
|
96549
96676
|
const blockedMsg = `Blocked by skill propagation gate: Delegating to ${targetBase} without SKILLS field. ` + `Available skills: ${skillNames.join(", ")}. ` + `Add a SKILLS: field or set enforce: false in config.`;
|
|
96550
|
-
return { blocked: true, reason: blockedMsg };
|
|
96677
|
+
return { blocked: true, reason: blockedMsg, recommendedSkills: undefined };
|
|
96551
96678
|
}
|
|
96552
|
-
return { blocked: false, reason: warningMsg };
|
|
96679
|
+
return { blocked: false, reason: warningMsg, recommendedSkills: undefined };
|
|
96553
96680
|
}
|
|
96554
96681
|
var COMPLIANCE_PATTERN = /SKILL_COMPLIANCE\s*:\s*(COMPLIANT|PARTIAL|VIOLATED)(?:\s*(?:—|-)\s*(.*))?\s*$/i;
|
|
96555
96682
|
var CODER_SKILLS_PATTERN = /SKILLS_USED_BY_CODER\s*:\s*(.+)/i;
|
|
@@ -96658,10 +96785,8 @@ async function skillPropagationTransformScan(directory, output, sessionID) {
|
|
|
96658
96785
|
continue;
|
|
96659
96786
|
let currentTargetAgent = "";
|
|
96660
96787
|
let skillsField = "";
|
|
96661
|
-
const
|
|
96662
|
-
`)
|
|
96663
|
-
for (let lineIndex = 0;lineIndex < textLines.length; lineIndex++) {
|
|
96664
|
-
const line = textLines[lineIndex] ?? "";
|
|
96788
|
+
for (const line of text.split(`
|
|
96789
|
+
`)) {
|
|
96665
96790
|
const trimmed = line.trim();
|
|
96666
96791
|
if (trimmed.match(/TO\s+(coder|reviewer|test_engineer|sme|docs|designer)/i)) {
|
|
96667
96792
|
const agentMatch = trimmed.match(/TO\s+(coder|reviewer|test_engineer|sme|docs|designer)/i);
|
|
@@ -96669,8 +96794,7 @@ async function skillPropagationTransformScan(directory, output, sessionID) {
|
|
|
96669
96794
|
currentTargetAgent = agentMatch[1].toLowerCase();
|
|
96670
96795
|
}
|
|
96671
96796
|
if (trimmed.startsWith("SKILLS:")) {
|
|
96672
|
-
skillsField =
|
|
96673
|
-
`));
|
|
96797
|
+
skillsField = trimmed.slice("SKILLS:".length).trim();
|
|
96674
96798
|
}
|
|
96675
96799
|
if (currentTargetAgent && skillsField && skillsField.toLowerCase() !== "none") {
|
|
96676
96800
|
const skillPaths = _internals43.parseSkillPaths(skillsField);
|
|
@@ -96706,10 +96830,11 @@ _internals43.skillPropagationTransformScan = skillPropagationTransformScan;
|
|
|
96706
96830
|
_internals43.writeWarnEvent = writeWarnEvent2;
|
|
96707
96831
|
_internals43.discoverAvailableSkills = discoverAvailableSkills;
|
|
96708
96832
|
_internals43.parseDelegationArgs = parseDelegationArgs;
|
|
96709
|
-
_internals43.extractSkillsFieldFromPrompt = extractSkillsFieldFromPrompt;
|
|
96710
96833
|
_internals43.parseSkillPaths = parseSkillPaths;
|
|
96711
96834
|
_internals43.extractTaskIdFromPrompt = extractTaskIdFromPrompt;
|
|
96835
|
+
_internals43.extractSkillsFieldFromPrompt = extractSkillsFieldFromPrompt;
|
|
96712
96836
|
_internals43.formatSkillIndexWithContext = formatSkillIndexWithContext;
|
|
96837
|
+
_internals43.loadRoutingSkills = loadRoutingSkills;
|
|
96713
96838
|
|
|
96714
96839
|
// src/hooks/slop-detector.ts
|
|
96715
96840
|
import * as fs64 from "node:fs";
|
|
@@ -99991,7 +100116,7 @@ import {
|
|
|
99991
100116
|
readFileSync as readFileSync44,
|
|
99992
100117
|
writeFileSync as writeFileSync17
|
|
99993
100118
|
} from "node:fs";
|
|
99994
|
-
import { join as
|
|
100119
|
+
import { join as join84 } from "node:path";
|
|
99995
100120
|
var EVIDENCE_DIR2 = ".swarm/evidence";
|
|
99996
100121
|
var VALID_TASK_ID = /^\d+\.\d+(\.\d+)*$/;
|
|
99997
100122
|
var COUNCIL_GATE_NAME = "council";
|
|
@@ -100025,9 +100150,9 @@ function writeCouncilEvidence(workingDir, synthesis) {
|
|
|
100025
100150
|
if (!VALID_TASK_ID.test(synthesis.taskId)) {
|
|
100026
100151
|
throw new Error(`writeCouncilEvidence: invalid taskId "${synthesis.taskId}" — must match N.M or N.M.P format`);
|
|
100027
100152
|
}
|
|
100028
|
-
const dir =
|
|
100153
|
+
const dir = join84(workingDir, EVIDENCE_DIR2);
|
|
100029
100154
|
mkdirSync25(dir, { recursive: true });
|
|
100030
|
-
const filePath =
|
|
100155
|
+
const filePath = join84(dir, `${synthesis.taskId}.json`);
|
|
100031
100156
|
const existingRoot = Object.create(null);
|
|
100032
100157
|
if (existsSync53(filePath)) {
|
|
100033
100158
|
try {
|
|
@@ -100061,7 +100186,7 @@ function writeCouncilEvidence(workingDir, synthesis) {
|
|
|
100061
100186
|
updated.required_gates = [];
|
|
100062
100187
|
writeFileSync17(filePath, JSON.stringify(updated, null, 2));
|
|
100063
100188
|
try {
|
|
100064
|
-
const councilDir =
|
|
100189
|
+
const councilDir = join84(workingDir, ".swarm", "council");
|
|
100065
100190
|
mkdirSync25(councilDir, { recursive: true });
|
|
100066
100191
|
const auditLine = JSON.stringify({
|
|
100067
100192
|
round: synthesis.roundNumber,
|
|
@@ -100069,7 +100194,7 @@ function writeCouncilEvidence(workingDir, synthesis) {
|
|
|
100069
100194
|
timestamp: synthesis.timestamp,
|
|
100070
100195
|
vetoedBy: synthesis.vetoedBy
|
|
100071
100196
|
});
|
|
100072
|
-
appendFileSync12(
|
|
100197
|
+
appendFileSync12(join84(councilDir, `${synthesis.taskId}.rounds.jsonl`), `${auditLine}
|
|
100073
100198
|
`);
|
|
100074
100199
|
} catch (auditError) {
|
|
100075
100200
|
console.warn(`writeCouncilEvidence: failed to append round-history audit log: ${auditError instanceof Error ? auditError.message : String(auditError)}`);
|
|
@@ -100392,20 +100517,20 @@ function buildFinalCouncilFeedback(projectSummary, verdict, vetoedBy, requiredFi
|
|
|
100392
100517
|
|
|
100393
100518
|
// src/council/criteria-store.ts
|
|
100394
100519
|
import { existsSync as existsSync54, mkdirSync as mkdirSync26, readFileSync as readFileSync45, writeFileSync as writeFileSync18 } from "node:fs";
|
|
100395
|
-
import { join as
|
|
100520
|
+
import { join as join85 } from "node:path";
|
|
100396
100521
|
var COUNCIL_DIR = ".swarm/council";
|
|
100397
100522
|
function writeCriteria(workingDir, taskId, criteria) {
|
|
100398
|
-
const dir =
|
|
100523
|
+
const dir = join85(workingDir, COUNCIL_DIR);
|
|
100399
100524
|
mkdirSync26(dir, { recursive: true });
|
|
100400
100525
|
const payload = {
|
|
100401
100526
|
taskId,
|
|
100402
100527
|
criteria,
|
|
100403
100528
|
declaredAt: new Date().toISOString()
|
|
100404
100529
|
};
|
|
100405
|
-
writeFileSync18(
|
|
100530
|
+
writeFileSync18(join85(dir, `${safeId(taskId)}.json`), JSON.stringify(payload, null, 2));
|
|
100406
100531
|
}
|
|
100407
100532
|
function readCriteria(workingDir, taskId) {
|
|
100408
|
-
const filePath =
|
|
100533
|
+
const filePath = join85(workingDir, COUNCIL_DIR, `${safeId(taskId)}.json`);
|
|
100409
100534
|
if (!existsSync54(filePath))
|
|
100410
100535
|
return null;
|
|
100411
100536
|
try {
|
|
@@ -118927,6 +119052,73 @@ async function initializeOpenCodeSwarm(ctx) {
|
|
|
118927
119052
|
skillSession.pendingAdvisoryMessages ??= [];
|
|
118928
119053
|
skillSession.pendingAdvisoryMessages.push(skillResult.reason);
|
|
118929
119054
|
}
|
|
119055
|
+
if (skillResult.recommendedSkills && skillResult.recommendedSkills.length > 0) {
|
|
119056
|
+
const argsRecord = input.args;
|
|
119057
|
+
const promptRaw = argsRecord.prompt;
|
|
119058
|
+
if (typeof promptRaw === "string") {
|
|
119059
|
+
const parsedDelegation = parseDelegationArgs(input.args);
|
|
119060
|
+
if (parsedDelegation) {
|
|
119061
|
+
const existingSkills = parsedDelegation.skillsField.trim();
|
|
119062
|
+
if (!existingSkills) {
|
|
119063
|
+
const qualified = skillResult.recommendedSkills.filter((s) => s.score >= 0.5);
|
|
119064
|
+
if (qualified.length === 0) {
|
|
119065
|
+
argsRecord.prompt = `SKILLS: none
|
|
119066
|
+
|
|
119067
|
+
${promptRaw}`;
|
|
119068
|
+
console.warn("[skill-propagation-gate] No skills above threshold 0.5 — injected SKILLS: none");
|
|
119069
|
+
} else {
|
|
119070
|
+
const topSkills = qualified.slice(0, 5);
|
|
119071
|
+
const SKILL_DESCRIPTIONS = {
|
|
119072
|
+
"writing-tests": "Guidelines for writing tests",
|
|
119073
|
+
"engineering-conventions": "Engineering invariants and conventions",
|
|
119074
|
+
"running-tests": "Safe test execution patterns",
|
|
119075
|
+
"commit-pr": "Commit and PR workflow",
|
|
119076
|
+
"swarm-implement": "Swarm implementation workflow",
|
|
119077
|
+
"issue-tracer": "Issue investigation workflow",
|
|
119078
|
+
"qa-sweep": "QA sweep workflow",
|
|
119079
|
+
"research-first": "Research-driven approach",
|
|
119080
|
+
"swarm-pr-review": "PR review workflow",
|
|
119081
|
+
"tech-debt-ci-review": "Tech debt and CI review",
|
|
119082
|
+
browse: "Fast web browsing",
|
|
119083
|
+
code: "Expert coding workflow",
|
|
119084
|
+
review: "Pre-landing PR review",
|
|
119085
|
+
"ci-failure-resolver": "CI/CD failure resolution"
|
|
119086
|
+
};
|
|
119087
|
+
const skillPaths = topSkills.map((s) => {
|
|
119088
|
+
const dirName = path144.basename(path144.dirname(s.skillPath));
|
|
119089
|
+
const desc = SKILL_DESCRIPTIONS[dirName] ?? dirName;
|
|
119090
|
+
return `file:${s.skillPath} (-- ${desc})`;
|
|
119091
|
+
}).join(", ");
|
|
119092
|
+
const skillsLine = `SKILLS: ${skillPaths}`;
|
|
119093
|
+
const newPrompt = `${skillsLine}
|
|
119094
|
+
|
|
119095
|
+
${promptRaw}`;
|
|
119096
|
+
argsRecord.prompt = newPrompt;
|
|
119097
|
+
const skillNames = topSkills.map((s) => `${path144.basename(s.skillPath)} (score: ${s.score.toFixed(2)})`).join(", ");
|
|
119098
|
+
console.warn(`[skill-propagation-gate] Injected skills: ${skillNames}`);
|
|
119099
|
+
for (const skill of topSkills) {
|
|
119100
|
+
try {
|
|
119101
|
+
appendSkillUsageEntry(ctx.directory, {
|
|
119102
|
+
skillPath: skill.skillPath,
|
|
119103
|
+
agentName: String(input.agent),
|
|
119104
|
+
taskID: "injection",
|
|
119105
|
+
timestamp: new Date().toISOString(),
|
|
119106
|
+
complianceVerdict: "not_checked",
|
|
119107
|
+
sessionID: input.sessionID
|
|
119108
|
+
});
|
|
119109
|
+
} catch {}
|
|
119110
|
+
}
|
|
119111
|
+
const targetAgent = parsedDelegation.targetAgent.toLowerCase();
|
|
119112
|
+
if (targetAgent.includes("reviewer")) {
|
|
119113
|
+
const usedByCoderLine = `SKILLS_USED_BY_CODER: ${topSkills.map((s) => `file:${s.skillPath}`).join(", ")}`;
|
|
119114
|
+
argsRecord.prompt = `${newPrompt}
|
|
119115
|
+
${usedByCoderLine}`;
|
|
119116
|
+
}
|
|
119117
|
+
}
|
|
119118
|
+
}
|
|
119119
|
+
}
|
|
119120
|
+
}
|
|
119121
|
+
}
|
|
118930
119122
|
if (swarmState.lastBudgetPct >= 50) {
|
|
118931
119123
|
const pressureSession = ensureAgentSession(input.sessionID, swarmState.activeAgent.get(input.sessionID) ?? ORCHESTRATOR_NAME);
|
|
118932
119124
|
if (!pressureSession.contextPressureWarningSent) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opencode-swarm",
|
|
3
|
-
"version": "7.
|
|
3
|
+
"version": "7.29.0",
|
|
4
4
|
"description": "Architect-centric agentic swarm plugin for OpenCode - hub-and-spoke orchestration with SME consultation, code generation, and QA review",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|