opencode-swarm-plugin 0.33.0 → 0.35.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/.hive/issues.jsonl +12 -0
  2. package/.hive/memories.jsonl +255 -1
  3. package/.turbo/turbo-build.log +4 -4
  4. package/.turbo/turbo-test.log +289 -289
  5. package/CHANGELOG.md +133 -0
  6. package/README.md +29 -1
  7. package/bin/swarm.test.ts +342 -1
  8. package/bin/swarm.ts +351 -4
  9. package/dist/compaction-hook.d.ts +1 -1
  10. package/dist/compaction-hook.d.ts.map +1 -1
  11. package/dist/index.d.ts +95 -0
  12. package/dist/index.d.ts.map +1 -1
  13. package/dist/index.js +11848 -124
  14. package/dist/logger.d.ts +34 -0
  15. package/dist/logger.d.ts.map +1 -0
  16. package/dist/plugin.js +11722 -112
  17. package/dist/swarm-orchestrate.d.ts +105 -0
  18. package/dist/swarm-orchestrate.d.ts.map +1 -1
  19. package/dist/swarm-prompts.d.ts +54 -2
  20. package/dist/swarm-prompts.d.ts.map +1 -1
  21. package/dist/swarm-research.d.ts +127 -0
  22. package/dist/swarm-research.d.ts.map +1 -0
  23. package/dist/swarm-review.d.ts.map +1 -1
  24. package/dist/swarm.d.ts +56 -1
  25. package/dist/swarm.d.ts.map +1 -1
  26. package/evals/compaction-resumption.eval.ts +289 -0
  27. package/evals/coordinator-behavior.eval.ts +307 -0
  28. package/evals/fixtures/compaction-cases.ts +350 -0
  29. package/evals/scorers/compaction-scorers.ts +305 -0
  30. package/evals/scorers/index.ts +12 -0
  31. package/package.json +5 -2
  32. package/src/compaction-hook.test.ts +639 -1
  33. package/src/compaction-hook.ts +488 -18
  34. package/src/index.ts +29 -0
  35. package/src/logger.test.ts +189 -0
  36. package/src/logger.ts +135 -0
  37. package/src/swarm-decompose.ts +0 -7
  38. package/src/swarm-prompts.test.ts +164 -1
  39. package/src/swarm-prompts.ts +179 -12
  40. package/src/swarm-review.test.ts +177 -0
  41. package/src/swarm-review.ts +12 -47
@@ -31,6 +31,28 @@
31
31
 
32
32
  import { getHiveAdapter, getHiveWorkingDirectory } from "./hive";
33
33
  import { checkSwarmHealth } from "swarm-mail";
34
+ import { createChildLogger } from "./logger";
35
+
36
+ let _logger: any | undefined;
37
+
38
+ /**
39
+ * Get logger instance (lazy initialization for testability)
40
+ *
41
+ * Logs to: ~/.config/swarm-tools/logs/compaction.1log
42
+ *
43
+ * Log structure:
44
+ * - START: session_id, trigger
45
+ * - GATHER: source (swarm-mail|hive), duration_ms, stats/counts
46
+ * - DETECT: confidence, detected, reason_count, reasons
47
+ * - INJECT: confidence, context_length, context_type (full|fallback|none)
48
+ * - COMPLETE: duration_ms, success, detected, confidence, context_injected
49
+ */
50
+ function getLog() {
51
+ if (!_logger) {
52
+ _logger = createChildLogger("compaction");
53
+ }
54
+ return _logger;
55
+ }
34
56
 
35
57
  // ============================================================================
36
58
  // Compaction Context
@@ -145,6 +167,252 @@ Include this in your summary:
145
167
  "This is an active swarm. Check swarm_status and swarmmail_inbox immediately."
