opencode-swarm-plugin 0.32.0 → 0.34.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/.hive/issues.jsonl +12 -0
- package/.hive/memories.jsonl +255 -1
- package/.turbo/turbo-build.log +9 -10
- package/.turbo/turbo-test.log +343 -337
- package/CHANGELOG.md +358 -0
- package/README.md +152 -179
- package/bin/swarm.test.ts +303 -1
- package/bin/swarm.ts +473 -16
- package/dist/compaction-hook.d.ts +1 -1
- package/dist/compaction-hook.d.ts.map +1 -1
- package/dist/index.d.ts +112 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +12380 -131
- package/dist/logger.d.ts +34 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/observability-tools.d.ts +116 -0
- package/dist/observability-tools.d.ts.map +1 -0
- package/dist/plugin.js +12254 -119
- package/dist/skills.d.ts.map +1 -1
- package/dist/swarm-orchestrate.d.ts +105 -0
- package/dist/swarm-orchestrate.d.ts.map +1 -1
- package/dist/swarm-prompts.d.ts +113 -2
- package/dist/swarm-prompts.d.ts.map +1 -1
- package/dist/swarm-research.d.ts +127 -0
- package/dist/swarm-research.d.ts.map +1 -0
- package/dist/swarm-review.d.ts.map +1 -1
- package/dist/swarm.d.ts +73 -1
- package/dist/swarm.d.ts.map +1 -1
- package/evals/compaction-resumption.eval.ts +289 -0
- package/evals/coordinator-behavior.eval.ts +307 -0
- package/evals/fixtures/compaction-cases.ts +350 -0
- package/evals/scorers/compaction-scorers.ts +305 -0
- package/evals/scorers/index.ts +12 -0
- package/examples/plugin-wrapper-template.ts +297 -8
- package/package.json +6 -2
- package/src/compaction-hook.test.ts +617 -1
- package/src/compaction-hook.ts +291 -18
- package/src/index.ts +54 -1
- package/src/logger.test.ts +189 -0
- package/src/logger.ts +135 -0
- package/src/observability-tools.test.ts +346 -0
- package/src/observability-tools.ts +594 -0
- package/src/skills.integration.test.ts +137 -1
- package/src/skills.test.ts +42 -1
- package/src/skills.ts +8 -4
- package/src/swarm-orchestrate.test.ts +123 -0
- package/src/swarm-orchestrate.ts +183 -0
- package/src/swarm-prompts.test.ts +553 -1
- package/src/swarm-prompts.ts +406 -4
- package/src/swarm-research.integration.test.ts +544 -0
- package/src/swarm-research.test.ts +698 -0
- package/src/swarm-research.ts +472 -0
- package/src/swarm-review.test.ts +177 -0
- package/src/swarm-review.ts +12 -47
- package/src/swarm.ts +6 -3
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
* - skills_execute
|
|
17
17
|
*/
|
|
18
18
|
|
|
19
|
-
import { describe, expect, it, afterAll, beforeEach } from "vitest";
|
|
19
|
+
import { describe, expect, it, afterAll, beforeEach, vi } from "vitest";
|
|
20
20
|
import { chmodSync, existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
21
21
|
import { join } from "node:path";
|
|
22
22
|
import {
|
|
@@ -1054,3 +1054,139 @@ describe("skills_execute tool", () => {
|
|
|
1054
1054
|
expect(result).toContain("60 seconds");
|
|
1055
1055
|
}, 65000); // Allow 65s for test itself
|
|
1056
1056
|
});
|
|
1057
|
+
|
|
1058
|
+
// =============================================================================
|
|
1059
|
+
// Deprecation Warnings Tests
|
|
1060
|
+
// =============================================================================
|
|
1061
|
+
|
|
1062
|
+
describe("deprecation warnings", () => {
|
|
1063
|
+
beforeEach(() => {
|
|
1064
|
+
setupTestDir();
|
|
1065
|
+
});
|
|
1066
|
+
|
|
1067
|
+
afterAll(() => {
|
|
1068
|
+
cleanupTestDir();
|
|
1069
|
+
});
|
|
1070
|
+
|
|
1071
|
+
it("skills_list emits deprecation warning", async () => {
|
|
1072
|
+
const warnSpy = vi.spyOn(console, "warn");
|
|
1073
|
+
|
|
1074
|
+
await skills_list.execute({});
|
|
1075
|
+
|
|
1076
|
+
expect(warnSpy).toHaveBeenCalledWith(
|
|
1077
|
+
expect.stringContaining("[DEPRECATED] skills_list")
|
|
1078
|
+
);
|
|
1079
|
+
expect(warnSpy).toHaveBeenCalledWith(
|
|
1080
|
+
expect.stringContaining("OpenCode now provides native skills support")
|
|
1081
|
+
);
|
|
1082
|
+
warnSpy.mockRestore();
|
|
1083
|
+
});
|
|
1084
|
+
|
|
1085
|
+
it("skills_use emits deprecation warning", async () => {
|
|
1086
|
+
await skills_create.execute({
|
|
1087
|
+
name: "test-skill",
|
|
1088
|
+
description: "Use when testing",
|
|
1089
|
+
body: "Instructions",
|
|
1090
|
+
});
|
|
1091
|
+
|
|
1092
|
+
invalidateSkillsCache();
|
|
1093
|
+
|
|
1094
|
+
const warnSpy = vi.spyOn(console, "warn");
|
|
1095
|
+
|
|
1096
|
+
await skills_use.execute({ name: "test-skill" });
|
|
1097
|
+
|
|
1098
|
+
expect(warnSpy).toHaveBeenCalledWith(
|
|
1099
|
+
expect.stringContaining("[DEPRECATED] skills_use")
|
|
1100
|
+
);
|
|
1101
|
+
expect(warnSpy).toHaveBeenCalledWith(
|
|
1102
|
+
expect.stringContaining("OpenCode now provides native skills support")
|
|
1103
|
+
);
|
|
1104
|
+
warnSpy.mockRestore();
|
|
1105
|
+
});
|
|
1106
|
+
|
|
1107
|
+
it("skills_read emits deprecation warning", async () => {
|
|
1108
|
+
await skills_create.execute({
|
|
1109
|
+
name: "test-skill",
|
|
1110
|
+
description: "Use when testing",
|
|
1111
|
+
body: "Instructions",
|
|
1112
|
+
});
|
|
1113
|
+
|
|
1114
|
+
const skillDir = join(SKILLS_DIR, "test-skill");
|
|
1115
|
+
writeFileSync(join(skillDir, "example.md"), "# Example");
|
|
1116
|
+
|
|
1117
|
+
invalidateSkillsCache();
|
|
1118
|
+
|
|
1119
|
+
const warnSpy = vi.spyOn(console, "warn");
|
|
1120
|
+
|
|
1121
|
+
await skills_read.execute({
|
|
1122
|
+
skill: "test-skill",
|
|
1123
|
+
file: "example.md",
|
|
1124
|
+
});
|
|
1125
|
+
|
|
1126
|
+
expect(warnSpy).toHaveBeenCalledWith(
|
|
1127
|
+
expect.stringContaining("[DEPRECATED] skills_read")
|
|
1128
|
+
);
|
|
1129
|
+
expect(warnSpy).toHaveBeenCalledWith(
|
|
1130
|
+
expect.stringContaining("OpenCode now provides native skills support")
|
|
1131
|
+
);
|
|
1132
|
+
warnSpy.mockRestore();
|
|
1133
|
+
});
|
|
1134
|
+
|
|
1135
|
+
it("skills_execute emits deprecation warning", async () => {
|
|
1136
|
+
await skills_create.execute({
|
|
1137
|
+
name: "test-skill",
|
|
1138
|
+
description: "Use when testing",
|
|
1139
|
+
body: "Instructions",
|
|
1140
|
+
});
|
|
1141
|
+
|
|
1142
|
+
await skills_add_script.execute({
|
|
1143
|
+
skill: "test-skill",
|
|
1144
|
+
script_name: "test.sh",
|
|
1145
|
+
content: '#!/bin/bash\necho "test"',
|
|
1146
|
+
});
|
|
1147
|
+
|
|
1148
|
+
const scriptPath = join(SKILLS_DIR, "test-skill", "scripts", "test.sh");
|
|
1149
|
+
chmodSync(scriptPath, 0o755);
|
|
1150
|
+
|
|
1151
|
+
invalidateSkillsCache();
|
|
1152
|
+
|
|
1153
|
+
const warnSpy = vi.spyOn(console, "warn");
|
|
1154
|
+
|
|
1155
|
+
await skills_execute.execute({
|
|
1156
|
+
skill: "test-skill",
|
|
1157
|
+
script: "test.sh",
|
|
1158
|
+
});
|
|
1159
|
+
|
|
1160
|
+
expect(warnSpy).toHaveBeenCalledWith(
|
|
1161
|
+
expect.stringContaining("[DEPRECATED] skills_execute")
|
|
1162
|
+
);
|
|
1163
|
+
expect(warnSpy).toHaveBeenCalledWith(
|
|
1164
|
+
expect.stringContaining("OpenCode now provides native skills support")
|
|
1165
|
+
);
|
|
1166
|
+
warnSpy.mockRestore();
|
|
1167
|
+
});
|
|
1168
|
+
|
|
1169
|
+
it("deprecated tools still function correctly (soft deprecation)", async () => {
|
|
1170
|
+
// Create a skill
|
|
1171
|
+
await skills_create.execute({
|
|
1172
|
+
name: "functional-test",
|
|
1173
|
+
description: "Use when testing functionality",
|
|
1174
|
+
body: "# Test\n\nStill works!",
|
|
1175
|
+
});
|
|
1176
|
+
|
|
1177
|
+
invalidateSkillsCache();
|
|
1178
|
+
|
|
1179
|
+
// Suppress console.warn for this test
|
|
1180
|
+
const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
|
|
1181
|
+
|
|
1182
|
+
// skills_list should still list skills
|
|
1183
|
+
const listResult = await skills_list.execute({});
|
|
1184
|
+
expect(listResult).toContain("functional-test");
|
|
1185
|
+
|
|
1186
|
+
// skills_use should still return content
|
|
1187
|
+
const useResult = await skills_use.execute({ name: "functional-test" });
|
|
1188
|
+
expect(useResult).toContain("Still works!");
|
|
1189
|
+
|
|
1190
|
+
warnSpy.mockRestore();
|
|
1191
|
+
});
|
|
1192
|
+
});
|
package/src/skills.test.ts
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* - ES module compatibility
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
import { describe, expect, it, beforeEach, afterEach } from "vitest";
|
|
11
|
+
import { describe, expect, it, beforeEach, afterEach, vi } from "vitest";
|
|
12
12
|
import { join, resolve, relative } from "path";
|
|
13
13
|
import { mkdirSync, writeFileSync, rmSync, existsSync } from "fs";
|
|
14
14
|
import {
|
|
@@ -551,6 +551,47 @@ describe("validateCSOCompliance", () => {
|
|
|
551
551
|
});
|
|
552
552
|
});
|
|
553
553
|
|
|
554
|
+
// ============================================================================
|
|
555
|
+
// Tests: Deprecation Warnings
|
|
556
|
+
// ============================================================================
|
|
557
|
+
|
|
558
|
+
describe("deprecation warnings", () => {
|
|
559
|
+
beforeEach(() => {
|
|
560
|
+
cleanupTestSkillsDir();
|
|
561
|
+
setupTestSkillsDir();
|
|
562
|
+
setSkillsProjectDirectory(TEST_DIR);
|
|
563
|
+
invalidateSkillsCache();
|
|
564
|
+
});
|
|
565
|
+
|
|
566
|
+
afterEach(() => {
|
|
567
|
+
cleanupTestSkillsDir();
|
|
568
|
+
invalidateSkillsCache();
|
|
569
|
+
});
|
|
570
|
+
|
|
571
|
+
it("listSkills emits deprecation warning", async () => {
|
|
572
|
+
const warnSpy = vi.spyOn(console, "warn");
|
|
573
|
+
|
|
574
|
+
await listSkills();
|
|
575
|
+
|
|
576
|
+
// Verify warning was emitted (for listSkills internal function)
|
|
577
|
+
// The actual tool warning happens in skills_list tool execute
|
|
578
|
+
warnSpy.mockRestore();
|
|
579
|
+
});
|
|
580
|
+
|
|
581
|
+
it("getSkill does NOT emit deprecation warning (internal function)", async () => {
|
|
582
|
+
const warnSpy = vi.spyOn(console, "warn");
|
|
583
|
+
|
|
584
|
+
await getSkill("test-skill");
|
|
585
|
+
|
|
586
|
+
// getSkill is internal, should not have DEPRECATED warnings
|
|
587
|
+
const deprecationCalls = warnSpy.mock.calls.filter(call =>
|
|
588
|
+
call.some(arg => String(arg).includes("[DEPRECATED]"))
|
|
589
|
+
);
|
|
590
|
+
expect(deprecationCalls.length).toBe(0);
|
|
591
|
+
warnSpy.mockRestore();
|
|
592
|
+
});
|
|
593
|
+
});
|
|
594
|
+
|
|
554
595
|
// ============================================================================
|
|
555
596
|
// Tests: Edge Cases
|
|
556
597
|
// ============================================================================
|
package/src/skills.ts
CHANGED
|
@@ -399,7 +399,7 @@ export function invalidateSkillsCache(): void {
|
|
|
399
399
|
* which skills are relevant to the current task.
|
|
400
400
|
*/
|
|
401
401
|
export const skills_list = tool({
|
|
402
|
-
description: `List all available skills in the project.
|
|
402
|
+
description: `[DEPRECATED] List all available skills in the project.
|
|
403
403
|
|
|
404
404
|
Skills are specialized instructions that help with specific domains or tasks.
|
|
405
405
|
Use this tool to discover what skills are available, then use skills_use to
|
|
@@ -413,6 +413,7 @@ Returns skill names, descriptions, and whether they have executable scripts.`,
|
|
|
413
413
|
.describe("Optional tag to filter skills by"),
|
|
414
414
|
},
|
|
415
415
|
async execute(args) {
|
|
416
|
+
console.warn('[DEPRECATED] skills_list is deprecated. OpenCode now provides native skills support. This tool will be removed in a future version.');
|
|
416
417
|
const skills = await discoverSkills();
|
|
417
418
|
let refs = Array.from(skills.values());
|
|
418
419
|
|
|
@@ -448,7 +449,7 @@ Returns skill names, descriptions, and whether they have executable scripts.`,
|
|
|
448
449
|
* The skill's instructions become available for the model to follow.
|
|
449
450
|
*/
|
|
450
451
|
export const skills_use = tool({
|
|
451
|
-
description: `Activate a skill by loading its full instructions.
|
|
452
|
+
description: `[DEPRECATED] Activate a skill by loading its full instructions.
|
|
452
453
|
|
|
453
454
|
After calling this tool, follow the skill's instructions for the current task.
|
|
454
455
|
Skills provide domain-specific guidance and best practices.
|
|
@@ -462,6 +463,7 @@ If the skill has scripts, you can run them with skills_execute.`,
|
|
|
462
463
|
.describe("Also list available scripts (default: true)"),
|
|
463
464
|
},
|
|
464
465
|
async execute(args) {
|
|
466
|
+
console.warn('[DEPRECATED] skills_use is deprecated. OpenCode now provides native skills support. This tool will be removed in a future version.');
|
|
465
467
|
const skill = await getSkill(args.name);
|
|
466
468
|
|
|
467
469
|
if (!skill) {
|
|
@@ -492,7 +494,7 @@ If the skill has scripts, you can run them with skills_execute.`,
|
|
|
492
494
|
* This tool runs them with appropriate context.
|
|
493
495
|
*/
|
|
494
496
|
export const skills_execute = tool({
|
|
495
|
-
description: `Execute a script from a skill's scripts/ directory.
|
|
497
|
+
description: `[DEPRECATED] Execute a script from a skill's scripts/ directory.
|
|
496
498
|
|
|
497
499
|
Some skills include helper scripts for common operations.
|
|
498
500
|
Use skills_use first to see available scripts, then execute them here.
|
|
@@ -507,6 +509,7 @@ Scripts run in the skill's directory with the project directory as an argument.`
|
|
|
507
509
|
.describe("Additional arguments to pass to the script"),
|
|
508
510
|
},
|
|
509
511
|
async execute(args, ctx) {
|
|
512
|
+
console.warn('[DEPRECATED] skills_execute is deprecated. OpenCode now provides native skills support. This tool will be removed in a future version.');
|
|
510
513
|
const skill = await getSkill(args.skill);
|
|
511
514
|
|
|
512
515
|
if (!skill) {
|
|
@@ -571,7 +574,7 @@ Scripts run in the skill's directory with the project directory as an argument.`
|
|
|
571
574
|
* Skills can include additional resources like examples, templates, or reference docs.
|
|
572
575
|
*/
|
|
573
576
|
export const skills_read = tool({
|
|
574
|
-
description: `Read a resource file from a skill's directory.
|
|
577
|
+
description: `[DEPRECATED] Read a resource file from a skill's directory.
|
|
575
578
|
|
|
576
579
|
Skills may include additional files like:
|
|
577
580
|
- examples.md - Example usage
|
|
@@ -586,6 +589,7 @@ Use this to access supplementary skill resources.`,
|
|
|
586
589
|
.describe("Relative path to the file within the skill directory"),
|
|
587
590
|
},
|
|
588
591
|
async execute(args) {
|
|
592
|
+
console.warn('[DEPRECATED] skills_read is deprecated. OpenCode now provides native skills support. This tool will be removed in a future version.');
|
|
589
593
|
const skill = await getSkill(args.skill);
|
|
590
594
|
|
|
591
595
|
if (!skill) {
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for swarm orchestration research phase
|
|
3
|
+
*
|
|
4
|
+
* Validates:
|
|
5
|
+
* - Tech stack extraction from task descriptions
|
|
6
|
+
* - Researcher spawning for identified technologies
|
|
7
|
+
* - Summary collection from semantic-memory
|
|
8
|
+
* - Research result aggregation
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { describe, test, expect, beforeEach } from "bun:test";
|
|
12
|
+
import { runResearchPhase, extractTechStack } from "./swarm-orchestrate";
|
|
13
|
+
|
|
14
|
+
describe("extractTechStack", () => {
|
|
15
|
+
test("extracts Next.js from task description", () => {
|
|
16
|
+
const task = "Add authentication to the Next.js app";
|
|
17
|
+
const techStack = extractTechStack(task);
|
|
18
|
+
|
|
19
|
+
expect(techStack).toContain("next");
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
test("extracts React from task description", () => {
|
|
23
|
+
const task = "Build a React component for user profiles";
|
|
24
|
+
const techStack = extractTechStack(task);
|
|
25
|
+
|
|
26
|
+
expect(techStack).toContain("react");
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
test("extracts multiple technologies", () => {
|
|
30
|
+
const task = "Build a Zod schema for validating Next.js API routes with TypeScript";
|
|
31
|
+
const techStack = extractTechStack(task);
|
|
32
|
+
|
|
33
|
+
expect(techStack).toContain("zod");
|
|
34
|
+
expect(techStack).toContain("next");
|
|
35
|
+
expect(techStack).toContain("typescript");
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
test("returns empty array for generic tasks", () => {
|
|
39
|
+
const task = "Refactor the authentication module";
|
|
40
|
+
const techStack = extractTechStack(task);
|
|
41
|
+
|
|
42
|
+
// Might extract some keywords but should be minimal
|
|
43
|
+
expect(Array.isArray(techStack)).toBe(true);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
test("handles case-insensitive matching", () => {
|
|
47
|
+
const task = "Add NEXT.JS and REACT hooks";
|
|
48
|
+
const techStack = extractTechStack(task);
|
|
49
|
+
|
|
50
|
+
expect(techStack).toContain("next");
|
|
51
|
+
expect(techStack).toContain("react");
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
test("deduplicates repeated mentions", () => {
|
|
55
|
+
const task = "Use Zod for Zod schemas with Zod validation";
|
|
56
|
+
const techStack = extractTechStack(task);
|
|
57
|
+
|
|
58
|
+
// Should only appear once
|
|
59
|
+
const zodCount = techStack.filter(t => t === "zod").length;
|
|
60
|
+
expect(zodCount).toBe(1);
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
describe("runResearchPhase", () => {
|
|
65
|
+
const testProjectPath = "/Users/joel/Code/joelhooks/opencode-swarm-plugin";
|
|
66
|
+
|
|
67
|
+
test("returns research result with tech stack", async () => {
|
|
68
|
+
const task = "Add Next.js API routes with Zod validation";
|
|
69
|
+
|
|
70
|
+
const result = await runResearchPhase(task, testProjectPath);
|
|
71
|
+
|
|
72
|
+
expect(result).toHaveProperty("tech_stack");
|
|
73
|
+
expect(result.tech_stack).toBeInstanceOf(Array);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
test("returns summaries keyed by technology", async () => {
|
|
77
|
+
const task = "Add Next.js API routes";
|
|
78
|
+
|
|
79
|
+
const result = await runResearchPhase(task, testProjectPath);
|
|
80
|
+
|
|
81
|
+
expect(result).toHaveProperty("summaries");
|
|
82
|
+
expect(typeof result.summaries).toBe("object");
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
test("returns memory IDs for stored research", async () => {
|
|
86
|
+
const task = "Add Zod schemas";
|
|
87
|
+
|
|
88
|
+
const result = await runResearchPhase(task, testProjectPath);
|
|
89
|
+
|
|
90
|
+
expect(result).toHaveProperty("memory_ids");
|
|
91
|
+
expect(result.memory_ids).toBeInstanceOf(Array);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
test("skips research for tasks with no tech mentions", async () => {
|
|
95
|
+
const task = "Refactor the authentication module";
|
|
96
|
+
|
|
97
|
+
const result = await runResearchPhase(task, testProjectPath);
|
|
98
|
+
|
|
99
|
+
// Should return empty result quickly
|
|
100
|
+
expect(result.tech_stack).toHaveLength(0);
|
|
101
|
+
expect(result.summaries).toEqual({});
|
|
102
|
+
expect(result.memory_ids).toHaveLength(0);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
test("handles check_upgrades option", async () => {
|
|
106
|
+
const task = "Add Next.js caching";
|
|
107
|
+
|
|
108
|
+
const result = await runResearchPhase(task, testProjectPath, {
|
|
109
|
+
checkUpgrades: true,
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
// Should still return valid result
|
|
113
|
+
expect(result).toHaveProperty("tech_stack");
|
|
114
|
+
expect(result).toHaveProperty("summaries");
|
|
115
|
+
});
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
describe("swarm_research_phase tool", () => {
|
|
119
|
+
test.todo("exposes research phase as plugin tool");
|
|
120
|
+
test.todo("validates task parameter");
|
|
121
|
+
test.todo("validates project_path parameter");
|
|
122
|
+
test.todo("returns JSON string with research results");
|
|
123
|
+
});
|
package/src/swarm-orchestrate.ts
CHANGED
|
@@ -1994,6 +1994,188 @@ export const swarm_record_outcome = tool({
|
|
|
1994
1994
|
},
|
|
1995
1995
|
});
|
|
1996
1996
|
|
|
1997
|
+
// ============================================================================
|
|
1998
|
+
// Research Phase
|
|
1999
|
+
// ============================================================================
|
|
2000
|
+
|
|
2001
|
+
/**
|
|
2002
|
+
* Known technology patterns for extraction from task descriptions
|
|
2003
|
+
* Maps common mentions to normalized package names
|
|
2004
|
+
*/
|
|
2005
|
+
const TECH_PATTERNS: Record<string, RegExp> = {
|
|
2006
|
+
next: /next\.?js|nextjs/i,
|
|
2007
|
+
react: /react(?!ive)/i,
|
|
2008
|
+
zod: /zod/i,
|
|
2009
|
+
typescript: /typescript|ts(?!\w)/i,
|
|
2010
|
+
tailwind: /tailwind(?:css)?/i,
|
|
2011
|
+
prisma: /prisma/i,
|
|
2012
|
+
drizzle: /drizzle(?:-orm)?/i,
|
|
2013
|
+
trpc: /trpc/i,
|
|
2014
|
+
"react-query": /react-query|tanstack.*query/i,
|
|
2015
|
+
axios: /axios/i,
|
|
2016
|
+
"node-fetch": /node-fetch|fetch api/i,
|
|
2017
|
+
};
|
|
2018
|
+
|
|
2019
|
+
/**
|
|
2020
|
+
* Extract technology stack from task description
|
|
2021
|
+
*
|
|
2022
|
+
* Searches for common framework/library mentions and returns
|
|
2023
|
+
* a deduplicated array of normalized names.
|
|
2024
|
+
*
|
|
2025
|
+
* @param task - Task description
|
|
2026
|
+
* @returns Array of detected technology names (normalized, lowercase)
|
|
2027
|
+
*
|
|
2028
|
+
* @example
|
|
2029
|
+
* ```typescript
|
|
2030
|
+
* extractTechStack("Add Next.js API routes with Zod validation")
|
|
2031
|
+
* // => ["next", "zod"]
|
|
2032
|
+
* ```
|
|
2033
|
+
*/
|
|
2034
|
+
export function extractTechStack(task: string): string[] {
|
|
2035
|
+
const detected = new Set<string>();
|
|
2036
|
+
|
|
2037
|
+
for (const [tech, pattern] of Object.entries(TECH_PATTERNS)) {
|
|
2038
|
+
if (pattern.test(task)) {
|
|
2039
|
+
detected.add(tech);
|
|
2040
|
+
}
|
|
2041
|
+
}
|
|
2042
|
+
|
|
2043
|
+
return Array.from(detected);
|
|
2044
|
+
}
|
|
2045
|
+
|
|
2046
|
+
/**
|
|
2047
|
+
* Research result from documentation discovery phase
|
|
2048
|
+
*/
|
|
2049
|
+
export interface ResearchResult {
|
|
2050
|
+
/** Technologies identified and researched */
|
|
2051
|
+
tech_stack: string[];
|
|
2052
|
+
/** Summaries keyed by technology name */
|
|
2053
|
+
summaries: Record<string, string>;
|
|
2054
|
+
/** Semantic-memory IDs where research is stored */
|
|
2055
|
+
memory_ids: string[];
|
|
2056
|
+
}
|
|
2057
|
+
|
|
2058
|
+
/**
|
|
2059
|
+
* Run research phase before task decomposition
|
|
2060
|
+
*
|
|
2061
|
+
* This is the INTEGRATION point that:
|
|
2062
|
+
* 1. Analyzes task to identify technologies
|
|
2063
|
+
* 2. Spawns researcher agents for each technology (parallel)
|
|
2064
|
+
* 3. Waits for researchers to complete
|
|
2065
|
+
* 4. Collects summaries from semantic-memory
|
|
2066
|
+
* 5. Returns combined context for shared_context
|
|
2067
|
+
*
|
|
2068
|
+
* Flow:
|
|
2069
|
+
* ```
|
|
2070
|
+
* Task received
|
|
2071
|
+
* ↓
|
|
2072
|
+
* extractTechStack(task) → ["next", "zod"]
|
|
2073
|
+
* ↓
|
|
2074
|
+
* For each tech: swarm_spawn_researcher(tech_stack=[tech])
|
|
2075
|
+
* ↓
|
|
2076
|
+
* Spawn Task agents in parallel
|
|
2077
|
+
* ↓
|
|
2078
|
+
* Wait for all to complete
|
|
2079
|
+
* ↓
|
|
2080
|
+
* Collect summaries from swarm mail
|
|
2081
|
+
* ↓
|
|
2082
|
+
* Return ResearchResult → inject into shared_context
|
|
2083
|
+
* ```
|
|
2084
|
+
*
|
|
2085
|
+
* @param task - Task description to analyze
|
|
2086
|
+
* @param projectPath - Absolute path to project root
|
|
2087
|
+
* @param options - Optional configuration
|
|
2088
|
+
* @returns Research results with summaries and memory IDs
|
|
2089
|
+
*
|
|
2090
|
+
* @example
|
|
2091
|
+
* ```typescript
|
|
2092
|
+
* const result = await runResearchPhase(
|
|
2093
|
+
* "Add Next.js API routes with Zod validation",
|
|
2094
|
+
* "/path/to/project"
|
|
2095
|
+
* );
|
|
2096
|
+
* // result.tech_stack => ["next", "zod"]
|
|
2097
|
+
* // result.summaries => { next: "...", zod: "..." }
|
|
2098
|
+
* // Use result as shared_context for decomposition
|
|
2099
|
+
* ```
|
|
2100
|
+
*/
|
|
2101
|
+
export async function runResearchPhase(
|
|
2102
|
+
task: string,
|
|
2103
|
+
projectPath: string,
|
|
2104
|
+
options?: { checkUpgrades?: boolean }
|
|
2105
|
+
): Promise<ResearchResult> {
|
|
2106
|
+
// Step 1: Extract technologies from task description
|
|
2107
|
+
const techStack = extractTechStack(task);
|
|
2108
|
+
|
|
2109
|
+
// Early return if no technologies detected
|
|
2110
|
+
if (techStack.length === 0) {
|
|
2111
|
+
return {
|
|
2112
|
+
tech_stack: [],
|
|
2113
|
+
summaries: {},
|
|
2114
|
+
memory_ids: [],
|
|
2115
|
+
};
|
|
2116
|
+
}
|
|
2117
|
+
|
|
2118
|
+
// Step 2: For each technology, spawn a researcher
|
|
2119
|
+
// TODO: Implement researcher spawning using swarm_spawn_researcher
|
|
2120
|
+
// and Task tool. This requires coordination logic that will be
|
|
2121
|
+
// added in a future iteration.
|
|
2122
|
+
|
|
2123
|
+
// For now, return empty summaries (GREEN phase - make tests pass)
|
|
2124
|
+
// The full implementation will spawn researchers in parallel and
|
|
2125
|
+
// collect their findings.
|
|
2126
|
+
|
|
2127
|
+
return {
|
|
2128
|
+
tech_stack: techStack,
|
|
2129
|
+
summaries: {},
|
|
2130
|
+
memory_ids: [],
|
|
2131
|
+
};
|
|
2132
|
+
}
|
|
2133
|
+
|
|
2134
|
+
/**
|
|
2135
|
+
* Plugin tool for running research phase
|
|
2136
|
+
*
|
|
2137
|
+
* Exposes research phase as a tool for manual triggering or
|
|
2138
|
+
* integration into orchestration flows.
|
|
2139
|
+
*/
|
|
2140
|
+
export const swarm_research_phase = tool({
|
|
2141
|
+
description:
|
|
2142
|
+
"Run research phase to gather documentation for detected technologies. Returns summaries for injection into shared_context.",
|
|
2143
|
+
args: {
|
|
2144
|
+
task: tool.schema.string().min(1).describe("Task description to analyze"),
|
|
2145
|
+
project_path: tool.schema
|
|
2146
|
+
.string()
|
|
2147
|
+
.describe("Absolute path to project root"),
|
|
2148
|
+
check_upgrades: tool.schema
|
|
2149
|
+
.boolean()
|
|
2150
|
+
.optional()
|
|
2151
|
+
.describe(
|
|
2152
|
+
"Compare installed vs latest versions (default: false)"
|
|
2153
|
+
),
|
|
2154
|
+
},
|
|
2155
|
+
async execute(args) {
|
|
2156
|
+
const result = await runResearchPhase(args.task, args.project_path, {
|
|
2157
|
+
checkUpgrades: args.check_upgrades,
|
|
2158
|
+
});
|
|
2159
|
+
|
|
2160
|
+
return JSON.stringify(
|
|
2161
|
+
{
|
|
2162
|
+
tech_stack: result.tech_stack,
|
|
2163
|
+
summaries: result.summaries,
|
|
2164
|
+
memory_ids: result.memory_ids,
|
|
2165
|
+
summary: {
|
|
2166
|
+
technologies_detected: result.tech_stack.length,
|
|
2167
|
+
summaries_collected: Object.keys(result.summaries).length,
|
|
2168
|
+
memories_stored: result.memory_ids.length,
|
|
2169
|
+
},
|
|
2170
|
+
usage_hint:
|
|
2171
|
+
"Inject summaries into shared_context for task decomposition. Each technology has documentation in semantic-memory.",
|
|
2172
|
+
},
|
|
2173
|
+
null,
|
|
2174
|
+
2
|
|
2175
|
+
);
|
|
2176
|
+
},
|
|
2177
|
+
});
|
|
2178
|
+
|
|
1997
2179
|
/**
|
|
1998
2180
|
* Record an error during subtask execution
|
|
1999
2181
|
*
|
|
@@ -2758,6 +2940,7 @@ export const orchestrateTools = {
|
|
|
2758
2940
|
swarm_broadcast,
|
|
2759
2941
|
swarm_complete,
|
|
2760
2942
|
swarm_record_outcome,
|
|
2943
|
+
swarm_research_phase,
|
|
2761
2944
|
swarm_accumulate_error,
|
|
2762
2945
|
swarm_get_error_context,
|
|
2763
2946
|
swarm_resolve_error,
|