opencode-conductor-plugin 1.22.1 → 1.24.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.test.md +51 -0
- package/dist/tools/commands.d.ts +10 -6
- package/dist/tools/commands.js +62 -113
- package/dist/utils/commandFactory.d.ts +10 -0
- package/dist/utils/commandFactory.js +53 -0
- package/package.json +7 -2
package/README.test.md
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# Testing
|
|
2
|
+
|
|
3
|
+
This project uses [Vitest](https://vitest.dev/) for testing.
|
|
4
|
+
|
|
5
|
+
## Setup
|
|
6
|
+
|
|
7
|
+
Install dependencies:
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Running Tests
|
|
14
|
+
|
|
15
|
+
Run all tests:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npm test
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Run tests in watch mode:
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
npm run test:watch
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Run tests with coverage:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
npm run test:coverage
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Test Structure
|
|
34
|
+
|
|
35
|
+
Tests are located alongside the source files with the `.test.ts` extension:
|
|
36
|
+
|
|
37
|
+
- `src/tools/commands.test.ts` - Tests for command tools
|
|
38
|
+
|
|
39
|
+
## Writing Tests
|
|
40
|
+
|
|
41
|
+
Tests use Vitest's API with TypeScript support. Example:
|
|
42
|
+
|
|
43
|
+
```typescript
|
|
44
|
+
import { describe, it, expect, vi } from "vitest"
|
|
45
|
+
|
|
46
|
+
describe("MyFeature", () => {
|
|
47
|
+
it("should work correctly", () => {
|
|
48
|
+
expect(true).toBe(true)
|
|
49
|
+
})
|
|
50
|
+
})
|
|
51
|
+
```
|
package/dist/tools/commands.d.ts
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
|
-
import { type PluginInput } from "@opencode-ai/plugin";
|
|
2
1
|
import { type ToolDefinition } from "@opencode-ai/plugin/tool";
|
|
3
|
-
export declare
|
|
4
|
-
export declare
|
|
5
|
-
export declare
|
|
6
|
-
export declare
|
|
7
|
-
export declare
|
|
2
|
+
export declare const setupCommand: (ctx: import("@opencode-ai/plugin").PluginInput) => ToolDefinition;
|
|
3
|
+
export declare const newTrackCommand: (ctx: import("@opencode-ai/plugin").PluginInput) => ToolDefinition;
|
|
4
|
+
export declare const implementCommand: (ctx: import("@opencode-ai/plugin").PluginInput) => ToolDefinition;
|
|
5
|
+
export declare const statusCommand: (ctx: import("@opencode-ai/plugin").PluginInput) => ToolDefinition;
|
|
6
|
+
export declare const revertCommand: (ctx: import("@opencode-ai/plugin").PluginInput) => ToolDefinition;
|
|
7
|
+
export declare function createSetupTool(ctx: any): ToolDefinition;
|
|
8
|
+
export declare function createNewTrackTool(ctx: any): ToolDefinition;
|
|
9
|
+
export declare function createImplementTool(ctx: any): ToolDefinition;
|
|
10
|
+
export declare function createStatusTool(ctx: any): ToolDefinition;
|
|
11
|
+
export declare function createRevertTool(ctx: any): ToolDefinition;
|
package/dist/tools/commands.js
CHANGED
|
@@ -1,132 +1,81 @@
|
|
|
1
1
|
import { tool } from "@opencode-ai/plugin/tool";
|
|
2
|
+
import { createConductorCommand } from "../utils/commandFactory.js";
|
|
2
3
|
import { join, dirname } from "path";
|
|
3
|
-
import { readFile } from "fs/promises";
|
|
4
4
|
import { fileURLToPath } from "url";
|
|
5
|
+
import { readFile } from "fs/promises";
|
|
5
6
|
const __filename = fileURLToPath(import.meta.url);
|
|
6
7
|
const __dirname = dirname(__filename);
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
8
|
+
export const setupCommand = createConductorCommand({
|
|
9
|
+
name: "setup.toml",
|
|
10
|
+
description: "Scaffolds the project and sets up the Conductor environment",
|
|
11
|
+
args: {},
|
|
12
|
+
});
|
|
13
|
+
export const newTrackCommand = createConductorCommand({
|
|
14
|
+
name: "newTrack.toml",
|
|
15
|
+
description: "Plans a track, generates track-specific spec documents and updates the tracks file",
|
|
16
|
+
args: {
|
|
17
|
+
description: tool.schema.string().optional().describe("Brief description of the track (feature, bug fix, chore, etc.)"),
|
|
18
|
+
},
|
|
19
|
+
additionalContext: async (ctx, args) => {
|
|
20
|
+
return {
|
|
21
|
+
args: args.description || "",
|
|
20
22
|
};
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
23
|
+
},
|
|
24
|
+
});
|
|
25
|
+
export const implementCommand = createConductorCommand({
|
|
26
|
+
name: "implement.toml",
|
|
27
|
+
description: "Executes the tasks defined in the specified track's plan",
|
|
28
|
+
args: {
|
|
29
|
+
track_name: tool.schema.string().optional().describe("Name or description of the track to implement"),
|
|
30
|
+
},
|
|
31
|
+
additionalContext: async (ctx, args) => {
|
|
32
|
+
// 1. Choose strategy based on OMO activity
|
|
33
|
+
const strategyFile = ctx.isOMOActive ? "delegate.md" : "manual.md";
|
|
34
|
+
const strategyPath = join(__dirname, "../prompts/strategies", strategyFile);
|
|
35
|
+
let strategySection = "";
|
|
36
|
+
try {
|
|
37
|
+
strategySection = await readFile(strategyPath, "utf-8");
|
|
38
|
+
}
|
|
39
|
+
catch (e) {
|
|
40
|
+
console.warn(`[Conductor] Failed to load strategy ${strategyFile}:`, e);
|
|
41
|
+
strategySection = "Error: Could not load execution strategy.";
|
|
24
42
|
}
|
|
25
|
-
return { prompt: promptText, description: description };
|
|
26
|
-
}
|
|
27
|
-
catch (error) {
|
|
28
|
-
console.error(`[Conductor] Error loading prompt ${filename}:`, error);
|
|
29
43
|
return {
|
|
30
|
-
|
|
31
|
-
|
|
44
|
+
strategy_section: strategySection,
|
|
45
|
+
track_name: args.track_name || "",
|
|
32
46
|
};
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
},
|
|
54
|
-
});
|
|
55
|
-
// Fetch and return the assistant's response
|
|
56
|
-
const messagesResult = await ctx.client.session.messages({
|
|
57
|
-
path: { id: sessionID },
|
|
58
|
-
});
|
|
59
|
-
const lastMessage = messagesResult.data
|
|
60
|
-
?.filter((m) => m.info.role === "assistant")
|
|
61
|
-
.pop();
|
|
62
|
-
const responseText = lastMessage?.parts
|
|
63
|
-
?.filter((p) => p.type === "text")
|
|
64
|
-
.map((p) => p.text)
|
|
65
|
-
.join("\n") || "No response.";
|
|
66
|
-
return `${responseText}\n\n<task_metadata>\nsession_id: ${sessionID}\n</task_metadata>`;
|
|
67
|
-
}
|
|
47
|
+
},
|
|
48
|
+
});
|
|
49
|
+
export const statusCommand = createConductorCommand({
|
|
50
|
+
name: "status.toml",
|
|
51
|
+
description: "Displays the current progress of the project",
|
|
52
|
+
args: {},
|
|
53
|
+
});
|
|
54
|
+
export const revertCommand = createConductorCommand({
|
|
55
|
+
name: "revert.toml",
|
|
56
|
+
description: "Reverts previous work",
|
|
57
|
+
args: {
|
|
58
|
+
target: tool.schema.string().optional().describe("Target to revert (e.g., 'track <track_id>', 'phase <phase_name>', 'task <task_name>')"),
|
|
59
|
+
},
|
|
60
|
+
additionalContext: async (ctx, args) => {
|
|
61
|
+
return {
|
|
62
|
+
target: args.target || "",
|
|
63
|
+
};
|
|
64
|
+
},
|
|
65
|
+
});
|
|
66
|
+
// Export as functions for backward compatibility
|
|
68
67
|
export function createSetupTool(ctx) {
|
|
69
|
-
return
|
|
70
|
-
description: "Scaffolds the project and sets up the Conductor environment",
|
|
71
|
-
args: {},
|
|
72
|
-
async execute(args, toolContext) {
|
|
73
|
-
const { prompt, description } = await loadPrompt("setup.toml");
|
|
74
|
-
return await executeCommand(ctx, toolContext, prompt, "conductor", description);
|
|
75
|
-
},
|
|
76
|
-
});
|
|
68
|
+
return setupCommand(ctx);
|
|
77
69
|
}
|
|
78
70
|
export function createNewTrackTool(ctx) {
|
|
79
|
-
return
|
|
80
|
-
description: "Plans a track, generates track-specific spec documents and updates the tracks file",
|
|
81
|
-
args: {
|
|
82
|
-
description: tool.schema.string().optional().describe("Brief description of the track (feature, bug fix, chore, etc.)"),
|
|
83
|
-
},
|
|
84
|
-
async execute(args, toolContext) {
|
|
85
|
-
const trackDescription = args.description || "";
|
|
86
|
-
const { prompt, description } = await loadPrompt("newTrack.toml", {
|
|
87
|
-
args: trackDescription,
|
|
88
|
-
});
|
|
89
|
-
return await executeCommand(ctx, toolContext, prompt, "conductor", description);
|
|
90
|
-
},
|
|
91
|
-
});
|
|
71
|
+
return newTrackCommand(ctx);
|
|
92
72
|
}
|
|
93
73
|
export function createImplementTool(ctx) {
|
|
94
|
-
return
|
|
95
|
-
description: "Executes the tasks defined in the specified track's plan",
|
|
96
|
-
args: {
|
|
97
|
-
track_name: tool.schema.string().optional().describe("Name or description of the track to implement"),
|
|
98
|
-
},
|
|
99
|
-
async execute(args, toolContext) {
|
|
100
|
-
const trackName = args.track_name || "";
|
|
101
|
-
const { prompt, description } = await loadPrompt("implement.toml", {
|
|
102
|
-
track_name: trackName,
|
|
103
|
-
});
|
|
104
|
-
return await executeCommand(ctx, toolContext, prompt, "conductor_implementer", description);
|
|
105
|
-
},
|
|
106
|
-
});
|
|
74
|
+
return implementCommand(ctx);
|
|
107
75
|
}
|
|
108
76
|
export function createStatusTool(ctx) {
|
|
109
|
-
return
|
|
110
|
-
description: "Displays the current progress of the project",
|
|
111
|
-
args: {},
|
|
112
|
-
async execute(args, toolContext) {
|
|
113
|
-
const { prompt, description } = await loadPrompt("status.toml");
|
|
114
|
-
return await executeCommand(ctx, toolContext, prompt, "conductor", description);
|
|
115
|
-
},
|
|
116
|
-
});
|
|
77
|
+
return statusCommand(ctx);
|
|
117
78
|
}
|
|
118
79
|
export function createRevertTool(ctx) {
|
|
119
|
-
return
|
|
120
|
-
description: "Reverts previous work",
|
|
121
|
-
args: {
|
|
122
|
-
target: tool.schema.string().optional().describe("Target to revert (e.g., 'track <track_id>', 'phase <phase_name>', 'task <task_name>')"),
|
|
123
|
-
},
|
|
124
|
-
async execute(args, toolContext) {
|
|
125
|
-
const target = args.target || "";
|
|
126
|
-
const { prompt, description } = await loadPrompt("revert.toml", {
|
|
127
|
-
target: target,
|
|
128
|
-
});
|
|
129
|
-
return await executeCommand(ctx, toolContext, prompt, "conductor", description);
|
|
130
|
-
},
|
|
131
|
-
});
|
|
80
|
+
return revertCommand(ctx);
|
|
132
81
|
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { type PluginInput } from "@opencode-ai/plugin";
|
|
2
|
+
import { type ToolDefinition } from "@opencode-ai/plugin/tool";
|
|
3
|
+
interface ConductorCommandConfig {
|
|
4
|
+
name: string;
|
|
5
|
+
description: string;
|
|
6
|
+
args: Record<string, any>;
|
|
7
|
+
additionalContext?: (ctx: PluginInput, args: any) => Promise<Record<string, string>>;
|
|
8
|
+
}
|
|
9
|
+
export declare function createConductorCommand(config: ConductorCommandConfig): (ctx: PluginInput) => ToolDefinition;
|
|
10
|
+
export {};
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { tool } from "@opencode-ai/plugin/tool";
|
|
2
|
+
import { join, dirname } from "path";
|
|
3
|
+
import { readFile } from "fs/promises";
|
|
4
|
+
import { fileURLToPath } from "url";
|
|
5
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
6
|
+
const __dirname = dirname(__filename);
|
|
7
|
+
// Helper to load and process prompt templates
|
|
8
|
+
async function loadPrompt(filename, replacements = {}) {
|
|
9
|
+
const promptPath = join(__dirname, "..", "prompts", filename);
|
|
10
|
+
try {
|
|
11
|
+
const content = await readFile(promptPath, "utf-8");
|
|
12
|
+
const descMatch = content.match(/description\s*=\s*"([^"]+)"/);
|
|
13
|
+
const description = descMatch ? descMatch[1] : "Conductor Command";
|
|
14
|
+
const promptMatch = content.match(/prompt\s*=\s*"""([\s\S]*?)"""/);
|
|
15
|
+
let promptText = promptMatch ? promptMatch[1] : "";
|
|
16
|
+
if (!promptText)
|
|
17
|
+
throw new Error(`Could not parse prompt text from ${filename}`);
|
|
18
|
+
const defaults = {
|
|
19
|
+
templatesDir: join(dirname(dirname(__dirname)), "templates"),
|
|
20
|
+
};
|
|
21
|
+
const finalReplacements = { ...defaults, ...replacements };
|
|
22
|
+
for (const [key, value] of Object.entries(finalReplacements)) {
|
|
23
|
+
promptText = promptText.replaceAll(`{{${key}}}`, value || "");
|
|
24
|
+
}
|
|
25
|
+
return { prompt: promptText, description: description };
|
|
26
|
+
}
|
|
27
|
+
catch (error) {
|
|
28
|
+
console.error(`[Conductor] Error loading prompt ${filename}:`, error);
|
|
29
|
+
return {
|
|
30
|
+
prompt: `SYSTEM ERROR: Failed to load prompt ${filename}`,
|
|
31
|
+
description: "Error loading command",
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
export function createConductorCommand(config) {
|
|
36
|
+
return (ctx) => {
|
|
37
|
+
return tool({
|
|
38
|
+
description: config.description,
|
|
39
|
+
args: config.args,
|
|
40
|
+
async execute(args) {
|
|
41
|
+
// Get additional context if provided (this can override/extend args)
|
|
42
|
+
const additionalContext = config.additionalContext
|
|
43
|
+
? await config.additionalContext(ctx, args)
|
|
44
|
+
: {};
|
|
45
|
+
// Merge additionalContext into replacements
|
|
46
|
+
// additionalContext takes precedence and can provide custom mappings
|
|
47
|
+
const replacements = { ...additionalContext };
|
|
48
|
+
const { prompt } = await loadPrompt(config.name, replacements);
|
|
49
|
+
return prompt;
|
|
50
|
+
},
|
|
51
|
+
});
|
|
52
|
+
};
|
|
53
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opencode-conductor-plugin",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.24.0",
|
|
4
4
|
"description": "Conductor plugin for OpenCode",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"repository": "derekbar90/opencode-conductor",
|
|
@@ -32,6 +32,9 @@
|
|
|
32
32
|
"build": "tsc && npm run copy-prompts && npm run copy-templates",
|
|
33
33
|
"copy-prompts": "mkdir -p dist/prompts && cp src/prompts/*.toml src/prompts/*.md dist/prompts/ && mkdir -p dist/prompts/agent && cp src/prompts/agent/*.md dist/prompts/agent/ && mkdir -p dist/prompts/strategies && cp src/prompts/strategies/*.md dist/prompts/strategies/",
|
|
34
34
|
"copy-templates": "mkdir -p dist/templates && cp -r src/templates/* dist/templates/",
|
|
35
|
+
"test": "vitest",
|
|
36
|
+
"test:watch": "vitest --watch",
|
|
37
|
+
"test:coverage": "vitest --coverage",
|
|
35
38
|
"prepublishOnly": "npm run build"
|
|
36
39
|
},
|
|
37
40
|
"dependencies": {
|
|
@@ -46,8 +49,10 @@
|
|
|
46
49
|
"@semantic-release/npm": "^12.0.1",
|
|
47
50
|
"@semantic-release/release-notes-generator": "^14.0.0",
|
|
48
51
|
"@types/node": "^20.0.0",
|
|
52
|
+
"@vitest/ui": "^2.0.0",
|
|
49
53
|
"semantic-release": "^24.2.1",
|
|
50
|
-
"typescript": "^5.0.0"
|
|
54
|
+
"typescript": "^5.0.0",
|
|
55
|
+
"vitest": "^2.0.0"
|
|
51
56
|
},
|
|
52
57
|
"release": {
|
|
53
58
|
"branches": [
|