opencode-conductor-plugin 1.9.0 → 1.11.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/dist/index.js +123 -77
- package/package.json +2 -2
- package/dist/commands/implement.d.ts +0 -1
- package/dist/commands/implement.js +0 -31
- package/dist/commands/newTrack.d.ts +0 -1
- package/dist/commands/newTrack.js +0 -12
- package/dist/commands/revert.d.ts +0 -1
- package/dist/commands/revert.js +0 -9
- package/dist/commands/setup.d.ts +0 -1
- package/dist/commands/setup.js +0 -10
- package/dist/commands/status.d.ts +0 -1
- package/dist/commands/status.js +0 -6
- package/dist/prompts/commands/conductor:implement.md +0 -4
- package/dist/prompts/commands/conductor:newTrack.md +0 -4
- package/dist/prompts/commands/conductor:revert.md +0 -4
- package/dist/prompts/commands/conductor:setup.md +0 -4
- package/dist/prompts/commands/conductor:status.md +0 -4
- package/dist/utils/commandFactory.d.ts +0 -10
- package/dist/utils/commandFactory.js +0 -47
- package/dist/utils/stateManager.d.ts +0 -10
- package/dist/utils/stateManager.js +0 -30
package/dist/index.js
CHANGED
|
@@ -1,8 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { newTrackCommand } from "./commands/newTrack.js";
|
|
3
|
-
import { implementCommand } from "./commands/implement.js";
|
|
4
|
-
import { statusCommand } from "./commands/status.js";
|
|
5
|
-
import { revertCommand } from "./commands/revert.js";
|
|
1
|
+
import { tool } from "@opencode-ai/plugin/tool";
|
|
6
2
|
import { join, dirname } from "path";
|
|
7
3
|
import { homedir } from "os";
|
|
8
4
|
import { existsSync, readFileSync } from "fs";
|
|
@@ -11,84 +7,134 @@ import { fileURLToPath } from "url";
|
|
|
11
7
|
const __filename = fileURLToPath(import.meta.url);
|
|
12
8
|
const __dirname = dirname(__filename);
|
|
13
9
|
const ConductorPlugin = async (ctx) => {
|
|
14
|
-
// Detect oh-my-opencode for synergy features
|
|
15
|
-
const configPath = join(homedir(), ".config", "opencode", "opencode.json");
|
|
16
|
-
let isOMOActive = false;
|
|
17
10
|
try {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
11
|
+
console.log("[Conductor] Initializing plugin...");
|
|
12
|
+
// 1. Detect oh-my-opencode for synergy features
|
|
13
|
+
const configPath = join(homedir(), ".config", "opencode", "opencode.json");
|
|
14
|
+
let isOMOActive = false;
|
|
15
|
+
try {
|
|
16
|
+
if (existsSync(configPath)) {
|
|
17
|
+
const config = JSON.parse(readFileSync(configPath, "utf-8"));
|
|
18
|
+
isOMOActive = config.plugin?.some((p) => p.includes("oh-my-opencode"));
|
|
19
|
+
}
|
|
21
20
|
}
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
isOMOActive
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
21
|
+
catch (e) {
|
|
22
|
+
const omoPath = join(homedir(), ".config", "opencode", "node_modules", "oh-my-opencode");
|
|
23
|
+
isOMOActive = existsSync(omoPath);
|
|
24
|
+
}
|
|
25
|
+
console.log(`[Conductor] Plugin environment detected. (OMO Synergy: ${isOMOActive ? "Enabled" : "Disabled"})`);
|
|
26
|
+
// 2. Helper to load and process prompt templates (Manual TOML Parsing)
|
|
27
|
+
const loadPrompt = async (filename, replacements = {}) => {
|
|
28
|
+
const promptPath = join(__dirname, "prompts", filename);
|
|
29
|
+
try {
|
|
30
|
+
const content = await readFile(promptPath, "utf-8");
|
|
31
|
+
const descMatch = content.match(/description\s*=\s*"([^"]+)"/);
|
|
32
|
+
const description = descMatch ? descMatch[1] : "Conductor Command";
|
|
33
|
+
const promptMatch = content.match(/prompt\s*=\s*"""([\s\S]*?)"""/);
|
|
34
|
+
let promptText = promptMatch ? promptMatch[1] : "";
|
|
35
|
+
if (!promptText)
|
|
36
|
+
throw new Error(`Could not parse prompt text from ${filename}`);
|
|
37
|
+
const defaults = {
|
|
38
|
+
isOMOActive: isOMOActive ? "true" : "false",
|
|
39
|
+
templatesDir: join(dirname(__dirname), "templates")
|
|
40
|
+
};
|
|
41
|
+
const finalReplacements = { ...defaults, ...replacements };
|
|
42
|
+
for (const [key, value] of Object.entries(finalReplacements)) {
|
|
43
|
+
promptText = promptText.replaceAll(`{{${key}}}`, value || "");
|
|
44
|
+
}
|
|
45
|
+
return { prompt: promptText, description: description };
|
|
46
|
+
}
|
|
47
|
+
catch (error) {
|
|
48
|
+
console.error(`[Conductor] Error loading prompt ${filename}:`, error);
|
|
49
|
+
return { prompt: `SYSTEM ERROR: Failed to load prompt ${filename}`, description: "Error loading command" };
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
// 3. Load Strategies
|
|
53
|
+
let strategySection = "";
|
|
54
|
+
try {
|
|
55
|
+
const strategyFile = isOMOActive ? "delegate.md" : "manual.md";
|
|
56
|
+
const strategyPath = join(__dirname, "prompts", "strategies", strategyFile);
|
|
57
|
+
strategySection = await readFile(strategyPath, "utf-8");
|
|
58
|
+
}
|
|
59
|
+
catch (e) {
|
|
60
|
+
strategySection = "SYSTEM ERROR: Could not load execution strategy.";
|
|
61
|
+
}
|
|
62
|
+
// 4. Load all Command Prompts (Parallel)
|
|
63
|
+
const [setup, newTrack, implement, status, revert] = await Promise.all([
|
|
64
|
+
loadPrompt("setup.toml"),
|
|
65
|
+
loadPrompt("newTrack.toml", { args: "$ARGUMENTS" }),
|
|
66
|
+
loadPrompt("implement.toml", { track_name: "$ARGUMENTS", strategy_section: strategySection }),
|
|
67
|
+
loadPrompt("status.toml"),
|
|
68
|
+
loadPrompt("revert.toml", { target: "$ARGUMENTS" })
|
|
69
|
+
]);
|
|
70
|
+
// 5. Extract Agent Prompt
|
|
71
|
+
const agentMd = await readFile(join(__dirname, "prompts", "agent", "conductor.md"), "utf-8");
|
|
72
|
+
const agentPrompt = agentMd.split("---").pop()?.trim() || "";
|
|
73
|
+
console.log("[Conductor] All components ready. Injecting config...");
|
|
74
|
+
return {
|
|
75
|
+
tool: {
|
|
76
|
+
"conductor_health": tool({
|
|
77
|
+
description: "Health check",
|
|
78
|
+
args: {},
|
|
79
|
+
async execute() { return "Conductor is active."; }
|
|
80
|
+
})
|
|
81
|
+
},
|
|
82
|
+
config: async (config) => {
|
|
83
|
+
console.log("[Conductor] config handler: Merging commands and agent...");
|
|
84
|
+
config.command = {
|
|
85
|
+
...(config.command || {}),
|
|
86
|
+
"conductor:setup": { template: setup.prompt, description: setup.description, agent: "conductor" },
|
|
87
|
+
"conductor:newTrack": { template: newTrack.prompt, description: newTrack.description, agent: "conductor" },
|
|
88
|
+
"conductor:implement": { template: implement.prompt, description: implement.description, agent: "conductor" },
|
|
89
|
+
"conductor:status": { template: status.prompt, description: status.description, agent: "conductor" },
|
|
90
|
+
"conductor:revert": { template: revert.prompt, description: revert.description, agent: "conductor" }
|
|
91
|
+
};
|
|
92
|
+
config.agent = {
|
|
93
|
+
...(config.agent || {}),
|
|
94
|
+
"conductor": {
|
|
95
|
+
description: "Spec-Driven Development Architect.",
|
|
96
|
+
mode: "primary",
|
|
97
|
+
prompt: agentPrompt,
|
|
98
|
+
permission: {
|
|
99
|
+
conductor_setup: "allow",
|
|
100
|
+
conductor_new_track: "allow",
|
|
101
|
+
conductor_implement: "allow",
|
|
102
|
+
conductor_status: "allow",
|
|
103
|
+
conductor_revert: "allow"
|
|
54
104
|
}
|
|
55
|
-
catch (e) { /* ignore */ }
|
|
56
|
-
return null;
|
|
57
|
-
};
|
|
58
|
-
// 1. Read Project Context Files
|
|
59
|
-
// We need to find the active track to get the correct spec/plan.
|
|
60
|
-
// Since we don't know the track ID easily here, we look for the 'plan.md' that might be in the args
|
|
61
|
-
// OR we just rely on the Conductor having already done the setup.
|
|
62
|
-
// WAIT: We can't easily guess the track ID here.
|
|
63
|
-
// BETTER APPROACH: We rely on the generic 'conductor/workflow.md' and 'prompts/implement.toml'.
|
|
64
|
-
// For 'spec.md' and 'plan.md', the Conductor usually puts the path in the message.
|
|
65
|
-
// However, to be robust, we will read the GLOBAL workflow and the IMPLEMENT prompt.
|
|
66
|
-
// We will explicitly inject the IMPLEMENT PROMPT as requested.
|
|
67
|
-
const implementToml = await safeRead(join(promptsDir, "implement.toml"));
|
|
68
|
-
const workflowMd = await safeRead(join(conductorDir, "workflow.md"));
|
|
69
|
-
// Construct the injection block
|
|
70
|
-
let injection = "\n\n--- [SYSTEM INJECTION: CONDUCTOR CONTEXT PACKET] ---\n";
|
|
71
|
-
injection += "You are receiving this task from the Conductor Architect.\n";
|
|
72
|
-
if (implementToml) {
|
|
73
|
-
injection += "\n### 1. ARCHITECTURAL PROTOCOL (Reference Only)\n";
|
|
74
|
-
injection += "Use this protocol to understand the project's rigorous standards. DO NOT restart the project management lifecycle (e.g. track selection).\n";
|
|
75
|
-
injection += "```toml\n" + implementToml + "\n```\n";
|
|
76
105
|
}
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
106
|
+
};
|
|
107
|
+
},
|
|
108
|
+
"tool.execute.before": async (input, output) => {
|
|
109
|
+
if (input.tool === "delegate_to_agent") {
|
|
110
|
+
const agentName = (output.args.agent_name || output.args.agent || "").toLowerCase();
|
|
111
|
+
if (agentName.includes("sisyphus")) {
|
|
112
|
+
const conductorDir = join(ctx.directory, "conductor");
|
|
113
|
+
const safeRead = async (path) => {
|
|
114
|
+
try {
|
|
115
|
+
if (existsSync(path))
|
|
116
|
+
return await readFile(path, "utf-8");
|
|
117
|
+
}
|
|
118
|
+
catch (e) { }
|
|
119
|
+
return null;
|
|
120
|
+
};
|
|
121
|
+
const workflowMd = await safeRead(join(conductorDir, "workflow.md"));
|
|
122
|
+
let injection = "\n\n--- [SYSTEM INJECTION: CONDUCTOR CONTEXT PACKET] ---\n";
|
|
123
|
+
injection += "You are receiving this task from the Conductor Architect.\n";
|
|
124
|
+
if (workflowMd) {
|
|
125
|
+
injection += "\n### DEVELOPMENT WORKFLOW\n" + workflowMd + "\n";
|
|
126
|
+
}
|
|
127
|
+
injection += "\n### DELEGATED AUTHORITY\n- **EXECUTE:** Implement the requested task.\n- **REFINE:** You have authority to update `plan.md`.\n";
|
|
128
|
+
injection += "--- [END INJECTION] ---\n";
|
|
129
|
+
output.args.objective += injection;
|
|
81
130
|
}
|
|
82
|
-
injection += "\n### 3. DELEGATED AUTHORITY\n";
|
|
83
|
-
injection += "- **EXECUTE:** Implement the requested task using the Workflow.\n";
|
|
84
|
-
injection += "- **REFINE:** You have authority to update `plan.md` if it is flawed.\n";
|
|
85
|
-
injection += "- **ESCALATE:** If you modify the Plan or Spec, report 'PLAN_UPDATED' immediately.\n";
|
|
86
|
-
injection += "--- [END INJECTION] ---\n";
|
|
87
|
-
// Append to the objective
|
|
88
|
-
output.args.objective += injection;
|
|
89
131
|
}
|
|
90
132
|
}
|
|
91
|
-
}
|
|
92
|
-
}
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
catch (err) {
|
|
136
|
+
console.error("[Conductor] FATAL: Plugin initialization failed:", err);
|
|
137
|
+
throw err;
|
|
138
|
+
}
|
|
93
139
|
};
|
|
94
140
|
export default ConductorPlugin;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opencode-conductor-plugin",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.11.0",
|
|
4
4
|
"description": "Conductor plugin for OpenCode",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"repository": "derekbar90/opencode-conductor",
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
"scripts": {
|
|
31
31
|
"postinstall": "node scripts/postinstall.cjs",
|
|
32
32
|
"build": "tsc && npm run copy-prompts && npm run copy-templates",
|
|
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/
|
|
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
35
|
"prepublishOnly": "npm run build"
|
|
36
36
|
},
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export declare const implementCommand: (ctx: any) => import("@opencode-ai/plugin/tool").ToolDefinition;
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import { tool } from "@opencode-ai/plugin/tool";
|
|
2
|
-
import { createConductorCommand } from "../utils/commandFactory.js";
|
|
3
|
-
import { join, dirname } from "path";
|
|
4
|
-
import { fileURLToPath } from "url";
|
|
5
|
-
import { readFile } from "fs/promises";
|
|
6
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
7
|
-
const __dirname = dirname(__filename);
|
|
8
|
-
export const implementCommand = createConductorCommand({
|
|
9
|
-
name: "implement.toml",
|
|
10
|
-
description: "Implements tasks from a Conductor track.",
|
|
11
|
-
args: {
|
|
12
|
-
track_name: tool.schema.string().optional().describe("Specific track to implement. If omitted, selects the next incomplete track."),
|
|
13
|
-
},
|
|
14
|
-
additionalContext: async (ctx, args) => {
|
|
15
|
-
// 1. Choose strategy based on OMO activity
|
|
16
|
-
const strategyFile = ctx.isOMOActive ? "delegate.md" : "manual.md";
|
|
17
|
-
const strategyPath = join(__dirname, "../prompts/strategies", strategyFile);
|
|
18
|
-
let strategySection = "";
|
|
19
|
-
try {
|
|
20
|
-
strategySection = await readFile(strategyPath, "utf-8");
|
|
21
|
-
}
|
|
22
|
-
catch (e) {
|
|
23
|
-
console.warn(`[Conductor] Failed to load strategy ${strategyFile}:`, e);
|
|
24
|
-
strategySection = "Error: Could not load execution strategy.";
|
|
25
|
-
}
|
|
26
|
-
return {
|
|
27
|
-
strategy_section: strategySection,
|
|
28
|
-
track_name: args.track_name || ""
|
|
29
|
-
};
|
|
30
|
-
}
|
|
31
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export declare const newTrackCommand: (ctx: any) => import("@opencode-ai/plugin/tool").ToolDefinition;
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import { tool } from "@opencode-ai/plugin/tool";
|
|
2
|
-
import { createConductorCommand } from "../utils/commandFactory.js";
|
|
3
|
-
export const newTrackCommand = createConductorCommand({
|
|
4
|
-
name: "newTrack.toml",
|
|
5
|
-
description: "Creates a new track (feature/bug) in the Conductor system. IMPORTANT: Do NOT create any todos using 'todowrite' or 'task' tools before or during this command, as it manages its own interactive state and will conflict with continuation enforcers.",
|
|
6
|
-
args: {
|
|
7
|
-
description: tool.schema.string().optional().describe("Brief description of the track."),
|
|
8
|
-
},
|
|
9
|
-
additionalContext: async (_, args) => ({
|
|
10
|
-
args: args.description || ""
|
|
11
|
-
})
|
|
12
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export declare const revertCommand: (ctx: any) => import("@opencode-ai/plugin/tool").ToolDefinition;
|
package/dist/commands/revert.js
DELETED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import { createConductorCommand } from "../utils/commandFactory.js";
|
|
2
|
-
import { tool } from "@opencode-ai/plugin/tool";
|
|
3
|
-
export const revertCommand = createConductorCommand({
|
|
4
|
-
name: "revert.toml",
|
|
5
|
-
description: "Reverts a Conductor track, phase, or task.",
|
|
6
|
-
args: {
|
|
7
|
-
target: tool.schema.string().optional().describe("ID or description of what to revert."),
|
|
8
|
-
}
|
|
9
|
-
});
|
package/dist/commands/setup.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export declare const setupCommand: (ctx: any) => import("@opencode-ai/plugin/tool").ToolDefinition;
|
package/dist/commands/setup.js
DELETED
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import { tool } from "@opencode-ai/plugin/tool";
|
|
2
|
-
import { createConductorCommand } from "../utils/commandFactory.js";
|
|
3
|
-
export const setupCommand = createConductorCommand({
|
|
4
|
-
name: "setup.toml",
|
|
5
|
-
description: "Sets up the Conductor environment for the project. Call this to start or resume the setup process. IMPORTANT: Do NOT create any todos using 'todowrite' or 'task' tools before or during this command, as it manages its own interactive state and will conflict with continuation enforcers.",
|
|
6
|
-
args: {
|
|
7
|
-
user_input: tool.schema.string().optional().describe("The user's response to a previous question, if applicable."),
|
|
8
|
-
},
|
|
9
|
-
requiresSetup: false // Setup command is what creates the setup
|
|
10
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export declare const statusCommand: (ctx: any) => import("@opencode-ai/plugin/tool").ToolDefinition;
|
package/dist/commands/status.js
DELETED
|
@@ -1,10 +0,0 @@
|
|
|
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 {};
|
|
@@ -1,47 +0,0 @@
|
|
|
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
|
-
});
|
|
@@ -1,30 +0,0 @@
|
|
|
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
|
-
}
|