opencode-swarm 6.1.2 → 6.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,9 +1,9 @@
1
1
  <p align="center">
2
- <img src="https://img.shields.io/badge/version-6.1.2-blue" alt="Version">
2
+ <img src="https://img.shields.io/badge/version-6.2.0-blue" alt="Version">
3
3
  <img src="https://img.shields.io/badge/license-MIT-green" alt="License">
4
4
  <img src="https://img.shields.io/badge/opencode-plugin-purple" alt="OpenCode Plugin">
5
5
  <img src="https://img.shields.io/badge/agents-9-orange" alt="Agents">
6
- <img src="https://img.shields.io/badge/tests-1280-brightgreen" alt="Tests">
6
+ <img src="https://img.shields.io/badge/tests-1391-brightgreen" alt="Tests">
7
7
  </p>
8
8
 
9
9
  <h1 align="center">🐝 OpenCode Swarm</h1>
@@ -233,6 +233,21 @@ Current Phase: 2
233
233
 
234
234
  **Start a new session tomorrow?** The Architect reads these files and picks up exactly where you left off.
235
235
 
236
+ ### Evidence Types
237
+
238
+ Each task in `.swarm/evidence/` contains structured evidence bundles with typed entries:
239
+
240
+ | Type | Purpose | Key Fields |
241
+ |------|---------|------------|
242
+ | `review` | Code review verdict | `verdict`, `risk`, `issues[]` |
243
+ | `test` | Test run results | `verdict`, `tests_passed`, `tests_failed`, `coverage` |
244
+ | `diff` | Git diff summary | `files_changed[]`, `additions`, `deletions` |
245
+ | `approval` | Stakeholder sign-off | `approver`, `notes` |
246
+ | `note` | General observations | `content` |
247
+ | `retrospective` | Phase metrics & lessons | `phase_number`, `total_tool_calls`, `coder_revisions`, `reviewer_rejections`, `test_failures`, `security_findings`, `task_count`, `task_complexity`, `top_rejection_reasons[]`, `lessons_learned[]` |
248
+
249
+ **Retrospective Evidence** (v6.2.0+): After each phase completes, the architect writes a retrospective evidence bundle capturing what went well and what didn't. The system enhancer injects the most recent retrospective as a `[SWARM RETROSPECTIVE]` hint at the start of the next phase, enabling continuous improvement across phases.
250
+
236
251
  ---
237
252
 
238
253
  ## Heterogeneous Models = Better Code
@@ -343,6 +358,12 @@ bunx opencode-swarm uninstall --clean
343
358
 
344
359
  ## What's New
345
360
 
361
+ ### v6.2.0 — System Intelligence
362
+ - **Retrospective evidence** — New evidence type that captures phase metrics (tool calls, revisions, rejections, test failures, security findings) and lessons learned. Architect writes it after each phase; system enhancer injects the most recent one as a `[SWARM RETROSPECTIVE]` hint for the next phase, enabling continuous improvement across phases.
363
+ - **Soft compaction advisory** — System enhancer injects a `[SWARM HINT]` when the architect's tool-call count crosses configurable thresholds (default 50/75/100/125/150). A `lastCompactionHint` guard prevents re-injection at the same threshold. Configurable via `compaction_advisory` block.
364
+ - **Coverage reporting** — Test engineer now reports line/branch/function coverage percentages and flags files below 70%. Architect uses this in Phase 5 step 5d to request additional test passes when coverage is insufficient.
365
+ - **111 new tests** — 1391 total tests across 62+ files (up from 1280 in v6.1.2).
366
+
346
367
  ### v6.1.2 — Guardrails Remediation
347
368
  - **Fail-safe config validation** — Config validation failures now disable guardrails as a safety precaution (previously Zod defaults could silently re-enable them).
348
369
  - **Architect exemption fix** — Architect/orchestrator sessions can no longer inherit 30-minute base limits during delegation race conditions.
@@ -593,6 +614,22 @@ Control whether contract change detection triggers impact analysis:
593
614
  }
594
615
  ```
595
616
 
617
+ ### Compaction Advisory
618
+
619
+ Control when the system hints about context compaction thresholds:
620
+
621
+ ```jsonc
622
+ {
623
+ "compaction_advisory": {
624
+ "enabled": true, // default: true
625
+ "thresholds": [50, 75, 100, 125, 150], // tool-call counts that trigger hints
626
+ "message": "Large context may benefit from compaction" // custom message
627
+ }
628
+ }
629
+ ```
630
+
631
+ When the architect's tool-call count crosses a threshold, the system enhancer injects a `[SWARM HINT]` suggesting context management. Each threshold fires only once per session (tracked via `lastCompactionHint`).
632
+
596
633
  > **Architect is exempt/unlimited by default:** The architect agent has no guardrail limits by default. To override, add a `profiles.architect` entry in your guardrails config.
597
634
 
598
635
  ### Per-Invocation Budgets
@@ -659,7 +696,7 @@ bun test
659
696
  bun test tests/unit/config/schema.test.ts
660
697
  ```
