opencode-swarm-plugin 0.44.0 → 0.44.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/swarm.serve.test.ts +6 -4
- package/bin/swarm.ts +16 -10
- package/dist/compaction-prompt-scoring.js +139 -0
- package/dist/eval-capture.js +12811 -0
- package/dist/hive.d.ts.map +1 -1
- package/dist/index.js +7644 -62599
- package/dist/plugin.js +23766 -78721
- package/dist/swarm-orchestrate.d.ts.map +1 -1
- package/dist/swarm-prompts.d.ts.map +1 -1
- package/dist/swarm-review.d.ts.map +1 -1
- package/package.json +17 -5
- package/.changeset/swarm-insights-data-layer.md +0 -63
- package/.hive/analysis/eval-failure-analysis-2025-12-25.md +0 -331
- package/.hive/analysis/session-data-quality-audit.md +0 -320
- package/.hive/eval-results.json +0 -483
- package/.hive/issues.jsonl +0 -138
- package/.hive/memories.jsonl +0 -729
- package/.opencode/eval-history.jsonl +0 -327
- package/.turbo/turbo-build.log +0 -9
- package/CHANGELOG.md +0 -2286
- package/SCORER-ANALYSIS.md +0 -598
- package/docs/analysis/subagent-coordination-patterns.md +0 -902
- package/docs/analysis-socratic-planner-pattern.md +0 -504
- package/docs/planning/ADR-001-monorepo-structure.md +0 -171
- package/docs/planning/ADR-002-package-extraction.md +0 -393
- package/docs/planning/ADR-003-performance-improvements.md +0 -451
- package/docs/planning/ADR-004-message-queue-features.md +0 -187
- package/docs/planning/ADR-005-devtools-observability.md +0 -202
- package/docs/planning/ADR-007-swarm-enhancements-worktree-review.md +0 -168
- package/docs/planning/ADR-008-worker-handoff-protocol.md +0 -293
- package/docs/planning/ADR-009-oh-my-opencode-patterns.md +0 -353
- package/docs/planning/ADR-010-cass-inhousing.md +0 -1215
- package/docs/planning/ROADMAP.md +0 -368
- package/docs/semantic-memory-cli-syntax.md +0 -123
- package/docs/swarm-mail-architecture.md +0 -1147
- package/docs/testing/context-recovery-test.md +0 -470
- package/evals/ARCHITECTURE.md +0 -1189
- package/evals/README.md +0 -768
- package/evals/compaction-prompt.eval.ts +0 -149
- package/evals/compaction-resumption.eval.ts +0 -289
- package/evals/coordinator-behavior.eval.ts +0 -307
- package/evals/coordinator-session.eval.ts +0 -154
- package/evals/evalite.config.ts.bak +0 -15
- package/evals/example.eval.ts +0 -31
- package/evals/fixtures/cass-baseline.ts +0 -217
- package/evals/fixtures/compaction-cases.ts +0 -350
- package/evals/fixtures/compaction-prompt-cases.ts +0 -311
- package/evals/fixtures/coordinator-sessions.ts +0 -328
- package/evals/fixtures/decomposition-cases.ts +0 -105
- package/evals/lib/compaction-loader.test.ts +0 -248
- package/evals/lib/compaction-loader.ts +0 -320
- package/evals/lib/data-loader.evalite-test.ts +0 -289
- package/evals/lib/data-loader.test.ts +0 -345
- package/evals/lib/data-loader.ts +0 -281
- package/evals/lib/llm.ts +0 -115
- package/evals/scorers/compaction-prompt-scorers.ts +0 -145
- package/evals/scorers/compaction-scorers.ts +0 -305
- package/evals/scorers/coordinator-discipline.evalite-test.ts +0 -539
- package/evals/scorers/coordinator-discipline.ts +0 -325
- package/evals/scorers/index.test.ts +0 -146
- package/evals/scorers/index.ts +0 -328
- package/evals/scorers/outcome-scorers.evalite-test.ts +0 -27
- package/evals/scorers/outcome-scorers.ts +0 -349
- package/evals/swarm-decomposition.eval.ts +0 -121
- package/examples/commands/swarm.md +0 -745
- package/examples/plugin-wrapper-template.ts +0 -2515
- package/examples/skills/hive-workflow/SKILL.md +0 -212
- package/examples/skills/skill-creator/SKILL.md +0 -223
- package/examples/skills/swarm-coordination/SKILL.md +0 -292
- package/global-skills/cli-builder/SKILL.md +0 -344
- package/global-skills/cli-builder/references/advanced-patterns.md +0 -244
- package/global-skills/learning-systems/SKILL.md +0 -644
- package/global-skills/skill-creator/LICENSE.txt +0 -202
- package/global-skills/skill-creator/SKILL.md +0 -352
- package/global-skills/skill-creator/references/output-patterns.md +0 -82
- package/global-skills/skill-creator/references/workflows.md +0 -28
- package/global-skills/swarm-coordination/SKILL.md +0 -995
- package/global-skills/swarm-coordination/references/coordinator-patterns.md +0 -235
- package/global-skills/swarm-coordination/references/strategies.md +0 -138
- package/global-skills/system-design/SKILL.md +0 -213
- package/global-skills/testing-patterns/SKILL.md +0 -430
- package/global-skills/testing-patterns/references/dependency-breaking-catalog.md +0 -586
- package/opencode-swarm-plugin-0.30.7.tgz +0 -0
- package/opencode-swarm-plugin-0.31.0.tgz +0 -0
- package/scripts/cleanup-test-memories.ts +0 -346
- package/scripts/init-skill.ts +0 -222
- package/scripts/migrate-unknown-sessions.ts +0 -349
- package/scripts/validate-skill.ts +0 -204
- package/src/agent-mail.ts +0 -1724
- package/src/anti-patterns.test.ts +0 -1167
- package/src/anti-patterns.ts +0 -448
- package/src/compaction-capture.integration.test.ts +0 -257
- package/src/compaction-hook.test.ts +0 -838
- package/src/compaction-hook.ts +0 -1204
- package/src/compaction-observability.integration.test.ts +0 -139
- package/src/compaction-observability.test.ts +0 -187
- package/src/compaction-observability.ts +0 -324
- package/src/compaction-prompt-scorers.test.ts +0 -475
- package/src/compaction-prompt-scoring.ts +0 -300
- package/src/contributor-tools.test.ts +0 -133
- package/src/contributor-tools.ts +0 -201
- package/src/dashboard.test.ts +0 -611
- package/src/dashboard.ts +0 -462
- package/src/error-enrichment.test.ts +0 -403
- package/src/error-enrichment.ts +0 -219
- package/src/eval-capture.test.ts +0 -1015
- package/src/eval-capture.ts +0 -929
- package/src/eval-gates.test.ts +0 -306
- package/src/eval-gates.ts +0 -218
- package/src/eval-history.test.ts +0 -508
- package/src/eval-history.ts +0 -214
- package/src/eval-learning.test.ts +0 -378
- package/src/eval-learning.ts +0 -360
- package/src/eval-runner.test.ts +0 -223
- package/src/eval-runner.ts +0 -402
- package/src/export-tools.test.ts +0 -476
- package/src/export-tools.ts +0 -257
- package/src/hive.integration.test.ts +0 -2241
- package/src/hive.ts +0 -1628
- package/src/index.ts +0 -940
- package/src/learning.integration.test.ts +0 -1815
- package/src/learning.ts +0 -1079
- package/src/logger.test.ts +0 -189
- package/src/logger.ts +0 -135
- package/src/mandate-promotion.test.ts +0 -473
- package/src/mandate-promotion.ts +0 -239
- package/src/mandate-storage.integration.test.ts +0 -601
- package/src/mandate-storage.test.ts +0 -578
- package/src/mandate-storage.ts +0 -794
- package/src/mandates.ts +0 -540
- package/src/memory-tools.test.ts +0 -195
- package/src/memory-tools.ts +0 -344
- package/src/memory.integration.test.ts +0 -334
- package/src/memory.test.ts +0 -158
- package/src/memory.ts +0 -527
- package/src/model-selection.test.ts +0 -188
- package/src/model-selection.ts +0 -68
- package/src/observability-tools.test.ts +0 -359
- package/src/observability-tools.ts +0 -871
- package/src/output-guardrails.test.ts +0 -438
- package/src/output-guardrails.ts +0 -381
- package/src/pattern-maturity.test.ts +0 -1160
- package/src/pattern-maturity.ts +0 -525
- package/src/planning-guardrails.test.ts +0 -491
- package/src/planning-guardrails.ts +0 -438
- package/src/plugin.ts +0 -23
- package/src/post-compaction-tracker.test.ts +0 -251
- package/src/post-compaction-tracker.ts +0 -237
- package/src/query-tools.test.ts +0 -636
- package/src/query-tools.ts +0 -324
- package/src/rate-limiter.integration.test.ts +0 -466
- package/src/rate-limiter.ts +0 -774
- package/src/replay-tools.test.ts +0 -496
- package/src/replay-tools.ts +0 -240
- package/src/repo-crawl.integration.test.ts +0 -441
- package/src/repo-crawl.ts +0 -610
- package/src/schemas/cell-events.test.ts +0 -347
- package/src/schemas/cell-events.ts +0 -807
- package/src/schemas/cell.ts +0 -257
- package/src/schemas/evaluation.ts +0 -166
- package/src/schemas/index.test.ts +0 -199
- package/src/schemas/index.ts +0 -286
- package/src/schemas/mandate.ts +0 -232
- package/src/schemas/swarm-context.ts +0 -115
- package/src/schemas/task.ts +0 -161
- package/src/schemas/worker-handoff.test.ts +0 -302
- package/src/schemas/worker-handoff.ts +0 -131
- package/src/sessions/agent-discovery.test.ts +0 -137
- package/src/sessions/agent-discovery.ts +0 -112
- package/src/sessions/index.ts +0 -15
- package/src/skills.integration.test.ts +0 -1192
- package/src/skills.test.ts +0 -643
- package/src/skills.ts +0 -1549
- package/src/storage.integration.test.ts +0 -341
- package/src/storage.ts +0 -884
- package/src/structured.integration.test.ts +0 -817
- package/src/structured.test.ts +0 -1046
- package/src/structured.ts +0 -762
- package/src/swarm-decompose.test.ts +0 -188
- package/src/swarm-decompose.ts +0 -1302
- package/src/swarm-deferred.integration.test.ts +0 -157
- package/src/swarm-deferred.test.ts +0 -38
- package/src/swarm-insights.test.ts +0 -214
- package/src/swarm-insights.ts +0 -459
- package/src/swarm-mail.integration.test.ts +0 -970
- package/src/swarm-mail.ts +0 -739
- package/src/swarm-orchestrate.integration.test.ts +0 -282
- package/src/swarm-orchestrate.test.ts +0 -548
- package/src/swarm-orchestrate.ts +0 -3084
- package/src/swarm-prompts.test.ts +0 -1270
- package/src/swarm-prompts.ts +0 -2077
- package/src/swarm-research.integration.test.ts +0 -701
- package/src/swarm-research.test.ts +0 -698
- package/src/swarm-research.ts +0 -472
- package/src/swarm-review.integration.test.ts +0 -285
- package/src/swarm-review.test.ts +0 -879
- package/src/swarm-review.ts +0 -709
- package/src/swarm-strategies.ts +0 -407
- package/src/swarm-worktree.test.ts +0 -501
- package/src/swarm-worktree.ts +0 -575
- package/src/swarm.integration.test.ts +0 -2377
- package/src/swarm.ts +0 -38
- package/src/tool-adapter.integration.test.ts +0 -1221
- package/src/tool-availability.ts +0 -461
- package/tsconfig.json +0 -28
package/src/replay-tools.test.ts
DELETED
|
@@ -1,496 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Replay Tools Tests (RED Phase)
|
|
3
|
-
*
|
|
4
|
-
* TDD: These tests SHOULD FAIL until implementation exists.
|
|
5
|
-
*
|
|
6
|
-
* Tests verify:
|
|
7
|
-
* 1. fetchEpicEvents() - retrieves events for epic_id from libSQL
|
|
8
|
-
* 2. filterEvents() - filters by type/agent/time range
|
|
9
|
-
* 3. replayWithTiming() - yields events with correct delays at different speeds
|
|
10
|
-
* 4. formatReplayEvent() - produces color-coded output with relationships
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
import { describe, test, expect, beforeAll, afterAll } from "bun:test";
|
|
14
|
-
import {
|
|
15
|
-
fetchEpicEvents,
|
|
16
|
-
filterEvents,
|
|
17
|
-
replayWithTiming,
|
|
18
|
-
formatReplayEvent,
|
|
19
|
-
type ReplayEvent,
|
|
20
|
-
type ReplayFilter,
|
|
21
|
-
type ReplaySpeed,
|
|
22
|
-
} from "./replay-tools";
|
|
23
|
-
import {
|
|
24
|
-
closeSwarmMailLibSQL,
|
|
25
|
-
createInMemorySwarmMailLibSQL,
|
|
26
|
-
type SwarmMailAdapter,
|
|
27
|
-
} from "swarm-mail";
|
|
28
|
-
import { writeFileSync, mkdirSync } from "node:fs";
|
|
29
|
-
import { join } from "node:path";
|
|
30
|
-
import { tmpdir } from "node:os";
|
|
31
|
-
|
|
32
|
-
describe("replay-tools (RED phase - tests should FAIL)", () => {
|
|
33
|
-
let swarmMail: SwarmMailAdapter;
|
|
34
|
-
let sessionFile: string;
|
|
35
|
-
const projectPath = "/test/replay-project";
|
|
36
|
-
const epicId = "epic-abc123";
|
|
37
|
-
const testTimestamp = new Date("2025-12-25T12:00:00.000Z");
|
|
38
|
-
|
|
39
|
-
beforeAll(async () => {
|
|
40
|
-
// Create in-memory database
|
|
41
|
-
swarmMail = await createInMemorySwarmMailLibSQL(projectPath);
|
|
42
|
-
|
|
43
|
-
// Create test session file with sample events
|
|
44
|
-
const sessionDir = join(tmpdir(), "swarm-sessions-test");
|
|
45
|
-
mkdirSync(sessionDir, { recursive: true });
|
|
46
|
-
sessionFile = join(sessionDir, `session-${epicId}.jsonl`);
|
|
47
|
-
|
|
48
|
-
// Write test events (JSONL format - one JSON object per line)
|
|
49
|
-
const events = [
|
|
50
|
-
{
|
|
51
|
-
session_id: "session-1",
|
|
52
|
-
epic_id: epicId,
|
|
53
|
-
timestamp: new Date(testTimestamp.getTime()).toISOString(),
|
|
54
|
-
event_type: "DECISION",
|
|
55
|
-
decision_type: "decomposition_complete",
|
|
56
|
-
payload: {
|
|
57
|
-
subtask_count: 2,
|
|
58
|
-
strategy_used: "file-based",
|
|
59
|
-
epic_title: "Test Epic",
|
|
60
|
-
},
|
|
61
|
-
},
|
|
62
|
-
{
|
|
63
|
-
session_id: "session-1",
|
|
64
|
-
epic_id: epicId,
|
|
65
|
-
timestamp: new Date(testTimestamp.getTime() + 1000).toISOString(), // +1s
|
|
66
|
-
event_type: "DECISION",
|
|
67
|
-
decision_type: "worker_spawned",
|
|
68
|
-
payload: {
|
|
69
|
-
agent_name: "AgentA",
|
|
70
|
-
bead_id: `${epicId}.1`,
|
|
71
|
-
files: ["src/a.ts"],
|
|
72
|
-
},
|
|
73
|
-
},
|
|
74
|
-
{
|
|
75
|
-
session_id: "session-1",
|
|
76
|
-
epic_id: epicId,
|
|
77
|
-
timestamp: new Date(testTimestamp.getTime() + 2500).toISOString(), // +2.5s
|
|
78
|
-
event_type: "VIOLATION",
|
|
79
|
-
violation_type: "coordinator_editing_files",
|
|
80
|
-
payload: {
|
|
81
|
-
tool_name: "edit",
|
|
82
|
-
file_path: "src/bad.ts",
|
|
83
|
-
},
|
|
84
|
-
},
|
|
85
|
-
{
|
|
86
|
-
session_id: "session-1",
|
|
87
|
-
epic_id: epicId,
|
|
88
|
-
timestamp: new Date(testTimestamp.getTime() + 5000).toISOString(), // +5s
|
|
89
|
-
event_type: "OUTCOME",
|
|
90
|
-
outcome_type: "subtask_success",
|
|
91
|
-
payload: {
|
|
92
|
-
bead_id: `${epicId}.1`,
|
|
93
|
-
agent_name: "AgentA",
|
|
94
|
-
duration_ms: 3500,
|
|
95
|
-
files_touched: ["src/a.ts"],
|
|
96
|
-
},
|
|
97
|
-
},
|
|
98
|
-
];
|
|
99
|
-
|
|
100
|
-
writeFileSync(sessionFile, events.map((e) => JSON.stringify(e)).join("\n"));
|
|
101
|
-
});
|
|
102
|
-
|
|
103
|
-
afterAll(async () => {
|
|
104
|
-
await closeSwarmMailLibSQL(projectPath);
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
describe("fetchEpicEvents()", () => {
|
|
108
|
-
test("retrieves all events for a given epic_id", async () => {
|
|
109
|
-
// Should read JSONL file and parse events
|
|
110
|
-
const events = await fetchEpicEvents(epicId, sessionFile);
|
|
111
|
-
|
|
112
|
-
expect(events).toBeDefined();
|
|
113
|
-
expect(Array.isArray(events)).toBe(true);
|
|
114
|
-
expect(events.length).toBe(4);
|
|
115
|
-
|
|
116
|
-
// First event should be decomposition
|
|
117
|
-
expect(events[0].event_type).toBe("DECISION");
|
|
118
|
-
expect(events[0].epic_id).toBe(epicId);
|
|
119
|
-
});
|
|
120
|
-
|
|
121
|
-
test("returns events in chronological order", async () => {
|
|
122
|
-
const events = await fetchEpicEvents(epicId, sessionFile);
|
|
123
|
-
|
|
124
|
-
// Timestamps should be ascending
|
|
125
|
-
for (let i = 1; i < events.length; i++) {
|
|
126
|
-
const prevTime = new Date(events[i - 1].timestamp).getTime();
|
|
127
|
-
const currTime = new Date(events[i].timestamp).getTime();
|
|
128
|
-
expect(currTime).toBeGreaterThanOrEqual(prevTime);
|
|
129
|
-
}
|
|
130
|
-
});
|
|
131
|
-
|
|
132
|
-
test("calculates correct time delta between events", async () => {
|
|
133
|
-
const events = await fetchEpicEvents(epicId, sessionFile);
|
|
134
|
-
|
|
135
|
-
// Events have delta_ms property for replay timing
|
|
136
|
-
expect(events[0].delta_ms).toBe(0); // First event has no delta
|
|
137
|
-
expect(events[1].delta_ms).toBe(1000); // +1s from first
|
|
138
|
-
expect(events[2].delta_ms).toBe(1500); // +1.5s from second
|
|
139
|
-
expect(events[3].delta_ms).toBe(2500); // +2.5s from third
|
|
140
|
-
});
|
|
141
|
-
|
|
142
|
-
test("handles empty or non-existent files", async () => {
|
|
143
|
-
const events = await fetchEpicEvents("nonexistent-epic", "/tmp/nope.jsonl");
|
|
144
|
-
expect(events).toBeDefined();
|
|
145
|
-
expect(events.length).toBe(0);
|
|
146
|
-
});
|
|
147
|
-
|
|
148
|
-
test("preserves all event metadata", async () => {
|
|
149
|
-
const events = await fetchEpicEvents(epicId, sessionFile);
|
|
150
|
-
|
|
151
|
-
// Should include session_id, epic_id, timestamp, event_type, payload
|
|
152
|
-
const firstEvent = events[0];
|
|
153
|
-
expect(firstEvent.session_id).toBe("session-1");
|
|
154
|
-
expect(firstEvent.epic_id).toBe(epicId);
|
|
155
|
-
expect(firstEvent.timestamp).toBeTruthy();
|
|
156
|
-
expect(firstEvent.event_type).toBe("DECISION");
|
|
157
|
-
expect(firstEvent.payload).toBeDefined();
|
|
158
|
-
});
|
|
159
|
-
});
|
|
160
|
-
|
|
161
|
-
describe("filterEvents()", () => {
|
|
162
|
-
let allEvents: ReplayEvent[];
|
|
163
|
-
|
|
164
|
-
beforeAll(async () => {
|
|
165
|
-
allEvents = await fetchEpicEvents(epicId, sessionFile);
|
|
166
|
-
});
|
|
167
|
-
|
|
168
|
-
test("filters by event type", () => {
|
|
169
|
-
const filter: ReplayFilter = {
|
|
170
|
-
type: ["DECISION"],
|
|
171
|
-
};
|
|
172
|
-
|
|
173
|
-
const filtered = filterEvents(allEvents, filter);
|
|
174
|
-
expect(filtered.length).toBe(2); // 2 DECISION events
|
|
175
|
-
expect(filtered.every((e) => e.event_type === "DECISION")).toBe(true);
|
|
176
|
-
});
|
|
177
|
-
|
|
178
|
-
test("filters by multiple event types", () => {
|
|
179
|
-
const filter: ReplayFilter = {
|
|
180
|
-
type: ["DECISION", "OUTCOME"],
|
|
181
|
-
};
|
|
182
|
-
|
|
183
|
-
const filtered = filterEvents(allEvents, filter);
|
|
184
|
-
expect(filtered.length).toBe(3); // 2 DECISION + 1 OUTCOME
|
|
185
|
-
expect(
|
|
186
|
-
filtered.every((e) => e.event_type === "DECISION" || e.event_type === "OUTCOME"),
|
|
187
|
-
).toBe(true);
|
|
188
|
-
});
|
|
189
|
-
|
|
190
|
-
test("filters by agent name from payload", () => {
|
|
191
|
-
const filter: ReplayFilter = {
|
|
192
|
-
agent: "AgentA",
|
|
193
|
-
};
|
|
194
|
-
|
|
195
|
-
const filtered = filterEvents(allEvents, filter);
|
|
196
|
-
// Should match events with agent_name in payload
|
|
197
|
-
expect(filtered.length).toBeGreaterThan(0);
|
|
198
|
-
expect(
|
|
199
|
-
filtered.every((e) => {
|
|
200
|
-
const payload = e.payload as any;
|
|
201
|
-
return payload.agent_name === "AgentA";
|
|
202
|
-
}),
|
|
203
|
-
).toBe(true);
|
|
204
|
-
});
|
|
205
|
-
|
|
206
|
-
test("filters by time range (since)", () => {
|
|
207
|
-
const filter: ReplayFilter = {
|
|
208
|
-
since: new Date(testTimestamp.getTime() + 2000), // After +2s
|
|
209
|
-
};
|
|
210
|
-
|
|
211
|
-
const filtered = filterEvents(allEvents, filter);
|
|
212
|
-
// Should only include events after +2s (VIOLATION and OUTCOME)
|
|
213
|
-
expect(filtered.length).toBe(2);
|
|
214
|
-
});
|
|
215
|
-
|
|
216
|
-
test("filters by time range (until)", () => {
|
|
217
|
-
const filter: ReplayFilter = {
|
|
218
|
-
until: new Date(testTimestamp.getTime() + 2000), // Before +2s
|
|
219
|
-
};
|
|
220
|
-
|
|
221
|
-
const filtered = filterEvents(allEvents, filter);
|
|
222
|
-
// Should only include first 2 events (DECISION at 0s and 1s)
|
|
223
|
-
expect(filtered.length).toBe(2);
|
|
224
|
-
});
|
|
225
|
-
|
|
226
|
-
test("filters by time range (since + until)", () => {
|
|
227
|
-
const filter: ReplayFilter = {
|
|
228
|
-
since: new Date(testTimestamp.getTime() + 1000), // After +1s
|
|
229
|
-
until: new Date(testTimestamp.getTime() + 3000), // Before +3s
|
|
230
|
-
};
|
|
231
|
-
|
|
232
|
-
const filtered = filterEvents(allEvents, filter);
|
|
233
|
-
// Should include events at +1s and +2.5s
|
|
234
|
-
expect(filtered.length).toBe(2);
|
|
235
|
-
});
|
|
236
|
-
|
|
237
|
-
test("combines multiple filters (AND logic)", () => {
|
|
238
|
-
const filter: ReplayFilter = {
|
|
239
|
-
type: ["OUTCOME"],
|
|
240
|
-
agent: "AgentA",
|
|
241
|
-
};
|
|
242
|
-
|
|
243
|
-
const filtered = filterEvents(allEvents, filter);
|
|
244
|
-
expect(filtered.length).toBe(1);
|
|
245
|
-
expect(filtered[0].event_type).toBe("OUTCOME");
|
|
246
|
-
expect((filtered[0].payload as any).agent_name).toBe("AgentA");
|
|
247
|
-
});
|
|
248
|
-
|
|
249
|
-
test("returns all events when no filter provided", () => {
|
|
250
|
-
const filtered = filterEvents(allEvents, {});
|
|
251
|
-
expect(filtered.length).toBe(allEvents.length);
|
|
252
|
-
});
|
|
253
|
-
});
|
|
254
|
-
|
|
255
|
-
describe("replayWithTiming()", () => {
|
|
256
|
-
let allEvents: ReplayEvent[];
|
|
257
|
-
|
|
258
|
-
beforeAll(async () => {
|
|
259
|
-
allEvents = await fetchEpicEvents(epicId, sessionFile);
|
|
260
|
-
});
|
|
261
|
-
|
|
262
|
-
test("yields events at 1x speed with correct delays", async () => {
|
|
263
|
-
const speed: ReplaySpeed = "1x";
|
|
264
|
-
const startTime = Date.now();
|
|
265
|
-
const timings: number[] = [];
|
|
266
|
-
|
|
267
|
-
// Collect events and their actual timing
|
|
268
|
-
for await (const event of replayWithTiming(allEvents, speed)) {
|
|
269
|
-
timings.push(Date.now() - startTime);
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
expect(timings.length).toBe(4);
|
|
273
|
-
|
|
274
|
-
// First event should be immediate
|
|
275
|
-
expect(timings[0]).toBeLessThan(50); // <50ms tolerance
|
|
276
|
-
|
|
277
|
-
// Second event should be ~1000ms after start
|
|
278
|
-
expect(timings[1]).toBeGreaterThanOrEqual(950);
|
|
279
|
-
expect(timings[1]).toBeLessThanOrEqual(1050);
|
|
280
|
-
|
|
281
|
-
// Third event should be ~2500ms after start
|
|
282
|
-
expect(timings[2]).toBeGreaterThanOrEqual(2450);
|
|
283
|
-
expect(timings[2]).toBeLessThanOrEqual(2550);
|
|
284
|
-
|
|
285
|
-
// Fourth event should be ~5000ms after start
|
|
286
|
-
expect(timings[3]).toBeGreaterThanOrEqual(4950);
|
|
287
|
-
expect(timings[3]).toBeLessThanOrEqual(5050);
|
|
288
|
-
});
|
|
289
|
-
|
|
290
|
-
test("yields events at 2x speed with half delays", async () => {
|
|
291
|
-
const speed: ReplaySpeed = "2x";
|
|
292
|
-
const startTime = Date.now();
|
|
293
|
-
const timings: number[] = [];
|
|
294
|
-
|
|
295
|
-
for await (const event of replayWithTiming(allEvents, speed)) {
|
|
296
|
-
timings.push(Date.now() - startTime);
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
expect(timings.length).toBe(4);
|
|
300
|
-
|
|
301
|
-
// Second event should be ~500ms after start (half of 1000ms)
|
|
302
|
-
expect(timings[1]).toBeGreaterThanOrEqual(450);
|
|
303
|
-
expect(timings[1]).toBeLessThanOrEqual(550);
|
|
304
|
-
|
|
305
|
-
// Third event should be ~1250ms after start (half of 2500ms)
|
|
306
|
-
expect(timings[2]).toBeGreaterThanOrEqual(1200);
|
|
307
|
-
expect(timings[2]).toBeLessThanOrEqual(1300);
|
|
308
|
-
|
|
309
|
-
// Fourth event should be ~2500ms after start (half of 5000ms)
|
|
310
|
-
expect(timings[3]).toBeGreaterThanOrEqual(2450);
|
|
311
|
-
expect(timings[3]).toBeLessThanOrEqual(2550);
|
|
312
|
-
});
|
|
313
|
-
|
|
314
|
-
test("yields events instantly with no delays", async () => {
|
|
315
|
-
const speed: ReplaySpeed = "instant";
|
|
316
|
-
const startTime = Date.now();
|
|
317
|
-
|
|
318
|
-
const events: ReplayEvent[] = [];
|
|
319
|
-
for await (const event of replayWithTiming(allEvents, speed)) {
|
|
320
|
-
events.push(event);
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
const totalTime = Date.now() - startTime;
|
|
324
|
-
|
|
325
|
-
// All events should complete in <100ms
|
|
326
|
-
expect(totalTime).toBeLessThan(100);
|
|
327
|
-
expect(events.length).toBe(4);
|
|
328
|
-
});
|
|
329
|
-
|
|
330
|
-
test("preserves event data through iteration", async () => {
|
|
331
|
-
const collected: ReplayEvent[] = [];
|
|
332
|
-
for await (const event of replayWithTiming(allEvents, "instant")) {
|
|
333
|
-
collected.push(event);
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
expect(collected.length).toBe(allEvents.length);
|
|
337
|
-
expect(collected[0].event_type).toBe(allEvents[0].event_type);
|
|
338
|
-
expect(collected[0].payload).toEqual(allEvents[0].payload);
|
|
339
|
-
});
|
|
340
|
-
|
|
341
|
-
test("handles empty event array", async () => {
|
|
342
|
-
const events: ReplayEvent[] = [];
|
|
343
|
-
const collected: ReplayEvent[] = [];
|
|
344
|
-
|
|
345
|
-
for await (const event of replayWithTiming(events, "instant")) {
|
|
346
|
-
collected.push(event);
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
expect(collected.length).toBe(0);
|
|
350
|
-
});
|
|
351
|
-
});
|
|
352
|
-
|
|
353
|
-
describe("formatReplayEvent()", () => {
|
|
354
|
-
let sampleEvent: ReplayEvent;
|
|
355
|
-
|
|
356
|
-
beforeAll(async () => {
|
|
357
|
-
const events = await fetchEpicEvents(epicId, sessionFile);
|
|
358
|
-
sampleEvent = events[0];
|
|
359
|
-
});
|
|
360
|
-
|
|
361
|
-
test("produces color-coded output string", () => {
|
|
362
|
-
const formatted = formatReplayEvent(sampleEvent);
|
|
363
|
-
|
|
364
|
-
expect(typeof formatted).toBe("string");
|
|
365
|
-
expect(formatted.length).toBeGreaterThan(0);
|
|
366
|
-
|
|
367
|
-
// Should contain ANSI color codes (e.g., \x1b[32m for green)
|
|
368
|
-
expect(formatted).toMatch(/\x1b\[\d+m/);
|
|
369
|
-
});
|
|
370
|
-
|
|
371
|
-
test("includes timestamp prefix", () => {
|
|
372
|
-
const formatted = formatReplayEvent(sampleEvent);
|
|
373
|
-
|
|
374
|
-
// Should start with timestamp like [12:00:00.000]
|
|
375
|
-
expect(formatted).toMatch(/^\[[\d:\.]+\]/);
|
|
376
|
-
});
|
|
377
|
-
|
|
378
|
-
test("shows event type prominently", () => {
|
|
379
|
-
const formatted = formatReplayEvent(sampleEvent);
|
|
380
|
-
|
|
381
|
-
// Should contain event type (DECISION)
|
|
382
|
-
expect(formatted).toContain("DECISION");
|
|
383
|
-
});
|
|
384
|
-
|
|
385
|
-
test("displays epic_id relationship", () => {
|
|
386
|
-
const formatted = formatReplayEvent(sampleEvent);
|
|
387
|
-
|
|
388
|
-
// Should show epic relationship
|
|
389
|
-
expect(formatted).toContain(epicId);
|
|
390
|
-
});
|
|
391
|
-
|
|
392
|
-
test("displays bead_id when present in payload", () => {
|
|
393
|
-
const events = [
|
|
394
|
-
{
|
|
395
|
-
session_id: "s1",
|
|
396
|
-
epic_id: epicId,
|
|
397
|
-
timestamp: new Date().toISOString(),
|
|
398
|
-
event_type: "OUTCOME",
|
|
399
|
-
outcome_type: "subtask_success",
|
|
400
|
-
payload: {
|
|
401
|
-
bead_id: `${epicId}.1`,
|
|
402
|
-
},
|
|
403
|
-
delta_ms: 0,
|
|
404
|
-
},
|
|
405
|
-
] as ReplayEvent[];
|
|
406
|
-
|
|
407
|
-
const formatted = formatReplayEvent(events[0]);
|
|
408
|
-
expect(formatted).toContain(`${epicId}.1`);
|
|
409
|
-
});
|
|
410
|
-
|
|
411
|
-
test("uses box-drawing characters for structure", () => {
|
|
412
|
-
const formatted = formatReplayEvent(sampleEvent);
|
|
413
|
-
|
|
414
|
-
// Should contain box-drawing chars: ┌ ─ │ └ ├ ┤ ┬ ┴ ┼
|
|
415
|
-
const boxChars = ["─", "│", "┌", "┐", "└", "┘", "├", "┤", "┬", "┴", "┼"];
|
|
416
|
-
const hasBoxChars = boxChars.some((char) => formatted.includes(char));
|
|
417
|
-
expect(hasBoxChars).toBe(true);
|
|
418
|
-
});
|
|
419
|
-
|
|
420
|
-
test("color codes by event type", () => {
|
|
421
|
-
// DECISION events should use one color (e.g., blue)
|
|
422
|
-
const decisionEvent = sampleEvent;
|
|
423
|
-
const decisionFormatted = formatReplayEvent(decisionEvent);
|
|
424
|
-
|
|
425
|
-
// VIOLATION events should use different color (e.g., red)
|
|
426
|
-
const violationEvent: ReplayEvent = {
|
|
427
|
-
session_id: "s1",
|
|
428
|
-
epic_id: epicId,
|
|
429
|
-
timestamp: new Date().toISOString(),
|
|
430
|
-
event_type: "VIOLATION",
|
|
431
|
-
violation_type: "coordinator_editing_files",
|
|
432
|
-
payload: {},
|
|
433
|
-
delta_ms: 0,
|
|
434
|
-
};
|
|
435
|
-
const violationFormatted = formatReplayEvent(violationEvent);
|
|
436
|
-
|
|
437
|
-
// Different event types should have different color codes
|
|
438
|
-
// (We can't assert exact codes without knowing implementation,
|
|
439
|
-
// but we can check they're not identical)
|
|
440
|
-
expect(decisionFormatted).not.toBe(violationFormatted);
|
|
441
|
-
});
|
|
442
|
-
|
|
443
|
-
test("includes relevant payload fields", () => {
|
|
444
|
-
const formatted = formatReplayEvent(sampleEvent);
|
|
445
|
-
|
|
446
|
-
// Should include key payload info (strategy_used, subtask_count)
|
|
447
|
-
const payload = sampleEvent.payload as any;
|
|
448
|
-
if (payload.strategy_used) {
|
|
449
|
-
expect(formatted).toContain(payload.strategy_used);
|
|
450
|
-
}
|
|
451
|
-
if (payload.subtask_count !== undefined) {
|
|
452
|
-
expect(formatted).toContain(String(payload.subtask_count));
|
|
453
|
-
}
|
|
454
|
-
});
|
|
455
|
-
|
|
456
|
-
test("handles events without payload gracefully", () => {
|
|
457
|
-
const minimalEvent: ReplayEvent = {
|
|
458
|
-
session_id: "s1",
|
|
459
|
-
epic_id: epicId,
|
|
460
|
-
timestamp: new Date().toISOString(),
|
|
461
|
-
event_type: "DECISION",
|
|
462
|
-
decision_type: "minimal",
|
|
463
|
-
payload: {},
|
|
464
|
-
delta_ms: 0,
|
|
465
|
-
};
|
|
466
|
-
|
|
467
|
-
const formatted = formatReplayEvent(minimalEvent);
|
|
468
|
-
expect(formatted).toBeTruthy();
|
|
469
|
-
expect(formatted.length).toBeGreaterThan(0);
|
|
470
|
-
});
|
|
471
|
-
});
|
|
472
|
-
|
|
473
|
-
describe("integration - full replay workflow", () => {
|
|
474
|
-
test("fetch -> filter -> replay -> format pipeline", async () => {
|
|
475
|
-
// 1. Fetch events
|
|
476
|
-
const events = await fetchEpicEvents(epicId, sessionFile);
|
|
477
|
-
expect(events.length).toBe(4);
|
|
478
|
-
|
|
479
|
-
// 2. Filter to only DECISION events
|
|
480
|
-
const filtered = filterEvents(events, { type: ["DECISION"] });
|
|
481
|
-
expect(filtered.length).toBe(2);
|
|
482
|
-
|
|
483
|
-
// 3. Replay at instant speed
|
|
484
|
-
const replayed: ReplayEvent[] = [];
|
|
485
|
-
for await (const event of replayWithTiming(filtered, "instant")) {
|
|
486
|
-
replayed.push(event);
|
|
487
|
-
}
|
|
488
|
-
expect(replayed.length).toBe(2);
|
|
489
|
-
|
|
490
|
-
// 4. Format each event
|
|
491
|
-
const formatted = replayed.map(formatReplayEvent);
|
|
492
|
-
expect(formatted.length).toBe(2);
|
|
493
|
-
expect(formatted.every((f) => f.length > 0)).toBe(true);
|
|
494
|
-
});
|
|
495
|
-
});
|
|
496
|
-
});
|