146
168
  `;
147
169
 
170
+ // ============================================================================
171
+ // Dynamic Context Building
172
+ // ============================================================================
173
+
174
+ /**
175
+ * Build dynamic swarm state section from detected state
176
+ *
177
+ * This injects SPECIFIC values instead of placeholders, making the context
178
+ * immediately actionable on resume.
179
+ */
180
+ function buildDynamicSwarmState(state: SwarmState): string {
181
+ const parts: string[] = [];
182
+
183
+ parts.push("## 🐝 Current Swarm State\n");
184
+
185
+ if (state.epicId && state.epicTitle) {
186
+ parts.push(`**Epic:** ${state.epicId} - ${state.epicTitle}`);
187
+
188
+ const totalSubtasks = state.subtasks.closed + state.subtasks.in_progress +
189
+ state.subtasks.open + state.subtasks.blocked;
190
+
191
+ if (totalSubtasks > 0) {
192
+ parts.push(`**Subtasks:**`);
193
+ if (state.subtasks.closed > 0) parts.push(` - ${state.subtasks.closed} closed`);
194
+ if (state.subtasks.in_progress > 0) parts.push(` - ${state.subtasks.in_progress} in_progress`);
195
+ if (state.subtasks.open > 0) parts.push(` - ${state.subtasks.open} open`);
196
+ if (state.subtasks.blocked > 0) parts.push(` - ${state.subtasks.blocked} blocked`);
197
+ }
198
+ }
199
+
200
+ parts.push(`**Project:** ${state.projectPath}`);
201
+
202
+ if (state.epicId) {
203
+ parts.push(`\n## 🎯 YOU ARE THE COORDINATOR`);
204
+ parts.push(``);
205
+ parts.push(`**Primary role:** Orchestrate workers, review their output, unblock dependencies.`);
206
+ parts.push(`**Spawn workers** for implementation tasks - don't do them yourself.`);
207
+ parts.push(``);
208
+ parts.push(`**RESUME STEPS:**`);
209
+ parts.push(`1. Check swarm status: \`swarm_status(epic_id="${state.epicId}", project_key="${state.projectPath}")\``);
210
+ parts.push(`2. Check inbox for worker messages: \`swarmmail_inbox(limit=5)\``);
211
+ parts.push(`3. For in_progress subtasks: Review worker results with \`swarm_review\``);
212
+ parts.push(`4. For open subtasks: Spawn workers with \`swarm_spawn_subtask\``);
213
+ parts.push(`5. For blocked subtasks: Investigate and unblock`);
214
+ }
215
+
216
+ return parts.join("\n");
217
+ }
218
+
219
+ // ============================================================================
220
+ // SDK Message Scanning
221
+ // ============================================================================
222
+
223
+ /**
224
+ * Tool part with completed state containing input/output
225
+ */
226
+ interface ToolPart {
227
+ id: string;
228
+ sessionID: string;
229
+ messageID: string;
230
+ type: "tool";
231
+ callID: string;
232
+ tool: string;
233
+ state: ToolState;
234
+ }
235
+
236
+ /**
237
+ * Tool state (completed tools have input/output we need)
238
+ */
239
+ type ToolState = {
240
+ status: "completed";
241
+ input: { [key: string]: unknown };
242
+ output: string;
243
+ title: string;
244
+ metadata: { [key: string]: unknown };
245
+ time: { start: number; end: number };
246
+ } | {
247
+ status: string;
248
+ [key: string]: unknown;
249
+ };
250
+
251
+ /**
252
+ * SDK Client type (minimal interface for scanSessionMessages)
253
+ */
254
+ interface OpencodeClient {
255
+ session: {
256
+ messages: (opts: { sessionID: string; limit?: number }) => Promise<{
257
+ info: { id: string; sessionID: string };
258
+ parts: ToolPart[];
259
+ }[]>;
260
+ };
261
+ }
262
+
263
+ /**
264
+ * Scanned swarm state extracted from session messages
265
+ */
266
+ export interface ScannedSwarmState {
267
+ epicId?: string;
268
+ epicTitle?: string;
269
+ projectPath?: string;
270
+ agentName?: string;
271
+ subtasks: Map<string, { title: string; status: string; worker?: string; files?: string[] }>;
272
+ lastAction?: { tool: string; args: unknown; timestamp: number };
273
+ }
274
+
275
+ /**
276
+ * Scan session messages for swarm state using SDK client
277
+ *
278
+ * Extracts swarm coordination state from actual tool calls:
279
+ * - swarm_spawn_subtask → subtask tracking
280
+ * - swarmmail_init → agent name, project path
281
+ * - hive_create_epic → epic ID and title
282
+ * - swarm_status → epic reference
283
+ * - swarm_complete → subtask completion
284
+ *
285
+ * @param client - OpenCode SDK client (undefined if not available)
286
+ * @param sessionID - Session to scan
287
+ * @param limit - Max messages to fetch (default 100)
288
+ * @returns Extracted swarm state
289
+ */
290
+ export async function scanSessionMessages(
291
+ client: OpencodeClient | undefined,
292
+ sessionID: string,
293
+ limit: number = 100
294
+ ): Promise<ScannedSwarmState> {
295
+ const state: ScannedSwarmState = {
296
+ subtasks: new Map(),
297
+ };
298
+
299
+ if (!client) {
300
+ return state;
301
+ }
302
+
303
+ try {
304
+ const messages = await client.session.messages({ sessionID, limit });
305
+
306
+ for (const message of messages) {
307
+ for (const part of message.parts) {
308
+ if (part.type !== "tool" || part.state.status !== "completed") {
309
+ continue;
310
+ }
311
+
312
+ const { tool, state: toolState } = part;
313
+ const { input, output, time } = toolState as Extract<ToolState, { status: "completed" }>;
314
+
315
+ // Track last action
316
+ state.lastAction = {
317
+ tool,
318
+ args: input,
319
+ timestamp: time.end,
320
+ };
321
+
322
+ // Extract swarm state based on tool type
323
+ switch (tool) {
324
+ case "hive_create_epic": {
325
+ try {
326
+ const parsed = JSON.parse(output);
327
+ if (parsed.epic?.id) {
328
+ state.epicId = parsed.epic.id;
329
+ }
330
+ if (input.epic_title && typeof input.epic_title === "string") {
331
+ state.epicTitle = input.epic_title;
332
+ }
333
+ } catch {
334
+ // Invalid JSON, skip
335
+ }
336
+ break;
337
+ }
338
+
339
+ case "swarmmail_init": {
340
+ try {
341
+ const parsed = JSON.parse(output);
342
+ if (parsed.agent_name) {
343
+ state.agentName = parsed.agent_name;
344
+ }
345
+ if (parsed.project_key) {
346
+ state.projectPath = parsed.project_key;
347
+ }
348
+ } catch {
349
+ // Invalid JSON, skip
350
+ }
351
+ break;
352
+ }
353
+
354
+ case "swarm_spawn_subtask": {
355
+ const beadId = input.bead_id as string | undefined;
356
+ const epicId = input.epic_id as string | undefined;
357
+ const title = input.subtask_title as string | undefined;
358
+ const files = input.files as string[] | undefined;
359
+
360
+ if (beadId && title) {
361
+ let worker: string | undefined;
362
+ try {
363
+ const parsed = JSON.parse(output);
364
+ worker = parsed.worker;
365
+ } catch {
366
+ // No worker in output
367
+ }
368
+
369
+ state.subtasks.set(beadId, {
370
+ title,
371
+ status: "spawned",
372
+ worker,
373
+ files,
374
+ });
375
+
376
+ if (epicId && !state.epicId) {
377
+ state.epicId = epicId;
378
+ }
379
+ }
380
+ break;
381
+ }
382
+
383
+ case "swarm_complete": {
384
+ const beadId = input.bead_id as string | undefined;
385
+ if (beadId && state.subtasks.has(beadId)) {
386
+ const existing = state.subtasks.get(beadId)!;
387
+ state.subtasks.set(beadId, {
388
+ ...existing,
389
+ status: "completed",
390
+ });
391
+ }
392
+ break;
393
+ }
394
+
395
+ case "swarm_status": {
396
+ const epicId = input.epic_id as string | undefined;
397
+ if (epicId && !state.epicId) {
398
+ state.epicId = epicId;
399
+ }
400
+ const projectKey = input.project_key as string | undefined;
401
+ if (projectKey && !state.projectPath) {
402
+ state.projectPath = projectKey;
403
+ }
404
+ break;
405
+ }
406
+ }
407
+ }
408
+ }
409
+ } catch (error) {
410
+ // SDK not available or error fetching messages - return what we have
411
+ }
412
+
413
+ return state;
414
+ }
415
+
148
416
  // ============================================================================