661
698
 
662
- 1280 tests across 57+ files covering config, tools, agents, hooks, commands, state, guardrails, evidence, plan schemas, circuit breaker race conditions, invocation windows, multi-invocation isolation, security categories, review/integration schemas, and diff tool. Uses Bun's built-in test runner — zero additional test dependencies.
699
+ 1391 tests across 62+ files covering config, tools, agents, hooks, commands, state, guardrails, evidence, plan schemas, circuit breaker race conditions, invocation windows, multi-invocation isolation, security categories, review/integration schemas, and diff tool. Uses Bun's built-in test runner — zero additional test dependencies.
663
700
 
664
701
  ## Troubleshooting
665
702
 
@@ -8,6 +8,7 @@ export declare const EvidenceTypeSchema: z.ZodEnum<{
8
8
  diff: "diff";
9
9
  approval: "approval";
10
10
  note: "note";
11
+ retrospective: "retrospective";
11
12
  }>;
12
13
  export type EvidenceType = z.infer<typeof EvidenceTypeSchema>;
13
14
  export declare const EvidenceVerdictSchema: z.ZodEnum<{
@@ -26,6 +27,7 @@ export declare const BaseEvidenceSchema: z.ZodObject<{
26
27
  diff: "diff";
27
28
  approval: "approval";
28
29
  note: "note";
30
+ retrospective: "retrospective";
29
31
  }>;
30
32
  timestamp: z.ZodString;
31
33
  agent: z.ZodString;
@@ -147,6 +149,38 @@ export declare const NoteEvidenceSchema: z.ZodObject<{
147
149
  type: z.ZodLiteral<"note">;
148
150
  }, z.core.$strip>;
149
151
  export type NoteEvidence = z.infer<typeof NoteEvidenceSchema>;
152
+ export declare const RetrospectiveEvidenceSchema: z.ZodObject<{
153
+ task_id: z.ZodString;
154
+ timestamp: z.ZodString;
155
+ agent: z.ZodString;
156
+ verdict: z.ZodEnum<{
157
+ pass: "pass";
158
+ fail: "fail";
159
+ approved: "approved";
160
+ rejected: "rejected";
161
+ info: "info";
162
+ }>;
163
+ summary: z.ZodString;
164
+ metadata: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
165
+ type: z.ZodLiteral<"retrospective">;
166
+ phase_number: z.ZodNumber;
167
+ total_tool_calls: z.ZodNumber;
168
+ coder_revisions: z.ZodNumber;
169
+ reviewer_rejections: z.ZodNumber;
170
+ test_failures: z.ZodNumber;
171
+ security_findings: z.ZodNumber;
172
+ integration_issues: z.ZodNumber;
173
+ task_count: z.ZodNumber;
174
+ task_complexity: z.ZodEnum<{
175
+ trivial: "trivial";
176
+ simple: "simple";
177
+ moderate: "moderate";
178
+ complex: "complex";
179
+ }>;
180
+ top_rejection_reasons: z.ZodDefault<z.ZodArray<z.ZodString>>;
181
+ lessons_learned: z.ZodDefault<z.ZodArray<z.ZodString>>;
182
+ }, z.core.$strip>;
183
+ export type RetrospectiveEvidence = z.infer<typeof RetrospectiveEvidenceSchema>;
150
184
  export declare const EvidenceSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
151
185
  task_id: z.ZodString;
152
186
  timestamp: z.ZodString;
@@ -244,6 +278,36 @@ export declare const EvidenceSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
244
278
  summary: z.ZodString;
245
279
  metadata: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
246
280
  type: z.ZodLiteral<"note">;
281
+ }, z.core.$strip>, z.ZodObject<{
282
+ task_id: z.ZodString;
283
+ timestamp: z.ZodString;
284
+ agent: z.ZodString;
285
+ verdict: z.ZodEnum<{
286
+ pass: "pass";
287
+ fail: "fail";
288
+ approved: "approved";
289
+ rejected: "rejected";
290
+ info: "info";
291
+ }>;
292
+ summary: z.ZodString;
293
+ metadata: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
294
+ type: z.ZodLiteral<"retrospective">;
295
+ phase_number: z.ZodNumber;
296
+ total_tool_calls: z.ZodNumber;
297
+ coder_revisions: z.ZodNumber;
298
+ reviewer_rejections: z.ZodNumber;
299
+ test_failures: z.ZodNumber;
300
+ security_findings: z.ZodNumber;
301
+ integration_issues: z.ZodNumber;
302
+ task_count: z.ZodNumber;
303
+ task_complexity: z.ZodEnum<{
304
+ trivial: "trivial";
305
+ simple: "simple";
306
+ moderate: "moderate";
307
+ complex: "complex";
308
+ }>;
309
+ top_rejection_reasons: z.ZodDefault<z.ZodArray<z.ZodString>>;
310
+ lessons_learned: z.ZodDefault<z.ZodArray<z.ZodString>>;
247
311
  }, z.core.$strip>], "type">;
