opencode-swarm 7.28.3 → 7.29.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/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.1",
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",
@@ -61164,19 +61164,9 @@ function validateConfigKey(path31, value, _config) {
61164
61164
  case "guardrails.profiles": {
61165
61165
  const profiles = value;
61166
61166
  if (profiles) {
61167
- const validAgents = [
61168
- "architect",
61169
- "coder",
61170
- "test_engineer",
61171
- "explorer",
61172
- "reviewer",
61173
- "critic",
61174
- "sme",
61175
- "docs",
61176
- "designer"
61177
- ];
61167
+ const validAgents = new Set(ALL_AGENT_NAMES);
61178
61168
  for (const [agentName, profile] of Object.entries(profiles)) {
61179
- if (!validAgents.includes(agentName)) {
61169
+ if (!validAgents.has(agentName)) {
61180
61170
  findings.push({
61181
61171
  id: "unknown-agent-profile",
61182
61172
  title: "Unknown agent profile",
@@ -61337,23 +61327,23 @@ function validateConfigKey(path31, value, _config) {
61337
61327
  case "swarms": {
61338
61328
  const swarms = value;
61339
61329
  if (swarms && typeof swarms === "object") {
61330
+ const validAgents = new Set(ALL_AGENT_NAMES);
61340
61331
  for (const [swarmId, swarmConfig] of Object.entries(swarms)) {
61341
61332
  const swarm = swarmConfig;
61342
61333
  if (swarm.agents && typeof swarm.agents === "object") {
61343
61334
  for (const [agentName] of Object.entries(swarm.agents)) {
61344
- const validAgents = [
61345
- "architect",
61346
- "coder",
61347
- "test_engineer",
61348
- "explorer",
61349
- "reviewer",
61350
- "critic",
61351
- "sme",
61352
- "docs",
61353
- "designer"
61354
- ];
61355
- const baseName = agentName.replace(/^[a-zA-Z0-9]+_/, "");
61356
- if (!validAgents.includes(baseName)) {
61335
+ const baseName = stripKnownSwarmPrefix(agentName);
61336
+ if (baseName !== agentName && agentName.startsWith(`${swarmId}_`) && validAgents.has(baseName)) {
61337
+ findings.push({
61338
+ id: "prefixed-swarm-agent-override",
61339
+ title: "Prefixed agent override is ignored",
61340
+ description: `Agent "${agentName}" in swarm "${swarmId}" uses a generated agent name. ` + `Per-swarm overrides must use the canonical key "${baseName}", e.g. ` + `"swarms.${swarmId}.agents.${baseName}.model". Otherwise the override is ignored and the agent falls back to its default model.`,
61341
+ severity: "warn",
61342
+ path: `swarms.${swarmId}.agents.${agentName}`,
61343
+ currentValue: swarm.agents[agentName],
61344
+ autoFixable: false
61345
+ });
61346
+ } else if (!validAgents.has(baseName)) {
61357
61347
  findings.push({
61358
61348
  id: "unknown-swarm-agent",
61359
61349
  title: "Unknown agent in swarm",
@@ -61704,6 +61694,8 @@ function removeStraySwarmDir(projectRoot, strayPath) {
61704
61694
  }
61705
61695
  var VALID_CONFIG_PATTERNS, DANGEROUS_PATH_SEGMENTS;
61706
61696
  var init_config_doctor = __esm(() => {
61697
+ init_constants();
61698
+ init_schema();
61707
61699
  init_utils();
61708
61700
  VALID_CONFIG_PATTERNS = [
61709
61701
  /^\.config[\\/]opencode[\\/]opencode-swarm\.json$/,
@@ -75598,7 +75590,7 @@ ANTI-RATIONALIZATION: Context does not clarify. Models revert to CC training.
75598
75590
  ## IDENTITY
75599
75591
 
75600
75592
  Swarm: {{SWARM_ID}}
75601
- Your agents: {{AGENT_PREFIX}}explorer, {{AGENT_PREFIX}}sme, {{AGENT_PREFIX}}coder, {{AGENT_PREFIX}}reviewer, {{AGENT_PREFIX}}test_engineer, {{AGENT_PREFIX}}critic, {{AGENT_PREFIX}}critic_sounding_board, {{AGENT_PREFIX}}docs, {{AGENT_PREFIX}}designer
75593
+ Your agents: {{AGENT_PREFIX}}explorer, {{AGENT_PREFIX}}sme, {{AGENT_PREFIX}}coder, {{AGENT_PREFIX}}reviewer, {{AGENT_PREFIX}}test_engineer, {{AGENT_PREFIX}}critic, {{AGENT_PREFIX}}critic_sounding_board, {{AGENT_PREFIX}}skill_improver, {{AGENT_PREFIX}}spec_writer, {{AGENT_PREFIX}}docs, {{AGENT_PREFIX}}designer
75602
75594
 
75603
75595
  ## PROJECT CONTEXT
75604
75596
  Session-start priming block. Use any known values immediately; if a field is still unresolved, run MODE: DISCOVER before relying on it.
@@ -75939,63 +75931,38 @@ SECURITY_KEYWORDS: password, secret, token, credential, auth, login, encryption,
75939
75931
  {{AGENT_PREFIX}}test_engineer - Test generation AND execution (writes tests, runs them, reports PASS/FAIL)
75940
75932
  {{AGENT_PREFIX}}critic - Plan review gate (reviews plan BEFORE implementation)
75941
75933
  {{AGENT_PREFIX}}critic_sounding_board - Pre-escalation pushback (honest engineer review before user contact)
75934
+ {{AGENT_PREFIX}}skill_improver - Low-frequency skill / knowledge / prompt improvement adviser
75935
+ {{AGENT_PREFIX}}spec_writer - .swarm/spec.md authoring via spec_write
75942
75936
  {{AGENT_PREFIX}}docs - Documentation updates (README, API docs, guides — NOT .swarm/ files)
75943
75937
  {{AGENT_PREFIX}}designer - UI/UX design specs (scaffold generation for UI components — runs BEFORE coder on UI tasks)
75944
75938
 
75945
75939
  ## SKILLS PROPAGATION
75946
75940
 
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.
75948
-
75949
- ### Step 1 — Discover available skills (once per session)
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]
75959
-
75960
- Use \`.swarm/skill-usage.jsonl\` when present; otherwise weight skills equally.
75961
-
75962
- If skill-usage.jsonl does not exist, proceed with equal weighting — no enrichment needed.
75941
+ 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.
75963
75942
 
75943
+ ### Step 1 — Skills are auto-discovered and scored
75964
75944
 
75965
- ### Step 2 Route skills to agents
75945
+ The hook system discovers available skills and scores them by relevance to the task. The hook auto-injects them into the delegation prompt.
75966
75946
 
75967
- Include a skill when its name/description matches:
75947
+ ### Step 2 SKILLS: field is auto-populated
75968
75948
 
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.
75949
+ The hook auto-populates the \`SKILLS:\` field with top recommended skills (max 5, threshold 0.5). Explicit \`SKILLS: none\` is preserved.
75971
75950
 
75972
- When uncertain, pass the skill. Missing applicable skills cause convention drift.
75951
+ ### Step 3 Skill references with context descriptions
75973
75952
 
75974
- ### Step 3 Include skill references in delegations
75953
+ When passing skill references, you may add brief context descriptions. The hook injects \`file:path (-- description)\` format.
75975
75954
 
75976
- Add \`SKILLS:\` to every coder, reviewer, test_engineer, sme, docs, and designer delegation:
75955
+ ### Step 4 Forward SKILLS_USED_BY_CODER to reviewer
75977
75956
 
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]
75957
+ 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.
75988
75958
 
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\`.
75959
+ 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.
75990
75960
 
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.
75961
+ **Skill-to-agent routing:** Managed via \`.opencode/skill-routing.yaml\`. The hook reads this file at delegation time.
75992
75962
 
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.
75963
+ **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
75964
 
75965
+ **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
75966
 
76000
75967
  ## SWARM KNOWLEDGE DIRECTIVES (v2 acknowledgment contract)
76001
75968
 
@@ -76016,16 +75983,20 @@ For every applicable directive in the block:
76016
75983
 
76017
75984
  You may also call the \`knowledge_ack\` tool to record an outcome explicitly when chat-text markers would be ambiguous (e.g. inside structured tool args).
76018
75985
 
76019
- ## SKILL IMPROVER (low-frequency, expensive-model adviser)
75986
+ ## SKILL IMPROVER
76020
75987
 
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.
75988
+ \`{{AGENT_PREFIX}}skill_improver\` / \`skill_improve\`: rare, quota-bounded,
75989
+ disabled by default, proposal-only. Use for repeated rejections,
75990
+ \`KNOWLEDGE_IGNORED\`, stale skills, or spec drift.
76022
75991
 
76023
75992
  When \`skill_improver.require_user_approval\` is true (default), ASK the user
76024
75993
  before running. Default outputs are proposals only — they never modify source.
76025
75994
 
76026
75995
  ## SPEC WRITER
76027
75996
 
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.
75997
+ For substantial spec authoring/revision, prefer \`{{AGENT_PREFIX}}spec_writer\`;
75998
+ it writes via \`spec_write\`. Use for major specs or non-trivial
75999
+ decomposition. Handle small touch-ups.
76029
76000
 
76030
76001
  ### ANTI-RATIONALIZATION
76031
76002
  - ✗ "The coder already knows these conventions" → Skills contain project-specific rules the model cannot know from training. Always pass.
@@ -76038,7 +76009,7 @@ For substantial spec authoring/revision, prefer \`spec_writer\`; it writes only
76038
76009
  ## SLASH COMMANDS
76039
76010
  {{SLASH_COMMANDS}}
76040
76011
  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.
76012
+ 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
76013
 
76043
76014
  SMEs advise only. Reviewer and critic review only. None of them write code.
76044
76015
 
@@ -76046,15 +76017,15 @@ Available Tools: {{AVAILABLE_TOOLS}}
76046
76017
 
76047
76018
  ## DELEGATION FORMAT
76048
76019
 
76049
- Delegations happen ONLY through the **Task** tool; chat text is not delivered to agents. Examples below are Task content.
76020
+ 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
76021
 
76051
- follow the receiving agent's INPUT FORMAT exactly; do NOT invent/omit fields. Begin with agent name, \`TASK:\`, and \`SKILLS:\` when supported.
76022
+ 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
76023
  Do NOT add conversational preamble before the agent prefix. Begin directly with the agent name.
76053
76024
 
76054
76025
  {{AGENT_PREFIX}}[agent]
76055
76026
  TASK: [single objective]
76056
76027
  [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]
76028
+ SKILLS: [either "none", repo-relative file: references, or inline skill bodies — see SKILLS PROPAGATION; use "none" only when no project-specific skill applies]
76058
76029
 
76059
76030
  Examples:
76060
76031
 
@@ -76087,22 +76058,22 @@ FILE: src/auth/login.ts
76087
76058
  INPUT: Validate email format, password >= 8 chars
76088
76059
  OUTPUT: Modified file
76089
76060
  CONSTRAINT: Do not modify other functions
76090
- SKILLS: file:.claude/skills/engineering-conventions/SKILL.md - safe code changes
76061
+ SKILLS: file:.claude/skills/engineering-conventions/SKILL.md
76091
76062
 
76092
76063
  {{AGENT_PREFIX}}reviewer
76093
76064
  TASK: Review login validation
76094
76065
  FILE: src/auth/login.ts
76095
76066
  CHECK: [security, correctness, edge-cases]
76096
76067
  GATES: lint=PASS, sast_scan=PASS, secretscan=PASS
76097
- SKILLS_USED_BY_CODER: file:.claude/skills/engineering-conventions/SKILL.md - safe code changes
76068
+ SKILLS_USED_BY_CODER: file:.claude/skills/engineering-conventions/SKILL.md
76098
76069
  OUTPUT: VERDICT + RISK + ISSUES
76099
- SKILLS: file:.claude/skills/engineering-conventions/SKILL.md - safe code changes
76070
+ SKILLS: file:.claude/skills/engineering-conventions/SKILL.md
76100
76071
 
76101
76072
  {{AGENT_PREFIX}}test_engineer
76102
76073
  TASK: Generate and run login validation tests
76103
76074
  FILE: src/auth/login.ts
76104
76075
  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
76076
+ SKILLS: file:.claude/skills/writing-tests/SKILL.md
76106
76077
 
76107
76078
  {{AGENT_PREFIX}}critic
76108
76079
  TASK: Review plan for user authentication feature
@@ -76117,14 +76088,14 @@ FILE: src/auth/login.ts
76117
76088
  CHECK: [security-only] — evaluate against OWASP Top 10, scan for hardcoded secrets, injection vectors, insecure crypto, missing input validation
76118
76089
  GATES: lint=PASS, sast_scan=PASS, secretscan=PASS
76119
76090
  OUTPUT: VERDICT + RISK + SECURITY ISSUES ONLY
76120
- SKILLS: file:.claude/skills/engineering-conventions/SKILL.md - safe code changes
76091
+ SKILLS: file:.claude/skills/engineering-conventions/SKILL.md
76121
76092
 
76122
76093
  {{AGENT_PREFIX}}test_engineer
76123
76094
  TASK: Adversarial security testing
76124
76095
  FILE: src/auth/login.ts
76125
76096
  CONSTRAINT: ONLY attack vectors — malformed inputs, oversized payloads, injection attempts, auth bypass, boundary violations
76126
76097
  OUTPUT: Test file + VERDICT: PASS/FAIL
76127
- SKILLS: file:.claude/skills/writing-tests/SKILL.md - tests
76098
+ SKILLS: file:.claude/skills/writing-tests/SKILL.md
76128
76099
 
76129
76100
  {{AGENT_PREFIX}}explorer
76130
76101
  TASK: Integration impact analysis
@@ -76206,11 +76177,12 @@ MODE: BRAINSTORM runs seven phases in strict order. Do not skip phases. Do not c
76206
76177
  - Exit with a design outline the user can skim in under two minutes.
76207
76178
 
76208
76179
  **Phase 5: SPEC WRITE + SELF-REVIEW (architect + reviewer).**
76209
- - Generate \`.swarm/spec.md\` following the same SPEC CONTENT RULES that MODE: SPECIFY uses: WHAT/WHY only, no tech stack, no implementation details, FR-### / SC-### numbering, Given/When/Then scenarios, \`[NEEDS CLARIFICATION]\` markers (max 3).
76180
+ - Delegate substantial spec drafting to \`{{AGENT_PREFIX}}spec_writer\` with the chosen design, dialogue notes, SME context, and SPEC CONTENT RULES. The spec writer must persist \`.swarm/spec.md\` through \`spec_write\`.
76181
+ - The spec must follow the same SPEC CONTENT RULES that MODE: SPECIFY uses: WHAT/WHY only, no tech stack, no implementation details, FR-### / SC-### numbering, Given/When/Then scenarios, \`[NEEDS CLARIFICATION]\` markers (max 3).
76210
76182
  - Cross-reference design sections by name where relevant context helps (but keep HOW out of the spec).
76211
76183
  - Delegate to \`{{AGENT_PREFIX}}reviewer\` for an independent review of the draft spec. Reviewer must flag: requirements that encode HOW, untestable requirements, missing edge cases, silent assumptions.
76212
76184
  - Apply reviewer feedback. If reviewer rejects, iterate once and re-review. After two rounds, surface remaining disagreements to the user.
76213
- - Write the final spec to \`.swarm/spec.md\`.
76185
+ - Read back and lint the final spec after \`{{AGENT_PREFIX}}spec_writer\` writes it.
76214
76186
  - Exit when reviewer signs off (or user explicitly accepts remaining disagreements).
76215
76187
 
76216
76188
  **Phase 6: QA GATE SELECTION (architect, dialogue only).**
@@ -76282,7 +76254,7 @@ Activates when: user asks to "specify", "define requirements", "write a spec", o
76282
76254
  1b. Run CODEBASE REALITY CHECK for any codebase references mentioned by the user or implied by the feature. Skip if work is purely greenfield (no existing codebase to check). Report discrepancies before proceeding to explorer.
76283
76255
  2. Delegate to \`{{AGENT_PREFIX}}explorer\` to scan the codebase for relevant context (existing patterns, related code, affected areas).
76284
76256
  3. Delegate to \`{{AGENT_PREFIX}}sme\` for domain research on the feature area to surface known constraints, best practices, and integration concerns.
76285
- 4. Generate \`.swarm/spec.md\` capturing:
76257
+ 4. Delegate substantial spec drafting to \`{{AGENT_PREFIX}}spec_writer\`. Include the user requirements, explorer findings, SME constraints, and these required contents:
76286
76258
  - First line must be: \`# Specification: <feature-name>\`
76287
76259
  - Feature description: WHAT users need and WHY — never HOW to implement
76288
76260
  - User scenarios with acceptance criteria (Given/When/Then format)
@@ -76291,7 +76263,7 @@ Activates when: user asks to "specify", "define requirements", "write a spec", o
76291
76263
  - Key entities if data is involved (no schema or field definitions — entity names only)
76292
76264
  - Edge cases and known failure modes
76293
76265
  - \`[NEEDS CLARIFICATION]\` markers (max 3) for items where uncertainty could change scope, security, or core behavior; prefer informed defaults over asking
76294
- 5. Write the spec to \`.swarm/spec.md\`.
76266
+ 5. Require \`{{AGENT_PREFIX}}spec_writer\` to write the spec via \`spec_write\`, then read back and lint \`.swarm/spec.md\`.
76295
76267
  5b. **QA GATE SELECTION (dialogue only).**
76296
76268
  {{QA_GATE_DIALOGUE_SPECIFY}}
76297
76269
 
@@ -79494,8 +79466,24 @@ function createSwarmAgents(swarmId, swarmConfig, isDefault, pluginConfig, projec
79494
79466
  const swarmIdentity = isDefault ? "default" : swarmId;
79495
79467
  const agentPrefix = prefix;
79496
79468
  architect.config.prompt = architect.config.prompt?.replace(/\{\{SWARM_ID\}\}/g, swarmIdentity).replace(/\{\{AGENT_PREFIX\}\}/g, agentPrefix).replace(/\{\{QA_RETRY_LIMIT\}\}/g, String(qaRetryLimit)).replace(/\{\{PROJECT_LANGUAGE\}\}/g, projectContext.PROJECT_LANGUAGE).replace(/\{\{PROJECT_FRAMEWORK\}\}/g, projectContext.PROJECT_FRAMEWORK).replace(/\{\{BUILD_CMD\}\}/g, projectContext.BUILD_CMD).replace(/\{\{TEST_CMD\}\}/g, projectContext.TEST_CMD).replace(/\{\{LINT_CMD\}\}/g, projectContext.LINT_CMD).replace(/\{\{ENTRY_POINTS\}\}/g, projectContext.ENTRY_POINTS).replace(/\{\{CODER_CONSTRAINTS\}\}/g, projectContext.CODER_CONSTRAINTS).replace(/\{\{TEST_CONSTRAINTS\}\}/g, projectContext.TEST_CONSTRAINTS).replace(/\{\{REVIEWER_CHECKLIST\}\}/g, projectContext.REVIEWER_CHECKLIST).replace(/\{\{PROJECT_CONTEXT_SECONDARY_LANGUAGES\}\}/g, projectContext.PROJECT_CONTEXT_SECONDARY_LANGUAGES);
79469
+ const skillImproverEnabled = !isAgentDisabled("skill_improver", swarmAgents, swarmPrefix);
79470
+ const specWriterEnabled = !isAgentDisabled("spec_writer", swarmAgents, swarmPrefix);
79471
+ if (!skillImproverEnabled) {
79472
+ architect.config.prompt = architect.config.prompt?.replace(`, ${agentPrefix}skill_improver`, "").replace(`
79473
+ ${agentPrefix}skill_improver - Low-frequency skill / knowledge / prompt improvement adviser`, "").replace(/\n## SKILL IMPROVER[\s\S]*?(?=\n\n## SPEC WRITER)/, "");
79474
+ }
79475
+ if (!specWriterEnabled) {
79476
+ const escapedAgentPrefix = agentPrefix.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
79477
+ architect.config.prompt = architect.config.prompt?.replace(`, ${agentPrefix}spec_writer`, "").replace(`
79478
+ ${agentPrefix}spec_writer - .swarm/spec.md authoring via spec_write`, "").replace(/\n## SPEC WRITER[\s\S]*?(?=\n\n### ANTI-RATIONALIZATION)/, "").replace(new RegExp(`- Delegate substantial spec drafting to \`${escapedAgentPrefix}spec_writer\`[^\\n]*\\n`, "g"), "- spec_writer is disabled. Ask the user to enable the spec_writer agent before creating or revising `.swarm/spec.md`.\n").replace(new RegExp(`4\\. Delegate substantial spec drafting to \`${escapedAgentPrefix}spec_writer\`[^\\n]*`), "4. spec_writer is disabled. Ask the user to enable the spec_writer agent before creating or revising `.swarm/spec.md`.").replace(new RegExp(`5\\. Require \`${escapedAgentPrefix}spec_writer\` to write the spec via \`spec_write\`, then read back and lint \`\\.swarm/spec\\.md\`\\.`), "5. Do not continue SPECIFY until spec_writer is available.").replace(new RegExp(`- Read back and lint the final spec after \`${escapedAgentPrefix}spec_writer\` writes it\\.`), "- Do not continue BRAINSTORM spec writing until spec_writer is available.");
79479
+ }
79497
79480
  if (!isDefault) {
79498
79481
  architect.description = `[${swarmName}] ${architect.description}`;
79482
+ const optionalAgentLines = [
79483
+ skillImproverEnabled ? `- @${swarmId}_skill_improver (not @skill_improver)` : undefined,
79484
+ specWriterEnabled ? `- @${swarmId}_spec_writer (not @spec_writer)` : undefined
79485
+ ].filter((line) => Boolean(line)).join(`
79486
+ `);
79499
79487
  const swarmHeader = `## ⚠️ YOU ARE THE ${swarmName.toUpperCase()} SWARM ARCHITECT
79500
79488
 
79501
79489
  Your swarm ID is "${swarmId}". ALL your agents have the "${swarmId}_" prefix:
@@ -79503,7 +79491,8 @@ Your swarm ID is "${swarmId}". ALL your agents have the "${swarmId}_" prefix:
79503
79491
  - @${swarmId}_coder (not @coder)
79504
79492
  - @${swarmId}_sme (not @sme)
79505
79493
  - @${swarmId}_reviewer (not @reviewer)
79506
- - etc.
79494
+ ${optionalAgentLines ? `${optionalAgentLines}
79495
+ ` : ""}- etc.
79507
79496
 
79508
79497
  CRITICAL: Agents without the "${swarmId}_" prefix DO NOT EXIST or belong to a DIFFERENT swarm.
79509
79498
  If you call @coder instead of @${swarmId}_coder, the call will FAIL or go to the wrong swarm.
@@ -95117,6 +95106,9 @@ function utcDayKey(d = new Date) {
95117
95106
  function buildAckDedupKey(sessionId, id, result, now = new Date) {
95118
95107
  return `${sessionId}|${id}|${result}|${utcDayKey(now)}`;
95119
95108
  }
95109
+ function filterHighConfidenceKnowledge(entries, threshold = 0.8) {
95110
+ return entries.filter((entry) => entry.confidence >= threshold);
95111
+ }
95120
95112
 
95121
95113
  // src/hooks/knowledge-application-gate.ts
95122
95114
  var HIGH_RISK_TOOLS = new Set([
@@ -95569,7 +95561,8 @@ function createKnowledgeInjectorHook(directory, config3) {
95569
95561
  currentPhase: phaseDescription
95570
95562
  };
95571
95563
  const entries = await readContextualKnowledge(directory, config3, retrievalCtx);
95572
- cachedShownIds = entries.map((e) => e.id);
95564
+ const filteredEntries = filterHighConfidenceKnowledge(entries);
95565
+ cachedShownIds = filteredEntries.map((e) => e.id);
95573
95566
  let freshPreamble = null;
95574
95567
  try {
95575
95568
  const driftReports = await readPriorDriftReports(directory);
@@ -95590,7 +95583,7 @@ function createKnowledgeInjectorHook(directory, config3) {
95590
95583
  ${freshPreamble}` : `<curator_briefing>${truncatedBriefing}</curator_briefing>`;
95591
95584
  }
95592
95585
  } catch {}
95593
- if (entries.length === 0) {
95586
+ if (filteredEntries.length === 0) {
95594
95587
  if (freshPreamble === null)
95595
95588
  return;
95596
95589
  cachedInjectionText = freshPreamble;
@@ -95601,9 +95594,9 @@ ${freshPreamble}` : `<curator_briefing>${truncatedBriefing}</curator_briefing>`;
95601
95594
  const isFullBudget = effectiveBudget === maxInjectChars;
95602
95595
  const directiveBudget = Math.floor(effectiveBudget * 0.45);
95603
95596
  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);
95597
+ 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
95598
  const directiveBlock = buildDirectiveBlock(directiveEntries, directiveBudget, config3);
95606
- const lessonBlock = buildKnowledgeBlock(entries, lessonBudget, config3, projectName);
95599
+ const lessonBlock = buildKnowledgeBlock(filteredEntries, lessonBudget, config3, projectName);
95607
95600
  const parts2 = [];
95608
95601
  let remaining = effectiveBudget;
95609
95602
  if (directiveBlock) {
@@ -95645,7 +95638,7 @@ ${freshPreamble}` : `<curator_briefing>${truncatedBriefing}</curator_briefing>`;
95645
95638
  injectKnowledgeMessage(output, cachedInjectionText);
95646
95639
  const sessionID = systemMsg?.info?.sessionID;
95647
95640
  if (sessionID) {
95648
- const criticalIds = entries.filter((e) => e.directive_priority === "critical" && e.status !== "archived").map((e) => e.id);
95641
+ const criticalIds = filteredEntries.filter((e) => e.directive_priority === "critical" && e.status !== "archived").map((e) => e.id);
95649
95642
  if (criticalIds.length > 0) {
95650
95643
  setCriticalShownIds(sessionID, {
95651
95644
  ids: criticalIds,
@@ -96179,8 +96172,12 @@ function getSkillStats(skillPath, directory) {
96179
96172
  };
96180
96173
  }
96181
96174
  function formatSkillIndexWithContext(skills, directory) {
96182
- const allEntries = readSkillUsageEntries(directory);
96183
- const hasHistory = allEntries.length > 0;
96175
+ const usageLogPath = path89.join(directory, ".swarm", "skill-usage.jsonl");
96176
+ let hasHistory = false;
96177
+ try {
96178
+ const stat7 = fs62.statSync(usageLogPath);
96179
+ hasHistory = stat7.size > 0;
96180
+ } catch {}
96184
96181
  if (!hasHistory) {
96185
96182
  return skills.map((sp) => {
96186
96183
  const meta3 = _internals42.readSkillMetadata(sp, directory);
@@ -96210,6 +96207,121 @@ _internals42.computeRecencyScore = computeRecencyScore;
96210
96207
  _internals42.computeContextMatchScore = computeContextMatchScore;
96211
96208
 
96212
96209
  // src/hooks/skill-propagation-gate.ts
96210
+ function parseSimpleYaml(content) {
96211
+ const lines = content.split(`
96212
+ `);
96213
+ const result = {};
96214
+ let _currentSection = null;
96215
+ let currentSubSection = null;
96216
+ let currentList = [];
96217
+ let currentListItem = null;
96218
+ for (const line of lines) {
96219
+ if (!line.trim() || line.trim().startsWith("#"))
96220
+ continue;
96221
+ const indent = line.search(/\S/);
96222
+ const trimmed = line.trim();
96223
+ if (indent === 0 && trimmed.endsWith(":")) {
96224
+ if (_currentSection && currentSubSection && currentList.length > 0) {
96225
+ if (!result[_currentSection]) {
96226
+ result[_currentSection] = {};
96227
+ }
96228
+ result[_currentSection][currentSubSection] = currentList;
96229
+ } else if (currentSubSection && currentList.length > 0) {
96230
+ result[currentSubSection] = currentList;
96231
+ }
96232
+ _currentSection = trimmed.slice(0, -1);
96233
+ currentSubSection = null;
96234
+ currentList = [];
96235
+ currentListItem = null;
96236
+ continue;
96237
+ }
96238
+ if (indent === 2 && trimmed.endsWith(":")) {
96239
+ if (_currentSection && currentSubSection && currentList.length > 0) {
96240
+ if (!result[_currentSection]) {
96241
+ result[_currentSection] = {};
96242
+ }
96243
+ result[_currentSection][currentSubSection] = currentList;
96244
+ } else if (currentSubSection && currentList.length > 0) {
96245
+ result[currentSubSection] = currentList;
96246
+ }
96247
+ currentSubSection = trimmed.slice(0, -1);
96248
+ currentList = [];
96249
+ currentListItem = null;
96250
+ continue;
96251
+ }
96252
+ if (trimmed.startsWith("- ")) {
96253
+ currentListItem = null;
96254
+ const rest = trimmed.slice(2);
96255
+ if (rest.includes(":")) {
96256
+ const colonIndex = rest.indexOf(":");
96257
+ const key = rest.slice(0, colonIndex).trim();
96258
+ const value = rest.slice(colonIndex + 1).trim();
96259
+ currentListItem = { [key]: parseYamlValue(value) };
96260
+ } else {
96261
+ currentListItem = { path: trimmed.slice(2) };
96262
+ }
96263
+ currentList.push(currentListItem);
96264
+ continue;
96265
+ }
96266
+ if (indent >= 4 && currentListItem) {
96267
+ if (trimmed.includes(":")) {
96268
+ const colonIndex = trimmed.indexOf(":");
96269
+ const key = trimmed.slice(0, colonIndex).trim();
96270
+ const value = trimmed.slice(colonIndex + 1).trim();
96271
+ currentListItem[key] = parseYamlValue(value);
96272
+ }
96273
+ }
96274
+ }
96275
+ if (currentSubSection && currentList.length > 0) {
96276
+ if (_currentSection) {
96277
+ if (!result[_currentSection]) {
96278
+ result[_currentSection] = {};
96279
+ }
96280
+ result[_currentSection][currentSubSection] = currentList;
96281
+ } else {
96282
+ result[currentSubSection] = currentList;
96283
+ }
96284
+ }
96285
+ return result;
96286
+ }
96287
+ function parseYamlValue(value) {
96288
+ const trimmed = value.trim();
96289
+ if (trimmed.startsWith('"') && trimmed.endsWith('"') || trimmed.startsWith("'") && trimmed.endsWith("'")) {
96290
+ return trimmed.slice(1, -1);
96291
+ }
96292
+ if (trimmed.startsWith("[") && trimmed.endsWith("]")) {
96293
+ const inner = trimmed.slice(1, -1);
96294
+ if (!inner.trim())
96295
+ return [];
96296
+ return inner.split(",").map((s) => s.trim().replace(/^["']|["']$/g, ""));
96297
+ }
96298
+ if (trimmed.toLowerCase() === "true")
96299
+ return true;
96300
+ if (trimmed.toLowerCase() === "false")
96301
+ return false;
96302
+ if (/^-?\d+(\.\d+)?$/.test(trimmed)) {
96303
+ return trimmed.includes(".") ? parseFloat(trimmed) : parseInt(trimmed, 10);
96304
+ }
96305
+ return trimmed;
96306
+ }
96307
+ function loadRoutingSkills(directory, targetAgent) {
96308
+ const routingPath = path90.join(directory, ".opencode", "skill-routing.yaml");
96309
+ if (!_internals43.existsSync(routingPath))
96310
+ return [];
96311
+ try {
96312
+ const content = _internals43.readFileSync(routingPath, "utf-8");
96313
+ const config3 = parseSimpleYaml(content);
96314
+ if (!config3?.routing)
96315
+ return [];
96316
+ const routing = config3.routing;
96317
+ const routingEntries = routing[targetAgent];
96318
+ if (!routingEntries || routingEntries.length === 0)
96319
+ return [];
96320
+ return routingEntries.map((entry) => entry.path);
96321
+ } catch {
96322
+ return [];
96323
+ }
96324
+ }
96213
96325
  var SKILL_CAPABLE_AGENTS = new Set([
96214
96326
  "coder",
96215
96327
  "reviewer",
@@ -96242,11 +96354,12 @@ var _internals43 = {
96242
96354
  appendSkillUsageEntry,
96243
96355
  readSkillUsageEntries,
96244
96356
  readSkillUsageEntriesTail,
96245
- extractSkillsFieldFromPrompt: null,
96246
96357
  parseSkillPaths: null,
96247
96358
  extractTaskIdFromPrompt: null,
96359
+ extractSkillsFieldFromPrompt: null,
96248
96360
  computeSkillRelevanceScore,
96249
- formatSkillIndexWithContext
96361
+ formatSkillIndexWithContext,
96362
+ loadRoutingSkills: null
96250
96363
  };
96251
96364
  function discoverAvailableSkills(directory) {
96252
96365
  const results = [];
@@ -96354,22 +96467,21 @@ function parseSkillPaths(fieldValue) {
96354
96467
  const trimmed = fieldValue.trim();
96355
96468
  if (trimmed.toLowerCase() === "none" || trimmed === "")
96356
96469
  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)];
96470
+ const commaParts = trimmed.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
96471
+ if (commaParts.length === 1 && commaParts[0].startsWith("- ")) {
96472
+ const newlineParts = trimmed.split(`
96473
+ `).map((s) => s.trim()).filter((s) => s.startsWith("- ")).map((s) => s.slice(2).trim());
96474
+ return newlineParts.map((s) => {
96475
+ const parenIndex = s.indexOf("(--");
96476
+ const pathOnly = parenIndex !== -1 ? s.slice(0, parenIndex).trim() : s;
96477
+ const dashIndex = pathOnly.search(/\s+[-–—]\s+/);
96478
+ return dashIndex !== -1 ? pathOnly.slice(0, dashIndex).trim() : pathOnly;
96479
+ }).filter((s) => s.length > 0);
96480
+ }
96481
+ return commaParts.map((s) => {
96482
+ const parenIndex = s.indexOf("(--");
96483
+ return parenIndex !== -1 ? s.slice(0, parenIndex).trim() : s;
96484
+ }).filter((s) => s.length > 0);
96373
96485
  }
96374
96486
  function extractTaskIdFromPrompt(prompt) {
96375
96487
  if (!prompt || typeof prompt !== "string")
@@ -96384,22 +96496,22 @@ function extractTaskIdFromPrompt(prompt) {
96384
96496
  }
96385
96497
  async function skillPropagationGateBefore(directory, input, config3) {
96386
96498
  if (!config3.enabled)
96387
- return { blocked: false, reason: null };
96499
+ return { blocked: false, reason: null, recommendedSkills: undefined };
96388
96500
  const toolName = typeof input.tool === "string" ? input.tool : "";
96389
96501
  if (toolName !== "task" && toolName !== "Task")
96390
- return { blocked: false, reason: null };
96502
+ return { blocked: false, reason: null, recommendedSkills: undefined };
96391
96503
  const agentRaw = typeof input.agent === "string" ? input.agent : "";
96392
96504
  if (!agentRaw)
96393
- return { blocked: false, reason: null };
96505
+ return { blocked: false, reason: null, recommendedSkills: undefined };
96394
96506
  const baseAgent = stripKnownSwarmPrefix(agentRaw);
96395
96507
  if (baseAgent !== "architect")
96396
- return { blocked: false, reason: null };
96508
+ return { blocked: false, reason: null, recommendedSkills: undefined };
96397
96509
  const parsed = _internals43.parseDelegationArgs(input.args);
96398
96510
  if (!parsed)
96399
- return { blocked: false, reason: null };
96511
+ return { blocked: false, reason: null, recommendedSkills: undefined };
96400
96512
  const targetBase = stripKnownSwarmPrefix(parsed.targetAgent);
96401
96513
  if (!_internals43.SKILL_CAPABLE_AGENTS.has(targetBase))
96402
- return { blocked: false, reason: null };
96514
+ return { blocked: false, reason: null, recommendedSkills: undefined };
96403
96515
  const sessionID = typeof input.sessionID === "string" ? input.sessionID : "unknown";
96404
96516
  const availableSkills = _internals43.discoverAvailableSkills(directory);
96405
96517
  const skillsValue = parsed.skillsField.trim();
@@ -96462,6 +96574,23 @@ async function skillPropagationGateBefore(directory, input, config3) {
96462
96574
  warn(`[skill-propagation-gate] skill scoring failed (non-blocking): ${err2 instanceof Error ? err2.message : String(err2)}`);
96463
96575
  }
96464
96576
  }
96577
+ try {
96578
+ const routingPaths = _internals43.loadRoutingSkills(directory, targetBase);
96579
+ if (routingPaths.length > 0) {
96580
+ const existingPaths = new Set(scored.map((s) => s.skillPath));
96581
+ for (const routingPath of routingPaths) {
96582
+ if (!existingPaths.has(routingPath)) {
96583
+ scored.push({
96584
+ skillPath: routingPath,
96585
+ score: 0.9,
96586
+ usageCount: 0
96587
+ });
96588
+ existingPaths.add(routingPath);
96589
+ }
96590
+ }
96591
+ scored.sort((a, b) => b.score - a.score || b.usageCount - a.usageCount);
96592
+ }
96593
+ } catch {}
96465
96594
  if (availableSkills.length > 0) {
96466
96595
  try {
96467
96596
  let skillsForIndex = availableSkills;
@@ -96520,14 +96649,14 @@ ${newSection}`;
96520
96649
  const coderHadSkills = skillsValue.length > 0 && skillsValue.toLowerCase() !== "none";
96521
96650
  if (!hasSkillsUsedByCoder && coderHadSkills) {
96522
96651
  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 };
96652
+ return { blocked: false, reason: message, recommendedSkills: undefined };
96524
96653
  }
96525
96654
  }
96526
96655
  if (availableSkills.length === 0)
96527
- return { blocked: false, reason: null };
96656
+ return { blocked: false, reason: null, recommendedSkills: undefined };
96528
96657
  const skillsLower = skillsValue.toLowerCase();
96529
96658
  if (skillsValue && skillsLower !== "none")
96530
- return { blocked: false, reason: null };
96659
+ return { blocked: false, reason: null, recommendedSkills: scored };
96531
96660
  const skillNames = availableSkills.map((p) => {
96532
96661
  const parts2 = p.split("/");
96533
96662
  return parts2[parts2.length - 2] ?? p;
@@ -96547,9 +96676,9 @@ ${newSection}`;
96547
96676
  } catch {}
96548
96677
  if (config3.enforce) {
96549
96678
  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 };
96679
+ return { blocked: true, reason: blockedMsg, recommendedSkills: undefined };
96551
96680
  }
96552
- return { blocked: false, reason: warningMsg };
96681
+ return { blocked: false, reason: warningMsg, recommendedSkills: undefined };
96553
96682
  }
96554
96683
  var COMPLIANCE_PATTERN = /SKILL_COMPLIANCE\s*:\s*(COMPLIANT|PARTIAL|VIOLATED)(?:\s*(?:—|-)\s*(.*))?\s*$/i;
96555
96684
  var CODER_SKILLS_PATTERN = /SKILLS_USED_BY_CODER\s*:\s*(.+)/i;
@@ -96658,10 +96787,8 @@ async function skillPropagationTransformScan(directory, output, sessionID) {
96658
96787
  continue;
96659
96788
  let currentTargetAgent = "";
96660
96789
  let skillsField = "";
96661
- const textLines = text.split(`
96662
- `);
96663
- for (let lineIndex = 0;lineIndex < textLines.length; lineIndex++) {
96664
- const line = textLines[lineIndex] ?? "";
96790
+ for (const line of text.split(`
96791
+ `)) {
96665
96792
  const trimmed = line.trim();
96666
96793
  if (trimmed.match(/TO\s+(coder|reviewer|test_engineer|sme|docs|designer)/i)) {
96667
96794
  const agentMatch = trimmed.match(/TO\s+(coder|reviewer|test_engineer|sme|docs|designer)/i);
@@ -96669,8 +96796,7 @@ async function skillPropagationTransformScan(directory, output, sessionID) {
96669
96796
  currentTargetAgent = agentMatch[1].toLowerCase();
96670
96797
  }
96671
96798
  if (trimmed.startsWith("SKILLS:")) {
96672
- skillsField = _internals43.extractSkillsFieldFromPrompt(textLines.slice(lineIndex).join(`
96673
- `));
96799
+ skillsField = trimmed.slice("SKILLS:".length).trim();
96674
96800
  }
96675
96801
  if (currentTargetAgent && skillsField && skillsField.toLowerCase() !== "none") {
96676
96802
  const skillPaths = _internals43.parseSkillPaths(skillsField);
@@ -96706,10 +96832,11 @@ _internals43.skillPropagationTransformScan = skillPropagationTransformScan;
96706
96832
  _internals43.writeWarnEvent = writeWarnEvent2;
96707
96833
  _internals43.discoverAvailableSkills = discoverAvailableSkills;
96708
96834
  _internals43.parseDelegationArgs = parseDelegationArgs;
96709
- _internals43.extractSkillsFieldFromPrompt = extractSkillsFieldFromPrompt;
96710
96835
  _internals43.parseSkillPaths = parseSkillPaths;
96711
96836
  _internals43.extractTaskIdFromPrompt = extractTaskIdFromPrompt;
96837
+ _internals43.extractSkillsFieldFromPrompt = extractSkillsFieldFromPrompt;
96712
96838
  _internals43.formatSkillIndexWithContext = formatSkillIndexWithContext;
96839
+ _internals43.loadRoutingSkills = loadRoutingSkills;
96713
96840
 
96714
96841
  // src/hooks/slop-detector.ts
96715
96842
  import * as fs64 from "node:fs";
@@ -99991,7 +100118,7 @@ import {
99991
100118
  readFileSync as readFileSync44,
99992
100119
  writeFileSync as writeFileSync17
99993
100120
  } from "node:fs";
99994
- import { join as join83 } from "node:path";
100121
+ import { join as join84 } from "node:path";
99995
100122
  var EVIDENCE_DIR2 = ".swarm/evidence";
99996
100123
  var VALID_TASK_ID = /^\d+\.\d+(\.\d+)*$/;
99997
100124
  var COUNCIL_GATE_NAME = "council";
@@ -100025,9 +100152,9 @@ function writeCouncilEvidence(workingDir, synthesis) {
100025
100152
  if (!VALID_TASK_ID.test(synthesis.taskId)) {
100026
100153
  throw new Error(`writeCouncilEvidence: invalid taskId "${synthesis.taskId}" — must match N.M or N.M.P format`);
100027
100154
  }
100028
- const dir = join83(workingDir, EVIDENCE_DIR2);
100155
+ const dir = join84(workingDir, EVIDENCE_DIR2);
100029
100156
  mkdirSync25(dir, { recursive: true });
100030
- const filePath = join83(dir, `${synthesis.taskId}.json`);
100157
+ const filePath = join84(dir, `${synthesis.taskId}.json`);
100031
100158
  const existingRoot = Object.create(null);
100032
100159
  if (existsSync53(filePath)) {
100033
100160
  try {
@@ -100061,7 +100188,7 @@ function writeCouncilEvidence(workingDir, synthesis) {
100061
100188
  updated.required_gates = [];
100062
100189
  writeFileSync17(filePath, JSON.stringify(updated, null, 2));
100063
100190
  try {
100064
- const councilDir = join83(workingDir, ".swarm", "council");
100191
+ const councilDir = join84(workingDir, ".swarm", "council");
100065
100192
  mkdirSync25(councilDir, { recursive: true });
100066
100193
  const auditLine = JSON.stringify({
100067
100194
  round: synthesis.roundNumber,
@@ -100069,7 +100196,7 @@ function writeCouncilEvidence(workingDir, synthesis) {
100069
100196
  timestamp: synthesis.timestamp,
100070
100197
  vetoedBy: synthesis.vetoedBy
100071
100198
  });
100072
- appendFileSync12(join83(councilDir, `${synthesis.taskId}.rounds.jsonl`), `${auditLine}
100199
+ appendFileSync12(join84(councilDir, `${synthesis.taskId}.rounds.jsonl`), `${auditLine}
100073
100200
  `);
100074
100201
  } catch (auditError) {
100075
100202
  console.warn(`writeCouncilEvidence: failed to append round-history audit log: ${auditError instanceof Error ? auditError.message : String(auditError)}`);
@@ -100392,20 +100519,20 @@ function buildFinalCouncilFeedback(projectSummary, verdict, vetoedBy, requiredFi
100392
100519
 
100393
100520
  // src/council/criteria-store.ts
100394
100521
  import { existsSync as existsSync54, mkdirSync as mkdirSync26, readFileSync as readFileSync45, writeFileSync as writeFileSync18 } from "node:fs";
100395
- import { join as join84 } from "node:path";
100522
+ import { join as join85 } from "node:path";
100396
100523
  var COUNCIL_DIR = ".swarm/council";
100397
100524
  function writeCriteria(workingDir, taskId, criteria) {
100398
- const dir = join84(workingDir, COUNCIL_DIR);
100525
+ const dir = join85(workingDir, COUNCIL_DIR);
100399
100526
  mkdirSync26(dir, { recursive: true });
100400
100527
  const payload = {
100401
100528
  taskId,
100402
100529
  criteria,
100403
100530
  declaredAt: new Date().toISOString()
100404
100531
  };
100405
- writeFileSync18(join84(dir, `${safeId(taskId)}.json`), JSON.stringify(payload, null, 2));
100532
+ writeFileSync18(join85(dir, `${safeId(taskId)}.json`), JSON.stringify(payload, null, 2));
100406
100533
  }
100407
100534
  function readCriteria(workingDir, taskId) {
100408
- const filePath = join84(workingDir, COUNCIL_DIR, `${safeId(taskId)}.json`);
100535
+ const filePath = join85(workingDir, COUNCIL_DIR, `${safeId(taskId)}.json`);
100409
100536
  if (!existsSync54(filePath))
100410
100537
  return null;
100411
100538
  try {
@@ -118927,6 +119054,73 @@ async function initializeOpenCodeSwarm(ctx) {
118927
119054
  skillSession.pendingAdvisoryMessages ??= [];
118928
119055
  skillSession.pendingAdvisoryMessages.push(skillResult.reason);
118929
119056
  }
119057
+ if (skillResult.recommendedSkills && skillResult.recommendedSkills.length > 0) {
119058
+ const argsRecord = input.args;
119059
+ const promptRaw = argsRecord.prompt;
119060
+ if (typeof promptRaw === "string") {
119061
+ const parsedDelegation = parseDelegationArgs(input.args);
119062
+ if (parsedDelegation) {
119063
+ const existingSkills = parsedDelegation.skillsField.trim();
119064
+ if (!existingSkills) {
119065
+ const qualified = skillResult.recommendedSkills.filter((s) => s.score >= 0.5);
119066
+ if (qualified.length === 0) {
119067
+ argsRecord.prompt = `SKILLS: none
119068
+
119069
+ ${promptRaw}`;
119070
+ console.warn("[skill-propagation-gate] No skills above threshold 0.5 — injected SKILLS: none");
119071
+ } else {
119072
+ const topSkills = qualified.slice(0, 5);
119073
+ const SKILL_DESCRIPTIONS = {
119074
+ "writing-tests": "Guidelines for writing tests",
119075
+ "engineering-conventions": "Engineering invariants and conventions",
119076
+ "running-tests": "Safe test execution patterns",
119077
+ "commit-pr": "Commit and PR workflow",
119078
+ "swarm-implement": "Swarm implementation workflow",
119079
+ "issue-tracer": "Issue investigation workflow",
119080
+ "qa-sweep": "QA sweep workflow",
119081
+ "research-first": "Research-driven approach",
119082
+ "swarm-pr-review": "PR review workflow",
119083
+ "tech-debt-ci-review": "Tech debt and CI review",
119084
+ browse: "Fast web browsing",
119085
+ code: "Expert coding workflow",
119086
+ review: "Pre-landing PR review",
119087
+ "ci-failure-resolver": "CI/CD failure resolution"
119088
+ };
119089
+ const skillPaths = topSkills.map((s) => {
119090
+ const dirName = path144.basename(path144.dirname(s.skillPath));
119091
+ const desc = SKILL_DESCRIPTIONS[dirName] ?? dirName;
119092
+ return `file:${s.skillPath} (-- ${desc})`;
119093
+ }).join(", ");
119094
+ const skillsLine = `SKILLS: ${skillPaths}`;
119095
+ const newPrompt = `${skillsLine}
119096
+
119097
+ ${promptRaw}`;
119098
+ argsRecord.prompt = newPrompt;
119099
+ const skillNames = topSkills.map((s) => `${path144.basename(s.skillPath)} (score: ${s.score.toFixed(2)})`).join(", ");
119100
+ console.warn(`[skill-propagation-gate] Injected skills: ${skillNames}`);
119101
+ for (const skill of topSkills) {
119102
+ try {
119103
+ appendSkillUsageEntry(ctx.directory, {
119104
+ skillPath: skill.skillPath,
119105
+ agentName: String(input.agent),
119106
+ taskID: "injection",
119107
+ timestamp: new Date().toISOString(),
119108
+ complianceVerdict: "not_checked",
119109
+ sessionID: input.sessionID
119110
+ });
119111
+ } catch {}
119112
+ }
119113
+ const targetAgent = parsedDelegation.targetAgent.toLowerCase();
119114
+ if (targetAgent.includes("reviewer")) {
119115
+ const usedByCoderLine = `SKILLS_USED_BY_CODER: ${topSkills.map((s) => `file:${s.skillPath}`).join(", ")}`;
119116
+ argsRecord.prompt = `${newPrompt}
119117
+ ${usedByCoderLine}`;
119118
+ }
119119
+ }
119120
+ }
119121
+ }
119122
+ }
119123
+ }
118930
119124
  if (swarmState.lastBudgetPct >= 50) {
118931
119125
  const pressureSession = ensureAgentSession(input.sessionID, swarmState.activeAgent.get(input.sessionID) ?? ORCHESTRATOR_NAME);
118932
119126
  if (!pressureSession.contextPressureWarningSent) {