opencode-swarm-plugin 0.30.0 → 0.30.3
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/.turbo/turbo-build.log +4 -4
- package/CHANGELOG.md +93 -0
- package/README.md +3 -6
- package/bin/swarm.ts +151 -22
- package/dist/hive.d.ts.map +1 -1
- package/dist/index.d.ts +94 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +18826 -3467
- package/dist/memory-tools.d.ts +209 -0
- package/dist/memory-tools.d.ts.map +1 -0
- package/dist/memory.d.ts +124 -0
- package/dist/memory.d.ts.map +1 -0
- package/dist/plugin.js +18766 -3418
- package/dist/schemas/index.d.ts +7 -0
- package/dist/schemas/index.d.ts.map +1 -1
- package/dist/schemas/worker-handoff.d.ts +78 -0
- package/dist/schemas/worker-handoff.d.ts.map +1 -0
- package/dist/swarm-orchestrate.d.ts +50 -0
- package/dist/swarm-orchestrate.d.ts.map +1 -1
- package/dist/swarm-prompts.d.ts +1 -1
- package/dist/swarm-prompts.d.ts.map +1 -1
- package/dist/swarm-review.d.ts +4 -0
- package/dist/swarm-review.d.ts.map +1 -1
- package/docs/planning/ADR-008-worker-handoff-protocol.md +293 -0
- package/package.json +4 -2
- package/src/hive.integration.test.ts +114 -0
- package/src/hive.ts +33 -22
- package/src/index.ts +38 -3
- package/src/memory-tools.test.ts +111 -0
- package/src/memory-tools.ts +273 -0
- package/src/memory.integration.test.ts +266 -0
- package/src/memory.test.ts +334 -0
- package/src/memory.ts +441 -0
- package/src/schemas/index.ts +18 -0
- package/src/schemas/worker-handoff.test.ts +271 -0
- package/src/schemas/worker-handoff.ts +131 -0
- package/src/swarm-orchestrate.ts +262 -24
- package/src/swarm-prompts.ts +48 -5
- package/src/swarm-review.ts +7 -0
- package/src/swarm.integration.test.ts +386 -9
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
swarm_progress,
|
|
16
16
|
swarm_complete,
|
|
17
17
|
swarm_subtask_prompt,
|
|
18
|
+
swarm_spawn_subtask,
|
|
18
19
|
swarm_evaluation_prompt,
|
|
19
20
|
swarm_select_strategy,
|
|
20
21
|
swarm_plan_prompt,
|
|
@@ -23,6 +24,7 @@ import {
|
|
|
23
24
|
swarm_checkpoint,
|
|
24
25
|
swarm_recover,
|
|
25
26
|
} from "./swarm";
|
|
27
|
+
import { swarm_review, swarm_review_feedback } from "./swarm-review";
|
|
26
28
|
import { mcpCall, setState, clearState, AGENT_MAIL_URL } from "./agent-mail";
|
|
27
29
|
|
|
28
30
|
// ============================================================================
|
|
@@ -1162,7 +1164,65 @@ describe("swarm_init", () => {
|
|
|
1162
1164
|
});
|
|
1163
1165
|
});
|
|
1164
1166
|
|
|
1165
|
-
describe("
|
|
1167
|
+
describe("Worker Handoff Generation", () => {
|
|
1168
|
+
it("generateWorkerHandoff creates valid WorkerHandoff object", () => {
|
|
1169
|
+
// This will test the new function once we implement it
|
|
1170
|
+
const { generateWorkerHandoff } = require("./swarm-orchestrate");
|
|
1171
|
+
|
|
1172
|
+
const handoff = generateWorkerHandoff({
|
|
1173
|
+
task_id: "opencode-swarm-monorepo-lf2p4u-abc123.1",
|
|
1174
|
+
files_owned: ["src/auth.ts", "src/middleware.ts"],
|
|
1175
|
+
epic_summary: "Add OAuth authentication",
|
|
1176
|
+
your_role: "Implement OAuth provider",
|
|
1177
|
+
dependencies_completed: ["Database schema ready"],
|
|
1178
|
+
what_comes_next: "Integration tests",
|
|
1179
|
+
});
|
|
1180
|
+
|
|
1181
|
+
// Verify contract section
|
|
1182
|
+
expect(handoff.contract.task_id).toBe("opencode-swarm-monorepo-lf2p4u-abc123.1");
|
|
1183
|
+
expect(handoff.contract.files_owned).toEqual(["src/auth.ts", "src/middleware.ts"]);
|
|
1184
|
+
expect(handoff.contract.files_readonly).toEqual([]);
|
|
1185
|
+
expect(handoff.contract.dependencies_completed).toEqual(["Database schema ready"]);
|
|
1186
|
+
expect(handoff.contract.success_criteria.length).toBeGreaterThan(0);
|
|
1187
|
+
|
|
1188
|
+
// Verify context section
|
|
1189
|
+
expect(handoff.context.epic_summary).toBe("Add OAuth authentication");
|
|
1190
|
+
expect(handoff.context.your_role).toBe("Implement OAuth provider");
|
|
1191
|
+
expect(handoff.context.what_comes_next).toBe("Integration tests");
|
|
1192
|
+
|
|
1193
|
+
// Verify escalation section
|
|
1194
|
+
expect(handoff.escalation.blocked_contact).toBe("coordinator");
|
|
1195
|
+
expect(handoff.escalation.scope_change_protocol).toContain("swarmmail_send");
|
|
1196
|
+
});
|
|
1197
|
+
|
|
1198
|
+
it("swarm_spawn_subtask includes handoff JSON in prompt", async () => {
|
|
1199
|
+
const result = await swarm_spawn_subtask.execute(
|
|
1200
|
+
{
|
|
1201
|
+
bead_id: "opencode-swarm-monorepo-lf2p4u-abc123.1",
|
|
1202
|
+
epic_id: "opencode-swarm-monorepo-lf2p4u-abc123",
|
|
1203
|
+
subtask_title: "Add OAuth provider",
|
|
1204
|
+
subtask_description: "Configure Google OAuth",
|
|
1205
|
+
files: ["src/auth/google.ts"],
|
|
1206
|
+
shared_context: "Using NextAuth.js v5",
|
|
1207
|
+
},
|
|
1208
|
+
mockContext,
|
|
1209
|
+
);
|
|
1210
|
+
|
|
1211
|
+
// Parse the JSON response
|
|
1212
|
+
const parsed = JSON.parse(result);
|
|
1213
|
+
const prompt = parsed.prompt;
|
|
1214
|
+
|
|
1215
|
+
// Should contain WorkerHandoff JSON section
|
|
1216
|
+
expect(prompt).toContain("## WorkerHandoff Contract");
|
|
1217
|
+
expect(prompt).toContain('"contract"');
|
|
1218
|
+
expect(prompt).toContain('"task_id"');
|
|
1219
|
+
expect(prompt).toContain('"files_owned"');
|
|
1220
|
+
expect(prompt).toContain('"success_criteria"');
|
|
1221
|
+
expect(prompt).toContain("opencode-swarm-monorepo-lf2p4u-abc123.1");
|
|
1222
|
+
});
|
|
1223
|
+
});
|
|
1224
|
+
|
|
1225
|
+
describe("Graceful Degradation", () => {
|
|
1166
1226
|
it("swarm_decompose works without CASS", async () => {
|
|
1167
1227
|
// This should work regardless of CASS availability
|
|
1168
1228
|
const result = await swarm_decompose.execute(
|
|
@@ -1245,8 +1305,8 @@ describe("Swarm Prompt V2 (with Swarm Mail/Beads)", () => {
|
|
|
1245
1305
|
describe("formatSubtaskPromptV2", () => {
|
|
1246
1306
|
it("generates correct prompt with all fields", () => {
|
|
1247
1307
|
const result = formatSubtaskPromptV2({
|
|
1248
|
-
bead_id: "
|
|
1249
|
-
epic_id: "
|
|
1308
|
+
bead_id: "test-swarm-plugin-lf2p4u-oauth123.1",
|
|
1309
|
+
epic_id: "test-swarm-plugin-lf2p4u-oauth123",
|
|
1250
1310
|
subtask_title: "Add OAuth provider",
|
|
1251
1311
|
subtask_description: "Configure Google OAuth in the auth config",
|
|
1252
1312
|
files: ["src/auth/google.ts", "src/auth/config.ts"],
|
|
@@ -1267,14 +1327,14 @@ describe("Swarm Prompt V2 (with Swarm Mail/Beads)", () => {
|
|
|
1267
1327
|
expect(result).toContain("We are using NextAuth.js v5");
|
|
1268
1328
|
|
|
1269
1329
|
// Check bead/epic IDs are substituted
|
|
1270
|
-
expect(result).toContain("
|
|
1271
|
-
expect(result).toContain("
|
|
1330
|
+
expect(result).toContain("test-swarm-plugin-lf2p4u-oauth123.1");
|
|
1331
|
+
expect(result).toContain("test-swarm-plugin-lf2p4u-oauth123");
|
|
1272
1332
|
});
|
|
1273
1333
|
|
|
1274
1334
|
it("handles missing optional fields", () => {
|
|
1275
1335
|
const result = formatSubtaskPromptV2({
|
|
1276
|
-
bead_id: "
|
|
1277
|
-
epic_id: "
|
|
1336
|
+
bead_id: "test-swarm-plugin-lf2p4u-simple456.1",
|
|
1337
|
+
epic_id: "test-swarm-plugin-lf2p4u-simple456",
|
|
1278
1338
|
subtask_title: "Simple task",
|
|
1279
1339
|
subtask_description: "",
|
|
1280
1340
|
files: [],
|
|
@@ -1295,8 +1355,8 @@ describe("Swarm Prompt V2 (with Swarm Mail/Beads)", () => {
|
|
|
1295
1355
|
|
|
1296
1356
|
it("handles files with special characters", () => {
|
|
1297
1357
|
const result = formatSubtaskPromptV2({
|
|
1298
|
-
bead_id: "
|
|
1299
|
-
epic_id: "
|
|
1358
|
+
bead_id: "test-swarm-plugin-lf2p4u-paths789.1",
|
|
1359
|
+
epic_id: "test-swarm-plugin-lf2p4u-paths789",
|
|
1300
1360
|
subtask_title: "Handle paths",
|
|
1301
1361
|
subtask_description: "Test file paths",
|
|
1302
1362
|
files: [
|
|
@@ -1965,3 +2025,320 @@ describe("Checkpoint/Recovery Flow (integration)", () => {
|
|
|
1965
2025
|
// and swarm_recover tests above. Auto-checkpoint at milestones (25%, 50%, 75%) is
|
|
1966
2026
|
// a convenience feature that doesn't need dedicated integration tests.
|
|
1967
2027
|
});
|
|
2028
|
+
|
|
2029
|
+
// ============================================================================
|
|
2030
|
+
// Contract Validation Tests
|
|
2031
|
+
// ============================================================================
|
|
2032
|
+
|
|
2033
|
+
describe("Contract Validation", () => {
|
|
2034
|
+
describe("validateContract", () => {
|
|
2035
|
+
it("passes when files_touched is subset of files_owned", () => {
|
|
2036
|
+
// This test will fail until we implement validateContract
|
|
2037
|
+
const { validateContract } = require("./swarm-orchestrate");
|
|
2038
|
+
|
|
2039
|
+
const result = validateContract(
|
|
2040
|
+
["src/auth.ts", "src/utils.ts"],
|
|
2041
|
+
["src/auth.ts", "src/utils.ts", "src/types.ts"]
|
|
2042
|
+
);
|
|
2043
|
+
|
|
2044
|
+
expect(result.valid).toBe(true);
|
|
2045
|
+
expect(result.violations).toHaveLength(0);
|
|
2046
|
+
});
|
|
2047
|
+
|
|
2048
|
+
it("fails when files_touched has extra files", () => {
|
|
2049
|
+
const { validateContract } = require("./swarm-orchestrate");
|
|
2050
|
+
|
|
2051
|
+
const result = validateContract(
|
|
2052
|
+
["src/auth.ts", "src/forbidden.ts"],
|
|
2053
|
+
["src/auth.ts"]
|
|
2054
|
+
);
|
|
2055
|
+
|
|
2056
|
+
expect(result.valid).toBe(false);
|
|
2057
|
+
expect(result.violations).toContain("src/forbidden.ts");
|
|
2058
|
+
});
|
|
2059
|
+
|
|
2060
|
+
it("matches glob patterns correctly", () => {
|
|
2061
|
+
const { validateContract } = require("./swarm-orchestrate");
|
|
2062
|
+
|
|
2063
|
+
const result = validateContract(
|
|
2064
|
+
["src/auth/service.ts", "src/auth/types.ts"],
|
|
2065
|
+
["src/auth/**/*.ts"]
|
|
2066
|
+
);
|
|
2067
|
+
|
|
2068
|
+
expect(result.valid).toBe(true);
|
|
2069
|
+
expect(result.violations).toHaveLength(0);
|
|
2070
|
+
});
|
|
2071
|
+
|
|
2072
|
+
it("detects violations outside glob pattern", () => {
|
|
2073
|
+
const { validateContract } = require("./swarm-orchestrate");
|
|
2074
|
+
|
|
2075
|
+
const result = validateContract(
|
|
2076
|
+
["src/auth/service.ts", "src/utils/helper.ts"],
|
|
2077
|
+
["src/auth/**"]
|
|
2078
|
+
);
|
|
2079
|
+
|
|
2080
|
+
expect(result.valid).toBe(false);
|
|
2081
|
+
expect(result.violations).toContain("src/utils/helper.ts");
|
|
2082
|
+
});
|
|
2083
|
+
|
|
2084
|
+
it("passes with empty files_touched (read-only work)", () => {
|
|
2085
|
+
const { validateContract } = require("./swarm-orchestrate");
|
|
2086
|
+
|
|
2087
|
+
const result = validateContract(
|
|
2088
|
+
[],
|
|
2089
|
+
["src/auth/**"]
|
|
2090
|
+
);
|
|
2091
|
+
|
|
2092
|
+
expect(result.valid).toBe(true);
|
|
2093
|
+
expect(result.violations).toHaveLength(0);
|
|
2094
|
+
});
|
|
2095
|
+
|
|
2096
|
+
it("handles multiple glob patterns", () => {
|
|
2097
|
+
const { validateContract } = require("./swarm-orchestrate");
|
|
2098
|
+
|
|
2099
|
+
const result = validateContract(
|
|
2100
|
+
["src/auth/service.ts", "tests/auth.test.ts"],
|
|
2101
|
+
["src/auth/**", "tests/**"]
|
|
2102
|
+
);
|
|
2103
|
+
|
|
2104
|
+
expect(result.valid).toBe(true);
|
|
2105
|
+
expect(result.violations).toHaveLength(0);
|
|
2106
|
+
});
|
|
2107
|
+
});
|
|
2108
|
+
|
|
2109
|
+
describe("swarm_complete with contract validation", () => {
|
|
2110
|
+
it("includes contract validation result when files_touched provided", async () => {
|
|
2111
|
+
// This test needs a real decomposition event, so it's more of an integration check
|
|
2112
|
+
// The actual validation logic is tested in unit tests above
|
|
2113
|
+
// Here we just verify the response includes contract_validation field
|
|
2114
|
+
|
|
2115
|
+
const mockResult = {
|
|
2116
|
+
success: true,
|
|
2117
|
+
contract_validation: {
|
|
2118
|
+
validated: false,
|
|
2119
|
+
reason: "No files_owned contract found (non-epic subtask or decomposition event missing)",
|
|
2120
|
+
},
|
|
2121
|
+
};
|
|
2122
|
+
|
|
2123
|
+
// Verify the structure exists
|
|
2124
|
+
expect(mockResult.contract_validation).toBeDefined();
|
|
2125
|
+
expect(mockResult.contract_validation.validated).toBe(false);
|
|
2126
|
+
});
|
|
2127
|
+
});
|
|
2128
|
+
|
|
2129
|
+
describe("swarm_complete project_key handling (bug fix)", () => {
|
|
2130
|
+
it("finds cells created with full path project_key", async () => {
|
|
2131
|
+
// BUG: swarm_complete was mangling project_key with .replace(/\//g, "-")
|
|
2132
|
+
// before querying, but cells are stored with the original path.
|
|
2133
|
+
// This caused "Bead not found" errors for cells created via hive_create_epic.
|
|
2134
|
+
|
|
2135
|
+
const testProjectPath = "/tmp/swarm-complete-projectkey-test-" + Date.now();
|
|
2136
|
+
const { getHiveAdapter } = await import("./hive");
|
|
2137
|
+
const adapter = await getHiveAdapter(testProjectPath);
|
|
2138
|
+
|
|
2139
|
+
// Create a cell using the full path as project_key (like hive_create_epic does)
|
|
2140
|
+
const cell = await adapter.createCell(testProjectPath, {
|
|
2141
|
+
title: "Test cell for project_key bug",
|
|
2142
|
+
type: "task",
|
|
2143
|
+
priority: 2,
|
|
2144
|
+
});
|
|
2145
|
+
|
|
2146
|
+
expect(cell.id).toBeDefined();
|
|
2147
|
+
|
|
2148
|
+
// Now try to complete it via swarm_complete with the same project_key
|
|
2149
|
+
const result = await swarm_complete.execute(
|
|
2150
|
+
{
|
|
2151
|
+
project_key: testProjectPath, // Full path, not mangled
|
|
2152
|
+
agent_name: "test-agent",
|
|
2153
|
+
bead_id: cell.id,
|
|
2154
|
+
summary: "Testing project_key handling",
|
|
2155
|
+
skip_verification: true,
|
|
2156
|
+
skip_review: true,
|
|
2157
|
+
},
|
|
2158
|
+
mockContext,
|
|
2159
|
+
);
|
|
2160
|
+
|
|
2161
|
+
const parsed = JSON.parse(result);
|
|
2162
|
+
|
|
2163
|
+
// This should succeed - the cell exists with this project_key
|
|
2164
|
+
// BUG: Before fix, this fails with "Bead not found" because swarm_complete
|
|
2165
|
+
// was looking for project_key "-tmp-swarm-complete-projectkey-test-xxx"
|
|
2166
|
+
expect(parsed.success).toBe(true);
|
|
2167
|
+
expect(parsed.error).toBeUndefined();
|
|
2168
|
+
expect(parsed.bead_id).toBe(cell.id);
|
|
2169
|
+
});
|
|
2170
|
+
|
|
2171
|
+
it("handles project_key with slashes correctly", async () => {
|
|
2172
|
+
// Verify that project_key like "/Users/joel/Code/project" works
|
|
2173
|
+
const testProjectPath = "/a/b/c/test-" + Date.now();
|
|
2174
|
+
const { getHiveAdapter } = await import("./hive");
|
|
2175
|
+
const adapter = await getHiveAdapter(testProjectPath);
|
|
2176
|
+
|
|
2177
|
+
const cell = await adapter.createCell(testProjectPath, {
|
|
2178
|
+
title: "Nested path test",
|
|
2179
|
+
type: "task",
|
|
2180
|
+
priority: 2,
|
|
2181
|
+
});
|
|
2182
|
+
|
|
2183
|
+
// Verify cell was created with correct project_key
|
|
2184
|
+
const retrieved = await adapter.getCell(testProjectPath, cell.id);
|
|
2185
|
+
expect(retrieved).not.toBeNull();
|
|
2186
|
+
expect(retrieved?.id).toBe(cell.id);
|
|
2187
|
+
|
|
2188
|
+
// swarm_complete should find it using the same project_key
|
|
2189
|
+
const result = await swarm_complete.execute(
|
|
2190
|
+
{
|
|
2191
|
+
project_key: testProjectPath,
|
|
2192
|
+
agent_name: "test-agent",
|
|
2193
|
+
bead_id: cell.id,
|
|
2194
|
+
summary: "Nested path test",
|
|
2195
|
+
skip_verification: true,
|
|
2196
|
+
skip_review: true,
|
|
2197
|
+
},
|
|
2198
|
+
mockContext,
|
|
2199
|
+
);
|
|
2200
|
+
|
|
2201
|
+
const parsed = JSON.parse(result);
|
|
2202
|
+
expect(parsed.success).toBe(true);
|
|
2203
|
+
});
|
|
2204
|
+
});
|
|
2205
|
+
|
|
2206
|
+
describe("swarm_complete review gate UX", () => {
|
|
2207
|
+
it("returns success: true with status: pending_review when review not attempted", async () => {
|
|
2208
|
+
const testProjectPath = "/tmp/swarm-review-gate-test-" + Date.now();
|
|
2209
|
+
const { getHiveAdapter } = await import("./hive");
|
|
2210
|
+
const adapter = await getHiveAdapter(testProjectPath);
|
|
2211
|
+
|
|
2212
|
+
// Create a task cell directly
|
|
2213
|
+
const cell = await adapter.createCell(testProjectPath, {
|
|
2214
|
+
title: "Test task for review gate",
|
|
2215
|
+
type: "task",
|
|
2216
|
+
priority: 2,
|
|
2217
|
+
});
|
|
2218
|
+
|
|
2219
|
+
// Start the task
|
|
2220
|
+
await adapter.updateCell(testProjectPath, cell.id, {
|
|
2221
|
+
status: "in_progress",
|
|
2222
|
+
});
|
|
2223
|
+
|
|
2224
|
+
// Try to complete without review (skip_review intentionally omitted - defaults to false)
|
|
2225
|
+
const result = await swarm_complete.execute(
|
|
2226
|
+
{
|
|
2227
|
+
project_key: testProjectPath,
|
|
2228
|
+
agent_name: "TestAgent",
|
|
2229
|
+
bead_id: cell.id,
|
|
2230
|
+
summary: "Done",
|
|
2231
|
+
files_touched: ["test.ts"],
|
|
2232
|
+
skip_verification: true,
|
|
2233
|
+
// skip_review intentionally omitted - defaults to false
|
|
2234
|
+
},
|
|
2235
|
+
mockContext,
|
|
2236
|
+
);
|
|
2237
|
+
|
|
2238
|
+
const parsed = JSON.parse(result);
|
|
2239
|
+
|
|
2240
|
+
// Should be success: true with workflow status
|
|
2241
|
+
expect(parsed.success).toBe(true);
|
|
2242
|
+
expect(parsed.status).toBe("pending_review");
|
|
2243
|
+
expect(parsed.message).toContain("awaiting coordinator review");
|
|
2244
|
+
expect(parsed.next_steps).toBeInstanceOf(Array);
|
|
2245
|
+
expect(parsed.next_steps.length).toBeGreaterThan(0);
|
|
2246
|
+
expect(parsed.review_status).toBeDefined();
|
|
2247
|
+
expect(parsed.review_status.reviewed).toBe(false);
|
|
2248
|
+
expect(parsed.review_status.approved).toBe(false);
|
|
2249
|
+
|
|
2250
|
+
// Should NOT have error field
|
|
2251
|
+
expect(parsed.error).toBeUndefined();
|
|
2252
|
+
});
|
|
2253
|
+
|
|
2254
|
+
it("returns success: true, not error, when review not approved", async () => {
|
|
2255
|
+
const testProjectPath = "/tmp/swarm-review-not-approved-test-" + Date.now();
|
|
2256
|
+
const { getHiveAdapter } = await import("./hive");
|
|
2257
|
+
const { markReviewRejected } = await import("./swarm-review");
|
|
2258
|
+
const adapter = await getHiveAdapter(testProjectPath);
|
|
2259
|
+
|
|
2260
|
+
// Create a task cell directly
|
|
2261
|
+
const cell = await adapter.createCell(testProjectPath, {
|
|
2262
|
+
title: "Test task for review not approved",
|
|
2263
|
+
type: "task",
|
|
2264
|
+
priority: 2,
|
|
2265
|
+
});
|
|
2266
|
+
|
|
2267
|
+
// Start the task
|
|
2268
|
+
await adapter.updateCell(testProjectPath, cell.id, {
|
|
2269
|
+
status: "in_progress",
|
|
2270
|
+
});
|
|
2271
|
+
|
|
2272
|
+
// Manually set review status to rejected (approved: false, but reviewed: true)
|
|
2273
|
+
// This simulates the review gate detecting a review was done but not approved
|
|
2274
|
+
markReviewRejected(cell.id);
|
|
2275
|
+
|
|
2276
|
+
// Try to complete with review not approved
|
|
2277
|
+
const result = await swarm_complete.execute(
|
|
2278
|
+
{
|
|
2279
|
+
project_key: testProjectPath,
|
|
2280
|
+
agent_name: "TestAgent",
|
|
2281
|
+
bead_id: cell.id,
|
|
2282
|
+
summary: "Done",
|
|
2283
|
+
files_touched: ["test.ts"],
|
|
2284
|
+
skip_verification: true,
|
|
2285
|
+
},
|
|
2286
|
+
mockContext,
|
|
2287
|
+
);
|
|
2288
|
+
|
|
2289
|
+
const parsed = JSON.parse(result);
|
|
2290
|
+
|
|
2291
|
+
// Should be success: true with workflow status (not error)
|
|
2292
|
+
expect(parsed.success).toBe(true);
|
|
2293
|
+
expect(parsed.status).toBe("needs_changes");
|
|
2294
|
+
expect(parsed.message).toContain("changes requested");
|
|
2295
|
+
expect(parsed.next_steps).toBeInstanceOf(Array);
|
|
2296
|
+
expect(parsed.next_steps.length).toBeGreaterThan(0);
|
|
2297
|
+
expect(parsed.review_status).toBeDefined();
|
|
2298
|
+
expect(parsed.review_status.reviewed).toBe(true);
|
|
2299
|
+
expect(parsed.review_status.approved).toBe(false);
|
|
2300
|
+
|
|
2301
|
+
// Should NOT have error field
|
|
2302
|
+
expect(parsed.error).toBeUndefined();
|
|
2303
|
+
});
|
|
2304
|
+
|
|
2305
|
+
it("completes successfully when skip_review=true", async () => {
|
|
2306
|
+
const testProjectPath = "/tmp/swarm-skip-review-test-" + Date.now();
|
|
2307
|
+
const { getHiveAdapter } = await import("./hive");
|
|
2308
|
+
const adapter = await getHiveAdapter(testProjectPath);
|
|
2309
|
+
|
|
2310
|
+
// Create a task cell directly
|
|
2311
|
+
const cell = await adapter.createCell(testProjectPath, {
|
|
2312
|
+
title: "Test task for skip review",
|
|
2313
|
+
type: "task",
|
|
2314
|
+
priority: 2,
|
|
2315
|
+
});
|
|
2316
|
+
|
|
2317
|
+
// Start the task
|
|
2318
|
+
await adapter.updateCell(testProjectPath, cell.id, {
|
|
2319
|
+
status: "in_progress",
|
|
2320
|
+
});
|
|
2321
|
+
|
|
2322
|
+
// Complete with skip_review
|
|
2323
|
+
const result = await swarm_complete.execute(
|
|
2324
|
+
{
|
|
2325
|
+
project_key: testProjectPath,
|
|
2326
|
+
agent_name: "TestAgent",
|
|
2327
|
+
bead_id: cell.id,
|
|
2328
|
+
summary: "Done",
|
|
2329
|
+
files_touched: ["test.ts"],
|
|
2330
|
+
skip_verification: true,
|
|
2331
|
+
skip_review: true,
|
|
2332
|
+
},
|
|
2333
|
+
mockContext,
|
|
2334
|
+
);
|
|
2335
|
+
|
|
2336
|
+
const parsed = JSON.parse(result);
|
|
2337
|
+
|
|
2338
|
+
// Should complete without review gate
|
|
2339
|
+
expect(parsed.success).toBe(true);
|
|
2340
|
+
expect(parsed.status).toBeUndefined(); // No workflow status when skipping
|
|
2341
|
+
expect(parsed.error).toBeUndefined();
|
|
2342
|
+
});
|
|
2343
|
+
});
|
|
2344
|
+
});
|