248
312
  export type Evidence = z.infer<typeof EvidenceSchema>;
249
313
  export declare const EvidenceBundleSchema: z.ZodObject<{
@@ -346,6 +410,36 @@ export declare const EvidenceBundleSchema: z.ZodObject<{
346
410
  summary: z.ZodString;
347
411
  metadata: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
348
412
  type: z.ZodLiteral<"note">;
413
+ }, z.core.$strip>, z.ZodObject<{
414
+ task_id: z.ZodString;
415
+ timestamp: z.ZodString;
416
+ agent: z.ZodString;
417
+ verdict: z.ZodEnum<{
418
+ pass: "pass";
419
+ fail: "fail";
420
+ approved: "approved";
421
+ rejected: "rejected";
422
+ info: "info";
423
+ }>;
424
+ summary: z.ZodString;
425
+ metadata: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
426
+ type: z.ZodLiteral<"retrospective">;
427
+ phase_number: z.ZodNumber;
428
+ total_tool_calls: z.ZodNumber;
429
+ coder_revisions: z.ZodNumber;
430
+ reviewer_rejections: z.ZodNumber;
431
+ test_failures: z.ZodNumber;
432
+ security_findings: z.ZodNumber;
433
+ integration_issues: z.ZodNumber;
434
+ task_count: z.ZodNumber;
435
+ task_complexity: z.ZodEnum<{
436
+ trivial: "trivial";
437
+ simple: "simple";
438
+ moderate: "moderate";
439
+ complex: "complex";
440
+ }>;
441
+ top_rejection_reasons: z.ZodDefault<z.ZodArray<z.ZodString>>;
442
+ lessons_learned: z.ZodDefault<z.ZodArray<z.ZodString>>;
349
443
  }, z.core.$strip>], "type">>>;
350
444
  created_at: z.ZodString;
351
445
  updated_at: z.ZodString;
@@ -148,6 +148,12 @@ export declare const UIReviewConfigSchema: z.ZodObject<{
148
148
  trigger_keywords: z.ZodDefault<z.ZodArray<z.ZodString>>;
149
149
  }, z.core.$strip>;
150
150
  export type UIReviewConfig = z.infer<typeof UIReviewConfigSchema>;
151
+ export declare const CompactionAdvisoryConfigSchema: z.ZodObject<{
152
+ enabled: z.ZodDefault<z.ZodBoolean>;
153
+ thresholds: z.ZodDefault<z.ZodArray<z.ZodNumber>>;
154
+ message: z.ZodDefault<z.ZodString>;
155
+ }, z.core.$strip>;
156
+ export type CompactionAdvisoryConfig = z.infer<typeof CompactionAdvisoryConfigSchema>;
151
157
  export declare const GuardrailsProfileSchema: z.ZodObject<{
152
158
  max_tool_calls: z.ZodOptional<z.ZodNumber>;
153
159
  max_duration_minutes: z.ZodOptional<z.ZodNumber>;
@@ -318,6 +324,11 @@ export declare const PluginConfigSchema: z.ZodObject<{
318
324
  trigger_paths: z.ZodDefault<z.ZodArray<z.ZodString>>;
319
325
  trigger_keywords: z.ZodDefault<z.ZodArray<z.ZodString>>;
320
326
  }, z.core.$strip>>;
327
+ compaction_advisory: z.ZodOptional<z.ZodObject<{
328
+ enabled: z.ZodDefault<z.ZodBoolean>;
329
+ thresholds: z.ZodDefault<z.ZodArray<z.ZodNumber>>;
330
+ message: z.ZodDefault<z.ZodString>;
331
+ }, z.core.$strip>>;
321
332
  }, z.core.$strip>;
322
333
  export type PluginConfig = z.infer<typeof PluginConfigSchema>;
323
334
  export type { AgentName, PipelineAgentName, QAAgentName, } from './constants';
package/dist/index.js CHANGED
@@ -1,20 +1,5 @@
1
1
  // @bun
2
- var __create = Object.create;
3
- var __getProtoOf = Object.getPrototypeOf;
4
2
  var __defProp = Object.defineProperty;
