opencode-conductor-plugin 1.25.0 → 1.26.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- export declare const implementCommand: (ctx: any) => import("@opencode-ai/plugin/tool").ToolDefinition;
1
+ export declare const implementCommand: (ctx: import("@opencode-ai/plugin").PluginInput) => import("@opencode-ai/plugin/tool").ToolDefinition;
@@ -1 +1 @@
1
- export declare const newTrackCommand: (ctx: any) => import("@opencode-ai/plugin/tool").ToolDefinition;
1
+ export declare const newTrackCommand: (ctx: import("@opencode-ai/plugin").PluginInput) => import("@opencode-ai/plugin/tool").ToolDefinition;
@@ -1 +1 @@
1
- export declare const revertCommand: (ctx: any) => import("@opencode-ai/plugin/tool").ToolDefinition;
1
+ export declare const revertCommand: (ctx: import("@opencode-ai/plugin").PluginInput) => import("@opencode-ai/plugin/tool").ToolDefinition;
@@ -1 +1 @@
1
- export declare const setupCommand: (ctx: any) => import("@opencode-ai/plugin/tool").ToolDefinition;
1
+ export declare const setupCommand: (ctx: import("@opencode-ai/plugin").PluginInput) => import("@opencode-ai/plugin/tool").ToolDefinition;
@@ -1 +1 @@
1
- export declare const statusCommand: (ctx: any) => import("@opencode-ai/plugin/tool").ToolDefinition;
1
+ export declare const statusCommand: (ctx: import("@opencode-ai/plugin").PluginInput) => import("@opencode-ai/plugin/tool").ToolDefinition;
@@ -1,4 +1,4 @@
1
1
  ---
2
2
  description: Show Conductor project status
3
3
  ---
4
- Invoke the conductor_status tool to summarize the project progress.
4
+ Invoke the conductor_status tool to start the status protocol directive.
@@ -1,9 +1,9 @@
1
1
  import { type ToolDefinition } from "@opencode-ai/plugin/tool";
2
- export declare const setupCommand: (ctx: any) => ToolDefinition;
3
- export declare const newTrackCommand: (ctx: any) => ToolDefinition;
4
- export declare const implementCommand: (ctx: any) => ToolDefinition;
5
- export declare const statusCommand: (ctx: any) => ToolDefinition;
6
- export declare const revertCommand: (ctx: any) => ToolDefinition;
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
7
  export declare function createSetupTool(ctx: any): ToolDefinition;
8
8
  export declare function createNewTrackTool(ctx: any): ToolDefinition;
9
9
  export declare function createImplementTool(ctx: any): ToolDefinition;
@@ -7,13 +7,12 @@ const __filename = fileURLToPath(import.meta.url);
7
7
  const __dirname = dirname(__filename);
8
8
  export const setupCommand = createConductorCommand({
9
9
  name: "setup.toml",
10
- description: "Scaffolds the project and sets up the Conductor environment",
11
- requiresSetup: false,
10
+ description: "Directives lookup tool for scaffolding the project and setting up the Conductor environment",
12
11
  args: {},
13
12
  });
