opencode-swarm-plugin 0.35.0 → 0.36.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.
Files changed (52) hide show
  1. package/.hive/issues.jsonl +4 -4
  2. package/.hive/memories.jsonl +274 -1
  3. package/.turbo/turbo-build.log +4 -4
  4. package/.turbo/turbo-test.log +307 -307
  5. package/CHANGELOG.md +133 -0
  6. package/bin/swarm.ts +234 -179
  7. package/dist/compaction-hook.d.ts +54 -4
  8. package/dist/compaction-hook.d.ts.map +1 -1
  9. package/dist/eval-capture.d.ts +122 -17
  10. package/dist/eval-capture.d.ts.map +1 -1
  11. package/dist/index.d.ts +1 -7
  12. package/dist/index.d.ts.map +1 -1
  13. package/dist/index.js +1278 -619
  14. package/dist/planning-guardrails.d.ts +121 -0
  15. package/dist/planning-guardrails.d.ts.map +1 -1
  16. package/dist/plugin.d.ts +9 -9
  17. package/dist/plugin.d.ts.map +1 -1
  18. package/dist/plugin.js +1283 -329
  19. package/dist/schemas/task.d.ts +0 -1
  20. package/dist/schemas/task.d.ts.map +1 -1
  21. package/dist/swarm-decompose.d.ts +0 -8
  22. package/dist/swarm-decompose.d.ts.map +1 -1
  23. package/dist/swarm-orchestrate.d.ts.map +1 -1
  24. package/dist/swarm-prompts.d.ts +0 -4
  25. package/dist/swarm-prompts.d.ts.map +1 -1
  26. package/dist/swarm-review.d.ts.map +1 -1
  27. package/dist/swarm.d.ts +0 -6
  28. package/dist/swarm.d.ts.map +1 -1
  29. package/evals/README.md +38 -0
  30. package/evals/coordinator-session.eval.ts +154 -0
  31. package/evals/fixtures/coordinator-sessions.ts +328 -0
  32. package/evals/lib/data-loader.ts +69 -0
  33. package/evals/scorers/coordinator-discipline.evalite-test.ts +536 -0
  34. package/evals/scorers/coordinator-discipline.ts +315 -0
  35. package/evals/scorers/index.ts +12 -0
  36. package/examples/plugin-wrapper-template.ts +747 -34
  37. package/package.json +2 -2
  38. package/src/compaction-hook.test.ts +234 -281
  39. package/src/compaction-hook.ts +221 -63
  40. package/src/eval-capture.test.ts +390 -0
  41. package/src/eval-capture.ts +168 -10
  42. package/src/index.ts +89 -2
  43. package/src/learning.integration.test.ts +0 -2
  44. package/src/planning-guardrails.test.ts +387 -2
  45. package/src/planning-guardrails.ts +289 -0
  46. package/src/plugin.ts +10 -10
  47. package/src/schemas/task.ts +0 -1
  48. package/src/swarm-decompose.ts +21 -8
  49. package/src/swarm-orchestrate.ts +44 -0
  50. package/src/swarm-prompts.ts +20 -0
  51. package/src/swarm-review.ts +41 -0
  52. package/src/swarm.integration.test.ts +0 -40