149
417
  // Swarm Detection
150
418
  // ============================================================================
@@ -156,6 +424,23 @@ interface SwarmDetection {
156
424
  detected: boolean;
157
425
  confidence: "high" | "medium" | "low" | "none";
158
426
  reasons: string[];
427
+ /** Specific swarm state data for context injection */
428
+ state?: SwarmState;
429
+ }
430
+
431
+ /**
432
+ * Specific swarm state captured during detection
433
+ */
434
+ interface SwarmState {
435
+ epicId?: string;
436
+ epicTitle?: string;
437
+ projectPath: string;
438
+ subtasks: {
439
+ closed: number;
440
+ in_progress: number;
441
+ open: number;
442
+ blocked: number;
443
+ };
159
444
  }
160
445
 
161
446
  /**
@@ -173,13 +458,38 @@ async function detectSwarm(): Promise<SwarmDetection> {
173
458
  let highConfidence = false;
174
459
  let mediumConfidence = false;
175
460
  let lowConfidence = false;
461
+ let state: SwarmState | undefined;
176
462
 
177
463
  try {
178
464
  const projectKey = getHiveWorkingDirectory();
465
+
466
+ // Initialize state with project path
467
+ state = {
468
+ projectPath: projectKey,
469
+ subtasks: {
470
+ closed: 0,
471
+ in_progress: 0,
472
+ open: 0,
473
+ blocked: 0,
474
+ },
475
+ };
179
476
 
180
477
  // Check 1: Active reservations in swarm-mail (HIGH confidence)
478
+ const swarmMailStart = Date.now();
181
479
  try {
182
480
  const health = await checkSwarmHealth(projectKey);
481
+ const duration = Date.now() - swarmMailStart;
482
+
483
+ getLog().debug(
484
+ {
485
+ source: "swarm-mail",
486
+ duration_ms: duration,
487
+ healthy: health.healthy,
488
+ stats: health.stats,
489
+ },
490
+ "checked swarm-mail health",
491
+ );
492
+
183
493
  if (health.healthy && health.stats) {
184
494
  if (health.stats.reservations > 0) {
185
495
  highConfidence = true;
@@ -194,14 +504,24 @@ async function detectSwarm(): Promise<SwarmDetection> {
194
504
  reasons.push(`${health.stats.messages} swarm messages`);
195
505
  }
196
506
  }
197
- } catch {
507
+ } catch (error) {
508
+ getLog().debug(
509
+ {
510
+ source: "swarm-mail",
511
+ duration_ms: Date.now() - swarmMailStart,
512
+ error: error instanceof Error ? error.message : String(error),
513
+ },
514
+ "swarm-mail check failed",
515
+ );
198
516
  // Swarm-mail not available, continue with other checks
199
517
  }
200
518
 
201
519
  // Check 2: Hive cells (various confidence levels)
520
+ const hiveStart = Date.now();
202
521
  try {
203
522
  const adapter = await getHiveAdapter(projectKey);
204
523
  const cells = await adapter.queryCells(projectKey, {});
524
+ const duration = Date.now() - hiveStart;
205
525
 
206
526
  if (Array.isArray(cells) && cells.length > 0) {
207
527
  // HIGH: Any in_progress cells
@@ -213,7 +533,7 @@ async function detectSwarm(): Promise<SwarmDetection> {
213
533
 
214
534
  // MEDIUM: Open subtasks (cells with parent_id)
215
535
  const subtasks = cells.filter(
216
- (c) => c.status === "open" && c.parent_id
536
+ (c) => c.status === "open" && c.parent_id,
217
537
  );
218
538
  if (subtasks.length > 0) {
219
539
  mediumConfidence = true;
@@ -222,11 +542,37 @@ async function detectSwarm(): Promise<SwarmDetection> {
222
542
 
223
543
  // MEDIUM: Unclosed epics
224
544
  const openEpics = cells.filter(
225
- (c) => c.type === "epic" && c.status !== "closed"
545
+ (c) => c.type === "epic" && c.status !== "closed",
226
546
  );
227
547
  if (openEpics.length > 0) {
228
548
  mediumConfidence = true;
229
549
  reasons.push(`${openEpics.length} unclosed epics`);
550
+
551
+ // Capture in_progress epic data for state
552
+ const inProgressEpic = openEpics.find((c) => c.status === "in_progress");
553
+ if (inProgressEpic && state) {
554
+ state.epicId = inProgressEpic.id;
555
+ state.epicTitle = inProgressEpic.title;
556
+
557
+ // Count subtasks for this epic
558
+ const epicSubtasks = cells.filter((c) => c.parent_id === inProgressEpic.id);
559
+ state.subtasks.closed = epicSubtasks.filter((c) => c.status === "closed").length;
560
+ state.subtasks.in_progress = epicSubtasks.filter((c) => c.status === "in_progress").length;
561
+ state.subtasks.open = epicSubtasks.filter((c) => c.status === "open").length;
562
+ state.subtasks.blocked = epicSubtasks.filter((c) => c.status === "blocked").length;
563
+
564
+ getLog().debug(
565
+ {
566
+ epic_id: state.epicId,
567
+ epic_title: state.epicTitle,
568
+ subtasks_closed: state.subtasks.closed,
569
+ subtasks_in_progress: state.subtasks.in_progress,
570
+ subtasks_open: state.subtasks.open,
571
+ subtasks_blocked: state.subtasks.blocked,
572
+ },
573
+ "captured epic state for context",
574
+ );
575
+ }
230
576
  }
231
577
 
232
578
  // MEDIUM: Recently updated cells (last hour)
@@ -242,14 +588,46 @@ async function detectSwarm(): Promise<SwarmDetection> {
242
588
  lowConfidence = true;
243
589
  reasons.push(`${cells.length} total cells in hive`);
244
590
  }
591
+
592
+ getLog().debug(
593
+ {
594
+ source: "hive",
595
+ duration_ms: duration,
596
+ total_cells: cells.length,
597
+ in_progress: inProgress.length,
598
+ open_subtasks: subtasks.length,
599
+ open_epics: openEpics.length,
600
+ recent_updates: recentCells.length,
601
+ },
602
+ "checked hive cells",
603
+ );
604
+ } else {
605
+ getLog().debug(
606
+ { source: "hive", duration_ms: duration, total_cells: 0 },
607
+ "hive empty",
608
+ );
245
609
  }
246
- } catch {
610
+ } catch (error) {
611
+ getLog().debug(
612
+ {
613
+ source: "hive",
614
+ duration_ms: Date.now() - hiveStart,
615
+ error: error instanceof Error ? error.message : String(error),
616
+ },
617
+ "hive check failed",
618
+ );
247
619
  // Hive not available, continue
248
620
  }
249
- } catch {
621
+ } catch (error) {
250
622
  // Project detection failed, use fallback
251
623
  lowConfidence = true;
252
624
  reasons.push("Could not detect project, using fallback");
625
+ getLog().debug(
626
+ {
627
+ error: error instanceof Error ? error.message : String(error),
628
+ },
629
+ "project detection failed",
630
+ );
253
631
  }
254
632
 
255
633
  // Determine overall confidence
@@ -264,11 +642,25 @@ async function detectSwarm(): Promise<SwarmDetection> {
264
642
  confidence = "none";
265
643
  }
266
644
 
267
- return {
645
+ const result = {
268
646
  detected: confidence !== "none",
269
647
  confidence,
270
648
  reasons,
649
+ state,
271
650
  };
651
+
652
+ getLog().debug(
653
+ {
654
+ detected: result.detected,
655
+ confidence: result.confidence,
656
+ reason_count: result.reasons.length,
657
+ reasons: result.reasons,
658
+ has_state: !!result.state,
659
+ },
660
+ "swarm detection complete",
661
+ );
662
+
663
+ return result;
272
664
  }
273
665
 
274
666
  // ============================================================================
@@ -298,20 +690,98 @@ async function detectSwarm(): Promise<SwarmDetection> {
298
690
  */
