opencode-swarm 7.112.2 → 7.113.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/.opencode/skills/brainstorm/SKILL.md +1 -0
- package/.opencode/skills/ci-failure-batching/SKILL.md +2 -1
- package/.opencode/skills/ci-fix-monitor/SKILL.md +203 -0
- package/.opencode/skills/clarify/SKILL.md +1 -0
- package/.opencode/skills/clarify-spec/SKILL.md +1 -0
- package/.opencode/skills/codebase-review-swarm/SKILL.md +1 -0
- package/.opencode/skills/commit-pr/SKILL.md +15 -0
- package/.opencode/skills/consult/SKILL.md +1 -0
- package/.opencode/skills/council/SKILL.md +1 -0
- package/.opencode/skills/critic-gate/SKILL.md +1 -0
- package/.opencode/skills/deep-dive/SKILL.md +1 -0
- package/.opencode/skills/deep-research/SKILL.md +1 -0
- package/.opencode/skills/design-docs/SKILL.md +2 -1
- package/.opencode/skills/discover/SKILL.md +1 -0
- package/.opencode/skills/engineering-conventions/SKILL.md +2 -1
- package/.opencode/skills/execute/SKILL.md +1 -0
- package/.opencode/skills/fork-pr-operations/SKILL.md +136 -0
- package/.opencode/skills/gate-attribution/SKILL.md +2 -1
- package/.opencode/skills/issue-ingest/SKILL.md +1 -0
- package/.opencode/skills/loop/SKILL.md +7 -6
- package/.opencode/skills/merge-queue-readiness/SKILL.md +2 -1
- package/.opencode/skills/parallel-work-check/SKILL.md +133 -0
- package/.opencode/skills/phase-wrap/SKILL.md +1 -0
- package/.opencode/skills/plan/SKILL.md +1 -0
- package/.opencode/skills/pre-phase-briefing/SKILL.md +1 -0
- package/.opencode/skills/resume/SKILL.md +1 -0
- package/.opencode/skills/running-tests/SKILL.md +1 -0
- package/.opencode/skills/skill-edit-validation/SKILL.md +2 -1
- package/.opencode/skills/specify/SKILL.md +1 -0
- package/.opencode/skills/swarm/SKILL.md +1 -0
- package/.opencode/skills/swarm-ci-monitor/SKILL.md +5 -4
- package/.opencode/skills/swarm-implement/SKILL.md +4 -4
- package/.opencode/skills/swarm-pr-feedback/SKILL.md +26 -5
- package/.opencode/skills/swarm-pr-review/SKILL.md +2 -1
- package/.opencode/skills/swarm-pr-subscribe/SKILL.md +1 -0
- package/.opencode/skills/test-file-split/SKILL.md +102 -0
- package/.opencode/skills/worktree-retry-cleanup/SKILL.md +2 -1
- package/.opencode/skills/writing-tests/SKILL.md +43 -0
- package/dist/cli/{config-doctor-mmw4c50z.js → config-doctor-az11cwf5.js} +2 -2
- package/dist/cli/{curator-llm-factory-5bns4xy6.js → curator-llm-factory-nmhzc774.js} +5 -5
- package/dist/cli/{curator-snvp9542.js → curator-nm2c9y1p.js} +5 -5
- package/dist/cli/{guardrail-explain-4s7djf9p.js → guardrail-explain-q1np1xaw.js} +6 -6
- package/dist/cli/{guardrail-log-xjgr00eq.js → guardrail-log-hysxdwg9.js} +3 -3
- package/dist/cli/{hive-promoter-zs295trk.js → hive-promoter-z4dezw62.js} +5 -5
- package/dist/cli/{index-xehrmy76.js → index-0gf6de8b.js} +1 -1
- package/dist/cli/{index-cq2hgvd2.js → index-1ey3dxq7.js} +9 -1
- package/dist/cli/{index-npxmfj48.js → index-8f256d7t.js} +1 -1
- package/dist/cli/{index-82v0d72f.js → index-h389ed8y.js} +424 -87
- package/dist/cli/{index-9fjpecax.js → index-ky8eb4q6.js} +2 -2
- package/dist/cli/{index-snmfqhez.js → index-npd9rypp.js} +2009 -958
- package/dist/cli/{index-w84stmkx.js → index-r9fjzr3k.js} +1 -1
- package/dist/cli/{index-k6wd3chp.js → index-rqpm9cwe.js} +7 -7
- package/dist/cli/index.js +5 -5
- package/dist/cli/{schema-2p15yrvn.js → schema-qb313fyy.js} +1 -1
- package/dist/cli/{skill-generator-fe6q8xgm.js → skill-generator-nnwn4sq0.js} +1 -1
- package/dist/commands/loop.d.ts +1 -1
- package/dist/commands/registry.d.ts +1 -1
- package/dist/config/bundled-skills.d.ts +15 -5
- package/dist/config/project-init.d.ts +1 -1
- package/dist/config/schema.d.ts +2 -0
- package/dist/hooks/skill-propagation-gate.d.ts +45 -5
- package/dist/hooks/skill-scoring.d.ts +22 -1
- package/dist/index.js +88 -88
- package/dist/sast/rules/index.d.ts +0 -9
- package/dist/utils/gitignore-warning.d.ts +8 -4
- package/package.json +5 -1
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: parallel-work-check
|
|
3
|
+
audience: swarm-plugin
|
|
4
|
+
description: >
|
|
5
|
+
Apply before starting work on an existing branch. Checks for parallel work by
|
|
6
|
+
other agents or developers that may supersede or conflict with your planned
|
|
7
|
+
changes. Prevents wasted effort on stale branches.
|
|
8
|
+
effort: small
|
|
9
|
+
generated_from_knowledge: []
|
|
10
|
+
source_knowledge_ids: ['f07c1f4d-9bb0-4219-9804-26aa8efe8146']
|
|
11
|
+
generated_at: 2026-06-14T16:50:00Z
|
|
12
|
+
confidence: 0.8
|
|
13
|
+
status: active
|
|
14
|
+
version: 4
|
|
15
|
+
skill_origin: generated
|
|
16
|
+
provenance_note: >
|
|
17
|
+
Re-linked to current knowledge entries (version 4). The original source ID
|
|
18
|
+
b8fee776... is no longer present in the active knowledge store. The skill
|
|
19
|
+
body and behavior are unchanged; only source_knowledge_ids metadata was
|
|
20
|
+
updated to point to the current lesson about verifying pre-existing state
|
|
21
|
+
on parent commit, which is directly relevant to the parallel-work-check
|
|
22
|
+
protocol.
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
# Parallel Work Check Protocol
|
|
26
|
+
|
|
27
|
+
Run this check before starting ANY work on an existing branch (not a fresh branch
|
|
28
|
+
you just created). This applies to PR branches, feature branches, and any branch
|
|
29
|
+
that may have concurrent contributors.
|
|
30
|
+
|
|
31
|
+
## Step 1 — Check current branch state
|
|
32
|
+
|
|
33
|
+
1. Determine the current branch name.
|
|
34
|
+
2. Determine the remote tracking branch (usually `origin/<branch-name>`).
|
|
35
|
+
|
|
36
|
+
## Step 2 — Fetch remote state
|
|
37
|
+
|
|
38
|
+
Fetch the latest state from the remote for the current branch. Do NOT skip this
|
|
39
|
+
step because "the branch looks recent" or "I just checked."
|
|
40
|
+
|
|
41
|
+
## Step 3 — Compare local vs remote
|
|
42
|
+
|
|
43
|
+
Compare the local HEAD commit hash with the remote HEAD commit hash:
|
|
44
|
+
|
|
45
|
+
- **Identical**: Remote has not diverged. Proceed with your work.
|
|
46
|
+
- **Remote ahead**: The remote branch has commits you don't have locally.
|
|
47
|
+
- Read the new commit messages with `git log local..remote`.
|
|
48
|
+
- Check if any of those commits touch files you plan to modify.
|
|
49
|
+
- If yes: evaluate whether the parallel work supersedes your planned changes.
|
|
50
|
+
- If the parallel work is superior: reset your local branch to match remote
|
|
51
|
+
and abandon your planned approach. Document the decision.
|
|
52
|
+
- If the parallel work is complementary: integrate it first, then proceed.
|
|
53
|
+
- **Local ahead**: You have local commits not on remote. This is normal if you
|
|
54
|
+
already started work. Proceed, but be aware that pushing may conflict with
|
|
55
|
+
subsequent remote changes.
|
|
56
|
+
- **Diverged**: Both local and remote have unique commits. This requires
|
|
57
|
+
integration. Merge or rebase as appropriate for the team's workflow.
|
|
58
|
+
|
|
59
|
+
## Step 4 — Check for parallel swarm/agent work
|
|
60
|
+
|
|
61
|
+
If the remote has new commits:
|
|
62
|
+
|
|
63
|
+
1. Check the commit authors. If commits are from a different swarm/agent
|
|
64
|
+
(different author name/email pattern), treat this as parallel swarm work.
|
|
65
|
+
2. Parallel swarm work is often superior because:
|
|
66
|
+
- It may have access to different context or tools
|
|
67
|
+
- It may have started earlier or had more iterations
|
|
68
|
+
- It may have taken a fundamentally better approach
|
|
69
|
+
3. Default stance: **prefer the parallel swarm's work** unless you can clearly
|
|
70
|
+
articulate why your approach is better.
|
|
71
|
+
|
|
72
|
+
## Step 5 — Decision and documentation
|
|
73
|
+
|
|
74
|
+
Before proceeding, document your decision:
|
|
75
|
+
|
|
76
|
+
```
|
|
77
|
+
PARALLEL WORK CHECK:
|
|
78
|
+
- Branch: <name>
|
|
79
|
+
- Local HEAD: <hash> <message>
|
|
80
|
+
- Remote HEAD: <hash> <message>
|
|
81
|
+
- Diverged: yes/no
|
|
82
|
+
- New commits on remote: <count>
|
|
83
|
+
- Parallel swarm work detected: yes/no
|
|
84
|
+
- Decision: [proceed / integrate-then-proceed / abandon-use-remote / needs-review]
|
|
85
|
+
- Rationale: <one sentence>
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## Anti-patterns — do NOT do these
|
|
89
|
+
|
|
90
|
+
- Skip the fetch because "I'm sure nothing changed."
|
|
91
|
+
- Ignore remote commits because "my approach is probably better."
|
|
92
|
+
- Start fixing code without checking if the remote already fixed it.
|
|
93
|
+
- Blindly overwrite remote work with local changes without evaluating first.
|
|
94
|
+
|
|
95
|
+
## Example: parallel swarm superseded local work
|
|
96
|
+
|
|
97
|
+
```
|
|
98
|
+
PARALLEL WORK CHECK:
|
|
99
|
+
- Branch: codex/issue-956-plan-completion-gate
|
|
100
|
+
- Local HEAD: 5aa34f88 fix(delegation-gate): block next task until completion is persisted
|
|
101
|
+
- Remote HEAD: 2b4e9266 fix: correct contradictory test title/comments
|
|
102
|
+
- Diverged: yes (remote is 8 commits ahead)
|
|
103
|
+
- New commits on remote: 8
|
|
104
|
+
- Parallel swarm work detected: yes (different commit author)
|
|
105
|
+
- Decision: abandon-use-remote
|
|
106
|
+
- Rationale: Parallel swarm restored file from main and re-integrated cleanly,
|
|
107
|
+
producing 486 passing tests vs our incremental patching which left 38 failures.
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
## Integration with swarm workflow
|
|
111
|
+
|
|
112
|
+
This check should run:
|
|
113
|
+
- At session start (MODE: RESUME or MODE: EXECUTE)
|
|
114
|
+
- Before creating a new plan for an existing branch
|
|
115
|
+
- Before dispatching the first coder task
|
|
116
|
+
- After any significant pause where parallel work could have occurred
|
|
117
|
+
|
|
118
|
+
## Integration with other skills
|
|
119
|
+
|
|
120
|
+
The parallel-work-check skill is referenced by other skills that start work on an existing branch:
|
|
121
|
+
|
|
122
|
+
| Skill | Usage |
|
|
123
|
+
|-------|-------|
|
|
124
|
+
| `file:.swarm/bundled-skills/swarm-pr-feedback/SKILL.md` | Checks before starting PR feedback fixes — ensures no parallel work has already addressed the same findings |
|
|
125
|
+
| Legacy `pr-review-fix` alias | Compatibility entry that delegates to the bundled `swarm-pr-feedback` protocol |
|
|
126
|
+
| `file:.swarm/bundled-skills/swarm-implement/SKILL.md` | Checks before implementation Phase 1 — ensures the branch is up-to-date before planning |
|
|
127
|
+
| Any skill that starts work on an existing branch | Run the parallel-work-check protocol before beginning fixes or implementation |
|
|
128
|
+
|
|
129
|
+
When a skill references parallel-work-check, the checking agent must:
|
|
130
|
+
1. Fetch and compare remote vs local state
|
|
131
|
+
2. Read any new commits from parallel work
|
|
132
|
+
3. Evaluate whether the parallel work supersedes, complements, or does not affect the planned work
|
|
133
|
+
4. Document the decision using the PARALLEL WORK CHECK template
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: swarm
|
|
3
|
+
audience: swarm-plugin
|
|
3
4
|
description: Cross-agent swarm-mode behavior model — a higher-rigor workflow using parallel investigation, independent reviewer validation, and critic challenge, plus the mandatory implementation closeout gate. Runtime adapters (.claude, .agents) add execution-specific notes and command wiring.
|
|
4
5
|
---
|
|
5
6
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: swarm-ci-monitor
|
|
3
|
+
audience: swarm-plugin
|
|
3
4
|
description: >
|
|
4
5
|
End-to-end CI monitor that takes an already-human-reviewed PR, exhaustively
|
|
5
6
|
researches every CI failure, fixes it end-to-end, iterates until all required
|
|
@@ -33,7 +34,7 @@ invoking user is the source of truth: only invoke after review is done.
|
|
|
33
34
|
|
|
34
35
|
Load these skills before doing anything destructive (push / merge):
|
|
35
36
|
|
|
36
|
-
-
|
|
37
|
+
- `file:.swarm/bundled-skills/ci-fix-monitor/SKILL.md` — for failure
|
|
37
38
|
classification and the per-type fix recipes (package-check, rebase,
|
|
38
39
|
format/lint, macOS file I/O, integration, security, smoke). Do not re-derive
|
|
39
40
|
these recipes here; ci-fix-monitor owns them.
|
|
@@ -94,9 +95,9 @@ Only after all three gates pass, enter the loop.
|
|
|
94
95
|
Maintain an iteration counter starting at 5 (decremented at the end of each
|
|
95
96
|
fix-push cycle, in 2g — this is a hard safety gate, not a soft target). At 0,
|
|
96
97
|
stop (Step 5). This loop can span multiple CI runs and several minutes per
|
|
97
|
-
iteration; if the session may compact mid-loop,
|
|
98
|
-
|
|
99
|
-
|
|
98
|
+
iteration; if the session may compact mid-loop, record the remaining count in
|
|
99
|
+
the active durable task/plan checkpoint and reload it on resume. Never reset
|
|
100
|
+
the counter to 5 after compaction.
|
|
100
101
|
|
|
101
102
|
### 2a. Fetch check runs for the PR head SHA
|
|
102
103
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: swarm-implement
|
|
3
|
+
audience: swarm-plugin
|
|
3
4
|
description: Execute complex implementation work with a swarm-like workflow: parallel exploration, scoped planning, objective validation, mandatory independent implementation review for changed work, and final critic approval. Use for feature work, bug fixes, refactors, and multi-file changes.
|
|
4
5
|
disable-model-invocation: true
|
|
5
6
|
---
|
|
@@ -58,10 +59,9 @@ before coding.
|
|
|
58
59
|
|
|
59
60
|
### Phase 0a - Parallel work check
|
|
60
61
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
branch:
|
|
62
|
+
Load and follow
|
|
63
|
+
`file:.swarm/bundled-skills/parallel-work-check/SKILL.md`. Then, before
|
|
64
|
+
starting implementation on an existing branch:
|
|
65
65
|
|
|
66
66
|
1. Fetch remote state and compare with local (`git fetch` plus HEAD hashes).
|
|
67
67
|
2. If parallel swarm work is detected on the target branch, read the new commits,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: swarm-pr-feedback
|
|
3
|
+
audience: swarm-plugin
|
|
3
4
|
description: >
|
|
4
5
|
Ingest and resolve known pull request feedback with skeptical source verification.
|
|
5
6
|
Use when addressing pasted PR feedback, GitHub review comments or threads,
|
|
@@ -183,8 +184,8 @@ tree:
|
|
|
183
184
|
|
|
184
185
|
- If `head_ref` is a remote branch that is not checked out locally, fetch it
|
|
185
186
|
(`git fetch origin <head_ref>`).
|
|
186
|
-
- **Check for parallel work first.** Before checkout, run
|
|
187
|
-
|
|
187
|
+
- **Check for parallel work first.** Before checkout, run
|
|
188
|
+
`file:.swarm/bundled-skills/parallel-work-check/SKILL.md` to
|
|
188
189
|
detect concurrent pushes from other agents (e.g., `hermes-pr-review` bot
|
|
189
190
|
following up, maintainer pushing fixes, parallel swarm work). If remote has new
|
|
190
191
|
commits: read `git log local..remote`, evaluate whether the parallel work
|
|
@@ -512,9 +513,29 @@ or compatibility policy, mark the item `NEEDS_USER_DECISION` and ask.
|
|
|
512
513
|
- When `main` has a merge queue enabled, do not rebase or force-push a PR only
|
|
513
514
|
because `main` advanced. Once required checks and review are green, queue the PR
|
|
514
515
|
and let the merge queue perform final current-base validation. Still resolve real
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
516
|
+
merge conflicts and SHA-dependent review threads before queuing.
|
|
517
|
+
|
|
518
|
+
### Operational Gotchas
|
|
519
|
+
|
|
520
|
+
- **Plan identity change:** When switching from a review plan to a feedback-closure
|
|
521
|
+
plan, `save_plan` rejects with `PLAN_IDENTITY_MISMATCH`. Pass
|
|
522
|
+
`confirm_identity_change: true` to acknowledge the intentional overwrite.
|
|
523
|
+
- **Stale gate evidence:** After a plan identity change, `check_gate_status` returns
|
|
524
|
+
timestamps from the *prior* plan. Reset task statuses and re-run Stage A gates
|
|
525
|
+
before trusting gate results. Do not accept cached gate verdicts from before the
|
|
526
|
+
identity change.
|
|
527
|
+
- **PowerShell PR comment posting:** Complex markdown bodies containing backticks,
|
|
528
|
+
dollar signs, or nested quotes fail in PowerShell here-strings. Write the body
|
|
529
|
+
to a temp file and use `gh pr comment <number> --body-file <tempfile>` instead
|
|
530
|
+
of inline `--body "..."`.
|
|
531
|
+
- **Same-file batching:** Multiple findings targeting the same file for the same
|
|
532
|
+
review cycle CAN be fixed in one coder task when the fixes are trivially
|
|
533
|
+
independent (e.g., a one-line guard and a typo fix). When findings require
|
|
534
|
+
different fixes on different code paths, use separate coder tasks even if
|
|
535
|
+
targeting the same file. The "ONE task per coder" rule is about distinct
|
|
536
|
+
objectives, not about N edits to one file.
|
|
537
|
+
|
|
538
|
+
## Mandatory Gates
|
|
518
539
|
|
|
519
540
|
**Stage A and Stage B gates and the reviewer + critic closeout gate are
|
|
520
541
|
MANDATORY for any change made as part of the PR-feedback process.** No fix
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: swarm-pr-review
|
|
3
|
+
audience: swarm-plugin
|
|
3
4
|
description: Run a graph-guided, tool-augmented Swarm PR review using context packing, parallel exploration, triggered plugin micro-lanes, independent reviewer validation, critic challenge, and metrics writeback. Use for deep pull request review with low false-positive tolerance and high recall.
|
|
4
5
|
disable-model-invocation: true
|
|
5
6
|
---
|
|
@@ -308,7 +309,7 @@ For each new commit on the remote:
|
|
|
308
309
|
- Does the remote commit introduce a new branch-state fact the handoff should
|
|
309
310
|
mention?
|
|
310
311
|
3. **Default stance: prefer the remote state as the next baseline.** Run
|
|
311
|
-
|
|
312
|
+
`file:.swarm/bundled-skills/parallel-work-check/SKILL.md`
|
|
312
313
|
protocol for the formal decision template and record the outcome in the
|
|
313
314
|
handoff artifact.
|
|
314
315
|
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: test-file-split
|
|
3
|
+
audience: swarm-plugin
|
|
4
|
+
description: Protocol for splitting test files that exceed the FR-006 500-line hard limit. Covers describe-block extraction, shared helper management, pure-function extraction, mock isolation verification, and cascading-split detection. Load when a test file approaches or exceeds 500 lines.
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Test File Split Protocol (FR-006)
|
|
8
|
+
|
|
9
|
+
CI enforces a **hard 500-line limit** per test file (FR-006 / SC-006.1). Files exceeding this limit fail the quality gate and block PR merge. This skill covers the complete splitting protocol.
|
|
10
|
+
|
|
11
|
+
Read first: `.opencode/skills/writing-tests/SKILL.md` (or `.claude/skills/writing-tests/SKILL.md`) for bun:test framework rules, mock isolation patterns, and file placement conventions.
|
|
12
|
+
|
|
13
|
+
## When to use this skill
|
|
14
|
+
|
|
15
|
+
- A test file exceeds or approaches 500 lines
|
|
16
|
+
- CI fails with an FR-006 file-size violation
|
|
17
|
+
- You are adding tests to a file that is already above 400 lines (proactive split)
|
|
18
|
+
|
|
19
|
+
## Step 1 — Measure and identify split boundaries
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
# Check the file
|
|
23
|
+
wc -l tests/unit/scripts/my-module.test.ts
|
|
24
|
+
|
|
25
|
+
# Find all test files exceeding 400 lines (early warning)
|
|
26
|
+
find tests/ -name "*.test.ts" -exec wc -l {} \; | sort -rn | awk '$1 > 400'
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Identify natural `describe()` block boundaries. Group blocks by functional area:
|
|
30
|
+
- Each `describe()` block should belong to exactly one split file
|
|
31
|
+
- Shared `beforeEach`/`afterEach` hooks determine which blocks must stay together
|
|
32
|
+
|
|
33
|
+
## Step 2 — Choose a suffix for the new file
|
|
34
|
+
|
|
35
|
+
| Pattern | Example | When to use |
|
|
36
|
+
|---------|---------|-------------|
|
|
37
|
+
| `<module>-<area>.test.ts` | `release-notes-fragments-sha.test.ts` | Split by functional area (SHA resolution, validation, merge logic) |
|
|
38
|
+
| `<module>-<area>.adversarial.test.ts` | `auth-login.adversarial.test.ts` | Split adversarial tests into their own file |
|
|
39
|
+
|
|
40
|
+
## Step 3 — Manage shared imports and helpers
|
|
41
|
+
|
|
42
|
+
Three options, in order of preference:
|
|
43
|
+
|
|
44
|
+
1. **Extract to shared utility (preferred for complex shared setup):**
|
|
45
|
+
Create `tests/helpers/<module>-shared.ts` with shared fixtures, mock factories, and setup functions. Import from both split files.
|
|
46
|
+
|
|
47
|
+
2. **Duplicate simple imports (for small overlap):**
|
|
48
|
+
If only `bun:test` imports and 1-2 source imports are shared, duplicate them in both files. Simpler than a utility module for trivial cases.
|
|
49
|
+
|
|
50
|
+
3. **Extract pure functions from source (for testability):**
|
|
51
|
+
If the source module has inline validation logic, extract them as exported pure functions (e.g., `isValidPrNumber`, `resolveAllCandidates`) so both test files can target them independently. See the PR #1762 example below. See `.opencode/skills/generated/safe-extraction/SKILL.md` for the source extraction pattern.
|
|
52
|
+
|
|
53
|
+
## Step 4 — Extract and move describe blocks
|
|
54
|
+
|
|
55
|
+
1. Cut the selected `describe()` blocks from the original file.
|
|
56
|
+
2. Paste them into the new file.
|
|
57
|
+
3. Add all necessary imports to the new file.
|
|
58
|
+
4. Remove now-unused imports from the original file.
|
|
59
|
+
|
|
60
|
+
## Step 5 — Verify both files
|
|
61
|
+
|
|
62
|
+
### Line count check
|
|
63
|
+
```bash
|
|
64
|
+
wc -l tests/unit/scripts/my-module.test.ts tests/unit/scripts/my-module-sha.test.ts
|
|
65
|
+
# Both must be under 500 lines
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### Isolated run
|
|
69
|
+
```bash
|
|
70
|
+
bun --smol test tests/unit/scripts/my-module.test.ts --timeout 60000
|
|
71
|
+
bun --smol test tests/unit/scripts/my-module-sha.test.ts --timeout 60000
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### Co-run (mock isolation verification)
|
|
75
|
+
```bash
|
|
76
|
+
# Critical: Bun shares a single process across test files.
|
|
77
|
+
# mock.module leaks can cause co-run failures even when isolated runs pass.
|
|
78
|
+
bun --smol test tests/unit/scripts/my-module*.test.ts --timeout 60000
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
If the co-run fails but isolated runs pass, check for `mock.module()` leakage. See `.opencode/skills/writing-tests/SKILL.md` → "Mock Isolation Rules" and the `_internals` DI seam pattern.
|
|
82
|
+
|
|
83
|
+
## Step 6 — Evaluate `_test_exports` opportunity
|
|
84
|
+
|
|
85
|
+
After splitting, evaluate whether internal utility functions in the source module can be exported via `_test_exports` for zero-mock testing. This is a natural cleanup moment — the split already forces you to review test coverage boundaries.
|
|
86
|
+
|
|
87
|
+
## Cascading split warning
|
|
88
|
+
|
|
89
|
+
If a previously split file exceeds 500 lines **again**, the test suite is structurally too large for a single module. Do not split a third time — reorganize the tests by source module boundaries instead. Repeated splitting produces fragmented test suites that are hard to navigate and maintain.
|
|
90
|
+
|
|
91
|
+
## Real-world example (PR #1762 — pending merge)
|
|
92
|
+
|
|
93
|
+
> **Note:** This example references functions and files from PR #1762's branch. These do not exist on `main` until the PR is merged. The pattern is still valid as an illustration of the splitting protocol.
|
|
94
|
+
|
|
95
|
+
`tests/unit/scripts/release-notes-fragments.test.ts` exceeded 500 lines. It was split into:
|
|
96
|
+
|
|
97
|
+
| File | Lines | Content |
|
|
98
|
+
|------|-------|---------|
|
|
99
|
+
| `release-notes-fragments.test.ts` | 379 | Fragment collection, deduplication, output formatting |
|
|
100
|
+
| `release-notes-fragments-sha.test.ts` | 261 | `extractCommitShasFromBody`, `mergeCandidateLists`, `resolveAllCandidates`, `isValidPrNumber`, `stripCustomReleaseNotesBlock` |
|
|
101
|
+
|
|
102
|
+
The split also extracted `isValidPrNumber` and `resolveAllCandidates` as pure exported functions from the source module, enabling independent testing.
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: writing-tests
|
|
3
|
+
audience: swarm-plugin
|
|
3
4
|
description: >
|
|
4
5
|
Guidelines for writing, organizing, and maintaining tests in the opencode-swarm repository.
|
|
5
6
|
Covers framework rules (bun:test), mock isolation, CI pipeline structure, file placement,
|
|
@@ -610,6 +611,48 @@ authority checks:
|
|
|
610
611
|
- For matcher caches or other shared state, test both priming orders when the
|
|
611
612
|
selected behavior depends on mode, platform, or prior calls.
|
|
612
613
|
|
|
614
|
+
## FR-006: Test File Size Limit (500 lines)
|
|
615
|
+
|
|
616
|
+
CI enforces a **hard 500-line limit** per test file (FR-006). Files exceeding this limit fail the quality gate and block PR merge.
|
|
617
|
+
|
|
618
|
+
### Checking file length
|
|
619
|
+
|
|
620
|
+
```bash
|
|
621
|
+
# Check a single file
|
|
622
|
+
wc -l tests/unit/scripts/my-test.test.ts
|
|
623
|
+
|
|
624
|
+
# Find all test files exceeding 400 lines (early warning threshold)
|
|
625
|
+
find tests/ -name "*.test.ts" -exec wc -l {} \; | sort -rn | awk '$1 > 400'
|
|
626
|
+
```
|
|
627
|
+
|
|
628
|
+
### Splitting pattern
|
|
629
|
+
|
|
630
|
+
When a test file approaches or exceeds 500 lines, split it by extracting cohesive `describe()` blocks into a new file with a descriptive suffix:
|
|
631
|
+
|
|
632
|
+
1. **Identify natural boundaries.** Group `describe()` blocks by functional area (e.g., SHA resolution, validation, merge logic).
|
|
633
|
+
2. **Create the new file** with a descriptive suffix: `<module>-<area>.test.ts` (e.g., `release-notes-fragments-sha.test.ts`, `release-notes-fragments-validation.test.ts`).
|
|
634
|
+
3. **Move shared imports and helpers.** Either:
|
|
635
|
+
- Duplicate shared imports in both files (simple, for small overlap), OR
|
|
636
|
+
- Extract shared test helpers to a utility module (e.g., `tests/helpers/<module>-shared.ts`) and import from both files (preferred for complex shared setup).
|
|
637
|
+
4. **Extract testable pure functions.** If the source module has inline validation logic (e.g., `isValidPrNumber`), extract it as an exported pure function so both test files can target it independently.
|
|
638
|
+
5. **Verify both files are under 500 lines.**
|
|
639
|
+
6. **Run both files independently AND co-run** to verify no mock isolation breakage:
|
|
640
|
+
```bash
|
|
641
|
+
bun --smol test tests/unit/scripts/release-notes-fragments.test.ts --timeout 60000
|
|
642
|
+
bun --smol test tests/unit/scripts/release-notes-fragments-sha.test.ts --timeout 60000
|
|
643
|
+
bun --smol test tests/unit/scripts/release-notes-fragments*.test.ts --timeout 60000
|
|
644
|
+
```
|
|
645
|
+
|
|
646
|
+
### When to split vs refactor
|
|
647
|
+
|
|
648
|
+
- **Split** when there are natural `describe()` boundaries (e.g., one file per functional area).
|
|
649
|
+
- **Refactor** when the file is a single monolithic test with no clear boundaries — consolidate test logic instead.
|
|
650
|
+
- **Warning:** If a previously split file exceeds 500 lines again, the test suite is structurally too large. Reorganize by module rather than continuing to split.
|
|
651
|
+
|
|
652
|
+
### Reference
|
|
653
|
+
|
|
654
|
+
See PR #1762 for a real-world example: `release-notes-fragments.test.ts` was split into `release-notes-fragments.test.ts` (379 lines) + `release-notes-fragments-sha.test.ts` (261 lines).
|
|
655
|
+
|
|
613
656
|
## Cross-Entry Invariants (config maps)
|
|
614
657
|
|
|
615
658
|
When you modify any entry of a "map of agents/tools/roles" in `src/config/constants.ts` (`AGENT_TOOL_MAP`, `DEFAULT_MODELS`, `QA_AGENTS`, `PIPELINE_AGENTS`, etc.) or tool-name registration in `src/tools/tool-names.ts`, there are tests that assert **parity across sibling entries**, not just shape of one entry.
|
|
@@ -12,8 +12,8 @@ import {
|
|
|
12
12
|
shouldRunOnStartup,
|
|
13
13
|
writeBackupArtifact,
|
|
14
14
|
writeDoctorArtifact
|
|
15
|
-
} from "./index-
|
|
16
|
-
import"./index-
|
|
15
|
+
} from "./index-0gf6de8b.js";
|
|
16
|
+
import"./index-1ey3dxq7.js";
|
|
17
17
|
import"./index-bfwt3abw.js";
|
|
18
18
|
import"./index-5e4e2hvv.js";
|
|
19
19
|
import"./index-p0arc26j.js";
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
// @bun
|
|
2
2
|
import {
|
|
3
3
|
createCuratorLLMDelegate
|
|
4
|
-
} from "./index-
|
|
5
|
-
import"./index-
|
|
4
|
+
} from "./index-npd9rypp.js";
|
|
5
|
+
import"./index-8f256d7t.js";
|
|
6
6
|
import"./index-c8s9a3zh.js";
|
|
7
|
-
import"./index-
|
|
8
|
-
import"./index-
|
|
7
|
+
import"./index-0gf6de8b.js";
|
|
8
|
+
import"./index-1ey3dxq7.js";
|
|
9
9
|
import"./index-d0w40jm3.js";
|
|
10
10
|
import"./index-scww5b77.js";
|
|
11
11
|
import"./index-9fxs0rm1.js";
|
|
12
12
|
import"./index-q1jaeynn.js";
|
|
13
|
-
import"./index-
|
|
13
|
+
import"./index-h389ed8y.js";
|
|
14
14
|
import"./index-30ejh1tx.js";
|
|
15
15
|
import"./index-t6wfwdm1.js";
|
|
16
16
|
import"./index-hpp2r0sd.js";
|
|
@@ -12,16 +12,16 @@ import {
|
|
|
12
12
|
runCuratorInit,
|
|
13
13
|
runCuratorPhase,
|
|
14
14
|
writeCuratorSummary
|
|
15
|
-
} from "./index-
|
|
16
|
-
import"./index-
|
|
15
|
+
} from "./index-npd9rypp.js";
|
|
16
|
+
import"./index-8f256d7t.js";
|
|
17
17
|
import"./index-c8s9a3zh.js";
|
|
18
|
-
import"./index-
|
|
19
|
-
import"./index-
|
|
18
|
+
import"./index-0gf6de8b.js";
|
|
19
|
+
import"./index-1ey3dxq7.js";
|
|
20
20
|
import"./index-d0w40jm3.js";
|
|
21
21
|
import"./index-scww5b77.js";
|
|
22
22
|
import"./index-9fxs0rm1.js";
|
|
23
23
|
import"./index-q1jaeynn.js";
|
|
24
|
-
import"./index-
|
|
24
|
+
import"./index-h389ed8y.js";
|
|
25
25
|
import"./index-30ejh1tx.js";
|
|
26
26
|
import"./index-t6wfwdm1.js";
|
|
27
27
|
import"./index-hpp2r0sd.js";
|
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
// @bun
|
|
2
2
|
import {
|
|
3
3
|
handleGuardrailExplain
|
|
4
|
-
} from "./index-
|
|
5
|
-
import"./index-
|
|
6
|
-
import"./index-
|
|
4
|
+
} from "./index-ky8eb4q6.js";
|
|
5
|
+
import"./index-npd9rypp.js";
|
|
6
|
+
import"./index-8f256d7t.js";
|
|
7
7
|
import"./index-c8s9a3zh.js";
|
|
8
|
-
import"./index-
|
|
9
|
-
import"./index-
|
|
8
|
+
import"./index-0gf6de8b.js";
|
|
9
|
+
import"./index-1ey3dxq7.js";
|
|
10
10
|
import"./index-d0w40jm3.js";
|
|
11
11
|
import"./index-scww5b77.js";
|
|
12
12
|
import"./index-9fxs0rm1.js";
|
|
13
13
|
import"./index-q1jaeynn.js";
|
|
14
|
-
import"./index-
|
|
14
|
+
import"./index-h389ed8y.js";
|
|
15
15
|
import"./index-30ejh1tx.js";
|
|
16
16
|
import"./index-t6wfwdm1.js";
|
|
17
17
|
import"./index-hpp2r0sd.js";
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
// @bun
|
|
2
2
|
import {
|
|
3
3
|
handleGuardrailLog
|
|
4
|
-
} from "./index-
|
|
5
|
-
import"./index-
|
|
6
|
-
import"./index-
|
|
4
|
+
} from "./index-r9fjzr3k.js";
|
|
5
|
+
import"./index-8f256d7t.js";
|
|
6
|
+
import"./index-1ey3dxq7.js";
|
|
7
7
|
import"./index-bfwt3abw.js";
|
|
8
8
|
import"./index-5e4e2hvv.js";
|
|
9
9
|
import"./index-p0arc26j.js";
|
|
@@ -5,16 +5,16 @@ import {
|
|
|
5
5
|
isHiveEligible,
|
|
6
6
|
promoteFromSwarm,
|
|
7
7
|
promoteToHive
|
|
8
|
-
} from "./index-
|
|
9
|
-
import"./index-
|
|
8
|
+
} from "./index-npd9rypp.js";
|
|
9
|
+
import"./index-8f256d7t.js";
|
|
10
10
|
import"./index-c8s9a3zh.js";
|
|
11
|
-
import"./index-
|
|
12
|
-
import"./index-
|
|
11
|
+
import"./index-0gf6de8b.js";
|
|
12
|
+
import"./index-1ey3dxq7.js";
|
|
13
13
|
import"./index-d0w40jm3.js";
|
|
14
14
|
import"./index-scww5b77.js";
|
|
15
15
|
import"./index-9fxs0rm1.js";
|
|
16
16
|
import"./index-q1jaeynn.js";
|
|
17
|
-
import"./index-
|
|
17
|
+
import"./index-h389ed8y.js";
|
|
18
18
|
import"./index-30ejh1tx.js";
|
|
19
19
|
import"./index-t6wfwdm1.js";
|
|
20
20
|
import"./index-hpp2r0sd.js";
|
|
@@ -1839,9 +1839,17 @@ var KnowledgeApplicationConfigSchema = exports_external.object({
|
|
|
1839
1839
|
"Task"
|
|
1840
1840
|
])
|
|
1841
1841
|
});
|
|
1842
|
+
var SkillPropagationAudienceSchema = exports_external.string().max(64).refine((value) => value !== "swarm-plugin", {
|
|
1843
|
+
message: "swarm-plugin is reserved for plugin-internal skill routing"
|
|
1844
|
+
}).refine((value) => !value.startsWith("runner:"), {
|
|
1845
|
+
message: "runner:* values are derived from the active runner"
|
|
1846
|
+
}).regex(/^[a-z0-9]+(?:[._-][a-z0-9]+)*$/, {
|
|
1847
|
+
message: "audiences must be lowercase domain tokens"
|
|
1848
|
+
});
|
|
1842
1849
|
var SkillPropagationConfigSchema = exports_external.object({
|
|
1843
1850
|
enabled: exports_external.boolean().default(true),
|
|
1844
|
-
enforce: exports_external.boolean().default(false)
|
|
1851
|
+
enforce: exports_external.boolean().default(false),
|
|
1852
|
+
audiences: exports_external.array(SkillPropagationAudienceSchema).max(16).transform((values) => [...new Set(values)]).default([])
|
|
1845
1853
|
});
|
|
1846
1854
|
var SkillImproverConfigSchema = exports_external.object({
|
|
1847
1855
|
enabled: exports_external.boolean().default(false),
|