@@ -0,0 +1,315 @@
1
+ /**
2
+ * Coordinator Discipline Scorers - Evaluate coordinator behavior
3
+ *
4
+ * These scorers measure whether a coordinator follows the protocol:
5
+ * 1. Don't edit files directly (spawn workers)
6
+ * 2. Don't run tests directly (workers do verification)
7
+ * 3. Spawn workers for all subtasks
8
+ * 4. Review worker output before accepting
9
+ * 5. Minimize time to first spawn (don't overthink)
10
+ *
11
+ * Inputs: CoordinatorSession from eval-capture
12
+ */
13
+
14
+ import { createScorer } from "evalite";
15
+ import type { CoordinatorSession } from "../../src/eval-capture.js";
16
+
17
+ /**
18
+ * Violation Count Scorer
19
+ *
20
+ * Counts VIOLATION events in the session.
21
+ * Each violation reduces score by 0.2.
22
+ *
23
+ * Violations tracked:
24
+ * - coordinator_edited_file (should spawn worker instead)
25
+ * - coordinator_ran_tests (workers do verification)
26
+ * - coordinator_reserved_files (only workers reserve)
27
+ * - no_worker_spawned (subtask exists but no worker)
28
+ *
29
+ * Score: 1.0 - (0.2 * violation_count), floored at 0.0
30
+ */
31
+ export const violationCount = createScorer({
32
+ name: "Violation Count",
33
+ description: "Coordinator followed protocol (no direct edits, tests, or reservations)",
34
+ scorer: ({ output }) => {
35
+ try {
36
+ const session = JSON.parse(String(output)) as CoordinatorSession;
37
+
38
+ // Count violations
39
+ const violations = session.events.filter(
40
+ (e) => e.event_type === "VIOLATION"
41
+ );
42
+
43
+ const count = violations.length;
44
+ const score = Math.max(0, 1.0 - count * 0.2);
45
+
46
+ if (count === 0) {
47
+ return {
48
+ score: 1.0,
49
+ message: "Perfect - 0 violations",
50
+ };
51
+ }
52
+
53
+ return {
54
+ score,
55
+ message: `${count} violations detected`,
56
+ };
57
+ } catch (error) {
58
+ return {
59
+ score: 0,
60
+ message: `Failed to parse CoordinatorSession: ${error}`,
61
+ };
62
+ }
63
+ },
64
+ });
65
+
66
+ /**
67
+ * Spawn Efficiency Scorer
68
+ *
69
+ * Measures whether workers were spawned for all subtasks.
70
+ * Coordinators should delegate work, not do it themselves.
71
+ *
72
+ * Score: workers_spawned / subtasks_planned
73
+ */
74
+ export const spawnEfficiency = createScorer({
75
+ name: "Spawn Efficiency",
76
+ description: "Workers spawned for all subtasks (delegation ratio)",
77
+ scorer: ({ output }) => {
78
+ try {
79
+ const session = JSON.parse(String(output)) as CoordinatorSession;
80
+
81
+ // Find decomposition_complete event (has subtask count)
82
+ const decomp = session.events.find(
83
+ (e) =>
84
+ e.event_type === "DECISION" &&
85
+ e.decision_type === "decomposition_complete"
86
+ );
87
+
88
+ if (!decomp) {
89
+ return {
90
+ score: 0,
91
+ message: "No decomposition event found",
92
+ };
93
+ }
94
+
95
+ const subtaskCount = (decomp.payload as { subtask_count?: number })?.subtask_count || 0;
96
+
97
+ if (subtaskCount === 0) {
98
+ return {
99
+ score: 0,
100
+ message: "No subtasks planned",
101
+ };
102
+ }
103
+
104
+ // Count worker_spawned events
105
+ const spawned = session.events.filter(
106
+ (e) =>
107
+ e.event_type === "DECISION" && e.decision_type === "worker_spawned"
108
+ ).length;
109
+
110
+ const score = spawned / subtaskCount;
111
+
112
+ return {
113
+ score,
114
+ message: `${spawned}/${subtaskCount} workers spawned (${(score * 100).toFixed(0)}%)`,
115
+ };
116
+ } catch (error) {
117
+ return {
118
+ score: 0,
119
+ message: `Failed to parse CoordinatorSession: ${error}`,
120
+ };
121
+ }
122
+ },
123
+ });
124
+
125
+ /**
126
+ * Review Thoroughness Scorer
127
+ *
128
+ * Measures whether coordinator reviewed worker output.
129
+ * Should have review_completed events for all finished subtasks.
130
+ *
131
+ * Score: reviews_completed / workers_finished
132
+ */
133
+ export const reviewThoroughness = createScorer({
134
+ name: "Review Thoroughness",
135
+ description: "Coordinator reviewed all worker output",
136
+ scorer: ({ output }) => {
137
+ try {
138
+ const session = JSON.parse(String(output)) as CoordinatorSession;
139
+
140
+ // Count finished workers (subtask_success or subtask_failed)
141
+ const finished = session.events.filter(
142
+ (e) =>
143
+ e.event_type === "OUTCOME" &&
144
+ (e.outcome_type === "subtask_success" ||
145
+ e.outcome_type === "subtask_failed")
146
+ ).length;
147
+
148
+ if (finished === 0) {
149
+ return {
150
+ score: 1.0,
151
+ message: "No finished workers to review",
152
+ };
153
+ }
154
+
155
+ // Count review_completed events
156
+ const reviewed = session.events.filter(
157
+ (e) =>
158
+ e.event_type === "DECISION" && e.decision_type === "review_completed"
159
+ ).length;
160
+
161
+ const score = reviewed / finished;
162
+
163
+ return {
164
+ score,
165
+ message: `${reviewed}/${finished} workers reviewed (${(score * 100).toFixed(0)}%)`,
166
+ };
167
+ } catch (error) {
168
+ return {
169
+ score: 0,
170
+ message: `Failed to parse CoordinatorSession: ${error}`,
171
+ };
172
+ }
173
+ },
174
+ });
175
+
176
+ /**
177
+ * Time to First Spawn Scorer
178
+ *
179
+ * Measures how fast the coordinator spawned the first worker.
180
+ * Overthinking and perfectionism delays workers and blocks progress.
181
+ *
182
+ * Normalization:
183
+ * - < 60s: 1.0 (excellent)
184
+ * - 60-300s: linear decay to 0.5
185
+ * - > 300s: 0.0 (way too slow)
186
+ *
187
+ * Score: normalized to 0-1 (faster is better)
188
+ */
189
+ export const timeToFirstSpawn = createScorer({
190
+ name: "Time to First Spawn",
191
+ description: "Coordinator spawned workers quickly (no overthinking)",
192
+ scorer: ({ output }) => {
193
+ try {
194
+ const session = JSON.parse(String(output)) as CoordinatorSession;
195
+
196
+ // Find decomposition_complete event
197
+ const decomp = session.events.find(
198
+ (e) =>
199
+ e.event_type === "DECISION" &&
200
+ e.decision_type === "decomposition_complete"
201
+ );
202
+
203
+ if (!decomp) {
204
+ return {
205
+ score: 0,
206
+ message: "No decomposition event found",
207
+ };
208
+ }
209
+
210
+ // Find first worker_spawned event
211
+ const firstSpawn = session.events.find(
212
+ (e) =>
213
+ e.event_type === "DECISION" && e.decision_type === "worker_spawned"
214
+ );
215
+
216
+ if (!firstSpawn) {
217
+ return {
218
+ score: 0,
219
+ message: "No worker spawned",
220
+ };
221
+ }
222
+
223
+ // Calculate time delta
224
+ const decompTime = new Date(decomp.timestamp).getTime();
225
+ const spawnTime = new Date(firstSpawn.timestamp).getTime();
226
+ const deltaMs = spawnTime - decompTime;
227
+
228
+ // Normalize: < 60s = 1.0, > 300s = 0.0, linear in between
229
+ const EXCELLENT_MS = 60_000;
230
+ const POOR_MS = 300_000;
231
+
232
+ let score: number;
233
+ if (deltaMs < EXCELLENT_MS) {
234
+ score = 1.0;
235
+ } else if (deltaMs > POOR_MS) {
236
+ score = 0.0;
237
+ } else {
238
+ // Linear decay from 1.0 to 0.0
239
+ score = 1.0 - (deltaMs - EXCELLENT_MS) / (POOR_MS - EXCELLENT_MS);
240
+ }
241
+
242
+ const seconds = Math.round(deltaMs / 1000);
243
+
244
+ return {
245
+ score,
246
+ message: `First spawn after ${deltaMs}ms (${seconds}s)`,
247
+ };
248
+ } catch (error) {
249
+ return {
250
+ score: 0,
251
+ message: `Failed to parse CoordinatorSession: ${error}`,
252
+ };
253
+ }
254
+ },
255
+ });
256
+
257
+ /**
258
+ * Overall Discipline Scorer
259
+ *
260
+ * Weighted composite of all coordinator discipline metrics.
261
+ *
262
+ * Weights:
263
+ * - Violations: 30% (most critical - breaking protocol)
264
+ * - Spawn efficiency: 25% (delegation is key)
265
+ * - Review thoroughness: 25% (quality gate)
266
+ * - Time to first spawn: 20% (bias toward action)
267
+ *
268
+ * Score: 0.0 to 1.0
269
+ */
270
+ export const overallDiscipline = createScorer({
271
+ name: "Overall Coordinator Discipline",
272
+ description: "Composite score for coordinator protocol adherence",
273
+ scorer: ({ output, expected }) => {
274
+ try {
275
+ // Run all scorers
276
+ const scores = {
277
+ violations: violationCount.scorer({ output, expected }),
278
+ spawn: spawnEfficiency.scorer({ output, expected }),
279
+ review: reviewThoroughness.scorer({ output, expected }),
280
+ speed: timeToFirstSpawn.scorer({ output, expected }),
281
+ };
282
+
283
+ // Weighted average
284
+ const weights = {
285
+ violations: 0.3,
286
+ spawn: 0.25,
287
+ review: 0.25,
288
+ speed: 0.2,
289
+ };
290
+
291
+ const totalScore =
292
+ scores.violations.score * weights.violations +
293
+ scores.spawn.score * weights.spawn +
294
+ scores.review.score * weights.review +
295
+ scores.speed.score * weights.speed;
296
+
297
+ const details = [
298
+ `Violations: ${(scores.violations.score * 100).toFixed(0)}%`,
299
+ `Spawn: ${(scores.spawn.score * 100).toFixed(0)}%`,
300
+ `Review: ${(scores.review.score * 100).toFixed(0)}%`,
301
+ `Speed: ${(scores.speed.score * 100).toFixed(0)}%`,
302
+ ].join(", ");
303
+
304
+ return {
305
+ score: totalScore,
306
+ message: `Overall: ${(totalScore * 100).toFixed(0)}% (${details})`,
307
+ };
308
+ } catch (error) {
309
+ return {
310
+ score: 0,
311
+ message: `Failed to compute composite score: ${error}`,
312
+ };
313
+ }
314
+ },
315
+ });
@@ -78,6 +78,18 @@ export {
78
78
  compactionQuality,
79
79
  } from "./compaction-scorers.js";
80
80
 
81
+ // ============================================================================
82
+ // Coordinator discipline scorers
83
+ // ============================================================================
84
+
85
+ export {
86
+ violationCount,
87
+ spawnEfficiency,
88
+ reviewThoroughness,
89
+ timeToFirstSpawn,
90
+ overallDiscipline,
91
+ } from "./coordinator-discipline.js";
92
+
81
93
  /**
82
94
  * Checks that subtasks cover the full task scope
83
95
  *