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/README.md +42 -442
- package/dist/cli/index.js +18 -26
- package/dist/hooks/knowledge-application.d.ts +12 -0
- package/dist/hooks/skill-propagation-gate.d.ts +16 -4
- package/dist/index.js +331 -137
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -48,7 +48,7 @@ var package_default;
|
|
|
48
48
|
var init_package = __esm(() => {
|
|
49
49
|
package_default = {
|
|
50
50
|
name: "opencode-swarm",
|
|
51
|
-
version: "7.
|
|
51
|
+
version: "7.29.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.
|
|
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
|
|
61345
|
-
|
|
61346
|
-
|
|
61347
|
-
|
|
61348
|
-
|
|
61349
|
-
|
|
61350
|
-
|
|
61351
|
-
|
|
61352
|
-
|
|
61353
|
-
|
|
61354
|
-
|
|
61355
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
75947
|
+
### Step 2 — SKILLS: field is auto-populated
|
|
75968
75948
|
|
|
75969
|
-
|
|
75970
|
-
Route tests to test_engineer/coder; conventions to coder/reviewer/test_engineer; implementation to coder/reviewer; review/security to reviewer; docs to docs; architecture/ui/domain topics to designer or sme.
|
|
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
|
-
|
|
75951
|
+
### Step 3 — Skill references with context descriptions
|
|
75973
75952
|
|
|
75974
|
-
|
|
75953
|
+
When passing skill references, you may add brief context descriptions. The hook injects \`file:path (-- description)\` format.
|
|
75975
75954
|
|
|
75976
|
-
|
|
75955
|
+
### Step 4 — Forward SKILLS_USED_BY_CODER to reviewer
|
|
75977
75956
|
|
|
75978
|
-
|
|
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
|
-
|
|
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
|
-
**
|
|
75961
|
+
**Skill-to-agent routing:** Managed via \`.opencode/skill-routing.yaml\`. The hook reads this file at delegation time.
|
|
75992
75962
|
|
|
75993
|
-
**
|
|
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
|
|
75986
|
+
## SKILL IMPROVER
|
|
76020
75987
|
|
|
76021
|
-
|
|
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\`;
|
|
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,
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
-
|
|
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
|
-
-
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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 (
|
|
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 =
|
|
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(
|
|
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 =
|
|
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
|
|
96183
|
-
|
|
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
|
|
96358
|
-
|
|
96359
|
-
|
|
96360
|
-
|
|
96361
|
-
|
|
96362
|
-
|
|
96363
|
-
|
|
96364
|
-
|
|
96365
|
-
|
|
96366
|
-
|
|
96367
|
-
|
|
96368
|
-
|
|
96369
|
-
|
|
96370
|
-
|
|
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
|
|
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 =
|
|
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
|
|
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 =
|
|
100155
|
+
const dir = join84(workingDir, EVIDENCE_DIR2);
|
|
100029
100156
|
mkdirSync25(dir, { recursive: true });
|
|
100030
|
-
const filePath =
|
|
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 =
|
|
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(
|
|
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
|
|
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 =
|
|
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(
|
|
100532
|
+
writeFileSync18(join85(dir, `${safeId(taskId)}.json`), JSON.stringify(payload, null, 2));
|
|
100406
100533
|
}
|
|
100407
100534
|
function readCriteria(workingDir, taskId) {
|
|
100408
|
-
const filePath =
|
|
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) {
|