opencode-swarm-plugin 0.44.0 → 0.44.2
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 +18 -12
- 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/hive.js +14834 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7743 -62593
- package/dist/plugin.js +24052 -78907
- package/dist/swarm-orchestrate.d.ts.map +1 -1
- package/dist/swarm-prompts.d.ts.map +1 -1
- package/dist/swarm-prompts.js +39407 -0
- package/dist/swarm-review.d.ts.map +1 -1
- package/dist/swarm-validation.d.ts +127 -0
- package/dist/swarm-validation.d.ts.map +1 -0
- package/dist/validators/index.d.ts +7 -0
- package/dist/validators/index.d.ts.map +1 -0
- package/dist/validators/schema-validator.d.ts +58 -0
- package/dist/validators/schema-validator.d.ts.map +1 -0
- 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/dashboard.test.ts
DELETED
|
@@ -1,611 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Dashboard Data Layer - RED Phase Tests
|
|
3
|
-
*
|
|
4
|
-
* Following TDD RED → GREEN → REFACTOR:
|
|
5
|
-
* - RED: These tests MUST fail (implementation doesn't exist yet)
|
|
6
|
-
* - GREEN: Next phase will implement minimal code to pass
|
|
7
|
-
* - REFACTOR: Clean up while tests stay green
|
|
8
|
-
*
|
|
9
|
-
* Data Sources:
|
|
10
|
-
* - libSQL events table (swarm-mail event sourcing)
|
|
11
|
-
* - agents projection (agent registration/activity)
|
|
12
|
-
* - messages projection (swarm mail)
|
|
13
|
-
* - reservations projection (file locks)
|
|
14
|
-
* - hive cells (work items)
|
|
15
|
-
*/
|
|
16
|
-
|
|
17
|
-
import { afterAll, beforeAll, describe, expect, test } from "bun:test";
|
|
18
|
-
import type { SwarmMailAdapter } from "swarm-mail";
|
|
19
|
-
import { createInMemorySwarmMailLibSQL } from "swarm-mail";
|
|
20
|
-
import {
|
|
21
|
-
getWorkerStatus,
|
|
22
|
-
getSubtaskProgress,
|
|
23
|
-
getFileLocks,
|
|
24
|
-
getRecentMessages,
|
|
25
|
-
getEpicList,
|
|
26
|
-
} from "./dashboard.js";
|
|
27
|
-
|
|
28
|
-
describe("Dashboard Data Layer - RED Phase", () => {
|
|
29
|
-
let swarmMail: SwarmMailAdapter;
|
|
30
|
-
const testProjectPath = "/test/dashboard";
|
|
31
|
-
|
|
32
|
-
beforeAll(async () => {
|
|
33
|
-
swarmMail = await createInMemorySwarmMailLibSQL(testProjectPath);
|
|
34
|
-
const db = await swarmMail.getDatabase();
|
|
35
|
-
|
|
36
|
-
// Seed test data for dashboard queries
|
|
37
|
-
const events = [
|
|
38
|
-
// Agent registration events
|
|
39
|
-
{
|
|
40
|
-
type: "agent_registered",
|
|
41
|
-
data: { agent_name: "AlphaAgent", project_key: testProjectPath },
|
|
42
|
-
timestamp: 1000,
|
|
43
|
-
},
|
|
44
|
-
{
|
|
45
|
-
type: "agent_registered",
|
|
46
|
-
data: { agent_name: "BetaAgent", project_key: testProjectPath },
|
|
47
|
-
timestamp: 1100,
|
|
48
|
-
},
|
|
49
|
-
|
|
50
|
-
// Task lifecycle events
|
|
51
|
-
{
|
|
52
|
-
type: "task_started",
|
|
53
|
-
data: { bead_id: "epic-1.1", agent_name: "AlphaAgent", title: "Setup auth" },
|
|
54
|
-
timestamp: 2000,
|
|
55
|
-
},
|
|
56
|
-
{
|
|
57
|
-
type: "task_started",
|
|
58
|
-
data: { bead_id: "epic-1.2", agent_name: "BetaAgent", title: "Add tests" },
|
|
59
|
-
timestamp: 2100,
|
|
60
|
-
},
|
|
61
|
-
|
|
62
|
-
// Progress events
|
|
63
|
-
{
|
|
64
|
-
type: "progress_reported",
|
|
65
|
-
data: {
|
|
66
|
-
bead_id: "epic-1.1",
|
|
67
|
-
agent_name: "AlphaAgent",
|
|
68
|
-
progress_percent: 50,
|
|
69
|
-
status: "in_progress",
|
|
70
|
-
},
|
|
71
|
-
timestamp: 3000,
|
|
72
|
-
},
|
|
73
|
-
{
|
|
74
|
-
type: "progress_reported",
|
|
75
|
-
data: {
|
|
76
|
-
bead_id: "epic-1.2",
|
|
77
|
-
agent_name: "BetaAgent",
|
|
78
|
-
progress_percent: 75,
|
|
79
|
-
status: "in_progress",
|
|
80
|
-
},
|
|
81
|
-
timestamp: 3100,
|
|
82
|
-
},
|
|
83
|
-
|
|
84
|
-
// Task blocked
|
|
85
|
-
{
|
|
86
|
-
type: "task_blocked",
|
|
87
|
-
data: {
|
|
88
|
-
bead_id: "epic-1.3",
|
|
89
|
-
agent_name: "AlphaAgent",
|
|
90
|
-
reason: "Waiting for schema",
|
|
91
|
-
},
|
|
92
|
-
timestamp: 4000,
|
|
93
|
-
},
|
|
94
|
-
|
|
95
|
-
// File reservation events
|
|
96
|
-
{
|
|
97
|
-
type: "reservation_acquired",
|
|
98
|
-
data: {
|
|
99
|
-
path_pattern: "src/auth/**",
|
|
100
|
-
agent_name: "AlphaAgent",
|
|
101
|
-
reason: "epic-1.1: Auth implementation",
|
|
102
|
-
exclusive: true,
|
|
103
|
-
ttl_seconds: 3600,
|
|
104
|
-
},
|
|
105
|
-
timestamp: 2000,
|
|
106
|
-
},
|
|
107
|
-
{
|
|
108
|
-
type: "reservation_acquired",
|
|
109
|
-
data: {
|
|
110
|
-
path_pattern: "src/auth/auth.test.ts",
|
|
111
|
-
agent_name: "BetaAgent",
|
|
112
|
-
reason: "epic-1.2: Test suite",
|
|
113
|
-
exclusive: false,
|
|
114
|
-
ttl_seconds: 1800,
|
|
115
|
-
},
|
|
116
|
-
timestamp: 2100,
|
|
117
|
-
},
|
|
118
|
-
|
|
119
|
-
// Swarm mail messages
|
|
120
|
-
{
|
|
121
|
-
type: "message_sent",
|
|
122
|
-
data: {
|
|
123
|
-
from: "AlphaAgent",
|
|
124
|
-
to: ["coordinator"],
|
|
125
|
-
subject: "Progress: epic-1.1",
|
|
126
|
-
body: "Auth service 50% complete",
|
|
127
|
-
importance: "normal",
|
|
128
|
-
thread_id: "epic-1",
|
|
129
|
-
},
|
|
130
|
-
timestamp: 3000,
|
|
131
|
-
},
|
|
132
|
-
{
|
|
133
|
-
type: "message_sent",
|
|
134
|
-
data: {
|
|
135
|
-
from: "BetaAgent",
|
|
136
|
-
to: ["coordinator"],
|
|
137
|
-
subject: "BLOCKED: epic-1.3",
|
|
138
|
-
body: "Need database schema from epic-1.1",
|
|
139
|
-
importance: "high",
|
|
140
|
-
thread_id: "epic-1",
|
|
141
|
-
},
|
|
142
|
-
timestamp: 4000,
|
|
143
|
-
},
|
|
144
|
-
{
|
|
145
|
-
type: "message_sent",
|
|
146
|
-
data: {
|
|
147
|
-
from: "coordinator",
|
|
148
|
-
to: ["AlphaAgent"],
|
|
149
|
-
subject: "Re: Progress",
|
|
150
|
-
body: "Good progress, continue",
|
|
151
|
-
importance: "normal",
|
|
152
|
-
thread_id: "epic-1",
|
|
153
|
-
},
|
|
154
|
-
timestamp: 3500,
|
|
155
|
-
},
|
|
156
|
-
];
|
|
157
|
-
|
|
158
|
-
// Insert events using parameterized queries
|
|
159
|
-
for (const event of events) {
|
|
160
|
-
await db.query(
|
|
161
|
-
"INSERT INTO events (type, project_key, timestamp, data) VALUES (?, ?, ?, ?)",
|
|
162
|
-
[event.type, testProjectPath, event.timestamp, JSON.stringify(event.data)],
|
|
163
|
-
);
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
// Seed hive cells (work items) - using direct INSERT for test simplicity
|
|
167
|
-
// In reality, these would come from HiveAdapter via swarm-mail
|
|
168
|
-
const cells = [
|
|
169
|
-
{
|
|
170
|
-
id: "epic-1",
|
|
171
|
-
title: "Authentication System",
|
|
172
|
-
type: "epic",
|
|
173
|
-
status: "in_progress",
|
|
174
|
-
priority: 2,
|
|
175
|
-
created_at: new Date(1000).toISOString(),
|
|
176
|
-
},
|
|
177
|
-
{
|
|
178
|
-
id: "epic-1.1",
|
|
179
|
-
parent_id: "epic-1",
|
|
180
|
-
title: "Setup auth service",
|
|
181
|
-
type: "task",
|
|
182
|
-
status: "in_progress",
|
|
183
|
-
priority: 2,
|
|
184
|
-
created_at: new Date(1100).toISOString(),
|
|
185
|
-
},
|
|
186
|
-
{
|
|
187
|
-
id: "epic-1.2",
|
|
188
|
-
parent_id: "epic-1",
|
|
189
|
-
title: "Add auth tests",
|
|
190
|
-
type: "task",
|
|
191
|
-
status: "in_progress",
|
|
192
|
-
priority: 2,
|
|
193
|
-
created_at: new Date(1200).toISOString(),
|
|
194
|
-
},
|
|
195
|
-
{
|
|
196
|
-
id: "epic-1.3",
|
|
197
|
-
parent_id: "epic-1",
|
|
198
|
-
title: "Database schema",
|
|
199
|
-
type: "task",
|
|
200
|
-
status: "blocked",
|
|
201
|
-
priority: 2,
|
|
202
|
-
created_at: new Date(1300).toISOString(),
|
|
203
|
-
},
|
|
204
|
-
{
|
|
205
|
-
id: "epic-2",
|
|
206
|
-
title: "Performance Optimization",
|
|
207
|
-
type: "epic",
|
|
208
|
-
status: "open",
|
|
209
|
-
priority: 1,
|
|
210
|
-
created_at: new Date(2000).toISOString(),
|
|
211
|
-
},
|
|
212
|
-
];
|
|
213
|
-
|
|
214
|
-
// Note: Hive cells use a separate schema. For this test, we'll mock the responses
|
|
215
|
-
// in the implementation or use HiveAdapter integration in GREEN phase.
|
|
216
|
-
// For RED phase, we're defining the contract - implementation will handle data source.
|
|
217
|
-
});
|
|
218
|
-
|
|
219
|
-
afterAll(async () => {
|
|
220
|
-
await swarmMail.close();
|
|
221
|
-
});
|
|
222
|
-
|
|
223
|
-
describe("getWorkerStatus()", () => {
|
|
224
|
-
test("should return array of WorkerStatus objects", async () => {
|
|
225
|
-
const db = await swarmMail.getDatabase();
|
|
226
|
-
const result = await getWorkerStatus(db);
|
|
227
|
-
|
|
228
|
-
expect(Array.isArray(result)).toBe(true);
|
|
229
|
-
expect(result.length).toBeGreaterThan(0);
|
|
230
|
-
});
|
|
231
|
-
|
|
232
|
-
test("should include agent_name, status, and last_activity", async () => {
|
|
233
|
-
const db = await swarmMail.getDatabase();
|
|
234
|
-
const result = await getWorkerStatus(db);
|
|
235
|
-
|
|
236
|
-
const worker = result[0];
|
|
237
|
-
expect(worker).toHaveProperty("agent_name");
|
|
238
|
-
expect(worker).toHaveProperty("status");
|
|
239
|
-
expect(worker).toHaveProperty("last_activity");
|
|
240
|
-
|
|
241
|
-
// Status must be valid enum
|
|
242
|
-
expect(["idle", "working", "blocked"]).toContain(worker.status);
|
|
243
|
-
|
|
244
|
-
// last_activity should be ISO timestamp
|
|
245
|
-
expect(typeof worker.last_activity).toBe("string");
|
|
246
|
-
expect(() => new Date(worker.last_activity)).not.toThrow();
|
|
247
|
-
});
|
|
248
|
-
|
|
249
|
-
test("should include current_task when agent is working", async () => {
|
|
250
|
-
const db = await swarmMail.getDatabase();
|
|
251
|
-
const result = await getWorkerStatus(db);
|
|
252
|
-
|
|
253
|
-
// Find a working agent
|
|
254
|
-
const workingAgent = result.find((w) => w.status === "working");
|
|
255
|
-
if (workingAgent) {
|
|
256
|
-
expect(workingAgent.current_task).toBeDefined();
|
|
257
|
-
expect(typeof workingAgent.current_task).toBe("string");
|
|
258
|
-
}
|
|
259
|
-
});
|
|
260
|
-
|
|
261
|
-
test("should derive status from latest events", async () => {
|
|
262
|
-
const db = await swarmMail.getDatabase();
|
|
263
|
-
const result = await getWorkerStatus(db);
|
|
264
|
-
|
|
265
|
-
// AlphaAgent has task_started + progress_reported → working
|
|
266
|
-
const alpha = result.find((w) => w.agent_name === "AlphaAgent");
|
|
267
|
-
expect(alpha?.status).toBe("working");
|
|
268
|
-
|
|
269
|
-
// BetaAgent has task_started + progress_reported → working
|
|
270
|
-
const beta = result.find((w) => w.agent_name === "BetaAgent");
|
|
271
|
-
expect(beta?.status).toBe("working");
|
|
272
|
-
});
|
|
273
|
-
|
|
274
|
-
test("should filter by project_key when provided", async () => {
|
|
275
|
-
const db = await swarmMail.getDatabase();
|
|
276
|
-
const result = await getWorkerStatus(db, { project_key: testProjectPath });
|
|
277
|
-
|
|
278
|
-
expect(result.length).toBeGreaterThan(0);
|
|
279
|
-
// All results should be from our test project
|
|
280
|
-
});
|
|
281
|
-
|
|
282
|
-
test("should return empty array when no agents found", async () => {
|
|
283
|
-
const db = await swarmMail.getDatabase();
|
|
284
|
-
const result = await getWorkerStatus(db, { project_key: "/nonexistent" });
|
|
285
|
-
|
|
286
|
-
expect(Array.isArray(result)).toBe(true);
|
|
287
|
-
expect(result.length).toBe(0);
|
|
288
|
-
});
|
|
289
|
-
});
|
|
290
|
-
|
|
291
|
-
describe("getSubtaskProgress()", () => {
|
|
292
|
-
test("should return array of SubtaskProgress objects", async () => {
|
|
293
|
-
const db = await swarmMail.getDatabase();
|
|
294
|
-
const result = await getSubtaskProgress(db, "epic-1");
|
|
295
|
-
|
|
296
|
-
expect(Array.isArray(result)).toBe(true);
|
|
297
|
-
expect(result.length).toBeGreaterThan(0);
|
|
298
|
-
});
|
|
299
|
-
|
|
300
|
-
test("should include bead_id, title, status, and progress_percent", async () => {
|
|
301
|
-
const db = await swarmMail.getDatabase();
|
|
302
|
-
const result = await getSubtaskProgress(db, "epic-1");
|
|
303
|
-
|
|
304
|
-
const subtask = result[0];
|
|
305
|
-
expect(subtask).toHaveProperty("bead_id");
|
|
306
|
-
expect(subtask).toHaveProperty("title");
|
|
307
|
-
expect(subtask).toHaveProperty("status");
|
|
308
|
-
expect(subtask).toHaveProperty("progress_percent");
|
|
309
|
-
|
|
310
|
-
// Status must be valid enum
|
|
311
|
-
expect(["open", "in_progress", "completed", "blocked"]).toContain(
|
|
312
|
-
subtask.status,
|
|
313
|
-
);
|
|
314
|
-
|
|
315
|
-
// Progress percent should be 0-100
|
|
316
|
-
expect(subtask.progress_percent).toBeGreaterThanOrEqual(0);
|
|
317
|
-
expect(subtask.progress_percent).toBeLessThanOrEqual(100);
|
|
318
|
-
});
|
|
319
|
-
|
|
320
|
-
test("should return subtasks for specified epic only", async () => {
|
|
321
|
-
const db = await swarmMail.getDatabase();
|
|
322
|
-
const result = await getSubtaskProgress(db, "epic-1");
|
|
323
|
-
|
|
324
|
-
// Should have epic-1.1, epic-1.2, epic-1.3
|
|
325
|
-
const beadIds = result.map((s) => s.bead_id);
|
|
326
|
-
expect(beadIds).toContain("epic-1.1");
|
|
327
|
-
expect(beadIds).toContain("epic-1.2");
|
|
328
|
-
expect(beadIds).toContain("epic-1.3");
|
|
329
|
-
|
|
330
|
-
// Should NOT have epic-2 subtasks
|
|
331
|
-
expect(beadIds.every((id) => id.startsWith("epic-1"))).toBe(true);
|
|
332
|
-
});
|
|
333
|
-
|
|
334
|
-
test("should derive progress from progress_reported events", async () => {
|
|
335
|
-
const db = await swarmMail.getDatabase();
|
|
336
|
-
const result = await getSubtaskProgress(db, "epic-1");
|
|
337
|
-
|
|
338
|
-
// epic-1.1 reported 50% progress
|
|
339
|
-
const task1 = result.find((s) => s.bead_id === "epic-1.1");
|
|
340
|
-
expect(task1?.progress_percent).toBe(50);
|
|
341
|
-
|
|
342
|
-
// epic-1.2 reported 75% progress
|
|
343
|
-
const task2 = result.find((s) => s.bead_id === "epic-1.2");
|
|
344
|
-
expect(task2?.progress_percent).toBe(75);
|
|
345
|
-
});
|
|
346
|
-
|
|
347
|
-
test("should default to 0% progress when no progress events exist", async () => {
|
|
348
|
-
const db = await swarmMail.getDatabase();
|
|
349
|
-
const result = await getSubtaskProgress(db, "epic-1");
|
|
350
|
-
|
|
351
|
-
// epic-1.3 is blocked but has no progress events
|
|
352
|
-
const task3 = result.find((s) => s.bead_id === "epic-1.3");
|
|
353
|
-
expect(task3?.progress_percent).toBe(0);
|
|
354
|
-
});
|
|
355
|
-
|
|
356
|
-
test("should return empty array when epic has no subtasks", async () => {
|
|
357
|
-
const db = await swarmMail.getDatabase();
|
|
358
|
-
const result = await getSubtaskProgress(db, "nonexistent-epic");
|
|
359
|
-
|
|
360
|
-
expect(Array.isArray(result)).toBe(true);
|
|
361
|
-
expect(result.length).toBe(0);
|
|
362
|
-
});
|
|
363
|
-
});
|
|
364
|
-
|
|
365
|
-
describe("getFileLocks()", () => {
|
|
366
|
-
test("should return array of FileLock objects", async () => {
|
|
367
|
-
const db = await swarmMail.getDatabase();
|
|
368
|
-
const result = await getFileLocks(db);
|
|
369
|
-
|
|
370
|
-
expect(Array.isArray(result)).toBe(true);
|
|
371
|
-
expect(result.length).toBeGreaterThan(0);
|
|
372
|
-
});
|
|
373
|
-
|
|
374
|
-
test("should include path, agent_name, reason, acquired_at, and ttl_seconds", async () => {
|
|
375
|
-
const db = await swarmMail.getDatabase();
|
|
376
|
-
const result = await getFileLocks(db);
|
|
377
|
-
|
|
378
|
-
const lock = result[0];
|
|
379
|
-
expect(lock).toHaveProperty("path");
|
|
380
|
-
expect(lock).toHaveProperty("agent_name");
|
|
381
|
-
expect(lock).toHaveProperty("reason");
|
|
382
|
-
expect(lock).toHaveProperty("acquired_at");
|
|
383
|
-
expect(lock).toHaveProperty("ttl_seconds");
|
|
384
|
-
|
|
385
|
-
// acquired_at should be ISO timestamp
|
|
386
|
-
expect(typeof lock.acquired_at).toBe("string");
|
|
387
|
-
expect(() => new Date(lock.acquired_at)).not.toThrow();
|
|
388
|
-
|
|
389
|
-
// ttl_seconds should be positive number
|
|
390
|
-
expect(lock.ttl_seconds).toBeGreaterThan(0);
|
|
391
|
-
});
|
|
392
|
-
|
|
393
|
-
test("should return current reservations from events", async () => {
|
|
394
|
-
const db = await swarmMail.getDatabase();
|
|
395
|
-
const result = await getFileLocks(db);
|
|
396
|
-
|
|
397
|
-
// Should have src/auth/** reserved by AlphaAgent
|
|
398
|
-
const authLock = result.find((l) => l.path === "src/auth/**");
|
|
399
|
-
expect(authLock).toBeDefined();
|
|
400
|
-
expect(authLock?.agent_name).toBe("AlphaAgent");
|
|
401
|
-
expect(authLock?.reason).toContain("epic-1.1");
|
|
402
|
-
|
|
403
|
-
// Should have src/auth/auth.test.ts reserved by BetaAgent
|
|
404
|
-
const testLock = result.find((l) => l.path === "src/auth/auth.test.ts");
|
|
405
|
-
expect(testLock).toBeDefined();
|
|
406
|
-
expect(testLock?.agent_name).toBe("BetaAgent");
|
|
407
|
-
expect(testLock?.reason).toContain("epic-1.2");
|
|
408
|
-
});
|
|
409
|
-
|
|
410
|
-
test("should filter by project_key when provided", async () => {
|
|
411
|
-
const db = await swarmMail.getDatabase();
|
|
412
|
-
const result = await getFileLocks(db, { project_key: testProjectPath });
|
|
413
|
-
|
|
414
|
-
expect(result.length).toBeGreaterThan(0);
|
|
415
|
-
});
|
|
416
|
-
|
|
417
|
-
test("should exclude released reservations", async () => {
|
|
418
|
-
const db = await swarmMail.getDatabase();
|
|
419
|
-
|
|
420
|
-
// Add a released reservation event
|
|
421
|
-
await db.query(
|
|
422
|
-
"INSERT INTO events (type, project_key, timestamp, data) VALUES (?, ?, ?, ?)",
|
|
423
|
-
[
|
|
424
|
-
"reservation_released",
|
|
425
|
-
testProjectPath,
|
|
426
|
-
5000,
|
|
427
|
-
JSON.stringify({
|
|
428
|
-
path_pattern: "src/old-reservation",
|
|
429
|
-
agent_name: "AlphaAgent",
|
|
430
|
-
}),
|
|
431
|
-
],
|
|
432
|
-
);
|
|
433
|
-
|
|
434
|
-
const result = await getFileLocks(db);
|
|
435
|
-
|
|
436
|
-
// Should NOT include released reservation
|
|
437
|
-
const releasedLock = result.find((l) => l.path === "src/old-reservation");
|
|
438
|
-
expect(releasedLock).toBeUndefined();
|
|
439
|
-
});
|
|
440
|
-
|
|
441
|
-
test("should return empty array when no active reservations", async () => {
|
|
442
|
-
const db = await swarmMail.getDatabase();
|
|
443
|
-
const result = await getFileLocks(db, { project_key: "/no-locks" });
|
|
444
|
-
|
|
445
|
-
expect(Array.isArray(result)).toBe(true);
|
|
446
|
-
expect(result.length).toBe(0);
|
|
447
|
-
});
|
|
448
|
-
});
|
|
449
|
-
|
|
450
|
-
describe("getRecentMessages()", () => {
|
|
451
|
-
test("should return array of RecentMessage objects", async () => {
|
|
452
|
-
const db = await swarmMail.getDatabase();
|
|
453
|
-
const result = await getRecentMessages(db);
|
|
454
|
-
|
|
455
|
-
expect(Array.isArray(result)).toBe(true);
|
|
456
|
-
expect(result.length).toBeGreaterThan(0);
|
|
457
|
-
});
|
|
458
|
-
|
|
459
|
-
test("should include id, from, to, subject, timestamp, and importance", async () => {
|
|
460
|
-
const db = await swarmMail.getDatabase();
|
|
461
|
-
const result = await getRecentMessages(db);
|
|
462
|
-
|
|
463
|
-
const message = result[0];
|
|
464
|
-
expect(message).toHaveProperty("id");
|
|
465
|
-
expect(message).toHaveProperty("from");
|
|
466
|
-
expect(message).toHaveProperty("to");
|
|
467
|
-
expect(message).toHaveProperty("subject");
|
|
468
|
-
expect(message).toHaveProperty("timestamp");
|
|
469
|
-
expect(message).toHaveProperty("importance");
|
|
470
|
-
|
|
471
|
-
// to should be array
|
|
472
|
-
expect(Array.isArray(message.to)).toBe(true);
|
|
473
|
-
|
|
474
|
-
// importance must be valid enum
|
|
475
|
-
expect(["low", "normal", "high", "urgent"]).toContain(message.importance);
|
|
476
|
-
|
|
477
|
-
// timestamp should be ISO string
|
|
478
|
-
expect(typeof message.timestamp).toBe("string");
|
|
479
|
-
expect(() => new Date(message.timestamp)).not.toThrow();
|
|
480
|
-
});
|
|
481
|
-
|
|
482
|
-
test("should return messages ordered by timestamp descending (newest first)", async () => {
|
|
483
|
-
const db = await swarmMail.getDatabase();
|
|
484
|
-
const result = await getRecentMessages(db);
|
|
485
|
-
|
|
486
|
-
// First message should be the latest (timestamp 4000)
|
|
487
|
-
expect(result[0].subject).toContain("BLOCKED");
|
|
488
|
-
|
|
489
|
-
// Check ordering
|
|
490
|
-
for (let i = 1; i < result.length; i++) {
|
|
491
|
-
const prev = new Date(result[i - 1].timestamp).getTime();
|
|
492
|
-
const curr = new Date(result[i].timestamp).getTime();
|
|
493
|
-
expect(prev).toBeGreaterThanOrEqual(curr);
|
|
494
|
-
}
|
|
495
|
-
});
|
|
496
|
-
|
|
497
|
-
test("should limit results to specified count", async () => {
|
|
498
|
-
const db = await swarmMail.getDatabase();
|
|
499
|
-
const result = await getRecentMessages(db, { limit: 2 });
|
|
500
|
-
|
|
501
|
-
expect(result.length).toBeLessThanOrEqual(2);
|
|
502
|
-
});
|
|
503
|
-
|
|
504
|
-
test("should default to limit of 10 when not specified", async () => {
|
|
505
|
-
const db = await swarmMail.getDatabase();
|
|
506
|
-
const result = await getRecentMessages(db);
|
|
507
|
-
|
|
508
|
-
// Even if we have fewer messages, should not exceed 10
|
|
509
|
-
expect(result.length).toBeLessThanOrEqual(10);
|
|
510
|
-
});
|
|
511
|
-
|
|
512
|
-
test("should filter by thread_id when provided", async () => {
|
|
513
|
-
const db = await swarmMail.getDatabase();
|
|
514
|
-
const result = await getRecentMessages(db, { thread_id: "epic-1" });
|
|
515
|
-
|
|
516
|
-
expect(result.length).toBeGreaterThan(0);
|
|
517
|
-
// All messages should be from epic-1 thread
|
|
518
|
-
});
|
|
519
|
-
|
|
520
|
-
test("should filter by importance when provided", async () => {
|
|
521
|
-
const db = await swarmMail.getDatabase();
|
|
522
|
-
const result = await getRecentMessages(db, { importance: "high" });
|
|
523
|
-
|
|
524
|
-
// Should only include high importance messages
|
|
525
|
-
expect(result.every((m) => m.importance === "high")).toBe(true);
|
|
526
|
-
});
|
|
527
|
-
|
|
528
|
-
test("should return empty array when no messages found", async () => {
|
|
529
|
-
const db = await swarmMail.getDatabase();
|
|
530
|
-
const result = await getRecentMessages(db, { thread_id: "nonexistent" });
|
|
531
|
-
|
|
532
|
-
expect(Array.isArray(result)).toBe(true);
|
|
533
|
-
expect(result.length).toBe(0);
|
|
534
|
-
});
|
|
535
|
-
});
|
|
536
|
-
|
|
537
|
-
describe("getEpicList()", () => {
|
|
538
|
-
test("should return array of EpicInfo objects", async () => {
|
|
539
|
-
const db = await swarmMail.getDatabase();
|
|
540
|
-
const result = await getEpicList(db);
|
|
541
|
-
|
|
542
|
-
expect(Array.isArray(result)).toBe(true);
|
|
543
|
-
expect(result.length).toBeGreaterThan(0);
|
|
544
|
-
});
|
|
545
|
-
|
|
546
|
-
test("should include epic_id, title, subtask_count, and completed_count", async () => {
|
|
547
|
-
const db = await swarmMail.getDatabase();
|
|
548
|
-
const result = await getEpicList(db);
|
|
549
|
-
|
|
550
|
-
const epic = result[0];
|
|
551
|
-
expect(epic).toHaveProperty("epic_id");
|
|
552
|
-
expect(epic).toHaveProperty("title");
|
|
553
|
-
expect(epic).toHaveProperty("subtask_count");
|
|
554
|
-
expect(epic).toHaveProperty("completed_count");
|
|
555
|
-
|
|
556
|
-
// Counts should be non-negative integers
|
|
557
|
-
expect(epic.subtask_count).toBeGreaterThanOrEqual(0);
|
|
558
|
-
expect(epic.completed_count).toBeGreaterThanOrEqual(0);
|
|
559
|
-
expect(Number.isInteger(epic.subtask_count)).toBe(true);
|
|
560
|
-
expect(Number.isInteger(epic.completed_count)).toBe(true);
|
|
561
|
-
});
|
|
562
|
-
|
|
563
|
-
test("should include all epics from hive", async () => {
|
|
564
|
-
const db = await swarmMail.getDatabase();
|
|
565
|
-
const result = await getEpicList(db);
|
|
566
|
-
|
|
567
|
-
const epicIds = result.map((e) => e.epic_id);
|
|
568
|
-
expect(epicIds).toContain("epic-1");
|
|
569
|
-
expect(epicIds).toContain("epic-2");
|
|
570
|
-
});
|
|
571
|
-
|
|
572
|
-
test("should calculate subtask_count correctly", async () => {
|
|
573
|
-
const db = await swarmMail.getDatabase();
|
|
574
|
-
const result = await getEpicList(db);
|
|
575
|
-
|
|
576
|
-
// epic-1 has 3 subtasks (epic-1.1, epic-1.2, epic-1.3)
|
|
577
|
-
const epic1 = result.find((e) => e.epic_id === "epic-1");
|
|
578
|
-
expect(epic1?.subtask_count).toBe(3);
|
|
579
|
-
|
|
580
|
-
// epic-2 has 0 subtasks
|
|
581
|
-
const epic2 = result.find((e) => e.epic_id === "epic-2");
|
|
582
|
-
expect(epic2?.subtask_count).toBe(0);
|
|
583
|
-
});
|
|
584
|
-
|
|
585
|
-
test("should calculate completed_count correctly", async () => {
|
|
586
|
-
const db = await swarmMail.getDatabase();
|
|
587
|
-
const result = await getEpicList(db);
|
|
588
|
-
|
|
589
|
-
// epic-1 has 0 completed subtasks (all in_progress or blocked)
|
|
590
|
-
const epic1 = result.find((e) => e.epic_id === "epic-1");
|
|
591
|
-
expect(epic1?.completed_count).toBe(0);
|
|
592
|
-
});
|
|
593
|
-
|
|
594
|
-
test("should filter by status when provided", async () => {
|
|
595
|
-
const db = await swarmMail.getDatabase();
|
|
596
|
-
const result = await getEpicList(db, { status: "in_progress" });
|
|
597
|
-
|
|
598
|
-
// Should only include epics with in_progress status
|
|
599
|
-
expect(result.some((e) => e.epic_id === "epic-1")).toBe(true);
|
|
600
|
-
expect(result.every((e) => e.epic_id !== "epic-2")).toBe(true); // epic-2 is open
|
|
601
|
-
});
|
|
602
|
-
|
|
603
|
-
test("should return empty array when no epics found", async () => {
|
|
604
|
-
const db = await swarmMail.getDatabase();
|
|
605
|
-
const result = await getEpicList(db, { status: "completed" });
|
|
606
|
-
|
|
607
|
-
expect(Array.isArray(result)).toBe(true);
|
|
608
|
-
expect(result.length).toBe(0);
|
|
609
|
-
});
|
|
610
|
-
});
|
|
611
|
-
});
|