5
- var __getOwnPropNames = Object.getOwnPropertyNames;
6
- var __hasOwnProp = Object.prototype.hasOwnProperty;
7
- var __toESM = (mod, isNodeMode, target) => {
8
- target = mod != null ? __create(__getProtoOf(mod)) : {};
9
- const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
10
- for (let key of __getOwnPropNames(mod))
11
- if (!__hasOwnProp.call(to, key))
12
- __defProp(to, key, {
13
- get: () => mod[key],
14
- enumerable: true
15
- });
16
- return to;
17
- };
18
3
  var __export = (target, all) => {
19
4
  for (var name in all)
20
5
  __defProp(target, name, {
@@ -13644,7 +13629,8 @@ var EvidenceTypeSchema = exports_external.enum([
13644
13629
  "test",
13645
13630
  "diff",
13646
13631
  "approval",
13647
- "note"
13632
+ "note",
13633
+ "retrospective"
13648
13634
  ]);
13649
13635
  var EvidenceVerdictSchema = exports_external.enum([
13650
13636
  "pass",
@@ -13695,12 +13681,27 @@ var ApprovalEvidenceSchema = BaseEvidenceSchema.extend({
13695
13681
  var NoteEvidenceSchema = BaseEvidenceSchema.extend({
13696
13682
  type: exports_external.literal("note")
13697
13683
  });
13684
+ var RetrospectiveEvidenceSchema = BaseEvidenceSchema.extend({
13685
+ type: exports_external.literal("retrospective"),
13686
+ phase_number: exports_external.number().int().min(0),
13687
+ total_tool_calls: exports_external.number().int().min(0),
13688
+ coder_revisions: exports_external.number().int().min(0),
13689
+ reviewer_rejections: exports_external.number().int().min(0),
13690
+ test_failures: exports_external.number().int().min(0),
13691
+ security_findings: exports_external.number().int().min(0),
13692
+ integration_issues: exports_external.number().int().min(0),
13693
+ task_count: exports_external.number().int().min(1),
13694
+ task_complexity: exports_external.enum(["trivial", "simple", "moderate", "complex"]),
13695
+ top_rejection_reasons: exports_external.array(exports_external.string()).default([]),
13696
+ lessons_learned: exports_external.array(exports_external.string()).max(5).default([])
13697
+ });
13698
13698
  var EvidenceSchema = exports_external.discriminatedUnion("type", [
13699
13699
  ReviewEvidenceSchema,
13700
13700
  TestEvidenceSchema,
13701
13701
  DiffEvidenceSchema,
13702
13702
  ApprovalEvidenceSchema,
13703
- NoteEvidenceSchema
13703
+ NoteEvidenceSchema,
13704
+ RetrospectiveEvidenceSchema
13704
13705
  ]);
13705
13706
  var EvidenceBundleSchema = exports_external.object({
13706
13707
  schema_version: exports_external.literal("1.0.0"),
@@ -13836,6 +13837,11 @@ var UIReviewConfigSchema = exports_external.object({
13836
13837
  "profile page"
13837
13838
  ])
13838
13839
  });
13840
+ var CompactionAdvisoryConfigSchema = exports_external.object({
13841
+ enabled: exports_external.boolean().default(true),
13842
+ thresholds: exports_external.array(exports_external.number().int().min(10).max(500)).default([50, 75, 100, 125, 150]),
13843
+ message: exports_external.string().default("[SWARM HINT] Session has ${totalToolCalls} tool calls. Consider compacting at next phase boundary to maintain context quality.")
13844
+ });
13839
13845
  var GuardrailsProfileSchema = exports_external.object({
13840
13846
  max_tool_calls: exports_external.number().min(0).max(1000).optional(),
13841
13847
  max_duration_minutes: exports_external.number().min(0).max(480).optional(),
@@ -13949,7 +13955,8 @@ var PluginConfigSchema = exports_external.object({
13949
13955
  review_passes: ReviewPassesConfigSchema.optional(),
13950
13956
  integration_analysis: IntegrationAnalysisConfigSchema.optional(),
13951
13957
  docs: DocsConfigSchema.optional(),
13952
- ui_review: UIReviewConfigSchema.optional()
13958
+ ui_review: UIReviewConfigSchema.optional(),
13959
+ compaction_advisory: CompactionAdvisoryConfigSchema.optional()
13953
13960
  });
13954
13961
 
13955
13962
  // src/config/loader.ts
@@ -14132,6 +14139,7 @@ You THINK. Subagents DO. You have the largest context window and strongest reaso
14132
14139
  - Target file is in: pages/, components/, views/, screens/, ui/, layouts/
14133
14140
  If triggered: delegate to {{AGENT_PREFIX}}designer FIRST to produce a code scaffold. Then pass the scaffold to {{AGENT_PREFIX}}coder as INPUT alongside the task. The coder implements the TODOs in the scaffold without changing component structure or accessibility attributes.
14134
14141
  If not triggered: delegate directly to {{AGENT_PREFIX}}coder as normal.
14142
+ 10. **RETROSPECTIVE TRACKING**: At the end of every phase, record phase metrics in .swarm/context.md under "## Phase Metrics" and write a retrospective evidence entry via the evidence manager. Track: phase_number, total_tool_calls, coder_revisions, reviewer_rejections, test_failures, security_findings, integration_issues, task_count, task_complexity, top_rejection_reasons, lessons_learned (max 5). Reset Phase Metrics to 0 after writing.
14135
14143
 
14136
14144
  ## AGENTS
14137
14145
 
@@ -14296,7 +14304,8 @@ For each task (respecting dependencies):
14296
14304
  5e. Security gate: if file matches security globs or content has security keywords \u2192 {{AGENT_PREFIX}}reviewer security-only. REJECTED \u2192 coder retry.
14297
14305
  5f. {{AGENT_PREFIX}}test_engineer - Verification tests. FAIL \u2192 coder retry from 5d.
14298
14306
  5g. {{AGENT_PREFIX}}test_engineer - Adversarial tests. FAIL \u2192 coder retry from 5d.
14299
- 5h. Update plan.md [x], proceed to next task.
14307
+ 5h. COVERAGE CHECK: If test_engineer reports coverage < 70% \u2192 delegate {{AGENT_PREFIX}}test_engineer for an additional test pass targeting uncovered paths. This is a soft guideline; use judgment for trivial tasks.
14308
+ 5i. Update plan.md [x], proceed to next task.
14300
14309
 
14301
14310
  ### Phase 6: Phase Complete
14302
14311
  1. {{AGENT_PREFIX}}explorer - Rescan
@@ -14305,8 +14314,9 @@ For each task (respecting dependencies):
14305
14314
  - Summary of what was added/modified/removed
14306
14315
  - List of doc files that may need updating (README.md, CONTRIBUTING.md, docs/)
14307
14316
  3. Update context.md
14308
- 4. Summarize to user
14309
- 5. Ask: "Ready for Phase [N+1]?"
14317
+ 4. Write retrospective evidence: record phase_number, total_tool_calls, coder_revisions, reviewer_rejections, test_failures, security_findings, integration_issues, task_count, task_complexity, top_rejection_reasons, lessons_learned to .swarm/evidence/ via the evidence manager. Reset Phase Metrics in context.md to 0.
14318
+ 5. Summarize to user
14319
+ 6. Ask: "Ready for Phase [N+1]?"
14310
14320
 
14311
14321
  ### Blockers
14312
14322
  Mark [BLOCKED] in plan.md, skip to next unblocked task, inform user.
@@ -14906,7 +14916,13 @@ OUTPUT FORMAT:
14906
14916
  VERDICT: PASS | FAIL
14907
14917
  TESTS: [total count] tests, [pass count] passed, [fail count] failed
14908
14918
  FAILURES: [list of failed test names + error messages, if any]
14909
- COVERAGE: [areas covered]`;
14919
+ COVERAGE: [areas covered]
14920
+
14921
+ COVERAGE REPORTING:
14922
+ - After running tests, report the line/branch coverage percentage if the test runner provides it.
14923
+ - Format: COVERAGE_PCT: [N]% (or "N/A" if not available)
14924
+ - If COVERAGE_PCT < 70%, add a note: "COVERAGE_WARNING: Below 70% threshold \u2014 consider additional test cases for uncovered paths."
14925
+ - The architect uses this to decide whether to request an additional test pass (Rule 10 / Phase 5 step 5h).`;
14910
14926
  function createTestEngineerAgent(model, customPrompt, customAppendPrompt) {
14911
14927
  let prompt = TEST_ENGINEER_PROMPT;
14912
14928
  if (customPrompt) {
@@ -15449,7 +15465,8 @@ function startAgentSession(sessionId, agentName, staleDurationMs = 7200000) {
15449
15465
  delegationActive: false,
15450
15466
  activeInvocationId: 0,
15451
15467
  lastInvocationIdByAgent: {},
15452
- windows: {}
15468
+ windows: {},
15469
+ lastCompactionHint: 0
15453
15470
  };
15454
15471
  swarmState.agentSessions.set(sessionId, sessionState);
15455
15472
  swarmState.activeAgent.set(sessionId, agentName);
@@ -15473,6 +15490,9 @@ function ensureAgentSession(sessionId, agentName) {
15473
15490
  session.lastInvocationIdByAgent = {};
15474
15491
  session.windows = {};
15475
15492
  }
15493
+ if (session.lastCompactionHint === undefined) {
15494
+ session.lastCompactionHint = 0;
15495
+ }
15476
15496
  session.lastToolCallTime = now;
15477
15497
  return session;
15478
15498
  }
@@ -17494,6 +17514,10 @@ ${originalText}`;
17494
17514
  })
17495
17515
  };
17496
17516
  }
17517
+ // src/hooks/system-enhancer.ts
17518
+ import * as fs3 from "fs";
17519
+ import * as path7 from "path";
17520
+
17497
17521
  // src/hooks/context-scoring.ts
17498
17522
  function calculateAgeFactor(ageHours, config2) {
17499
17523
  if (ageHours <= 0) {
@@ -17629,6 +17653,66 @@ function createSystemEnhancerHook(config2, directory) {
17629
17653
  if (config2.docs?.enabled === false) {
17630
17654
  tryInject("[SWARM CONFIG] Docs agent is DISABLED. Skip docs delegation in Phase 6.");
17631
17655
  }
17656
+ const sessionId_retro = _input.sessionID;
17657
+ const activeAgent_retro = swarmState.activeAgent.get(sessionId_retro ?? "");
17658
+ const isArchitect = !activeAgent_retro || stripKnownSwarmPrefix(activeAgent_retro) === "architect";
17659
+ if (isArchitect) {
17660
+ try {
17661
+ const evidenceDir = path7.join(directory, ".swarm", "evidence");
17662
+ if (fs3.existsSync(evidenceDir)) {
17663
+ const files = fs3.readdirSync(evidenceDir).filter((f) => f.endsWith(".json")).sort().reverse();
17664
+ for (const file2 of files.slice(0, 5)) {
17665
+ const content = JSON.parse(fs3.readFileSync(path7.join(evidenceDir, file2), "utf-8"));
17666
+ if (content.type === "retrospective") {
17667
+ const retro = content;
17668
+ const hints = [];
17669
+ if (retro.reviewer_rejections > 2) {
17670
+ hints.push(`Phase ${retro.phase_number} had ${retro.reviewer_rejections} reviewer rejections.`);
17671
+ }
17672
+ if (retro.top_rejection_reasons.length > 0) {
17673
+ hints.push(`Common rejection reasons: ${retro.top_rejection_reasons.join(", ")}.`);
17674
+ }
17675
+ if (retro.lessons_learned.length > 0) {
17676
+ hints.push(`Lessons: ${retro.lessons_learned.join("; ")}.`);
17677
+ }
17678
+ if (hints.length > 0) {
17679
+ const retroHint = `[SWARM RETROSPECTIVE] From Phase ${retro.phase_number}: ${hints.join(" ")}`;
17680
+ if (retroHint.length <= 800) {
17681
+ tryInject(retroHint);
17682
+ } else {
17683
+ tryInject(retroHint.substring(0, 800) + "...");
17684
+ }
17685
+ }
17686
+ break;
17687
+ }
17688
+ }
17689
+ }
17690
+ } catch {}
17691
+ const compactionConfig = config2.compaction_advisory;
17692
+ if (compactionConfig?.enabled !== false && sessionId_retro) {
17693
+ const session = swarmState.agentSessions.get(sessionId_retro);
17694
+ if (session) {
17695
+ const totalToolCalls = Array.from(swarmState.toolAggregates.values()).reduce((sum, agg) => sum + agg.count, 0);
17696
+ const thresholds = compactionConfig?.thresholds ?? [
17697
+ 50,
17698
+ 75,
17699
+ 100,
17700
+ 125,
17701
+ 150
17702
+ ];
17703
+ const lastHint = session.lastCompactionHint || 0;
17704
+ for (const threshold of thresholds) {
17705
+ if (totalToolCalls >= threshold && lastHint < threshold) {
17706
+ const messageTemplate = compactionConfig?.message ?? "[SWARM HINT] Session has ${totalToolCalls} tool calls. Consider compacting at next phase boundary to maintain context quality.";
17707
+ const message = messageTemplate.replace("${totalToolCalls}", String(totalToolCalls));
17708
+ tryInject(message);
17709
+ session.lastCompactionHint = threshold;
17710
+ break;
17711
+ }
17712
+ }
17713
+ }
17714
+ }
17715
+ }
17632
17716
  return;
17633
17717
  }
17634
17718
  const userScoringConfig = config2.context_budget?.scoring;
@@ -17752,6 +17836,77 @@ function createSystemEnhancerHook(config2, directory) {
17752
17836
  metadata: { contentType: "prose" }
17753
17837
  });
17754
17838
  }
17839
+ const sessionId_retro_b = _input.sessionID;
17840
+ const activeAgent_retro_b = swarmState.activeAgent.get(sessionId_retro_b ?? "");
17841
+ const isArchitect_b = !activeAgent_retro_b || stripKnownSwarmPrefix(activeAgent_retro_b) === "architect";
17842
+ if (isArchitect_b) {
17843
+ try {
17844
+ const evidenceDir_b = path7.join(directory, ".swarm", "evidence");
17845
+ if (fs3.existsSync(evidenceDir_b)) {
17846
+ const files_b = fs3.readdirSync(evidenceDir_b).filter((f) => f.endsWith(".json")).sort().reverse();
17847
+ for (const file2 of files_b.slice(0, 5)) {
17848
+ const content_b = JSON.parse(fs3.readFileSync(path7.join(evidenceDir_b, file2), "utf-8"));
17849
+ if (content_b.type === "retrospective") {
17850
+ const retro_b = content_b;
17851
+ const hints_b = [];
17852
+ if (retro_b.reviewer_rejections > 2) {
17853
+ hints_b.push(`Phase ${retro_b.phase_number} had ${retro_b.reviewer_rejections} reviewer rejections.`);
17854
+ }
17855
+ if (retro_b.top_rejection_reasons.length > 0) {
17856
+ hints_b.push(`Common rejection reasons: ${retro_b.top_rejection_reasons.join(", ")}.`);
17857
+ }
17858
+ if (retro_b.lessons_learned.length > 0) {
17859
+ hints_b.push(`Lessons: ${retro_b.lessons_learned.join("; ")}.`);
17860
+ }
17861
+ if (hints_b.length > 0) {
17862
+ const retroHint_b = `[SWARM RETROSPECTIVE] From Phase ${retro_b.phase_number}: ${hints_b.join(" ")}`;
17863
+ const retroText = retroHint_b.length <= 800 ? retroHint_b : retroHint_b.substring(0, 800) + "...";
17864
+ candidates.push({
17865
+ id: `candidate-${idCounter++}`,
17866
+ kind: "phase",
17867
+ text: retroText,
17868
+ tokens: estimateTokens(retroText),
17869
+ priority: 2,
17870
+ metadata: { contentType: "prose" }
17871
+ });
17872
+ }
17873
+ break;
17874
+ }
17875
+ }
17876
+ }
17877
+ } catch {}
17878
+ const compactionConfig_b = config2.compaction_advisory;
17879
+ if (compactionConfig_b?.enabled !== false && sessionId_retro_b) {
17880
+ const session_b = swarmState.agentSessions.get(sessionId_retro_b);
17881
+ if (session_b) {
17882
+ const totalToolCalls_b = Array.from(swarmState.toolAggregates.values()).reduce((sum, agg) => sum + agg.count, 0);
17883
+ const thresholds_b = compactionConfig_b?.thresholds ?? [
17884
+ 50,
17885
+ 75,
17886
+ 100,
17887
+ 125,
17888
+ 150
17889
+ ];
17890
+ const lastHint_b = session_b.lastCompactionHint || 0;
17891
+ for (const threshold of thresholds_b) {
17892
+ if (totalToolCalls_b >= threshold && lastHint_b < threshold) {
17893
+ const messageTemplate_b = compactionConfig_b?.message ?? "[SWARM HINT] Session has ${totalToolCalls} tool calls. Consider compacting at next phase boundary to maintain context quality.";
17894
+ const compactionText = messageTemplate_b.replace("${totalToolCalls}", String(totalToolCalls_b));
17895
+ candidates.push({
17896
+ id: `candidate-${idCounter++}`,
17897
+ kind: "phase",
17898
+ text: compactionText,
17899
+ tokens: estimateTokens(compactionText),
17900
+ priority: 1,
17901
+ metadata: { contentType: "prose" }
17902
+ });
17903
+ session_b.lastCompactionHint = threshold;
17904
+ break;
17905
+ }
17906
+ }
17907
+ }
17908
+ }
17909
+ }
17755
17910
  const ranked = rankCandidates(candidates, effectiveConfig);
17756
17911
  for (const candidate of ranked) {
17757
17912
  if (injectedTokens + candidate.tokens > maxInjectionTokens) {
@@ -18678,10 +18833,10 @@ function mergeDefs2(...defs) {
18678
18833
  function cloneDef2(schema) {
18679
18834
  return mergeDefs2(schema._zod.def);
18680
18835
  }
18681
- function getElementAtPath2(obj, path7) {
18682
- if (!path7)
18836
+ function getElementAtPath2(obj, path8) {
18837
+ if (!path8)
18683
18838
  return obj;
18684
- return path7.reduce((acc, key) => acc?.[key], obj);
18839
+ return path8.reduce((acc, key) => acc?.[key], obj);
18685
18840
  }
18686
18841
  function promiseAllObject2(promisesObj) {
18687
18842
  const keys = Object.keys(promisesObj);
@@ -19040,11 +19195,11 @@ function aborted2(x, startIndex = 0) {
19040
19195
  }
19041
19196
  return false;
19042
19197
  }
19043
- function prefixIssues2(path7, issues) {
19198
+ function prefixIssues2(path8, issues) {
19044
19199
  return issues.map((iss) => {
19045
19200
  var _a2;
19046
19201
  (_a2 = iss).path ?? (_a2.path = []);
19047
- iss.path.unshift(path7);
19202
+ iss.path.unshift(path8);
19048
19203
  return iss;
19049
19204
  });
19050
19205
  }
@@ -19212,7 +19367,7 @@ function treeifyError2(error49, _mapper) {
19212
19367
  return issue3.message;
19213
19368
  };
19214
19369
  const result = { errors: [] };
19215
- const processError = (error50, path7 = []) => {
19370
+ const processError = (error50, path8 = []) => {
19216
19371
  var _a2, _b;
19217
19372
  for (const issue3 of error50.issues) {
19218
19373
  if (issue3.code === "invalid_union" && issue3.errors.length) {
@@ -19222,7 +19377,7 @@ function treeifyError2(error49, _mapper) {
19222
19377
  } else if (issue3.code === "invalid_element") {
19223
19378
  processError({ issues: issue3.issues }, issue3.path);
19224
19379
  } else {
19225
- const fullpath = [...path7, ...issue3.path];
19380
+ const fullpath = [...path8, ...issue3.path];
19226
19381
  if (fullpath.length === 0) {
19227
19382
  result.errors.push(mapper(issue3));
19228
19383
  continue;
@@ -19254,8 +19409,8 @@ function treeifyError2(error49, _mapper) {
19254
19409
  }
19255
19410
  function toDotPath2(_path) {
19256
19411
  const segs = [];
19257
- const path7 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
19258
- for (const seg of path7) {
19412
+ const path8 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
19413
+ for (const seg of path8) {
19259
19414
  if (typeof seg === "number")
19260
19415
  segs.push(`[${seg}]`);
19261
19416
  else if (typeof seg === "symbol")
@@ -30295,14 +30450,14 @@ function validateBase(base) {
30295
30450
  function validatePaths(paths) {
30296
30451
  if (!paths)
30297
30452
  return null;
30298
- for (const path7 of paths) {
30299
- if (!path7 || path7.length === 0) {
30453
+ for (const path8 of paths) {
30454
+ if (!path8 || path8.length === 0) {
30300
30455
  return "empty path not allowed";
30301
30456
  }
30302
- if (path7.length > MAX_PATH_LENGTH) {
30457
+ if (path8.length > MAX_PATH_LENGTH) {
30303
30458
  return `path exceeds maximum length of ${MAX_PATH_LENGTH}`;
30304
30459
  }
30305
- if (SHELL_METACHARACTERS.test(path7)) {
30460
+ if (SHELL_METACHARACTERS.test(path8)) {
30306
30461
  return "path contains shell metacharacters";
30307
30462
  }
30308
30463
  }
@@ -30365,8 +30520,8 @@ var diff = tool({
30365
30520
  if (parts.length >= 3) {
30366
30521
  const additions = parseInt(parts[0]) || 0;
30367
30522
  const deletions = parseInt(parts[1]) || 0;
30368
- const path7 = parts[2];
30369
- files.push({ path: path7, additions, deletions });
30523
+ const path8 = parts[2];
30524
+ files.push({ path: path8, additions, deletions });
30370
30525
  }
30371
30526
  }
30372
30527
  const contractChanges = [];
@@ -30592,8 +30747,8 @@ Use these as DOMAIN values when delegating to @sme.`;
30592
30747
  }
30593
30748
  });
30594
30749
  // src/tools/file-extractor.ts
30595
- import * as fs3 from "fs";
30596
- import * as path7 from "path";
30750
+ import * as fs4 from "fs";
30751
+ import * as path8 from "path";
30597
30752
  var EXT_MAP = {
30598
30753
  python: ".py",
30599
30754
  py: ".py",
@@ -30655,8 +30810,8 @@ var extract_code_blocks = tool({
30655
30810
  execute: async (args) => {
30656
30811
  const { content, output_dir, prefix } = args;
30657
30812
  const targetDir = output_dir || process.cwd();
30658
- if (!fs3.existsSync(targetDir)) {
30659
- fs3.mkdirSync(targetDir, { recursive: true });
30813
+ if (!fs4.existsSync(targetDir)) {
30814
+ fs4.mkdirSync(targetDir, { recursive: true });
30660
30815
  }
30661
30816
  const pattern = /```(\w*)\n([\s\S]*?)```/g;
30662
30817
  const matches = [...content.matchAll(pattern)];
@@ -30671,16 +30826,16 @@ var extract_code_blocks = tool({
30671
30826
  if (prefix) {
30672
30827
  filename = `${prefix}_${filename}`;
30673
30828
  }
30674
- let filepath = path7.join(targetDir, filename);
30675
- const base = path7.basename(filepath, path7.extname(filepath));
30676
- const ext = path7.extname(filepath);
30829
+ let filepath = path8.join(targetDir, filename);
30830
+ const base = path8.basename(filepath, path8.extname(filepath));
30831
+ const ext = path8.extname(filepath);
30677
30832
  let counter = 1;
30678
- while (fs3.existsSync(filepath)) {
30679
- filepath = path7.join(targetDir, `${base}_${counter}${ext}`);
30833
+ while (fs4.existsSync(filepath)) {
30834
+ filepath = path8.join(targetDir, `${base}_${counter}${ext}`);
30680
30835
  counter++;
30681
30836
  }
30682
30837
  try {
30683
- fs3.writeFileSync(filepath, code.trim(), "utf-8");
30838
+ fs4.writeFileSync(filepath, code.trim(), "utf-8");
30684
30839
  savedFiles.push(filepath);
30685
30840
  } catch (error93) {
30686
30841
  errors5.push(`Failed to save ${filename}: ${error93 instanceof Error ? error93.message : String(error93)}`);
package/dist/state.d.ts CHANGED
@@ -53,6 +53,8 @@ export interface AgentSessionState {
53
53
  lastInvocationIdByAgent: Record<string, number>;
54
54
  /** Active invocation windows keyed by "${agentName}:${invId}" */
55
55
  windows: Record<string, InvocationWindow>;
56
+ /** Last tool-call threshold at which a compaction hint was issued */
57
+ lastCompactionHint: number;
56
58
  }
57
59
  /**
58
60
  * Represents a single agent invocation window with isolated guardrail budgets.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-swarm",
3
- "version": "6.1.2",
3
+ "version": "6.2.0",
4
4
  "description": "Architect-centric agentic swarm plugin for OpenCode - hub-and-spoke orchestration with SME consultation, code generation, and QA review",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",