299
691
  export function createCompactionHook() {
300
692
  return async (
301
- _input: { sessionID: string },
693
+ input: { sessionID: string },
302
694
  output: { context: string[] },
303
695
  ): Promise<void> => {
304
- const detection = await detectSwarm();
305
-
306
- if (detection.confidence === "high" || detection.confidence === "medium") {
307
- // Definite or probable swarm - inject full context
308
- const header = `[Swarm detected: ${detection.reasons.join(", ")}]\n\n`;
309
- output.context.push(header + SWARM_COMPACTION_CONTEXT);
310
- } else if (detection.confidence === "low") {
311
- // Possible swarm - inject fallback detection prompt
312
- const header = `[Possible swarm: ${detection.reasons.join(", ")}]\n\n`;
313
- output.context.push(header + SWARM_DETECTION_FALLBACK);
696
+ const startTime = Date.now();
697
+
698
+ getLog().info(
699
+ {
700
+ session_id: input.sessionID,
701
+ trigger: "session_compaction",
702
+ },
703
+ "compaction started",
704
+ );
705
+
706
+ try {
707
+ const detection = await detectSwarm();
708
+
709
+ if (
710
+ detection.confidence === "high" ||
711
+ detection.confidence === "medium"
712
+ ) {
713
+ // Definite or probable swarm - inject full context
714
+ const header = `[Swarm detected: ${detection.reasons.join(", ")}]\n\n`;
715
+
716
+ // Build dynamic state section if we have specific data
717
+ let dynamicState = "";
718
+ if (detection.state && detection.state.epicId) {
719
+ dynamicState = buildDynamicSwarmState(detection.state) + "\n\n";
720
+ }
721
+
722
+ const contextContent = header + dynamicState + SWARM_COMPACTION_CONTEXT;
723
+ output.context.push(contextContent);
724
+
725
+ getLog().info(
726
+ {
727
+ confidence: detection.confidence,
728
+ context_length: contextContent.length,
729
+ context_type: "full",
730
+ reasons: detection.reasons,
731
+ has_dynamic_state: !!dynamicState,
732
+ epic_id: detection.state?.epicId,
733
+ },
734
+ "injected swarm context",
735
+ );
736
+ } else if (detection.confidence === "low") {
737
+ // Possible swarm - inject fallback detection prompt
738
+ const header = `[Possible swarm: ${detection.reasons.join(", ")}]\n\n`;
739
+ const contextContent = header + SWARM_DETECTION_FALLBACK;
740
+ output.context.push(contextContent);
741
+
742
+ getLog().info(
743
+ {
744
+ confidence: detection.confidence,
745
+ context_length: contextContent.length,
746
+ context_type: "fallback",
747
+ reasons: detection.reasons,
748
+ },
749
+ "injected swarm context",
750
+ );
751
+ } else {
752
+ getLog().debug(
753
+ {
754
+ confidence: detection.confidence,
755
+ context_type: "none",
756
+ },
757
+ "no swarm detected, skipping injection",
758
+ );
759
+ }
760
+ // confidence === "none" - no injection, probably not a swarm
761
+
762
+ const duration = Date.now() - startTime;
763
+ getLog().info(
764
+ {
765
+ duration_ms: duration,
766
+ success: true,
767
+ detected: detection.detected,
768
+ confidence: detection.confidence,
769
+ context_injected: output.context.length > 0,
770
+ },
771
+ "compaction complete",
772
+ );
773
+ } catch (error) {
774
+ const duration = Date.now() - startTime;
775
+ getLog().error(
776
+ {
777
+ duration_ms: duration,
778
+ success: false,
779
+ error: error instanceof Error ? error.message : String(error),
780
+ stack: error instanceof Error ? error.stack : undefined,
781
+ },
782
+ "compaction failed",
783
+ );
784
+ // Don't throw - compaction hook failures shouldn't break the session
314
785
  }
315
- // confidence === "none" - no injection, probably not a swarm
316
786
  };
317
787
  }
package/src/index.ts CHANGED
@@ -684,6 +684,35 @@ export {
684
684
  } from "./memory-tools";
685
685
  export type { Memory, SearchResult, SearchOptions } from "swarm-mail";
686
686
 
687
+ /**
688
+ * Re-export logger infrastructure
689
+ *
690
+ * Includes:
691
+ * - getLogger - Gets or creates the main logger instance
692
+ * - createChildLogger - Creates a module-specific child logger with separate log file
693
+ * - logger - Default logger instance for immediate use
694
+ *
695
+ * Features:
696
+ * - Daily log rotation via pino-roll (numeric format: swarm.1log, swarm.2log, etc.)
697
+ * - 14-day retention
698
+ * - Module-specific child loggers
699
+ * - Pretty mode for development (SWARM_LOG_PRETTY=1)
700
+ * - Logs to ~/.config/swarm-tools/logs/
701
+ *
702
+ * @example
703
+ * ```typescript
704
+ * import { logger, createChildLogger } from "opencode-swarm-plugin";
705
+ *
706
+ * // Use default logger
707
+ * logger.info("Application started");
708
+ *
709
+ * // Create module-specific logger
710
+ * const compactionLog = createChildLogger("compaction");
711
+ * compactionLog.info("Compaction started");
712
+ * ```
713
+ */
714
+ export { getLogger, createChildLogger, logger } from "./logger";
715
+
687
716
  /**
688
717
  * Re-export swarm-research module
689
718
  *