lockstep-mcp 0.1.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/LICENSE +21 -0
- package/README.md +669 -0
- package/dist/cli.js +367 -0
- package/dist/config.js +48 -0
- package/dist/dashboard.js +1982 -0
- package/dist/install.js +252 -0
- package/dist/macos.js +55 -0
- package/dist/prompts.js +173 -0
- package/dist/server.js +1942 -0
- package/dist/storage.js +1235 -0
- package/dist/tmux.js +87 -0
- package/dist/utils.js +35 -0
- package/dist/worktree.js +356 -0
- package/package.json +66 -0
package/dist/install.js
ADDED
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import os from "node:os";
|
|
5
|
+
function expandHome(input) {
|
|
6
|
+
if (!input.startsWith("~"))
|
|
7
|
+
return input;
|
|
8
|
+
const home = os.homedir();
|
|
9
|
+
return path.join(home, input.slice(1));
|
|
10
|
+
}
|
|
11
|
+
function getClaudeConfigPath() {
|
|
12
|
+
// Claude Code uses project-level .mcp.json or we create in home
|
|
13
|
+
const localConfig = path.resolve(process.cwd(), ".mcp.json");
|
|
14
|
+
if (fs.existsSync(localConfig))
|
|
15
|
+
return localConfig;
|
|
16
|
+
return localConfig; // Default to creating in current directory
|
|
17
|
+
}
|
|
18
|
+
function getCodexConfigPath() {
|
|
19
|
+
return path.join(os.homedir(), ".codex", "config.toml");
|
|
20
|
+
}
|
|
21
|
+
function resolveConfigPath(configPath) {
|
|
22
|
+
if (configPath)
|
|
23
|
+
return path.resolve(expandHome(configPath));
|
|
24
|
+
const localConfig = path.resolve(process.cwd(), ".mcp.json");
|
|
25
|
+
if (fs.existsSync(localConfig))
|
|
26
|
+
return localConfig;
|
|
27
|
+
return undefined;
|
|
28
|
+
}
|
|
29
|
+
function loadJsonConfig(configPath) {
|
|
30
|
+
if (!fs.existsSync(configPath)) {
|
|
31
|
+
return { mcpServers: {} };
|
|
32
|
+
}
|
|
33
|
+
const raw = fs.readFileSync(configPath, "utf8");
|
|
34
|
+
const parsed = JSON.parse(raw);
|
|
35
|
+
if (!parsed.mcpServers || typeof parsed.mcpServers !== "object") {
|
|
36
|
+
parsed.mcpServers = {};
|
|
37
|
+
}
|
|
38
|
+
return parsed;
|
|
39
|
+
}
|
|
40
|
+
function resolveServerEntry() {
|
|
41
|
+
const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
42
|
+
const distCli = path.join(repoRoot, "dist", "cli.js");
|
|
43
|
+
const srcCli = path.join(repoRoot, "src", "cli.ts");
|
|
44
|
+
if (fs.existsSync(distCli)) {
|
|
45
|
+
return { command: "node", args: [distCli, "server"], distCli };
|
|
46
|
+
}
|
|
47
|
+
return { command: "node", args: ["--import", "tsx", srcCli, "server"], distCli };
|
|
48
|
+
}
|
|
49
|
+
function getNodePath() {
|
|
50
|
+
// Try to find node in common locations
|
|
51
|
+
const candidates = [
|
|
52
|
+
"/opt/homebrew/bin/node",
|
|
53
|
+
"/usr/local/bin/node",
|
|
54
|
+
"/usr/bin/node",
|
|
55
|
+
process.execPath,
|
|
56
|
+
];
|
|
57
|
+
for (const candidate of candidates) {
|
|
58
|
+
if (fs.existsSync(candidate))
|
|
59
|
+
return candidate;
|
|
60
|
+
}
|
|
61
|
+
return "node";
|
|
62
|
+
}
|
|
63
|
+
function buildServerArgs(options) {
|
|
64
|
+
const entry = resolveServerEntry();
|
|
65
|
+
const args = [...entry.args];
|
|
66
|
+
if (options.mode) {
|
|
67
|
+
args.push("--mode", options.mode);
|
|
68
|
+
}
|
|
69
|
+
if (options.roots) {
|
|
70
|
+
args.push("--roots", options.roots);
|
|
71
|
+
}
|
|
72
|
+
if (options.dataDir) {
|
|
73
|
+
args.push("--data-dir", options.dataDir);
|
|
74
|
+
}
|
|
75
|
+
if (options.logDir) {
|
|
76
|
+
args.push("--log-dir", options.logDir);
|
|
77
|
+
}
|
|
78
|
+
if (options.storage) {
|
|
79
|
+
args.push("--storage", options.storage);
|
|
80
|
+
}
|
|
81
|
+
if (options.dbPath) {
|
|
82
|
+
args.push("--db-path", options.dbPath);
|
|
83
|
+
}
|
|
84
|
+
if (options.commandMode) {
|
|
85
|
+
args.push("--command-mode", options.commandMode);
|
|
86
|
+
}
|
|
87
|
+
if (options.commandAllow) {
|
|
88
|
+
args.push("--command-allow", options.commandAllow);
|
|
89
|
+
}
|
|
90
|
+
return args;
|
|
91
|
+
}
|
|
92
|
+
// Install to Claude's .mcp.json
|
|
93
|
+
function installToClaude(options) {
|
|
94
|
+
const configPath = options.configPath ? expandHome(options.configPath) : getClaudeConfigPath();
|
|
95
|
+
const config = loadJsonConfig(configPath);
|
|
96
|
+
const entryName = options.name ?? "lockstep";
|
|
97
|
+
const entry = resolveServerEntry();
|
|
98
|
+
const args = buildServerArgs(options);
|
|
99
|
+
config.mcpServers[entryName] = {
|
|
100
|
+
command: getNodePath(),
|
|
101
|
+
args: args.slice(1), // Remove 'node' from args since command is node
|
|
102
|
+
};
|
|
103
|
+
// Ensure directory exists
|
|
104
|
+
const dir = path.dirname(configPath);
|
|
105
|
+
if (!fs.existsSync(dir)) {
|
|
106
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
107
|
+
}
|
|
108
|
+
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
109
|
+
return { configPath, name: entryName };
|
|
110
|
+
}
|
|
111
|
+
// Install to Codex's config.toml
|
|
112
|
+
function installToCodex(options) {
|
|
113
|
+
const configPath = getCodexConfigPath();
|
|
114
|
+
const entryName = options.name ?? "lockstep";
|
|
115
|
+
const entry = resolveServerEntry();
|
|
116
|
+
const args = buildServerArgs(options);
|
|
117
|
+
// Ensure directory exists
|
|
118
|
+
const dir = path.dirname(configPath);
|
|
119
|
+
if (!fs.existsSync(dir)) {
|
|
120
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
121
|
+
}
|
|
122
|
+
// Read existing config or create new
|
|
123
|
+
let existingContent = "";
|
|
124
|
+
if (fs.existsSync(configPath)) {
|
|
125
|
+
existingContent = fs.readFileSync(configPath, "utf8");
|
|
126
|
+
}
|
|
127
|
+
// Check if entry already exists
|
|
128
|
+
const sectionRegex = new RegExp(`\\[mcp_servers\\.${entryName}\\]`);
|
|
129
|
+
if (sectionRegex.test(existingContent)) {
|
|
130
|
+
// Update existing entry - remove old section first
|
|
131
|
+
const sectionStart = existingContent.search(sectionRegex);
|
|
132
|
+
const nextSectionMatch = existingContent.slice(sectionStart + 1).search(/\n\[/);
|
|
133
|
+
const sectionEnd = nextSectionMatch === -1
|
|
134
|
+
? existingContent.length
|
|
135
|
+
: sectionStart + 1 + nextSectionMatch;
|
|
136
|
+
existingContent = existingContent.slice(0, sectionStart) + existingContent.slice(sectionEnd);
|
|
137
|
+
}
|
|
138
|
+
// Build TOML entry
|
|
139
|
+
const nodePath = getNodePath();
|
|
140
|
+
const argsWithoutNode = args.slice(1); // Remove 'node' since command handles it
|
|
141
|
+
const argsToml = argsWithoutNode.map(a => `"${a}"`).join(", ");
|
|
142
|
+
const tomlEntry = `
|
|
143
|
+
[mcp_servers.${entryName}]
|
|
144
|
+
command = "${nodePath}"
|
|
145
|
+
args = [${argsToml}]
|
|
146
|
+
env = { }
|
|
147
|
+
`;
|
|
148
|
+
// Append to config
|
|
149
|
+
const newContent = existingContent.trim() + "\n" + tomlEntry;
|
|
150
|
+
fs.writeFileSync(configPath, newContent);
|
|
151
|
+
return { configPath, name: entryName };
|
|
152
|
+
}
|
|
153
|
+
// Uninstall from Claude's .mcp.json
|
|
154
|
+
function uninstallFromClaude(name, configPath) {
|
|
155
|
+
const fullPath = configPath ? expandHome(configPath) : getClaudeConfigPath();
|
|
156
|
+
if (!fs.existsSync(fullPath))
|
|
157
|
+
return false;
|
|
158
|
+
const config = loadJsonConfig(fullPath);
|
|
159
|
+
if (!config.mcpServers[name])
|
|
160
|
+
return false;
|
|
161
|
+
delete config.mcpServers[name];
|
|
162
|
+
fs.writeFileSync(fullPath, JSON.stringify(config, null, 2));
|
|
163
|
+
return true;
|
|
164
|
+
}
|
|
165
|
+
// Uninstall from Codex's config.toml
|
|
166
|
+
function uninstallFromCodex(name) {
|
|
167
|
+
const configPath = getCodexConfigPath();
|
|
168
|
+
if (!fs.existsSync(configPath))
|
|
169
|
+
return false;
|
|
170
|
+
let content = fs.readFileSync(configPath, "utf8");
|
|
171
|
+
const sectionRegex = new RegExp(`\\[mcp_servers\\.${name}\\]`);
|
|
172
|
+
if (!sectionRegex.test(content))
|
|
173
|
+
return false;
|
|
174
|
+
// Remove the section
|
|
175
|
+
const sectionStart = content.search(sectionRegex);
|
|
176
|
+
const nextSectionMatch = content.slice(sectionStart + 1).search(/\n\[/);
|
|
177
|
+
const sectionEnd = nextSectionMatch === -1
|
|
178
|
+
? content.length
|
|
179
|
+
: sectionStart + 1 + nextSectionMatch;
|
|
180
|
+
content = content.slice(0, sectionStart) + content.slice(sectionEnd);
|
|
181
|
+
fs.writeFileSync(configPath, content.trim() + "\n");
|
|
182
|
+
return true;
|
|
183
|
+
}
|
|
184
|
+
export function installMcpEntry(options) {
|
|
185
|
+
const target = options.target ?? "config";
|
|
186
|
+
const results = [];
|
|
187
|
+
if (target === "config") {
|
|
188
|
+
// Legacy behavior: install to specified config path
|
|
189
|
+
const configPath = resolveConfigPath(options.configPath);
|
|
190
|
+
if (!configPath) {
|
|
191
|
+
throw new Error("Missing --config. Provide the MCP config path (or run from a repo with .mcp.json).");
|
|
192
|
+
}
|
|
193
|
+
const config = loadJsonConfig(configPath);
|
|
194
|
+
const entryName = options.name ?? "lockstep-mcp";
|
|
195
|
+
const entry = resolveServerEntry();
|
|
196
|
+
const args = buildServerArgs(options);
|
|
197
|
+
config.mcpServers[entryName] = {
|
|
198
|
+
command: entry.command,
|
|
199
|
+
args,
|
|
200
|
+
};
|
|
201
|
+
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
202
|
+
return { configPath, name: entryName, serverPath: args.join(" ") };
|
|
203
|
+
}
|
|
204
|
+
if (target === "claude" || target === "all") {
|
|
205
|
+
const result = installToClaude(options);
|
|
206
|
+
results.push({ target: "claude", ...result });
|
|
207
|
+
}
|
|
208
|
+
if (target === "codex" || target === "all") {
|
|
209
|
+
const result = installToCodex(options);
|
|
210
|
+
results.push({ target: "codex", ...result });
|
|
211
|
+
}
|
|
212
|
+
return { results };
|
|
213
|
+
}
|
|
214
|
+
export function uninstallMcpEntry(options) {
|
|
215
|
+
const target = options.target ?? "all";
|
|
216
|
+
const name = options.name ?? "lockstep";
|
|
217
|
+
const results = [];
|
|
218
|
+
if (target === "claude" || target === "all") {
|
|
219
|
+
const removed = uninstallFromClaude(name, options.configPath);
|
|
220
|
+
results.push({ target: "claude", removed });
|
|
221
|
+
}
|
|
222
|
+
if (target === "codex" || target === "all") {
|
|
223
|
+
const removed = uninstallFromCodex(name);
|
|
224
|
+
results.push({ target: "codex", removed });
|
|
225
|
+
}
|
|
226
|
+
return { results };
|
|
227
|
+
}
|
|
228
|
+
export function getInstallStatus() {
|
|
229
|
+
const claudePath = getClaudeConfigPath();
|
|
230
|
+
const codexPath = getCodexConfigPath();
|
|
231
|
+
let claudeInstalled = false;
|
|
232
|
+
let codexInstalled = false;
|
|
233
|
+
if (fs.existsSync(claudePath)) {
|
|
234
|
+
try {
|
|
235
|
+
const config = loadJsonConfig(claudePath);
|
|
236
|
+
claudeInstalled = !!config.mcpServers["lockstep"] || !!config.mcpServers["lockstep-mcp"];
|
|
237
|
+
}
|
|
238
|
+
catch {
|
|
239
|
+
// ignore
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
if (fs.existsSync(codexPath)) {
|
|
243
|
+
try {
|
|
244
|
+
const content = fs.readFileSync(codexPath, "utf8");
|
|
245
|
+
codexInstalled = /\[mcp_servers\.lockstep/.test(content);
|
|
246
|
+
}
|
|
247
|
+
catch {
|
|
248
|
+
// ignore
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
return { claude: claudeInstalled, codex: codexInstalled, claudePath, codexPath };
|
|
252
|
+
}
|
package/dist/macos.js
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { spawnSync } from "node:child_process";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { sleep } from "./utils.js";
|
|
5
|
+
export async function launchMacos(options = {}) {
|
|
6
|
+
if (process.platform !== "darwin") {
|
|
7
|
+
throw new Error("macos launcher is only supported on macOS");
|
|
8
|
+
}
|
|
9
|
+
const repo = path.resolve(options.repo ?? process.cwd());
|
|
10
|
+
const claudeCmd = options.claudeCmd ?? "claude";
|
|
11
|
+
const codexCmd = options.codexCmd ?? "codex";
|
|
12
|
+
const dashboardHost = options.dashboardHost ?? "127.0.0.1";
|
|
13
|
+
const dashboardPort = options.dashboardPort ?? 8787;
|
|
14
|
+
const macosPath = path.resolve(fileURLToPath(import.meta.url));
|
|
15
|
+
const baseDir = path.dirname(macosPath);
|
|
16
|
+
const nodePath = process.execPath;
|
|
17
|
+
const cliPath = macosPath.endsWith(".ts")
|
|
18
|
+
? path.join(baseDir, "cli.ts")
|
|
19
|
+
: path.join(baseDir, "cli.js");
|
|
20
|
+
const dashboardArgs = ["dashboard", "--host", dashboardHost, "--port", String(dashboardPort)];
|
|
21
|
+
const dashboardCmd = cliPath.endsWith(".ts")
|
|
22
|
+
? `${nodePath} --import tsx ${cliPath} ${dashboardArgs.join(" ")}`
|
|
23
|
+
: `${nodePath} ${cliPath} ${dashboardArgs.join(" ")}`;
|
|
24
|
+
const commands = [
|
|
25
|
+
`cd "${repo}" && ${claudeCmd}`,
|
|
26
|
+
`cd "${repo}" && ${codexCmd}`,
|
|
27
|
+
dashboardCmd,
|
|
28
|
+
];
|
|
29
|
+
for (const command of commands) {
|
|
30
|
+
const openResult = spawnSync("open", ["-na", "Terminal"]);
|
|
31
|
+
if (openResult.status !== 0) {
|
|
32
|
+
throw new Error("Failed to open Terminal window");
|
|
33
|
+
}
|
|
34
|
+
const escaped = command.replace(/\"/g, "\\\"");
|
|
35
|
+
const script = `tell application \"Terminal\" to do script \"${escaped}\"`;
|
|
36
|
+
let success = false;
|
|
37
|
+
for (let attempt = 0; attempt < 5; attempt += 1) {
|
|
38
|
+
await sleep(500);
|
|
39
|
+
if (process.env.LOCKSTEP_DEBUG) {
|
|
40
|
+
process.stderr.write(`osascript: ${script}\n`);
|
|
41
|
+
}
|
|
42
|
+
const result = spawnSync("osascript", ["-e", script], { encoding: "utf8" });
|
|
43
|
+
if (result.status === 0) {
|
|
44
|
+
success = true;
|
|
45
|
+
break;
|
|
46
|
+
}
|
|
47
|
+
if (result.stderr) {
|
|
48
|
+
process.stderr.write(result.stderr);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
if (!success) {
|
|
52
|
+
throw new Error("Failed to run command in Terminal window");
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
package/dist/prompts.js
ADDED
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
export function getPlannerPrompt() {
|
|
2
|
+
return `You are the PLANNER for this lockstep coordination session.
|
|
3
|
+
|
|
4
|
+
⛔ ABSOLUTE PROHIBITIONS - VIOLATING THESE IS A CRITICAL FAILURE:
|
|
5
|
+
- NEVER use file write/edit/update tools - you are NOT allowed to modify files
|
|
6
|
+
- NEVER run build commands (pnpm build, npm build, tsc, etc.)
|
|
7
|
+
- NEVER run test commands (pnpm test, npm test, vitest, jest, etc.)
|
|
8
|
+
- NEVER fix code errors yourself - CREATE A TASK for an implementer
|
|
9
|
+
- NEVER create/modify source files (.ts, .tsx, .js, .jsx, .swift, etc.)
|
|
10
|
+
- If you catch yourself about to edit a file - STOP and create a task instead
|
|
11
|
+
|
|
12
|
+
YOUR ONLY ALLOWED ACTIONS:
|
|
13
|
+
1. Call lockstep_mcp tools (coordination_init, task_create, task_list, etc.)
|
|
14
|
+
2. Read files to understand the codebase (READ ONLY, never write)
|
|
15
|
+
3. Communicate with the user
|
|
16
|
+
4. Launch implementers with launch_implementer
|
|
17
|
+
5. Review and approve/reject tasks submitted by implementers
|
|
18
|
+
|
|
19
|
+
If you see a bug, build error, or code issue:
|
|
20
|
+
→ DO NOT FIX IT YOURSELF
|
|
21
|
+
→ CREATE A TASK with task_create and assign appropriate complexity
|
|
22
|
+
→ LAUNCH AN IMPLEMENTER if none are active
|
|
23
|
+
|
|
24
|
+
TASK COMPLEXITY - Set appropriately when creating tasks:
|
|
25
|
+
- SIMPLE: 1-2 files, obvious fix, no architectural decisions
|
|
26
|
+
- MEDIUM: 3-5 files, some ambiguity, needs verification
|
|
27
|
+
- COMPLEX: 6+ files, architectural decisions, cross-system impact
|
|
28
|
+
- CRITICAL: Database schema, security, affects other products (REQUIRES your approval)
|
|
29
|
+
|
|
30
|
+
TASK ISOLATION - Choose based on task nature:
|
|
31
|
+
- SHARED (default): Implementer works in main directory with file locks. Good for simple/medium tasks.
|
|
32
|
+
- WORKTREE: Implementer gets isolated git worktree with own branch. Use for:
|
|
33
|
+
- Complex refactoring that touches many files
|
|
34
|
+
- Parallel independent features
|
|
35
|
+
- Changes that might conflict with other implementers
|
|
36
|
+
- When you want clean git history per feature
|
|
37
|
+
|
|
38
|
+
When using worktree isolation:
|
|
39
|
+
- Use worktree_status to check implementer's progress (commits, changes)
|
|
40
|
+
- Use worktree_merge to merge their changes after approval
|
|
41
|
+
- If merge has conflicts, use task_request_changes to have implementer resolve
|
|
42
|
+
|
|
43
|
+
INITIALIZATION:
|
|
44
|
+
1. Call coordination_init({ role: "planner" }) to check project state
|
|
45
|
+
2. Follow the instructions in the response EXACTLY
|
|
46
|
+
|
|
47
|
+
PHASE 1 (gather_info):
|
|
48
|
+
If no project context exists:
|
|
49
|
+
1. ASK: "What project or task are we working on today?"
|
|
50
|
+
2. EXPLORE: Read README.md, package.json, CLAUDE.md to understand the codebase
|
|
51
|
+
3. SUMMARIZE: Tell user what you found about the project
|
|
52
|
+
4. ASK CLARIFYING QUESTIONS for anything missing:
|
|
53
|
+
- What is the desired end state/goal?
|
|
54
|
+
- Any specific requirements or constraints?
|
|
55
|
+
- What are the acceptance criteria?
|
|
56
|
+
- What tests should pass?
|
|
57
|
+
- What type of implementer - Claude or Codex?
|
|
58
|
+
5. SAVE: Call project_context_set with combined info
|
|
59
|
+
|
|
60
|
+
PHASE 2 (create_plan):
|
|
61
|
+
- Create implementation plan based on user's answers
|
|
62
|
+
- EXPLAIN the plan to the user (steps, reasoning, trade-offs)
|
|
63
|
+
- ASK for feedback: "Any additional context or changes needed?"
|
|
64
|
+
- ASK for permission: "Do I have your permission to proceed?"
|
|
65
|
+
- ONLY AFTER user approves: call project_context_set with implementationPlan
|
|
66
|
+
- Set status to "ready"
|
|
67
|
+
|
|
68
|
+
PHASE 3 (create_tasks):
|
|
69
|
+
- Create tasks using task_create with COMPLEXITY field (required!)
|
|
70
|
+
- Use launch_implementer to spawn workers (type based on user's preference)
|
|
71
|
+
- 1-2 implementers for simple projects, more for complex
|
|
72
|
+
|
|
73
|
+
PHASE 4 (monitor and review):
|
|
74
|
+
⚠️ FIRST: Check implementer_list - if NO active implementers, call launch_implementer IMMEDIATELY
|
|
75
|
+
- Check task_list FREQUENTLY - look for tasks in "review" status
|
|
76
|
+
- Check note_list for [REVIEW] notifications
|
|
77
|
+
- Check discussion_inbox({ agent: "planner" }) for discussions
|
|
78
|
+
- If tasks exist but no implementers are working, LAUNCH AN IMPLEMENTER
|
|
79
|
+
|
|
80
|
+
REVIEWING TASKS (critical responsibility):
|
|
81
|
+
When a task is in "review" status:
|
|
82
|
+
1. Read the task's reviewNotes to see what the implementer did
|
|
83
|
+
2. Consider: Does this fit the big picture? Will it work with other changes?
|
|
84
|
+
3. If good: task_approve({ id: "task-id", feedback: "optional notes" })
|
|
85
|
+
4. If needs work: task_request_changes({ id: "task-id", feedback: "what to fix" })
|
|
86
|
+
|
|
87
|
+
COORDINATION RESPONSIBILITIES:
|
|
88
|
+
- When implementers start COMPLEX/CRITICAL tasks, they should discuss with you first
|
|
89
|
+
- Respond promptly to discussion_inbox items
|
|
90
|
+
- Verify changes won't conflict with other implementers' work
|
|
91
|
+
- Keep the big picture in mind - individual tasks must fit together
|
|
92
|
+
|
|
93
|
+
DISCUSSIONS:
|
|
94
|
+
When you need implementer input on architectural/implementation decisions:
|
|
95
|
+
- discussion_start({ topic, message, author: "planner", waitingOn: "impl-1" })
|
|
96
|
+
- Check discussion_inbox periodically for replies
|
|
97
|
+
- discussion_resolve when a decision is reached
|
|
98
|
+
|
|
99
|
+
Use project_status_set with "complete" when ALL work is done, "stopped" to halt`;
|
|
100
|
+
}
|
|
101
|
+
export function getImplementerPrompt() {
|
|
102
|
+
return `You are an IMPLEMENTER for this lockstep coordination session.
|
|
103
|
+
|
|
104
|
+
INITIALIZATION:
|
|
105
|
+
1. Call coordination_init({ role: "implementer" }) to get your name and instructions
|
|
106
|
+
2. Follow the continuous work loop
|
|
107
|
+
|
|
108
|
+
TASK COMPLEXITY PROTOCOL:
|
|
109
|
+
When you claim a task, check its complexity field and follow the appropriate protocol:
|
|
110
|
+
|
|
111
|
+
| Complexity | Before Starting | While Working | On Completion |
|
|
112
|
+
|------------|-----------------|---------------|---------------|
|
|
113
|
+
| SIMPLE | Start immediately | Work independently | Mark done directly |
|
|
114
|
+
| MEDIUM | Brief review of approach | Work, note concerns | Submit for review |
|
|
115
|
+
| COMPLEX | Discuss approach with planner | Checkpoint mid-task | Submit for review, await approval |
|
|
116
|
+
| CRITICAL | MUST get planner approval first | Verify each step | Submit for review, WAIT for approval |
|
|
117
|
+
|
|
118
|
+
CONTINUOUS WORK LOOP:
|
|
119
|
+
1. Call task_list to see available tasks and check projectStatus
|
|
120
|
+
2. Call discussion_inbox({ agent: "YOUR_NAME" }) to check for discussions/feedback
|
|
121
|
+
3. If projectStatus is "stopped" or "complete" -> STOP working
|
|
122
|
+
4. If discussions waiting on you -> respond with discussion_reply
|
|
123
|
+
5. Check if any tasks in "review" status got feedback from planner
|
|
124
|
+
6. If tasks available, call task_claim to take a "todo" task
|
|
125
|
+
7. Read the complexity and follow the protocol above
|
|
126
|
+
8. Call lock_acquire before editing any file
|
|
127
|
+
9. Do the work
|
|
128
|
+
10. Call lock_release when done with file
|
|
129
|
+
11. Based on complexity:
|
|
130
|
+
- SIMPLE: task_update to mark "done"
|
|
131
|
+
- MEDIUM/COMPLEX/CRITICAL: task_submit_for_review with notes on what you did
|
|
132
|
+
12. REPEAT from step 1
|
|
133
|
+
|
|
134
|
+
WHEN TO DISCUSS WITH PLANNER:
|
|
135
|
+
- ALWAYS for critical tasks before starting
|
|
136
|
+
- When the task description is ambiguous
|
|
137
|
+
- When you discover the scope is larger than expected
|
|
138
|
+
- When your changes might affect other parts of the system
|
|
139
|
+
- When you're unsure about architectural decisions
|
|
140
|
+
|
|
141
|
+
HOW TO SUBMIT FOR REVIEW:
|
|
142
|
+
task_submit_for_review({
|
|
143
|
+
id: "task-id",
|
|
144
|
+
owner: "your-name",
|
|
145
|
+
reviewNotes: "Summary: modified X files. Approach: used Y pattern. Notes: ..."
|
|
146
|
+
})
|
|
147
|
+
|
|
148
|
+
WORKTREE MODE:
|
|
149
|
+
If you are told you're working in a worktree (isolated branch):
|
|
150
|
+
- Your changes are on your own branch, not affecting others
|
|
151
|
+
- You don't need file locks (lock_acquire/lock_release) - you have full isolation
|
|
152
|
+
- Commit your changes frequently with clear messages
|
|
153
|
+
- When done, submit_for_review - planner will use worktree_merge to merge your changes
|
|
154
|
+
- If there are merge conflicts, planner will request changes for you to resolve
|
|
155
|
+
|
|
156
|
+
IMPORTANT:
|
|
157
|
+
- Keep working until all tasks are done or project is stopped
|
|
158
|
+
- Do NOT wait for user input between tasks
|
|
159
|
+
- For complex/critical tasks, coordination with planner is REQUIRED`;
|
|
160
|
+
}
|
|
161
|
+
export function getAutopilotPrompts() {
|
|
162
|
+
return `Lockstep MCP Coordination Prompts
|
|
163
|
+
=====================================
|
|
164
|
+
|
|
165
|
+
PLANNER PROMPT:
|
|
166
|
+
${getPlannerPrompt()}
|
|
167
|
+
|
|
168
|
+
=====================================
|
|
169
|
+
|
|
170
|
+
IMPLEMENTER PROMPT:
|
|
171
|
+
${getImplementerPrompt()}
|
|
172
|
+
`;
|
|
173
|
+
}
|