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 CHANGED
@@ -538,469 +538,69 @@ All tools run locally. No Docker, no network calls.
538
538
 
539
539
  ### Context Budget Guard
540
540
 
541
- Monitors how much context Swarm injects to prevent overflow:
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
- Agents are classified into four categories for the monitor server `/metadata` endpoint:
543
+ ### Default Behavior
556
544
 
557
- | Category | Agents |
558
- |----------|--------|
559
- | `orchestrator` | architect |
560
- | `pipeline` | explorer, coder, test_engineer |
561
- | `qa` | reviewer, critic, critic_sounding_board, critic_drift_verifier |
562
- | `support` | sme, docs, designer |
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
- Use `getAgentCategory(agentName)` from `src/config/agent-categories.ts` to resolve an agent's category at runtime.
552
+ To disable entirely, set `context_budget.enabled: false` in your swarm config.
565
553
 
566
554
  ---
567
555
 
568
- <details>
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
- #### Selecting the primary agent in multi-swarm configs (`default_agent`)
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
- The top-level `default_agent` field controls which generated agents OpenCode treats as primary. **It is optional.** Behavior:
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
- - **Omitted** — every architect-role agent is primary. In a multi-swarm config that means each swarm exposes its own architect (`local_architect`, `mega_architect`, `paid_architect`, `modelrelay_architect`, …) as a selectable session default. This is the v7.0.0-compatible behavior and the recommended setup.
865
- - **Base role** (e.g. `"coder"`) every generated agent whose canonical base role matches becomes primary (`local_coder`, `mega_coder`, …).
866
- - **Exact generated name** (e.g. `"local_architect"`) — only that agent is primary.
867
- - **Unknown / invalid value** a one-time warning is logged and the resolver falls back to architect-role primaries (or the first generated agent if architects are disabled). The plugin never produces zero primaries when at least one agent exists.
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
- See [`docs/configuration.md`](docs/configuration.md) for the full table.
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
- "authority": {
577
+ "skill_propagation": {
882
578
  "enabled": true,
883
- "rules": {
884
- "coder": {
885
- "allowedPrefix": ["src/", "lib/", "scripts/"],
886
- "blockedPrefix": [".swarm/"],
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
- ### Advanced Examples
945
-
946
- #### Glob Pattern Support
947
-
948
- Use glob patterns for complex path matching:
949
-
950
- ```json
951
- {
952
- "authority": {
953
- "rules": {
954
- "coder": {
955
- "allowedGlobs": ["src/**/*.ts", "tests/**/*.test.ts"],
956
- "blockedGlobs": ["src/**/*.generated.ts", "**/*.d.ts"],
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
- **Glob Pattern Features:**
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.28.3",
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: false, reason: null } when no action needed.
121
- * { blocked: false, reason: "warning message" } when warning only (enforce=false).
122
- * { blocked: true, reason: "blocked: ..." } when blocking (enforce=true).
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.28.3",
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. Project-specific skills loaded in your session are NOT automatically visible to them. Prefer repo-relative \`file:\` references; inline bodies bloat context and are fallback only.
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 — Discover available skills (once per session)
75949
+ ### Step 1 — Skills are auto-discovered and scored
75950
75950
 
75951
- At session start, before your first delegation:
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
- Use \`.swarm/skill-usage.jsonl\` when present; otherwise weight skills equally.
75953
+ ### Step 2 SKILLS: field is auto-populated
75961
75954
 
75962
- If skill-usage.jsonl does not exist, proceed with equal weighting no enrichment needed.
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
- ### Step 2 Route skills to agents
75959
+ When passing skill references, you may add brief context descriptions. The hook injects \`file:path (-- description)\` format.
75966
75960
 
75967
- Include a skill when its name/description matches:
75961
+ ### Step 4 Forward SKILLS_USED_BY_CODER to reviewer
75968
75962
 
75969
- | Skill description / name contains… | Pass to agents… |
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
- When uncertain, pass the skill. Missing applicable skills cause convention drift.
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
- ### Step 3 Include skill references in delegations
75967
+ **Skill-to-agent routing:** Managed via \`.opencode/skill-routing.yaml\`. The hook reads this file at delegation time.
75975
75968
 
75976
- Add \`SKILLS:\` to every coder, reviewer, test_engineer, sme, docs, and designer delegation:
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
- Use \`skill_improver\` / \`skill_improve\` rarely for repeated review failures, ignored knowledge clusters, stale skills, or fresh spec drift. It is quota-bounded and proposal-only.
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/revision, prefer \`spec_writer\`; it writes only via \`spec_write\`. Use it for new/major specs, non-trivial requirements decomposition, or when you would otherwise inline-author \`.swarm/spec.md\`. Handle small typos/cross-references inline.
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, use \`bunx opencode-swarm run <command> [args]\`; never use \`bun -e\` or internal \`src/commands/\` paths. Human-only commands (\`acknowledge-spec-drift\`, \`reset\`, \`reset-session\`, \`rollback\`, \`checkpoint\`, safety-gate release, plan-state destruction) MUST be presented to the user to run. Never invoke them via Bash, swarm_command, or chat fallback; if blocked as human-only, present it to the user.
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 happen ONLY through the **Task** tool; chat text is not delivered to agents. Examples below are Task content.
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; do NOT invent/omit fields. Begin with agent name, \`TASK:\`, and \`SKILLS:\` when supported.
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 with descriptions, or inline skill bodies — see SKILLS PROPAGATION; use "none" only when no project-specific skill applies]
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 - safe code changes
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 - safe code changes
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 - safe code changes
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 - tests
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 - safe code changes
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 - tests
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
- cachedShownIds = entries.map((e) => e.id);
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 (entries.length === 0) {
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 = entries.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);
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(entries, lessonBudget, config3, projectName);
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 = entries.filter((e) => e.directive_priority === "critical" && e.status !== "archived").map((e) => e.id);
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 allEntries = readSkillUsageEntries(directory);
96183
- const hasHistory = allEntries.length > 0;
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 lines = trimmed.split(/\r?\n/);
96358
- const hasCatalogLines = lines.some((line) => /^(?:-|\*|\d+\.)\s+/.test(line.trim()));
96359
- const parts2 = hasCatalogLines ? lines : trimmed.split(",");
96360
- const paths = [];
96361
- for (const rawPart of parts2) {
96362
- const part = rawPart.trim().replace(/^(?:-|\*|\d+\.)\s+/, "").trim();
96363
- if (!part || part.toLowerCase() === "none")
96364
- continue;
96365
- const fileRef = part.match(/\bfile:[^\s,;)\]]+/);
96366
- if (fileRef) {
96367
- paths.push(fileRef[0].replace(/\\/g, "/"));
96368
- continue;
96369
- }
96370
- paths.push(part);
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 textLines = text.split(`
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 = _internals43.extractSkillsFieldFromPrompt(textLines.slice(lineIndex).join(`
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 join83 } from "node:path";
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 = join83(workingDir, EVIDENCE_DIR2);
100153
+ const dir = join84(workingDir, EVIDENCE_DIR2);
100029
100154
  mkdirSync25(dir, { recursive: true });
100030
- const filePath = join83(dir, `${synthesis.taskId}.json`);
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 = join83(workingDir, ".swarm", "council");
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(join83(councilDir, `${synthesis.taskId}.rounds.jsonl`), `${auditLine}
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 join84 } from "node:path";
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 = join84(workingDir, COUNCIL_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(join84(dir, `${safeId(taskId)}.json`), JSON.stringify(payload, null, 2));
100530
+ writeFileSync18(join85(dir, `${safeId(taskId)}.json`), JSON.stringify(payload, null, 2));
100406
100531
  }
100407
100532
  function readCriteria(workingDir, taskId) {
100408
- const filePath = join84(workingDir, COUNCIL_DIR, `${safeId(taskId)}.json`);
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.28.3",
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",