opencode-conductor-plugin 1.23.0 → 1.25.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.md +9 -9
- package/dist/commands/implement.d.ts +1 -0
- package/dist/commands/implement.js +31 -0
- package/dist/commands/newTrack.d.ts +1 -0
- package/dist/commands/newTrack.js +12 -0
- package/dist/commands/revert.d.ts +1 -0
- package/dist/commands/revert.js +9 -0
- package/dist/commands/setup.d.ts +1 -0
- package/dist/commands/setup.js +10 -0
- package/dist/commands/status.d.ts +1 -0
- package/dist/commands/status.js +6 -0
- package/dist/index.js +83 -212
- package/dist/prompts/agent/conductor.md +22 -12
- package/dist/prompts/agent.md +1 -1
- package/dist/prompts/commands/conductor:implement.md +4 -0
- package/dist/prompts/commands/conductor:newTrack.md +4 -0
- package/dist/prompts/commands/conductor:revert.md +4 -0
- package/dist/prompts/commands/conductor:setup.md +4 -0
- package/dist/prompts/commands/conductor:status.md +4 -0
- package/dist/prompts/implement.toml +11 -14
- package/dist/prompts/newTrack.toml +5 -3
- package/dist/prompts/revert.toml +4 -4
- package/dist/prompts/setup.toml +9 -9
- package/dist/prompts/status.toml +4 -4
- package/dist/prompts/strategies/delegate.md +2 -4
- package/dist/tools/commands.d.ts +10 -6
- package/dist/tools/commands.js +63 -80
- package/dist/tools/commands.test.d.ts +1 -0
- package/dist/tools/commands.test.js +151 -0
- package/dist/utils/commandFactory.d.ts +10 -0
- package/dist/utils/commandFactory.js +47 -0
- package/dist/utils/stateManager.d.ts +10 -0
- package/dist/utils/stateManager.js +30 -0
- package/package.json +4 -7
|
@@ -3,6 +3,8 @@ prompt = """
|
|
|
3
3
|
## 1.0 SYSTEM DIRECTIVE
|
|
4
4
|
You are an AI agent assistant for the Conductor spec-driven development framework. Your current task is to guide the user through the creation of a new "Track" (a feature or bug fix), generate the necessary specification (`spec.md`) and plan (`plan.md`) files, and organize them within a dedicated track directory.
|
|
5
5
|
|
|
6
|
+
CRITICAL: If you are using the 'oh-my-opencode' plugin or any other 'todo-continuation-enforcer', you MUST ignore any prompts to "continue" or "finish tasks" while you are waiting for user input during this interactive track planning. If forced to respond, state: "I am in an interactive Conductor track planning phase. Awaiting user response."
|
|
7
|
+
|
|
6
8
|
CRITICAL: You must validate the success of every tool call. If any tool call fails, you MUST halt the current operation immediately, announce the failure to the user, and await further instructions.
|
|
7
9
|
|
|
8
10
|
## 1.1 SETUP CHECK
|
|
@@ -15,7 +17,7 @@ CRITICAL: You must validate the success of every tool call. If any tool call fai
|
|
|
15
17
|
|
|
16
18
|
2. **Handle Missing Files:**
|
|
17
19
|
- If ANY of these files are missing, you MUST halt the operation immediately.
|
|
18
|
-
- Announce: "Conductor is not set up. Please run `/
|
|
20
|
+
- Announce: "Conductor is not set up. Please run `/conductor:setup` to set up the environment."
|
|
19
21
|
- Do NOT proceed to New Track Initialization.
|
|
20
22
|
|
|
21
23
|
---
|
|
@@ -107,7 +109,7 @@ CRITICAL: You must validate the success of every tool call. If any tool call fai
|
|
|
107
109
|
|
|
108
110
|
### 2.4 Create Track Artifacts and Update Main Plan
|
|
109
111
|
|
|
110
|
-
1. **Check for existing track name:** Before generating a new Track ID, list
|
|
112
|
+
1. **Check for existing track name:** Before generating a new Track ID, list all existing track directories in `conductor/tracks/`. Extract the short names from these track IDs (e.g., ``shortname_YYYYMMDD`` -> `shortname`). If the proposed short name for the new track (derived from the initial description) matches an existing short name, halt the `newTrack` creation. Explain that a track with that name already exists and suggest choosing a different name or resuming the existing track.
|
|
111
113
|
2. **Generate Track ID:** Create a unique Track ID (e.g., ``shortname_YYYYMMDD``).
|
|
112
114
|
3. **Create Directory:** Create a new directory: `conductor/tracks/<track_id>/`
|
|
113
115
|
4. **Create `metadata.json`:** Create a metadata file at `conductor/tracks/<track_id>/metadata.json` with content like:
|
|
@@ -137,6 +139,6 @@ CRITICAL: You must validate the success of every tool call. If any tool call fai
|
|
|
137
139
|
```
|
|
138
140
|
(Replace placeholders with actual values)
|
|
139
141
|
7. **Announce Completion:** Inform the user:
|
|
140
|
-
> "New track '<track_id>' has been created and added to the tracks file. You can now start implementation by running `/
|
|
142
|
+
> "New track '<track_id>' has been created and added to the tracks file. You can now start implementation by running `/conductor:implement`."
|
|
141
143
|
|
|
142
144
|
"""
|
package/dist/prompts/revert.toml
CHANGED
|
@@ -10,8 +10,8 @@ Your workflow MUST anticipate and handle common non-linear Git histories, such a
|
|
|
10
10
|
**CRITICAL**: The user's explicit confirmation is required at multiple checkpoints. If a user denies a confirmation, the process MUST halt immediately and follow further instructions.
|
|
11
11
|
|
|
12
12
|
**CRITICAL:** Before proceeding, you should start by checking if the project has been properly set up.
|
|
13
|
-
1. **Verify Tracks File:** Check if the file `conductor/tracks.md` exists. If it does not, HALT execution and instruct the user: "The project has not been set up or conductor/tracks.md has been corrupted. Please run `/
|
|
14
|
-
2. **Verify Track Exists:** Check if the file `conductor/tracks.md` is not empty. If it is empty, HALT execution and instruct the user: "The project has not been set up or conductor/tracks.md has been corrupted. Please run `/
|
|
13
|
+
1. **Verify Tracks File:** Check if the file `conductor/tracks.md` exists. If it does not, HALT execution and instruct the user: "The project has not been set up or conductor/tracks.md has been corrupted. Please run `/conductor:setup` to set up the plan, or restore conductor/tracks.md."
|
|
14
|
+
2. **Verify Track Exists:** Check if the file `conductor/tracks.md` is not empty. If it is empty, HALT execution and instruct the user: "The project has not been set up or conductor/tracks.md has been corrupted. Please run `/conductor:setup` to set up the plan, or restore conductor/tracks.md."
|
|
15
15
|
|
|
16
16
|
**CRITICAL**: You must validate the success of every tool call. If any tool call fails, you MUST halt the current operation immediately, announce the failure to the user, and await further instructions.
|
|
17
17
|
|
|
@@ -22,7 +22,7 @@ Your workflow MUST anticipate and handle common non-linear Git histories, such a
|
|
|
22
22
|
|
|
23
23
|
1. **Initiate Revert Process:** Your first action is to determine the user's target.
|
|
24
24
|
|
|
25
|
-
2. **Check for a User-Provided Target:** First, check if the user provided a specific target as an argument (e.g., `/
|
|
25
|
+
2. **Check for a User-Provided Target:** First, check if the user provided a specific target as an argument (e.g., `/conductor:revert track <track_id>`).
|
|
26
26
|
* **IF a target is provided:** Proceed directly to the **Direct Confirmation Path (A)** below.
|
|
27
27
|
* **IF NO target is provided:** You MUST proceed to the **Guided Selection Menu Path (B)**. This is the default behavior.
|
|
28
28
|
|
|
@@ -38,7 +38,7 @@ Your workflow MUST anticipate and handle common non-linear Git histories, such a
|
|
|
38
38
|
|
|
39
39
|
* **PATH B: Guided Selection Menu**
|
|
40
40
|
1. **Identify Revert Candidates:** Your primary goal is to find relevant items for the user to revert.
|
|
41
|
-
* **Scan All Plans:** You MUST
|
|
41
|
+
* **Scan All Plans:** You MUST read the main `conductor/tracks.md` and every `conductor/tracks/*/plan.md` file.
|
|
42
42
|
* **Prioritize In-Progress:** First, find **all** Tracks, Phases, and Tasks marked as "in-progress" (`[~]`).
|
|
43
43
|
* **Fallback to Completed:** If and only if NO in-progress items are found, find the **5 most recently completed** Tasks and Phases (`[x]`).
|
|
44
44
|
2. **Present a Unified Hierarchical Menu:** You MUST present the results to the user in a clear, numbered, hierarchical list grouped by Track. The introductory text MUST change based on the context.
|
package/dist/prompts/setup.toml
CHANGED
|
@@ -28,7 +28,7 @@ CRITICAL: When determining model complexity, ALWAYS select the "flash" model, re
|
|
|
28
28
|
- If `STEP` is "2.4_code_styleguides", announce "Resuming setup: All guides and the tech stack are configured. Next, we will define the project workflow." and proceed to **Section 2.5**.
|
|
29
29
|
- If `STEP` is "2.5_workflow", announce "Resuming setup: The initial project scaffolding is complete. Next, we will generate the first track." and proceed to **Phase 2 (3.0)**.
|
|
30
30
|
- If `STEP` is "3.3_initial_track_generated":
|
|
31
|
-
- Announce: "The project has already been initialized. You can create a new track with `/
|
|
31
|
+
- Announce: "The project has already been initialized. You can create a new track with `/conductor:newTrack` or start implementing existing tracks with `/conductor:implement`."
|
|
32
32
|
- Halt the `setup` process.
|
|
33
33
|
- If `STEP` is unrecognized, announce an error and halt.
|
|
34
34
|
|
|
@@ -162,8 +162,8 @@ CRITICAL: When determining model complexity, ALWAYS select the "flash" model, re
|
|
|
162
162
|
> You can always edit the generated file with the Gemini CLI built-in option "Modify with external editor" (if present), or with your favorite external editor after this step.
|
|
163
163
|
> Please respond with A or B."
|
|
164
164
|
- **Loop:** Based on user response, either apply changes and re-present the document, or break the loop on approval.
|
|
165
|
-
5. **Write File:** Once approved,
|
|
166
|
-
6. **Commit State:** Upon successful creation of the file, you MUST immediately
|
|
165
|
+
5. **Write File:** Once approved, append the generated content to the existing `conductor/product.md` file, preserving the `# Initial Concept` section.
|
|
166
|
+
6. **Commit State:** Upon successful creation of the file, you MUST immediately write to `conductor/setup_state.json` with the exact content:
|
|
167
167
|
`{"last_successful_step": "2.1_product_guide"}`
|
|
168
168
|
7. **Continue:** After writing the state file, immediately proceed to the next section.
|
|
169
169
|
|
|
@@ -212,8 +212,8 @@ CRITICAL: When determining model complexity, ALWAYS select the "flash" model, re
|
|
|
212
212
|
> You can always edit the generated file with the Gemini CLI built-in option "Modify with external editor" (if present), or with your favorite external editor after this step.
|
|
213
213
|
> Please respond with A or B."
|
|
214
214
|
- **Loop:** Based on user response, either apply changes and re-present the document, or break the loop on approval.
|
|
215
|
-
5. **Write File:** Once approved,
|
|
216
|
-
6. **Commit State:** Upon successful creation of the file, you MUST immediately
|
|
215
|
+
5. **Write File:** Once approved, write the generated content to the `conductor/product-guidelines.md` file.
|
|
216
|
+
6. **Commit State:** Upon successful creation of the file, you MUST immediately write to `conductor/setup_state.json` with the exact content:
|
|
217
217
|
`{"last_successful_step": "2.2_product_guidelines"}`
|
|
218
218
|
7. **Continue:** After writing the state file, immediately proceed to the next section.
|
|
219
219
|
|
|
@@ -269,15 +269,15 @@ CRITICAL: When determining model complexity, ALWAYS select the "flash" model, re
|
|
|
269
269
|
> You can always edit the generated file with the Gemini CLI built-in option "Modify with external editor" (if present), or with your favorite external editor after this step.
|
|
270
270
|
> Please respond with A or B."
|
|
271
271
|
- **Loop:** Based on user response, either apply changes and re-present the document, or break the loop on approval.
|
|
272
|
-
6. **Write File:** Once approved,
|
|
273
|
-
7. **Commit State:** Upon successful creation of the file, you MUST immediately
|
|
272
|
+
6. **Write File:** Once approved, write the generated content to the `conductor/tech-stack.md` file.
|
|
273
|
+
7. **Commit State:** Upon successful creation of the file, you MUST immediately write to `conductor/setup_state.json` with the exact content:
|
|
274
274
|
`{"last_successful_step": "2.3_tech_stack"}`
|
|
275
275
|
8. **Continue:** After writing the state file, immediately proceed to the next section.
|
|
276
276
|
|
|
277
277
|
### 2.4 Select Guides (Interactive)
|
|
278
278
|
1. **Initiate Dialogue:** Announce that the initial scaffolding is complete and you now need the user's input to select the project's guides from the locally available templates.
|
|
279
279
|
2. **Select Code Style Guides:**
|
|
280
|
-
- List the available style guides by
|
|
280
|
+
- List the available style guides by running `ls {{templatesDir}}/code_styleguides/`.
|
|
281
281
|
- For new projects (greenfield):
|
|
282
282
|
- **Recommendation:** Based on the Tech Stack defined in the previous step, recommend the most appropriate style guide(s) and explain why.
|
|
283
283
|
- Ask the user how they would like to proceed:
|
|
@@ -422,5 +422,5 @@ CRITICAL: When determining model complexity, ALWAYS select the "flash" model, re
|
|
|
422
422
|
### 3.4 Final Announcement
|
|
423
423
|
1. **Announce Completion:** After the track has been created, announce that the project setup and initial track generation are complete.
|
|
424
424
|
2. **Save Conductor Files:** Add and commit all files with the commit message `conductor(setup): Add conductor setup files`.
|
|
425
|
-
3. **Next Steps:** Inform the user that they can now begin work by running `/
|
|
425
|
+
3. **Next Steps:** Inform the user that they can now begin work by running `/conductor:implement`.
|
|
426
426
|
"""
|
package/dist/prompts/status.toml
CHANGED
|
@@ -4,8 +4,8 @@ prompt = """
|
|
|
4
4
|
You are an AI agent. Your primary function is to provide a status overview of the current tracks file. This involves reading the `conductor/tracks.md` file, parsing its content, and summarizing the progress of tasks.
|
|
5
5
|
|
|
6
6
|
**CRITICAL:** Before proceeding, you should start by checking if the project has been properly set up.
|
|
7
|
-
1. **Verify Tracks File:** Check if the file `conductor/tracks.md` exists. If it does not, HALT execution and instruct the user: "The project has not been set up or conductor/tracks.md has been corrupted. Please run `/
|
|
8
|
-
2. **Verify Track Exists:** Check if the file `conductor/tracks.md` is not empty. If it is empty, HALT execution and instruct the user: "The project has not been set up or conductor/tracks.md has been corrupted. Please run `/
|
|
7
|
+
1. **Verify Tracks File:** Check if the file `conductor/tracks.md` exists. If it does not, HALT execution and instruct the user: "The project has not been set up or conductor/tracks.md has been corrupted. Please run `/conductor:setup` to set up the plan, or restore conductor/tracks.md."
|
|
8
|
+
2. **Verify Track Exists:** Check if the file `conductor/tracks.md` is not empty. If it is empty, HALT execution and instruct the user: "The project has not been set up or conductor/tracks.md has been corrupted. Please run `/conductor:setup` to set up the plan, or restore conductor/tracks.md."
|
|
9
9
|
|
|
10
10
|
CRITICAL: You must validate the success of every tool call. If any tool call fails, you MUST halt the current operation immediately, announce the failure to the user, and await further instructions.
|
|
11
11
|
|
|
@@ -22,7 +22,7 @@ CRITICAL: You must validate the success of every tool call. If any tool call fai
|
|
|
22
22
|
|
|
23
23
|
2. **Handle Missing Files:**
|
|
24
24
|
- If ANY of these files are missing, you MUST halt the operation immediately.
|
|
25
|
-
- Announce: "Conductor is not set up. Please run `/
|
|
25
|
+
- Announce: "Conductor is not set up. Please run `/conductor:setup` to set up the environment."
|
|
26
26
|
- Do NOT proceed to Status Overview Protocol.
|
|
27
27
|
|
|
28
28
|
---
|
|
@@ -32,7 +32,7 @@ CRITICAL: You must validate the success of every tool call. If any tool call fai
|
|
|
32
32
|
|
|
33
33
|
### 2.1 Read Project Plan
|
|
34
34
|
1. **Locate and Read:** Read the content of the `conductor/tracks.md` file.
|
|
35
|
-
2. **Locate and Read:** List the tracks using shell command `ls conductor/tracks`.
|
|
35
|
+
2. **Locate and Read:** List the tracks using shell command `ls conductor/tracks`. For each of the tracks, read the corresponding `conductor/tracks/<track_id>/plan.md` file.
|
|
36
36
|
|
|
37
37
|
### 2.2 Parse and Summarize Plan
|
|
38
38
|
1. **Parse Content:**
|
|
@@ -2,10 +2,8 @@
|
|
|
2
2
|
You are acting as the **Architect**. Your responsibility is to oversee the track while delegating execution to **Sisyphus**.
|
|
3
3
|
|
|
4
4
|
**DIRECTIVE:**
|
|
5
|
-
1. **
|
|
6
|
-
|
|
7
|
-
* **Delegation:** Delegate to `@sisyphus` for complex, multi-file, or heavy-lifting implementation tasks.
|
|
8
|
-
2. **Delegation Protocol:** If you choose to delegate, call `@sisyphus`.
|
|
5
|
+
1. **Do NOT implement code yourself.**
|
|
6
|
+
2. **Delegate:** For each task in `plan.md`, call `@sisyphus`.
|
|
9
7
|
* *Note:* The system will automatically inject the full project context for you.
|
|
10
8
|
* *Instruction:* Tell Sisyphus: "Execute this task. You have authority to update `plan.md` if needed. Report 'PLAN_UPDATED' if you do."
|
|
11
9
|
3. **Verify:**
|
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: 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;
|
|
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,99 +1,82 @@
|
|
|
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
|
+
requiresSetup: false,
|
|
12
|
+
args: {},
|
|
13
|
+
});
|
|
14
|
+
export const newTrackCommand = createConductorCommand({
|
|
15
|
+
name: "newTrack.toml",
|
|
16
|
+
description: "Plans a track, generates track-specific spec documents and updates the tracks file",
|
|
17
|
+
args: {
|
|
18
|
+
description: tool.schema.string().optional().describe("Brief description of the track (feature, bug fix, chore, etc.)"),
|
|
19
|
+
},
|
|
20
|
+
additionalContext: async (ctx, args) => {
|
|
21
|
+
return {
|
|
22
|
+
args: args.description || "",
|
|
20
23
|
};
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
+
},
|
|
25
|
+
});
|
|
26
|
+
export const implementCommand = createConductorCommand({
|
|
27
|
+
name: "implement.toml",
|
|
28
|
+
description: "Executes the tasks defined in the specified track's plan",
|
|
29
|
+
args: {
|
|
30
|
+
track_name: tool.schema.string().optional().describe("Name or description of the track to implement"),
|
|
31
|
+
},
|
|
32
|
+
additionalContext: async (ctx, args) => {
|
|
33
|
+
// 1. Choose strategy based on OMO activity
|
|
34
|
+
const strategyFile = ctx.isOMOActive ? "delegate.md" : "manual.md";
|
|
35
|
+
const strategyPath = join(__dirname, "../prompts/strategies", strategyFile);
|
|
36
|
+
let strategySection = "";
|
|
37
|
+
try {
|
|
38
|
+
strategySection = await readFile(strategyPath, "utf-8");
|
|
39
|
+
}
|
|
40
|
+
catch (e) {
|
|
41
|
+
console.warn(`[Conductor] Failed to load strategy ${strategyFile}:`, e);
|
|
42
|
+
strategySection = "Error: Could not load execution strategy.";
|
|
24
43
|
}
|
|
25
|
-
return { prompt: promptText, description: description };
|
|
26
|
-
}
|
|
27
|
-
catch (error) {
|
|
28
|
-
console.error(`[Conductor] Error loading prompt ${filename}:`, error);
|
|
29
44
|
return {
|
|
30
|
-
|
|
31
|
-
|
|
45
|
+
strategy_section: strategySection,
|
|
46
|
+
track_name: args.track_name || "",
|
|
32
47
|
};
|
|
33
|
-
}
|
|
34
|
-
}
|
|
48
|
+
},
|
|
49
|
+
});
|
|
50
|
+
export const statusCommand = createConductorCommand({
|
|
51
|
+
name: "status.toml",
|
|
52
|
+
description: "Displays the current progress of the project",
|
|
53
|
+
args: {},
|
|
54
|
+
});
|
|
55
|
+
export const revertCommand = createConductorCommand({
|
|
56
|
+
name: "revert.toml",
|
|
57
|
+
description: "Reverts previous work",
|
|
58
|
+
args: {
|
|
59
|
+
target: tool.schema.string().optional().describe("Target to revert (e.g., 'track <track_id>', 'phase <phase_name>', 'task <task_name>')"),
|
|
60
|
+
},
|
|
61
|
+
additionalContext: async (ctx, args) => {
|
|
62
|
+
return {
|
|
63
|
+
target: args.target || "",
|
|
64
|
+
};
|
|
65
|
+
},
|
|
66
|
+
});
|
|
67
|
+
// Export as functions for backward compatibility
|
|
35
68
|
export function createSetupTool(ctx) {
|
|
36
|
-
return
|
|
37
|
-
description: "Scaffolds the project and sets up the Conductor environment",
|
|
38
|
-
args: {},
|
|
39
|
-
async execute(args) {
|
|
40
|
-
const { prompt } = await loadPrompt("setup.toml");
|
|
41
|
-
return prompt;
|
|
42
|
-
},
|
|
43
|
-
});
|
|
69
|
+
return setupCommand(ctx);
|
|
44
70
|
}
|
|
45
71
|
export function createNewTrackTool(ctx) {
|
|
46
|
-
return
|
|
47
|
-
description: "Plans a track, generates track-specific spec documents and updates the tracks file",
|
|
48
|
-
args: {
|
|
49
|
-
description: tool.schema.string().optional().describe("Brief description of the track (feature, bug fix, chore, etc.)"),
|
|
50
|
-
},
|
|
51
|
-
async execute(args) {
|
|
52
|
-
const trackDescription = args.description || "";
|
|
53
|
-
const { prompt } = await loadPrompt("newTrack.toml", {
|
|
54
|
-
args: trackDescription,
|
|
55
|
-
});
|
|
56
|
-
return prompt;
|
|
57
|
-
},
|
|
58
|
-
});
|
|
72
|
+
return newTrackCommand(ctx);
|
|
59
73
|
}
|
|
60
74
|
export function createImplementTool(ctx) {
|
|
61
|
-
return
|
|
62
|
-
description: "Executes the tasks defined in the specified track's plan",
|
|
63
|
-
args: {
|
|
64
|
-
track_name: tool.schema.string().optional().describe("Name or description of the track to implement"),
|
|
65
|
-
},
|
|
66
|
-
async execute(args) {
|
|
67
|
-
const trackName = args.track_name || "";
|
|
68
|
-
const { prompt } = await loadPrompt("implement.toml", {
|
|
69
|
-
track_name: trackName,
|
|
70
|
-
});
|
|
71
|
-
return prompt;
|
|
72
|
-
},
|
|
73
|
-
});
|
|
75
|
+
return implementCommand(ctx);
|
|
74
76
|
}
|
|
75
77
|
export function createStatusTool(ctx) {
|
|
76
|
-
return
|
|
77
|
-
description: "Displays the current progress of the project",
|
|
78
|
-
args: {},
|
|
79
|
-
async execute(args) {
|
|
80
|
-
const { prompt } = await loadPrompt("status.toml");
|
|
81
|
-
return prompt;
|
|
82
|
-
},
|
|
83
|
-
});
|
|
78
|
+
return statusCommand(ctx);
|
|
84
79
|
}
|
|
85
80
|
export function createRevertTool(ctx) {
|
|
86
|
-
return
|
|
87
|
-
description: "Reverts previous work",
|
|
88
|
-
args: {
|
|
89
|
-
target: tool.schema.string().optional().describe("Target to revert (e.g., 'track <track_id>', 'phase <phase_name>', 'task <task_name>')"),
|
|
90
|
-
},
|
|
91
|
-
async execute(args) {
|
|
92
|
-
const target = args.target || "";
|
|
93
|
-
const { prompt } = await loadPrompt("revert.toml", {
|
|
94
|
-
target: target,
|
|
95
|
-
});
|
|
96
|
-
return prompt;
|
|
97
|
-
},
|
|
98
|
-
});
|
|
81
|
+
return revertCommand(ctx);
|
|
99
82
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from "vitest";
|
|
2
|
+
import { createSetupTool, createNewTrackTool, createImplementTool, createStatusTool, createRevertTool, } from "./commands.js";
|
|
3
|
+
import { readFile } from "fs/promises";
|
|
4
|
+
import { existsSync } from "fs";
|
|
5
|
+
// Mock fs/promises
|
|
6
|
+
vi.mock("fs/promises", () => ({
|
|
7
|
+
readFile: vi.fn(),
|
|
8
|
+
}));
|
|
9
|
+
// Mock fs
|
|
10
|
+
vi.mock("fs", () => ({
|
|
11
|
+
existsSync: vi.fn(),
|
|
12
|
+
}));
|
|
13
|
+
describe("Command Tools", () => {
|
|
14
|
+
let mockCtx;
|
|
15
|
+
let mockToolContext;
|
|
16
|
+
beforeEach(() => {
|
|
17
|
+
vi.clearAllMocks();
|
|
18
|
+
mockCtx = {
|
|
19
|
+
directory: "/test/project",
|
|
20
|
+
isOMOActive: false,
|
|
21
|
+
};
|
|
22
|
+
mockToolContext = {
|
|
23
|
+
sessionID: "test-session-id",
|
|
24
|
+
messageID: "test-message-id",
|
|
25
|
+
};
|
|
26
|
+
// Default mocks
|
|
27
|
+
vi.mocked(readFile).mockResolvedValue(`
|
|
28
|
+
description = "Test command"
|
|
29
|
+
prompt = """
|
|
30
|
+
Test prompt content
|
|
31
|
+
"""
|
|
32
|
+
`);
|
|
33
|
+
vi.mocked(existsSync).mockReturnValue(true); // Assume setup exists by default
|
|
34
|
+
});
|
|
35
|
+
describe("createSetupTool", () => {
|
|
36
|
+
it("should create a tool with correct description", () => {
|
|
37
|
+
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");
|
|
48
|
+
});
|
|
49
|
+
it("should NOT require setup to exist", async () => {
|
|
50
|
+
vi.mocked(existsSync).mockReturnValue(false);
|
|
51
|
+
vi.mocked(readFile).mockResolvedValue(`
|
|
52
|
+
description = "Setup"
|
|
53
|
+
prompt = "Setup Prompt"
|
|
54
|
+
`);
|
|
55
|
+
const tool = createSetupTool(mockCtx);
|
|
56
|
+
const result = await tool.execute({}, mockToolContext);
|
|
57
|
+
expect(result).toBe("Setup Prompt");
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
describe("createNewTrackTool", () => {
|
|
61
|
+
it("should have optional description argument", () => {
|
|
62
|
+
const tool = createNewTrackTool(mockCtx);
|
|
63
|
+
expect(tool.args).toHaveProperty("description");
|
|
64
|
+
});
|
|
65
|
+
it("should replace description in prompt", async () => {
|
|
66
|
+
vi.mocked(readFile).mockResolvedValue(`
|
|
67
|
+
description = "New Track"
|
|
68
|
+
prompt = "Track description: {{args}}"
|
|
69
|
+
`);
|
|
70
|
+
const tool = createNewTrackTool(mockCtx);
|
|
71
|
+
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");
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
describe("createImplementTool", () => {
|
|
82
|
+
it("should have optional track_name argument", () => {
|
|
83
|
+
const tool = createImplementTool(mockCtx);
|
|
84
|
+
expect(tool.args).toHaveProperty("track_name");
|
|
85
|
+
});
|
|
86
|
+
it("should replace track_name in prompt", async () => {
|
|
87
|
+
vi.mocked(readFile).mockResolvedValue(`
|
|
88
|
+
description = "Implement"
|
|
89
|
+
prompt = "Track: {{track_name}}"
|
|
90
|
+
`);
|
|
91
|
+
const tool = createImplementTool(mockCtx);
|
|
92
|
+
const result = await tool.execute({ track_name: "auth-track" }, mockToolContext);
|
|
93
|
+
expect(result).toBe("Track: auth-track");
|
|
94
|
+
});
|
|
95
|
+
it("should include strategy section", async () => {
|
|
96
|
+
vi.mocked(readFile).mockImplementation(async (path) => {
|
|
97
|
+
if (typeof path === 'string' && path.endsWith("manual.md")) {
|
|
98
|
+
return "Manual Strategy";
|
|
99
|
+
}
|
|
100
|
+
return `
|
|
101
|
+
description = "Implement"
|
|
102
|
+
prompt = "Strategy: {{strategy_section}}"
|
|
103
|
+
`;
|
|
104
|
+
});
|
|
105
|
+
const tool = createImplementTool(mockCtx);
|
|
106
|
+
const result = await tool.execute({}, mockToolContext);
|
|
107
|
+
expect(result).toBe("Strategy: Manual Strategy");
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
describe("createStatusTool", () => {
|
|
111
|
+
it("should execute and return prompt", async () => {
|
|
112
|
+
vi.mocked(readFile).mockResolvedValue(`
|
|
113
|
+
description = "Status"
|
|
114
|
+
prompt = "Status Prompt"
|
|
115
|
+
`);
|
|
116
|
+
const tool = createStatusTool(mockCtx);
|
|
117
|
+
const result = await tool.execute({}, mockToolContext);
|
|
118
|
+
expect(result).toBe("Status Prompt");
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
describe("createRevertTool", () => {
|
|
122
|
+
it("should replace target in prompt", async () => {
|
|
123
|
+
vi.mocked(readFile).mockResolvedValue(`
|
|
124
|
+
description = "Revert"
|
|
125
|
+
prompt = "Target: {{target}}"
|
|
126
|
+
`);
|
|
127
|
+
const tool = createRevertTool(mockCtx);
|
|
128
|
+
const result = await tool.execute({ target: "track 1" }, mockToolContext);
|
|
129
|
+
expect(result).toBe("Target: track 1");
|
|
130
|
+
});
|
|
131
|
+
});
|
|
132
|
+
describe("Error Handling", () => {
|
|
133
|
+
it("should throw error if readFile fails", async () => {
|
|
134
|
+
vi.mocked(readFile).mockRejectedValue(new Error("File not found"));
|
|
135
|
+
const tool = createSetupTool(mockCtx);
|
|
136
|
+
await expect(tool.execute({}, mockToolContext)).rejects.toThrow("Failed to load prompt");
|
|
137
|
+
});
|
|
138
|
+
});
|
|
139
|
+
describe("Prompt Replacement", () => {
|
|
140
|
+
it("should replace standard variables", async () => {
|
|
141
|
+
vi.mocked(readFile).mockResolvedValue(`
|
|
142
|
+
description = "Test"
|
|
143
|
+
prompt = "Templates: {{templatesDir}}, OMO: {{isOMOActive}}"
|
|
144
|
+
`);
|
|
145
|
+
const tool = createNewTrackTool(mockCtx);
|
|
146
|
+
const result = await tool.execute({}, mockToolContext);
|
|
147
|
+
expect(result).toContain("Templates:");
|
|
148
|
+
expect(result).toContain("OMO: false");
|
|
149
|
+
});
|
|
150
|
+
});
|
|
151
|
+
});
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { type ToolDefinition } from "@opencode-ai/plugin/tool";
|
|
2
|
+
interface CommandOptions {
|
|
3
|
+
name: string;
|
|
4
|
+
description: string;
|
|
5
|
+
args?: any;
|
|
6
|
+
requiresSetup?: boolean;
|
|
7
|
+
additionalContext?: (ctx: any, args: any) => Promise<Record<string, string>>;
|
|
8
|
+
}
|
|
9
|
+
export declare const createConductorCommand: (options: CommandOptions) => (ctx: any) => ToolDefinition;
|
|
10
|
+
export {};
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { tool } from "@opencode-ai/plugin/tool";
|
|
2
|
+
import { existsSync } from "fs";
|
|
3
|
+
import { readFile } from "fs/promises";
|
|
4
|
+
import { join, dirname } from "path";
|
|
5
|
+
import { fileURLToPath } from "url";
|
|
6
|
+
import { parse } from "smol-toml";
|
|
7
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
8
|
+
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
|
|
35
|
+
};
|
|
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)) {
|
|
43
|
+
promptText = promptText.replaceAll(`{{${key}}}`, value || "");
|
|
44
|
+
}
|
|
45
|
+
return promptText;
|
|
46
|
+
},
|
|
47
|
+
});
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { join } from "path";
|
|
2
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync } from "fs";
|
|
3
|
+
export class StateManager {
|
|
4
|
+
statePath;
|
|
5
|
+
constructor(workDir) {
|
|
6
|
+
this.statePath = join(workDir, "conductor", "setup_state.json");
|
|
7
|
+
}
|
|
8
|
+
ensureConductorDir() {
|
|
9
|
+
const dir = join(this.statePath, "..");
|
|
10
|
+
if (!existsSync(dir)) {
|
|
11
|
+
mkdirSync(dir, { recursive: true });
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
readState() {
|
|
15
|
+
if (!existsSync(this.statePath)) {
|
|
16
|
+
return { last_successful_step: "" };
|
|
17
|
+
}
|
|
18
|
+
try {
|
|
19
|
+
return JSON.parse(readFileSync(this.statePath, "utf-8"));
|
|
20
|
+
}
|
|
21
|
+
catch (e) {
|
|
22
|
+
return { last_successful_step: "" };
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
writeState(step) {
|
|
26
|
+
this.ensureConductorDir();
|
|
27
|
+
const state = { last_successful_step: step };
|
|
28
|
+
writeFileSync(this.statePath, JSON.stringify(state, null, 2));
|
|
29
|
+
}
|
|
30
|
+
}
|