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 +40 -3
- package/dist/config/evidence-schema.d.ts +94 -0
- package/dist/config/schema.d.ts +11 -0
- package/dist/index.js +203 -48
- package/dist/state.d.ts +2 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
<p align="center">
|
|
2
|
-
<img src="https://img.shields.io/badge/version-6.
|
|
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-
|
|
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
|
-
|
|
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;
|
package/dist/config/schema.d.ts
CHANGED
|
@@ -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.
|
|
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.
|
|
14309
|
-
5.
|
|
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,
|
|
18682
|
-
if (!
|
|
18836
|
+
function getElementAtPath2(obj, path8) {
|
|
18837
|
+
if (!path8)
|
|
18683
18838
|
return obj;
|
|
18684
|
-
return
|
|
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(
|
|
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(
|
|
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,
|
|
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 = [...
|
|
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
|
|
19258
|
-
for (const seg of
|
|
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
|
|
30299
|
-
if (!
|
|
30453
|
+
for (const path8 of paths) {
|
|
30454
|
+
if (!path8 || path8.length === 0) {
|
|
30300
30455
|
return "empty path not allowed";
|
|
30301
30456
|
}
|
|
30302
|
-
if (
|
|
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(
|
|
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
|
|
30369
|
-
files.push({ path:
|
|
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
|
|
30596
|
-
import * as
|
|
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 (!
|
|
30659
|
-
|
|
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 =
|
|
30675
|
-
const base =
|
|
30676
|
-
const ext =
|
|
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 (
|
|
30679
|
-
filepath =
|
|
30833
|
+
while (fs4.existsSync(filepath)) {
|
|
30834
|
+
filepath = path8.join(targetDir, `${base}_${counter}${ext}`);
|
|
30680
30835
|
counter++;
|
|
30681
30836
|
}
|
|
30682
30837
|
try {
|
|
30683
|
-
|
|
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.
|
|
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",
|