opencode-swarm 7.28.2 → 7.29.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/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.2",
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",
@@ -16518,6 +16518,27 @@ function bunHash(input) {
16518
16518
  }
16519
16519
  return hash2;
16520
16520
  }
16521
+ function killProcessTreeImpl(pid, signal, directKill, wasDetached) {
16522
+ if (typeof pid !== "number" || pid <= 0) {
16523
+ directKill();
16524
+ return;
16525
+ }
16526
+ if (process.platform === "win32") {
16527
+ try {
16528
+ nodeSpawnSync("taskkill", ["/PID", String(pid), "/T", "/F"]);
16529
+ } catch {
16530
+ directKill();
16531
+ }
16532
+ return;
16533
+ }
16534
+ if (wasDetached) {
16535
+ try {
16536
+ process.kill(-pid, signal ?? "SIGKILL");
16537
+ return;
16538
+ } catch {}
16539
+ }
16540
+ directKill();
16541
+ }
16521
16542
  function streamFromNode(pipe2) {
16522
16543
  const collected = new Promise((resolve) => {
16523
16544
  if (!pipe2) {
@@ -16640,20 +16661,34 @@ function bunSpawn(cmd, options) {
16640
16661
  return proc2.exitCode;
16641
16662
  },
16642
16663
  kill(sig) {
16643
- proc2.kill(sig);
16664
+ if (options?.killProcessTree) {
16665
+ killProcessTreeImpl(proc2.pid, sig, () => proc2.kill(sig), false);
16666
+ } else {
16667
+ proc2.kill(sig);
16668
+ }
16644
16669
  }
16645
16670
  };
16646
16671
  }
16647
16672
  const [file2, ...args2] = cmd;
16673
+ const detached = options?.killProcessTree === true;
16648
16674
  const proc = nodeSpawn(file2, args2, {
16649
16675
  cwd: options?.cwd,
16650
16676
  env: options?.env,
16677
+ detached,
16678
+ windowsHide: true,
16651
16679
  stdio: [
16652
16680
  mapStdio(options?.stdin),
16653
16681
  mapStdio(options?.stdout),
16654
16682
  mapStdio(options?.stderr)
16655
16683
  ]
16656
16684
  });
16685
+ const killChild = (signal) => {
16686
+ if (detached) {
16687
+ killProcessTreeImpl(proc.pid, signal, () => proc.kill(signal), true);
16688
+ } else {
16689
+ proc.kill(signal);
16690
+ }
16691
+ };
16657
16692
  let timeoutHandle;
16658
16693
  const exited = new Promise((resolve) => {
16659
16694
  proc.on("exit", (code) => resolve(code ?? 0));
@@ -16661,7 +16696,7 @@ function bunSpawn(cmd, options) {
16661
16696
  if (options?.timeout && options.timeout > 0) {
16662
16697
  timeoutHandle = setTimeout(() => {
16663
16698
  try {
16664
- proc.kill("SIGKILL");
16699
+ killChild("SIGKILL");
16665
16700
  } catch {}
16666
16701
  }, options.timeout);
16667
16702
  if (typeof timeoutHandle.unref === "function") {
@@ -16681,7 +16716,7 @@ function bunSpawn(cmd, options) {
16681
16716
  },
16682
16717
  kill(signal) {
16683
16718
  try {
16684
- proc.kill(signal);
16719
+ killChild(signal);
16685
16720
  } catch {}
16686
16721
  }
16687
16722
  };
@@ -67348,7 +67383,7 @@ function defaultBuildTestCommand(profile, framework, files, dir = ".", opts = {}
67348
67383
  const coverage = opts.coverage ?? false;
67349
67384
  switch (framework) {
67350
67385
  case "bun": {
67351
- const args2 = ["bun", "test"];
67386
+ const args2 = ["bun", "--smol", "test"];
67352
67387
  if (coverage)
67353
67388
  args2.push("--coverage");
67354
67389
  if (scope !== "all" && files.length > 0)
@@ -69781,7 +69816,7 @@ function getTargetedExecutionUnsupportedReason(framework) {
69781
69816
  function buildTestCommand2(framework, scope, files, coverage, baseDir) {
69782
69817
  switch (framework) {
69783
69818
  case "bun": {
69784
- const args2 = ["bun", "test"];
69819
+ const args2 = ["bun", "--smol", "test"];
69785
69820
  if (coverage)
69786
69821
  args2.push("--coverage");
69787
69822
  if (scope !== "all" && files.length > 0) {
@@ -70318,17 +70353,24 @@ async function runTests(framework, scope, files, coverage, timeout_ms, cwd) {
70318
70353
  const proc = bunSpawn(command, {
70319
70354
  stdout: "pipe",
70320
70355
  stderr: "pipe",
70321
- cwd
70356
+ stdin: "ignore",
70357
+ cwd,
70358
+ killProcessTree: true
70359
+ });
70360
+ let timeoutHandle;
70361
+ const timeoutPromise = new Promise((resolve16) => {
70362
+ timeoutHandle = setTimeout(() => {
70363
+ proc.kill();
70364
+ resolve16(-1);
70365
+ }, timeout_ms);
70322
70366
  });
70323
- const timeoutPromise = new Promise((resolve16) => setTimeout(() => {
70324
- proc.kill();
70325
- resolve16(-1);
70326
- }, timeout_ms));
70327
70367
  const [exitCode, stdoutResult, stderrResult] = await Promise.all([
70328
70368
  Promise.race([proc.exited, timeoutPromise]),
70329
70369
  readBoundedStream(proc.stdout, MAX_OUTPUT_BYTES3),
70330
70370
  readBoundedStream(proc.stderr, MAX_OUTPUT_BYTES3)
70331
70371
  ]);
70372
+ if (timeoutHandle !== undefined)
70373
+ clearTimeout(timeoutHandle);
70332
70374
  const duration_ms = Date.now() - startTime;
70333
70375
  let output = stdoutResult.text;
70334
70376
  if (stderrResult.text) {
@@ -70631,7 +70673,6 @@ var init_test_runner = __esm(() => {
70631
70673
  files: exports_external.array(exports_external.string()).optional().describe('Specific files to test. For "convention", pass source files or direct test files. For "graph" and "impact", pass source files only.'),
70632
70674
  coverage: exports_external.boolean().optional().describe("Enable coverage reporting if supported"),
70633
70675
  timeout_ms: exports_external.number().optional().describe("Timeout in milliseconds (default 60000, max 300000)"),
70634
- allow_full_suite: exports_external.boolean().optional().describe('Explicit opt-in for scope "all". Required because full-suite output can destabilize SSE streaming.'),
70635
70676
  working_directory: exports_external.string().optional().describe("Explicit project root directory. When provided, tests run relative to this path instead of the plugin context directory. Use this when CWD differs from the actual project root.")
70636
70677
  },
70637
70678
  async execute(args2, directory) {
@@ -70705,7 +70746,8 @@ var init_test_runner = __esm(() => {
70705
70746
  }
70706
70747
  const scope = args2.scope || "all";
70707
70748
  if (scope === "all") {
70708
- if (!args2.allow_full_suite) {
70749
+ const fullSuiteAllowed = process.env.SWARM_ALLOW_FULL_SUITE === "1" || process.env.SWARM_ALLOW_FULL_SUITE === "true";
70750
+ if (!fullSuiteAllowed) {
70709
70751
  const errorResult = {
70710
70752
  success: false,
70711
70753
  framework: "none",
@@ -75902,58 +75944,31 @@ SECURITY_KEYWORDS: password, secret, token, credential, auth, login, encryption,
75902
75944
 
75903
75945
  ## SKILLS PROPAGATION
75904
75946
 
75905
- 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.
75906
-
75907
- ### Step 1 — Discover available skills (once per session)
75908
-
75909
- At session start, before your first delegation:
75910
- 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.
75911
- 2. Reuse \`<skill-context>\` skills when present.
75912
- 3. Otherwise use \`search\` with \`include\` patterns like \`.opencode/skills/*/SKILL.md,.claude/skills/*/SKILL.md\` and frontmatter queries \`^name:\` / \`^description:\`.
75913
- 4. Cache a short \`.swarm/context.md\` \`## Available Skills\` index. Include the repo-relative \`file:\` path and description so subagents can load on demand:
75914
- - writing-tests: Guidelines for writing tests (used: 12, compliance: 95%) → test_engineer, coder
75915
- - engineering-conventions: Engineering invariants (used: 8, compliance: 100%) → coder, reviewer, test_engineer
75916
- - [name]: [description] (used: N, compliance: N%) → [applicable agents]
75917
-
75918
- Use \`.swarm/skill-usage.jsonl\` when present; otherwise weight skills equally.
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.
75919
75948
 
75920
- If skill-usage.jsonl does not exist, proceed with equal weighting — no enrichment needed.
75949
+ ### Step 1 Skills are auto-discovered and scored
75921
75950
 
75951
+ The hook system discovers available skills and scores them by relevance to the task. The hook auto-injects them into the delegation prompt.
75922
75952
 
75923
- ### Step 2 — Route skills to agents
75953
+ ### Step 2 — SKILLS: field is auto-populated
75924
75954
 
75925
- Include a skill when its name/description matches:
75955
+ The hook auto-populates the \`SKILLS:\` field with top recommended skills (max 5, threshold 0.5). Explicit \`SKILLS: none\` is preserved.
75926
75956
 
75927
- | Skill description / name contains… | Pass to agents… |
75928
- 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.
75957
+ ### Step 3 Skill references with context descriptions
75929
75958
 
75930
- When uncertain, pass the skill. Missing applicable skills cause convention drift.
75959
+ When passing skill references, you may add brief context descriptions. The hook injects \`file:path (-- description)\` format.
75931
75960
 
75932
- ### Step 3Include skill references in delegations
75961
+ ### Step 4Forward SKILLS_USED_BY_CODER to reviewer
75933
75962
 
75934
- Add \`SKILLS:\` to every coder, reviewer, test_engineer, sme, docs, and designer delegation:
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.
75935
75964
 
75936
- - \`SKILLS: none\` only when no project-specific skill applies to that delegation
75937
- - \`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
75938
- - Descriptive multi-line catalog:
75939
- SKILLS:
75940
- - file:.claude/skills/writing-tests/SKILL.md - Guidelines for writing tests
75941
- - file:.claude/skills/engineering-conventions/SKILL.md - Engineering invariants for safe changes
75942
- - Inline fallback:
75943
- SKILLS:
75944
- --- [skill-name] ---
75945
- [full SKILL.md body content pasted here]
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.
75946
75966
 
75947
- 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\`.
75967
+ **Skill-to-agent routing:** Managed via \`.opencode/skill-routing.yaml\`. The hook reads this file at delegation time.
75948
75968
 
75949
- **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.
75950
-
75951
- **Mandatory for coding tasks:** provide \`writing-tests\` to test_engineer and \`engineering-conventions\` to coder + reviewer when present.
75952
-
75953
- ### Step 4 — Forward skills to reviewer
75954
-
75955
- 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.
75956
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.
75957
75972
 
75958
75973
  ## SWARM KNOWLEDGE DIRECTIVES (v2 acknowledgment contract)
75959
75974
 
@@ -75976,14 +75991,28 @@ You may also call the \`knowledge_ack\` tool to record an outcome explicitly whe
75976
75991
 
75977
75992
  ## SKILL IMPROVER (low-frequency, expensive-model adviser)
75978
75993
 
75979
- 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.
75980
76002
 
75981
76003
  When \`skill_improver.require_user_approval\` is true (default), ASK the user
75982
76004
  before running. Default outputs are proposals only — they never modify source.
75983
76005
 
75984
76006
  ## SPEC WRITER
75985
76007
 
75986
- 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.
75987
76016
 
75988
76017
  ### ANTI-RATIONALIZATION
75989
76018
  - ✗ "The coder already knows these conventions" → Skills contain project-specific rules the model cannot know from training. Always pass.
@@ -75996,7 +76025,7 @@ For substantial spec authoring/revision, prefer \`spec_writer\`; it writes only
75996
76025
  ## SLASH COMMANDS
75997
76026
  {{SLASH_COMMANDS}}
75998
76027
  Commands above are documented with args and behavioral details. Run commands via /swarm <command> [args].
75999
- 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.
76000
76029
 
76001
76030
  SMEs advise only. Reviewer and critic review only. None of them write code.
76002
76031
 
@@ -76004,15 +76033,15 @@ Available Tools: {{AVAILABLE_TOOLS}}
76004
76033
 
76005
76034
  ## DELEGATION FORMAT
76006
76035
 
76007
- 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.
76008
76037
 
76009
- 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.
76010
76039
  Do NOT add conversational preamble before the agent prefix. Begin directly with the agent name.
76011
76040
 
76012
76041
  {{AGENT_PREFIX}}[agent]
76013
76042
  TASK: [single objective]
76014
76043
  [agent-specific fields required by that agent's INPUT FORMAT]
76015
- 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]
76016
76045
 
76017
76046
  Examples:
76018
76047
 
@@ -76045,22 +76074,22 @@ FILE: src/auth/login.ts
76045
76074
  INPUT: Validate email format, password >= 8 chars
76046
76075
  OUTPUT: Modified file
76047
76076
  CONSTRAINT: Do not modify other functions
76048
- SKILLS: file:.claude/skills/engineering-conventions/SKILL.md - safe code changes
76077
+ SKILLS: file:.claude/skills/engineering-conventions/SKILL.md
76049
76078
 
76050
76079
  {{AGENT_PREFIX}}reviewer
76051
76080
  TASK: Review login validation
76052
76081
  FILE: src/auth/login.ts
76053
76082
  CHECK: [security, correctness, edge-cases]
76054
76083
  GATES: lint=PASS, sast_scan=PASS, secretscan=PASS
76055
- 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
76056
76085
  OUTPUT: VERDICT + RISK + ISSUES
76057
- SKILLS: file:.claude/skills/engineering-conventions/SKILL.md - safe code changes
76086
+ SKILLS: file:.claude/skills/engineering-conventions/SKILL.md
76058
76087
 
76059
76088
  {{AGENT_PREFIX}}test_engineer
76060
76089
  TASK: Generate and run login validation tests
76061
76090
  FILE: src/auth/login.ts
76062
76091
  OUTPUT: Test file at src/auth/login.test.ts + VERDICT: PASS/FAIL with failure details
76063
- SKILLS: file:.claude/skills/writing-tests/SKILL.md - tests
76092
+ SKILLS: file:.claude/skills/writing-tests/SKILL.md
76064
76093
 
76065
76094
  {{AGENT_PREFIX}}critic
76066
76095
  TASK: Review plan for user authentication feature
@@ -76075,14 +76104,14 @@ FILE: src/auth/login.ts
76075
76104
  CHECK: [security-only] — evaluate against OWASP Top 10, scan for hardcoded secrets, injection vectors, insecure crypto, missing input validation
76076
76105
  GATES: lint=PASS, sast_scan=PASS, secretscan=PASS
76077
76106
  OUTPUT: VERDICT + RISK + SECURITY ISSUES ONLY
76078
- SKILLS: file:.claude/skills/engineering-conventions/SKILL.md - safe code changes
76107
+ SKILLS: file:.claude/skills/engineering-conventions/SKILL.md
76079
76108
 
76080
76109
  {{AGENT_PREFIX}}test_engineer
76081
76110
  TASK: Adversarial security testing
76082
76111
  FILE: src/auth/login.ts
76083
76112
  CONSTRAINT: ONLY attack vectors — malformed inputs, oversized payloads, injection attempts, auth bypass, boundary violations
76084
76113
  OUTPUT: Test file + VERDICT: PASS/FAIL
76085
- SKILLS: file:.claude/skills/writing-tests/SKILL.md - tests
76114
+ SKILLS: file:.claude/skills/writing-tests/SKILL.md
76086
76115
 
76087
76116
  {{AGENT_PREFIX}}explorer
76088
76117
  TASK: Integration impact analysis
@@ -95075,6 +95104,9 @@ function utcDayKey(d = new Date) {
95075
95104
  function buildAckDedupKey(sessionId, id, result, now = new Date) {
95076
95105
  return `${sessionId}|${id}|${result}|${utcDayKey(now)}`;
95077
95106
  }
95107
+ function filterHighConfidenceKnowledge(entries, threshold = 0.8) {
95108
+ return entries.filter((entry) => entry.confidence >= threshold);
95109
+ }
95078
95110
 
95079
95111
  // src/hooks/knowledge-application-gate.ts
95080
95112
  var HIGH_RISK_TOOLS = new Set([
@@ -95527,7 +95559,8 @@ function createKnowledgeInjectorHook(directory, config3) {
95527
95559
  currentPhase: phaseDescription
95528
95560
  };
95529
95561
  const entries = await readContextualKnowledge(directory, config3, retrievalCtx);
95530
- cachedShownIds = entries.map((e) => e.id);
95562
+ const filteredEntries = filterHighConfidenceKnowledge(entries);
95563
+ cachedShownIds = filteredEntries.map((e) => e.id);
95531
95564
  let freshPreamble = null;
95532
95565
  try {
95533
95566
  const driftReports = await readPriorDriftReports(directory);
@@ -95548,7 +95581,7 @@ function createKnowledgeInjectorHook(directory, config3) {
95548
95581
  ${freshPreamble}` : `<curator_briefing>${truncatedBriefing}</curator_briefing>`;
95549
95582
  }
95550
95583
  } catch {}
95551
- if (entries.length === 0) {
95584
+ if (filteredEntries.length === 0) {
95552
95585
  if (freshPreamble === null)
95553
95586
  return;
95554
95587
  cachedInjectionText = freshPreamble;
@@ -95559,9 +95592,9 @@ ${freshPreamble}` : `<curator_briefing>${truncatedBriefing}</curator_briefing>`;
95559
95592
  const isFullBudget = effectiveBudget === maxInjectChars;
95560
95593
  const directiveBudget = Math.floor(effectiveBudget * 0.45);
95561
95594
  const lessonBudget = Math.floor(effectiveBudget * 0.3);
95562
- 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);
95563
95596
  const directiveBlock = buildDirectiveBlock(directiveEntries, directiveBudget, config3);
95564
- const lessonBlock = buildKnowledgeBlock(entries, lessonBudget, config3, projectName);
95597
+ const lessonBlock = buildKnowledgeBlock(filteredEntries, lessonBudget, config3, projectName);
95565
95598
  const parts2 = [];
95566
95599
  let remaining = effectiveBudget;
95567
95600
  if (directiveBlock) {
@@ -95603,7 +95636,7 @@ ${freshPreamble}` : `<curator_briefing>${truncatedBriefing}</curator_briefing>`;
95603
95636
  injectKnowledgeMessage(output, cachedInjectionText);
95604
95637
  const sessionID = systemMsg?.info?.sessionID;
95605
95638
  if (sessionID) {
95606
- 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);
95607
95640
  if (criticalIds.length > 0) {
95608
95641
  setCriticalShownIds(sessionID, {
95609
95642
  ids: criticalIds,
@@ -96137,8 +96170,12 @@ function getSkillStats(skillPath, directory) {
96137
96170
  };
96138
96171
  }
96139
96172
  function formatSkillIndexWithContext(skills, directory) {
96140
- const allEntries = readSkillUsageEntries(directory);
96141
- 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 {}
96142
96179
  if (!hasHistory) {
96143
96180
  return skills.map((sp) => {
96144
96181
  const meta3 = _internals42.readSkillMetadata(sp, directory);
@@ -96168,6 +96205,121 @@ _internals42.computeRecencyScore = computeRecencyScore;
96168
96205
  _internals42.computeContextMatchScore = computeContextMatchScore;
96169
96206
 
96170
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
+ }
96171
96323
  var SKILL_CAPABLE_AGENTS = new Set([
96172
96324
  "coder",
96173
96325
  "reviewer",
@@ -96200,11 +96352,12 @@ var _internals43 = {
96200
96352
  appendSkillUsageEntry,
96201
96353
  readSkillUsageEntries,
96202
96354
  readSkillUsageEntriesTail,
96203
- extractSkillsFieldFromPrompt: null,
96204
96355
  parseSkillPaths: null,
96205
96356
  extractTaskIdFromPrompt: null,
96357
+ extractSkillsFieldFromPrompt: null,
96206
96358
  computeSkillRelevanceScore,
96207
- formatSkillIndexWithContext
96359
+ formatSkillIndexWithContext,
96360
+ loadRoutingSkills: null
96208
96361
  };
96209
96362
  function discoverAvailableSkills(directory) {
96210
96363
  const results = [];
@@ -96312,22 +96465,21 @@ function parseSkillPaths(fieldValue) {
96312
96465
  const trimmed = fieldValue.trim();
96313
96466
  if (trimmed.toLowerCase() === "none" || trimmed === "")
96314
96467
  return [];
96315
- const lines = trimmed.split(/\r?\n/);
96316
- const hasCatalogLines = lines.some((line) => /^(?:-|\*|\d+\.)\s+/.test(line.trim()));
96317
- const parts2 = hasCatalogLines ? lines : trimmed.split(",");
96318
- const paths = [];
96319
- for (const rawPart of parts2) {
96320
- const part = rawPart.trim().replace(/^(?:-|\*|\d+\.)\s+/, "").trim();
96321
- if (!part || part.toLowerCase() === "none")
96322
- continue;
96323
- const fileRef = part.match(/\bfile:[^\s,;)\]]+/);
96324
- if (fileRef) {
96325
- paths.push(fileRef[0].replace(/\\/g, "/"));
96326
- continue;
96327
- }
96328
- paths.push(part);
96329
- }
96330
- 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);
96331
96483
  }
96332
96484
  function extractTaskIdFromPrompt(prompt) {
96333
96485
  if (!prompt || typeof prompt !== "string")
@@ -96342,22 +96494,22 @@ function extractTaskIdFromPrompt(prompt) {
96342
96494
  }
96343
96495
  async function skillPropagationGateBefore(directory, input, config3) {
96344
96496
  if (!config3.enabled)
96345
- return { blocked: false, reason: null };
96497
+ return { blocked: false, reason: null, recommendedSkills: undefined };
96346
96498
  const toolName = typeof input.tool === "string" ? input.tool : "";
96347
96499
  if (toolName !== "task" && toolName !== "Task")
96348
- return { blocked: false, reason: null };
96500
+ return { blocked: false, reason: null, recommendedSkills: undefined };
96349
96501
  const agentRaw = typeof input.agent === "string" ? input.agent : "";
96350
96502
  if (!agentRaw)
96351
- return { blocked: false, reason: null };
96503
+ return { blocked: false, reason: null, recommendedSkills: undefined };
96352
96504
  const baseAgent = stripKnownSwarmPrefix(agentRaw);
96353
96505
  if (baseAgent !== "architect")
96354
- return { blocked: false, reason: null };
96506
+ return { blocked: false, reason: null, recommendedSkills: undefined };
96355
96507
  const parsed = _internals43.parseDelegationArgs(input.args);
96356
96508
  if (!parsed)
96357
- return { blocked: false, reason: null };
96509
+ return { blocked: false, reason: null, recommendedSkills: undefined };
96358
96510
  const targetBase = stripKnownSwarmPrefix(parsed.targetAgent);
96359
96511
  if (!_internals43.SKILL_CAPABLE_AGENTS.has(targetBase))
96360
- return { blocked: false, reason: null };
96512
+ return { blocked: false, reason: null, recommendedSkills: undefined };
96361
96513
  const sessionID = typeof input.sessionID === "string" ? input.sessionID : "unknown";
96362
96514
  const availableSkills = _internals43.discoverAvailableSkills(directory);
96363
96515
  const skillsValue = parsed.skillsField.trim();
@@ -96420,6 +96572,23 @@ async function skillPropagationGateBefore(directory, input, config3) {
96420
96572
  warn(`[skill-propagation-gate] skill scoring failed (non-blocking): ${err2 instanceof Error ? err2.message : String(err2)}`);
96421
96573
  }
96422
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 {}
96423
96592
  if (availableSkills.length > 0) {
96424
96593
  try {
96425
96594
  let skillsForIndex = availableSkills;
@@ -96478,14 +96647,14 @@ ${newSection}`;
96478
96647
  const coderHadSkills = skillsValue.length > 0 && skillsValue.toLowerCase() !== "none";
96479
96648
  if (!hasSkillsUsedByCoder && coderHadSkills) {
96480
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.`;
96481
- return { blocked: false, reason: message };
96650
+ return { blocked: false, reason: message, recommendedSkills: undefined };
96482
96651
  }
96483
96652
  }
96484
96653
  if (availableSkills.length === 0)
96485
- return { blocked: false, reason: null };
96654
+ return { blocked: false, reason: null, recommendedSkills: undefined };
96486
96655
  const skillsLower = skillsValue.toLowerCase();
96487
96656
  if (skillsValue && skillsLower !== "none")
96488
- return { blocked: false, reason: null };
96657
+ return { blocked: false, reason: null, recommendedSkills: scored };
96489
96658
  const skillNames = availableSkills.map((p) => {
96490
96659
  const parts2 = p.split("/");
96491
96660
  return parts2[parts2.length - 2] ?? p;
@@ -96505,9 +96674,9 @@ ${newSection}`;
96505
96674
  } catch {}
96506
96675
  if (config3.enforce) {
96507
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.`;
96508
- return { blocked: true, reason: blockedMsg };
96677
+ return { blocked: true, reason: blockedMsg, recommendedSkills: undefined };
96509
96678
  }
96510
- return { blocked: false, reason: warningMsg };
96679
+ return { blocked: false, reason: warningMsg, recommendedSkills: undefined };
96511
96680
  }
96512
96681
  var COMPLIANCE_PATTERN = /SKILL_COMPLIANCE\s*:\s*(COMPLIANT|PARTIAL|VIOLATED)(?:\s*(?:—|-)\s*(.*))?\s*$/i;
96513
96682
  var CODER_SKILLS_PATTERN = /SKILLS_USED_BY_CODER\s*:\s*(.+)/i;
@@ -96616,10 +96785,8 @@ async function skillPropagationTransformScan(directory, output, sessionID) {
96616
96785
  continue;
96617
96786
  let currentTargetAgent = "";
96618
96787
  let skillsField = "";
96619
- const textLines = text.split(`
96620
- `);
96621
- for (let lineIndex = 0;lineIndex < textLines.length; lineIndex++) {
96622
- const line = textLines[lineIndex] ?? "";
96788
+ for (const line of text.split(`
96789
+ `)) {
96623
96790
  const trimmed = line.trim();
96624
96791
  if (trimmed.match(/TO\s+(coder|reviewer|test_engineer|sme|docs|designer)/i)) {
96625
96792
  const agentMatch = trimmed.match(/TO\s+(coder|reviewer|test_engineer|sme|docs|designer)/i);
@@ -96627,8 +96794,7 @@ async function skillPropagationTransformScan(directory, output, sessionID) {
96627
96794
  currentTargetAgent = agentMatch[1].toLowerCase();
96628
96795
  }
96629
96796
  if (trimmed.startsWith("SKILLS:")) {
96630
- skillsField = _internals43.extractSkillsFieldFromPrompt(textLines.slice(lineIndex).join(`
96631
- `));
96797
+ skillsField = trimmed.slice("SKILLS:".length).trim();
96632
96798
  }
96633
96799
  if (currentTargetAgent && skillsField && skillsField.toLowerCase() !== "none") {
96634
96800
  const skillPaths = _internals43.parseSkillPaths(skillsField);
@@ -96664,10 +96830,11 @@ _internals43.skillPropagationTransformScan = skillPropagationTransformScan;
96664
96830
  _internals43.writeWarnEvent = writeWarnEvent2;
96665
96831
  _internals43.discoverAvailableSkills = discoverAvailableSkills;
96666
96832
  _internals43.parseDelegationArgs = parseDelegationArgs;
96667
- _internals43.extractSkillsFieldFromPrompt = extractSkillsFieldFromPrompt;
96668
96833
  _internals43.parseSkillPaths = parseSkillPaths;
96669
96834
  _internals43.extractTaskIdFromPrompt = extractTaskIdFromPrompt;
96835
+ _internals43.extractSkillsFieldFromPrompt = extractSkillsFieldFromPrompt;
96670
96836
  _internals43.formatSkillIndexWithContext = formatSkillIndexWithContext;
96837
+ _internals43.loadRoutingSkills = loadRoutingSkills;
96671
96838
 
96672
96839
  // src/hooks/slop-detector.ts
96673
96840
  import * as fs64 from "node:fs";
@@ -99949,7 +100116,7 @@ import {
99949
100116
  readFileSync as readFileSync44,
99950
100117
  writeFileSync as writeFileSync17
99951
100118
  } from "node:fs";
99952
- import { join as join83 } from "node:path";
100119
+ import { join as join84 } from "node:path";
99953
100120
  var EVIDENCE_DIR2 = ".swarm/evidence";
99954
100121
  var VALID_TASK_ID = /^\d+\.\d+(\.\d+)*$/;
99955
100122
  var COUNCIL_GATE_NAME = "council";
@@ -99983,9 +100150,9 @@ function writeCouncilEvidence(workingDir, synthesis) {
99983
100150
  if (!VALID_TASK_ID.test(synthesis.taskId)) {
99984
100151
  throw new Error(`writeCouncilEvidence: invalid taskId "${synthesis.taskId}" — must match N.M or N.M.P format`);
99985
100152
  }
99986
- const dir = join83(workingDir, EVIDENCE_DIR2);
100153
+ const dir = join84(workingDir, EVIDENCE_DIR2);
99987
100154
  mkdirSync25(dir, { recursive: true });
99988
- const filePath = join83(dir, `${synthesis.taskId}.json`);
100155
+ const filePath = join84(dir, `${synthesis.taskId}.json`);
99989
100156
  const existingRoot = Object.create(null);
99990
100157
  if (existsSync53(filePath)) {
99991
100158
  try {
@@ -100019,7 +100186,7 @@ function writeCouncilEvidence(workingDir, synthesis) {
100019
100186
  updated.required_gates = [];
100020
100187
  writeFileSync17(filePath, JSON.stringify(updated, null, 2));
100021
100188
  try {
100022
- const councilDir = join83(workingDir, ".swarm", "council");
100189
+ const councilDir = join84(workingDir, ".swarm", "council");
100023
100190
  mkdirSync25(councilDir, { recursive: true });
100024
100191
  const auditLine = JSON.stringify({
100025
100192
  round: synthesis.roundNumber,
@@ -100027,7 +100194,7 @@ function writeCouncilEvidence(workingDir, synthesis) {
100027
100194
  timestamp: synthesis.timestamp,
100028
100195
  vetoedBy: synthesis.vetoedBy
100029
100196
  });
100030
- appendFileSync12(join83(councilDir, `${synthesis.taskId}.rounds.jsonl`), `${auditLine}
100197
+ appendFileSync12(join84(councilDir, `${synthesis.taskId}.rounds.jsonl`), `${auditLine}
100031
100198
  `);
100032
100199
  } catch (auditError) {
100033
100200
  console.warn(`writeCouncilEvidence: failed to append round-history audit log: ${auditError instanceof Error ? auditError.message : String(auditError)}`);
@@ -100350,20 +100517,20 @@ function buildFinalCouncilFeedback(projectSummary, verdict, vetoedBy, requiredFi
100350
100517
 
100351
100518
  // src/council/criteria-store.ts
100352
100519
  import { existsSync as existsSync54, mkdirSync as mkdirSync26, readFileSync as readFileSync45, writeFileSync as writeFileSync18 } from "node:fs";
100353
- import { join as join84 } from "node:path";
100520
+ import { join as join85 } from "node:path";
100354
100521
  var COUNCIL_DIR = ".swarm/council";
100355
100522
  function writeCriteria(workingDir, taskId, criteria) {
100356
- const dir = join84(workingDir, COUNCIL_DIR);
100523
+ const dir = join85(workingDir, COUNCIL_DIR);
100357
100524
  mkdirSync26(dir, { recursive: true });
100358
100525
  const payload = {
100359
100526
  taskId,
100360
100527
  criteria,
100361
100528
  declaredAt: new Date().toISOString()
100362
100529
  };
100363
- writeFileSync18(join84(dir, `${safeId(taskId)}.json`), JSON.stringify(payload, null, 2));
100530
+ writeFileSync18(join85(dir, `${safeId(taskId)}.json`), JSON.stringify(payload, null, 2));
100364
100531
  }
100365
100532
  function readCriteria(workingDir, taskId) {
100366
- const filePath = join84(workingDir, COUNCIL_DIR, `${safeId(taskId)}.json`);
100533
+ const filePath = join85(workingDir, COUNCIL_DIR, `${safeId(taskId)}.json`);
100367
100534
  if (!existsSync54(filePath))
100368
100535
  return null;
100369
100536
  try {
@@ -118885,6 +119052,73 @@ async function initializeOpenCodeSwarm(ctx) {
118885
119052
  skillSession.pendingAdvisoryMessages ??= [];
118886
119053
  skillSession.pendingAdvisoryMessages.push(skillResult.reason);
118887
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
+ }
118888
119122
  if (swarmState.lastBudgetPct >= 50) {
118889
119123
  const pressureSession = ensureAgentSession(input.sessionID, swarmState.activeAgent.get(input.sessionID) ?? ORCHESTRATOR_NAME);
118890
119124
  if (!pressureSession.contextPressureWarningSent) {