14
13
  export const newTrackCommand = createConductorCommand({
15
14
  name: "newTrack.toml",
16
- description: "Plans a track, generates track-specific spec documents and updates the tracks file",
15
+ description: "Directives lookup tool for planning a track, generating track-specific spec documents and updating the tracks file",
17
16
  args: {
18
17
  description: tool.schema.string().optional().describe("Brief description of the track (feature, bug fix, chore, etc.)"),
19
18
  },
@@ -25,7 +24,7 @@ export const newTrackCommand = createConductorCommand({
25
24
  });
26
25
  export const implementCommand = createConductorCommand({
27
26
  name: "implement.toml",
28
- description: "Executes the tasks defined in the specified track's plan",
27
+ description: "Directives lookup tool for executing the tasks defined in the specified track's plan",
29
28
  args: {
30
29
  track_name: tool.schema.string().optional().describe("Name or description of the track to implement"),
31
30
  },
@@ -49,12 +48,12 @@ export const implementCommand = createConductorCommand({
49
48
  });
50
49
  export const statusCommand = createConductorCommand({
51
50
  name: "status.toml",
52
- description: "Displays the current progress of the project",
51
+ description: "Directives lookup tool for displaying the current progress of the project",
53
52
  args: {},
54
53
  });
55
54
  export const revertCommand = createConductorCommand({
56
55
  name: "revert.toml",
57
- description: "Reverts previous work",
56
+ description: "Directives lookup tool for reverting previous work",
58
57
  args: {
59
58
  target: tool.schema.string().optional().describe("Target to revert (e.g., 'track <track_id>', 'phase <phase_name>', 'task <task_name>')"),
60
59
  },
@@ -1,15 +1,10 @@
1
1
  import { describe, it, expect, vi, beforeEach } from "vitest";
2
2
  import { createSetupTool, createNewTrackTool, createImplementTool, createStatusTool, createRevertTool, } from "./commands.js";
3
3
  import { readFile } from "fs/promises";
4
- import { existsSync } from "fs";
5
4
  // Mock fs/promises
6
5
  vi.mock("fs/promises", () => ({
7
6
  readFile: vi.fn(),
8
7
  }));
9
- // Mock fs
10
- vi.mock("fs", () => ({
11
- existsSync: vi.fn(),
12
- }));
13
8
  describe("Command Tools", () => {
14
9
  let mockCtx;
15
10
  let mockToolContext;
@@ -20,132 +15,131 @@ describe("Command Tools", () => {
20
15
  isOMOActive: false,
21
16
  };
22
17
  mockToolContext = {
23
- sessionID: "test-session-id",
24
- messageID: "test-message-id",
18
+ sessionID: "test-session",
19
+ messageID: "test-message",
25
20
  };
26
- // Default mocks
21
+ // Default mocks - must use triple quotes for the regex in commandFactory
27
22
  vi.mocked(readFile).mockResolvedValue(`
28
23
  description = "Test command"
29
24
  prompt = """
30
25
  Test prompt content
31
26
  """
32
27
  `);
33
- vi.mocked(existsSync).mockReturnValue(true); // Assume setup exists by default
34
28
  });
35
29
  describe("createSetupTool", () => {
36
30
  it("should create a tool with correct description", () => {
37
31
  const tool = createSetupTool(mockCtx);
38
- expect(tool.description).toBe("Scaffolds the project and sets up the Conductor environment");
39
- });
40
- it("should return prompt text when executed", async () => {
41
- vi.mocked(readFile).mockResolvedValue(`
42
- description = "Setup"
43
- prompt = "Setup Prompt"
44
- `);
45
- const tool = createSetupTool(mockCtx);
46
- const result = await tool.execute({}, mockToolContext);
47
- expect(result).toBe("Setup Prompt");
32
+ expect(tool.description).toBe("Directives lookup tool for scaffolding the project and setting up the Conductor environment");
48
33
  });
49
- it("should NOT require setup to exist", async () => {
50
- vi.mocked(existsSync).mockReturnValue(false);
34
+ it("should return directives JSON string when executed", async () => {
51
35
  vi.mocked(readFile).mockResolvedValue(`
52
36
  description = "Setup"
53
- prompt = "Setup Prompt"
37
+ prompt = """Setup Prompt"""
54
38
  `);
55
39
  const tool = createSetupTool(mockCtx);
56
40
  const result = await tool.execute({}, mockToolContext);
57
- expect(result).toBe("Setup Prompt");
41
+ expect(JSON.parse(result)).toEqual({ directives: "Setup Prompt" });
58
42
  });
59
43
  });
60
44
  describe("createNewTrackTool", () => {
45
+ it("should create a tool with correct description", () => {
46
+ const tool = createNewTrackTool(mockCtx);
47
+ expect(tool.description).toBe("Directives lookup tool for planning a track, generating track-specific spec documents and updating the tracks file");
48
+ });
61
49
  it("should have optional description argument", () => {
62
50
  const tool = createNewTrackTool(mockCtx);
63
51
  expect(tool.args).toHaveProperty("description");
64
52
  });
65
- it("should replace description in prompt", async () => {
53
+ it("should replace description in directives", async () => {
66
54
  vi.mocked(readFile).mockResolvedValue(`
67
55
  description = "New Track"
68
- prompt = "Track description: {{args}}"
56
+ prompt = """Track description: {{args}}"""
69
57
  `);
70
58
  const tool = createNewTrackTool(mockCtx);
71
59
  const result = await tool.execute({ description: "Login feature" }, mockToolContext);
72
- expect(result).toBe("Track description: Login feature");
73
- });
74
- it("should return error if not set up", async () => {
75
- vi.mocked(existsSync).mockReturnValue(false);
76
- const tool = createNewTrackTool(mockCtx);
77
- const result = await tool.execute({}, mockToolContext);
78
- expect(result).toContain("Conductor is not set up");
60
+ expect(JSON.parse(result)).toEqual({ directives: "Track description: Login feature" });
79
61
  });
80
62
  });
81
63
  describe("createImplementTool", () => {
64
+ it("should create a tool with correct description", () => {
65
+ const tool = createImplementTool(mockCtx);
66
+ expect(tool.description).toBe("Directives lookup tool for executing the tasks defined in the specified track's plan");
67
+ });
82
68
  it("should have optional track_name argument", () => {
83
69
  const tool = createImplementTool(mockCtx);
84
70
  expect(tool.args).toHaveProperty("track_name");
85
71
  });
86
- it("should replace track_name in prompt", async () => {
72
+ it("should replace track_name in directives", async () => {
87
73
  vi.mocked(readFile).mockResolvedValue(`
88
74
  description = "Implement"
89
- prompt = "Track: {{track_name}}"
75
+ prompt = """Track: {{track_name}}"""
90
76
  `);
91
77
  const tool = createImplementTool(mockCtx);
92
78
  const result = await tool.execute({ track_name: "auth-track" }, mockToolContext);
93
- expect(result).toBe("Track: auth-track");
79
+ expect(JSON.parse(result)).toEqual({ directives: "Track: auth-track" });
94
80
  });
95
- it("should include strategy section", async () => {
81
+ it("should include strategy section in directives", async () => {
96
82
  vi.mocked(readFile).mockImplementation(async (path) => {
97
83
  if (typeof path === 'string' && path.endsWith("manual.md")) {
98
84
  return "Manual Strategy";
99
85
  }
100
86
  return `
101
87
  description = "Implement"
102
- prompt = "Strategy: {{strategy_section}}"
88
+ prompt = """Strategy: {{strategy_section}}"""
103
89
  `;
104
90
  });
105
91
  const tool = createImplementTool(mockCtx);
106
92
  const result = await tool.execute({}, mockToolContext);
107
- expect(result).toBe("Strategy: Manual Strategy");
93
+ expect(JSON.parse(result)).toEqual({ directives: "Strategy: Manual Strategy" });
108
94
  });
109
95
  });
110
96
  describe("createStatusTool", () => {
111
- it("should execute and return prompt", async () => {
97
+ it("should create a tool with correct description", () => {
98
+ const tool = createStatusTool(mockCtx);
99
+ expect(tool.description).toBe("Directives lookup tool for displaying the current progress of the project");
100
+ });
101
+ it("should execute and return directives", async () => {
112
102
  vi.mocked(readFile).mockResolvedValue(`
113
103
  description = "Status"
114
- prompt = "Status Prompt"
104
+ prompt = """Status Prompt"""
115
105
  `);
116
106
  const tool = createStatusTool(mockCtx);
117
107
  const result = await tool.execute({}, mockToolContext);
118
- expect(result).toBe("Status Prompt");
108
+ expect(JSON.parse(result)).toEqual({ directives: "Status Prompt" });
119
109
  });
120
110
  });
121
111
  describe("createRevertTool", () => {
122
- it("should replace target in prompt", async () => {
112
+ it("should create a tool with correct description", () => {
113
+ const tool = createRevertTool(mockCtx);
114
+ expect(tool.description).toBe("Directives lookup tool for reverting previous work");
115
+ });
116
+ it("should replace target in directives", async () => {
123
117
  vi.mocked(readFile).mockResolvedValue(`
124
118
  description = "Revert"
125
- prompt = "Target: {{target}}"
119
+ prompt = """Target: {{target}}"""
126
120
  `);
127
121
  const tool = createRevertTool(mockCtx);
128
122
  const result = await tool.execute({ target: "track 1" }, mockToolContext);
129
- expect(result).toBe("Target: track 1");
123
+ expect(JSON.parse(result)).toEqual({ directives: "Target: track 1" });
130
124
  });
131
125
  });
132
126
  describe("Error Handling", () => {
133
- it("should throw error if readFile fails", async () => {
127
+ it("should return error in directives if readFile fails", async () => {
134
128
  vi.mocked(readFile).mockRejectedValue(new Error("File not found"));
135
129
  const tool = createSetupTool(mockCtx);
136
- await expect(tool.execute({}, mockToolContext)).rejects.toThrow("Failed to load prompt");
130
+ const result = await tool.execute({}, mockToolContext);
131
+ expect(JSON.parse(result).directives).toContain("SYSTEM ERROR: Failed to load prompt");
137
132
  });
138
133
  });
139
134
  describe("Prompt Replacement", () => {
140
- it("should replace standard variables", async () => {
135
+ it("should replace standard variables in directives", async () => {
141
136
  vi.mocked(readFile).mockResolvedValue(`
142
137
  description = "Test"
143
- prompt = "Templates: {{templatesDir}}, OMO: {{isOMOActive}}"
138
+ prompt = """Templates: {{templatesDir}}"""
144
139
  `);
145
140
  const tool = createNewTrackTool(mockCtx);
146
141
  const result = await tool.execute({}, mockToolContext);
147
- expect(result).toContain("Templates:");
148
- expect(result).toContain("OMO: false");
142
+ expect(JSON.parse(result).directives).toContain("Templates:");
149
143
  });
150
144
  });
151
145
  });
@@ -1,10 +1,11 @@
1
+ import { type PluginInput } from "@opencode-ai/plugin";
1
2
  import { type ToolDefinition } from "@opencode-ai/plugin/tool";
2
- interface CommandOptions {
3
+ interface ConductorCommandConfig {
3
4
  name: string;
4
5
  description: string;
5
- args?: any;
6
+ args: Record<string, any>;
6
7
  requiresSetup?: boolean;
7
- additionalContext?: (ctx: any, args: any) => Promise<Record<string, string>>;
8
+ additionalContext?: (ctx: PluginInput, args: any) => Promise<Record<string, string>>;
8
9
  }
9
- export declare const createConductorCommand: (options: CommandOptions) => (ctx: any) => ToolDefinition;
10
+ export declare function createConductorCommand(config: ConductorCommandConfig): (ctx: PluginInput) => ToolDefinition;
10
11
  export {};
@@ -1,47 +1,55 @@
1
1
  import { tool } from "@opencode-ai/plugin/tool";
2
- import { existsSync } from "fs";
3
- import { readFile } from "fs/promises";
4
2
  import { join, dirname } from "path";
3
+ import { readFile } from "fs/promises";
5
4
  import { fileURLToPath } from "url";
6
- import { parse } from "smol-toml";
7
5
  const __filename = fileURLToPath(import.meta.url);
8
6
  const __dirname = dirname(__filename);
9
- export const createConductorCommand = (options) => (ctx) => tool({
10
- description: options.description,
11
- args: options.args || {},
12
- async execute(args) {
13
- // 1. Setup Check
14
- if (options.requiresSetup !== false) {
15
- const conductorDir = join(ctx.directory, "conductor");
16
- if (!existsSync(join(conductorDir, "product.md"))) {
17
- return "Conductor is not set up. Please run `conductor_setup`.";
18
- }
19
- }
20
- // 2. Load Prompt from TOML
21
- const promptPath = join(__dirname, "../prompts", options.name);
22
- let promptText = "";
23
- try {
24
- const content = await readFile(promptPath, "utf-8");
25
- const parsed = parse(content);
26
- promptText = parsed.prompt;
27
- }
28
- catch (error) {
29
- throw new Error(`Failed to load prompt from ${promptPath}: ${error}`);
30
- }
31
- // 3. Prepare Replacements
32
- const replacements = {
33
- isOMOActive: ctx.isOMOActive ? "true" : "false",
34
- templatesDir: join(dirname(dirname(__dirname)), "dist/templates") // Fixing template path for setup
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"),
35
20
  };
36
- // 4. Inject Additional Context (e.g. from args)
37
- if (options.additionalContext) {
38
- const extra = await options.additionalContext(ctx, args);
39
- Object.assign(replacements, extra);
40
- }
41
- // 5. Apply Replacements
42
- for (const [key, value] of Object.entries(replacements)) {
21
+ const finalReplacements = { ...defaults, ...replacements };
22
+ for (const [key, value] of Object.entries(finalReplacements)) {
43
23
  promptText = promptText.replaceAll(`{{${key}}}`, value || "");
44
24
  }
45
- return promptText;
46
- },
47
- });
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, context) {
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 JSON.stringify({
50
+ directives: prompt
51
+ });
52
+ },
53
+ });
54
+ };
55
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-conductor-plugin",
3
- "version": "1.25.0",
3
+ "version": "1.26.1",
4
4
  "description": "Conductor plugin for OpenCode",
5
5
  "type": "module",
6
6
  "repository": "derekbar90/opencode-conductor",