@camoneart/maestro
Version:
A CLI tool that conducts Git worktrees like an orchestra and accelerates parallel development with Claude Code
847 lines (840 loc) • 30.1 kB
JavaScript
#!/usr/bin/env node
// src/mcp/server.ts
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
import { z as z2 } from "zod";
// src/core/git.ts
import simpleGit from "simple-git";
// src/core/config.ts
import { z } from "zod";
import Conf from "conf";
import path from "path";
import fs from "fs/promises";
var ConfigSchema = z.object({
// Git worktree設定
worktrees: z.object({
// worktreeを作成するディレクトリ(デフォルト: .git/orchestrations)
path: z.string().optional(),
// ブランチ名のプレフィックス
branchPrefix: z.string().optional(),
// ディレクトリ名のプレフィックス(デフォルト: 空文字列)
directoryPrefix: z.string().optional()
}).optional(),
// 開発環境設定
development: z.object({
// 自動でnpm installを実行
autoSetup: z.boolean().default(true),
// 同期するファイル(.envなど)
syncFiles: z.array(z.string()).default([".env", ".env.local"]),
// デフォルトのエディタ
defaultEditor: z.enum(["vscode", "cursor", "none"]).default("cursor")
}).optional(),
// tmux統合設定
tmux: z.object({
enabled: z.boolean().default(false),
// 新規ウィンドウかペインか
openIn: z.enum(["window", "pane"]).default("window"),
// セッション名の命名規則
sessionNaming: z.string().default("{branch}")
}).optional(),
// Claude Code統合設定
claude: z.object({
// CLAUDE.mdの処理方法
markdownMode: z.enum(["shared", "split"]).default("shared")
}).optional(),
// GitHub統合設定
github: z.object({
// 自動でfetchを実行
autoFetch: z.boolean().default(true),
// ブランチ命名規則
branchNaming: z.object({
// PR用のテンプレート (例: "pr-{number}-{title}")
prTemplate: z.string().default("pr-{number}"),
// Issue用のテンプレート (例: "issue-{number}-{title}")
issueTemplate: z.string().default("issue-{number}")
}).optional()
}).optional(),
// UI表示設定
ui: z.object({
// パス表示形式 ('absolute' | 'relative')
pathDisplay: z.enum(["absolute", "relative"]).default("absolute")
}).optional(),
// カスタムコマンドとファイルコピー設定
hooks: z.object({
// worktree作成後に実行(文字列または配列)
afterCreate: z.union([z.string(), z.array(z.string())]).optional(),
// worktree削除前に実行
beforeDelete: z.string().optional()
}).optional(),
// worktree作成時の処理
postCreate: z.object({
// コピーするファイル(gitignoreファイルも含む)
copyFiles: z.array(z.string()).optional(),
// 実行するコマンド
commands: z.array(z.string()).optional()
}).optional()
});
var DEFAULT_CONFIG = {
worktrees: {
path: "../maestro-{branch}",
directoryPrefix: ""
},
development: {
autoSetup: true,
syncFiles: [".env", ".env.local"],
defaultEditor: "cursor"
},
tmux: {
enabled: false,
openIn: "window",
sessionNaming: "{branch}"
},
claude: {
markdownMode: "shared"
},
github: {
autoFetch: true
},
ui: {
pathDisplay: "absolute"
},
hooks: {}
};
var ConfigManager = class {
conf;
projectConfig = null;
userConfig = null;
constructor() {
this.conf = new Conf({
projectName: "maestro",
defaults: DEFAULT_CONFIG
});
}
async loadProjectConfig() {
try {
await this.loadUserConfig();
const configPaths = [
path.join(process.cwd(), ".maestro.json"),
path.join(process.cwd(), ".maestrorc.json"),
path.join(process.cwd(), "maestro.config.json"),
// グローバル設定ファイル
path.join(process.env.HOME || "~", ".maestrorc"),
path.join(process.env.HOME || "~", ".maestrorc.json")
];
for (const configPath of configPaths) {
try {
const configData = await fs.readFile(configPath, "utf-8");
const parsedConfig = JSON.parse(configData);
this.projectConfig = ConfigSchema.parse(parsedConfig);
return;
} catch {
}
}
} catch (error) {
console.error("\u30D7\u30ED\u30B8\u30A7\u30AF\u30C8\u8A2D\u5B9A\u306E\u8AAD\u307F\u8FBC\u307F\u306B\u5931\u6557\u3057\u307E\u3057\u305F:", error);
}
}
async loadUserConfig() {
try {
const userConfigPath = path.join(process.cwd(), ".maestro.local.json");
const configData = await fs.readFile(userConfigPath, "utf-8");
const parsedConfig = JSON.parse(configData);
this.userConfig = ConfigSchema.parse(parsedConfig);
} catch {
this.userConfig = null;
}
}
// 設定を取得(ユーザー設定 > プロジェクト設定 > グローバル設定 > デフォルト)
get(key) {
if (this.userConfig && this.userConfig[key] !== void 0) {
return this.userConfig[key];
}
if (this.projectConfig && this.projectConfig[key] !== void 0) {
return this.projectConfig[key];
}
return this.conf.get(key) ?? DEFAULT_CONFIG[key];
}
// 設定を更新(グローバル設定のみ)
set(key, value) {
this.conf.set(key, value);
}
// 全設定を取得
getAll() {
const globalConfig = this.conf.store;
return {
...DEFAULT_CONFIG,
...globalConfig,
...this.projectConfig || {},
...this.userConfig || {}
};
}
// 設定ファイルのパスを取得
getConfigPath() {
return this.conf.path;
}
// ドット記法で設定値を取得
getConfigValue(keyPath) {
const keys = keyPath.split(".");
const config = this.getAll();
return keys.reduce((obj, key) => {
if (obj && typeof obj === "object" && key in obj) {
return obj[key];
}
return void 0;
}, config);
}
// ドット記法で設定値を設定
async setConfigValue(keyPath, value, target = "project") {
if (target === "user") {
await this.setUserConfigValue(keyPath, value);
} else {
await this.setProjectConfigValue(keyPath, value);
}
}
// ユーザー設定を設定
async setUserConfigValue(keyPath, value) {
const configPath = path.join(process.cwd(), ".maestro.local.json");
let userConfig = {};
try {
const configData = await fs.readFile(configPath, "utf-8");
userConfig = JSON.parse(configData);
} catch {
}
const keys = keyPath.split(".");
let current = userConfig;
for (let i = 0; i < keys.length - 1; i++) {
const key = keys[i];
if (!key) continue;
if (!current[key] || typeof current[key] !== "object") {
current[key] = {};
}
current = current[key];
}
const lastKey = keys[keys.length - 1];
if (lastKey) {
current[lastKey] = this.parseValue(value);
}
const validatedConfig = ConfigSchema.parse(userConfig);
await fs.writeFile(configPath, JSON.stringify(validatedConfig, null, 2) + "\n", "utf-8");
this.userConfig = validatedConfig;
}
// プロジェクト設定を設定
async setProjectConfigValue(keyPath, value) {
const configPath = path.join(process.cwd(), ".maestro.json");
let projectConfig = {};
try {
const configData = await fs.readFile(configPath, "utf-8");
projectConfig = JSON.parse(configData);
} catch {
}
const keys = keyPath.split(".");
let current = projectConfig;
for (let i = 0; i < keys.length - 1; i++) {
const key = keys[i];
if (!key) continue;
if (!current[key] || typeof current[key] !== "object") {
current[key] = {};
}
current = current[key];
}
const lastKey = keys[keys.length - 1];
if (lastKey) {
current[lastKey] = this.parseValue(value);
}
const validatedConfig = ConfigSchema.parse(projectConfig);
await fs.writeFile(configPath, JSON.stringify(validatedConfig, null, 2) + "\n", "utf-8");
this.projectConfig = validatedConfig;
}
// 設定値をリセット(デフォルトに戻す)
async resetConfigValue(keyPath) {
const configPath = path.join(process.cwd(), ".maestro.json");
let projectConfig = {};
try {
const configData = await fs.readFile(configPath, "utf-8");
projectConfig = JSON.parse(configData);
} catch {
return;
}
const keys = keyPath.split(".");
let current = projectConfig;
const parents = [];
for (let i = 0; i < keys.length - 1; i++) {
const key = keys[i];
if (!key || !current[key]) {
return;
}
parents.push({ obj: current, key });
current = current[key];
}
const lastKey = keys[keys.length - 1];
if (lastKey && current[lastKey] !== void 0) {
delete current[lastKey];
}
this.cleanEmptyObjects(projectConfig, keyPath.split(".").slice(0, -1));
await fs.writeFile(configPath, JSON.stringify(projectConfig, null, 2) + "\n", "utf-8");
this.projectConfig = Object.keys(projectConfig).length > 0 ? projectConfig : null;
}
// 値の型変換
parseValue(value) {
if (typeof value === "string") {
if (value === "true") return true;
if (value === "false") return false;
if (/^\d+$/.test(value)) return parseInt(value, 10);
if (/^\d+\.\d+$/.test(value)) return parseFloat(value);
}
return value;
}
// 空のオブジェクトを削除
cleanEmptyObjects(obj, keys) {
if (keys.length === 0) return;
let current = obj;
for (let i = 0; i < keys.length - 1; i++) {
const key = keys[i];
if (!key || !current[key]) return;
current = current[key];
}
const lastKey = keys[keys.length - 1];
if (lastKey && current[lastKey] && typeof current[lastKey] === "object" && Object.keys(current[lastKey]).length === 0) {
delete current[lastKey];
this.cleanEmptyObjects(obj, keys.slice(0, -1));
}
}
// プロジェクト設定ファイルの作成
async createProjectConfig(configPath) {
const targetPath = configPath || path.join(process.cwd(), ".maestro.json");
const exampleConfig = {
worktrees: {
path: "../maestro-{branch}",
branchPrefix: "feature/",
directoryPrefix: "maestro-"
},
development: {
autoSetup: true,
syncFiles: [".env", ".env.local"],
defaultEditor: "cursor"
},
tmux: {
enabled: true,
openIn: "window",
sessionNaming: "{branch}"
},
claude: {
markdownMode: "shared"
},
github: {
autoFetch: true,
branchNaming: {
prTemplate: "pr-{number}",
issueTemplate: "issue-{number}"
}
},
ui: {
pathDisplay: "absolute"
},
hooks: {
afterCreate: "npm install",
beforeDelete: 'echo "\u30AA\u30FC\u30B1\u30B9\u30C8\u30E9\u30E1\u30F3\u30D0\u30FC\u3092\u89E3\u6563\u3057\u307E\u3059: $MAESTRO_BRANCH"'
},
postCreate: {
copyFiles: [".env", ".env.local"],
commands: ["pnpm install", "pnpm run dev"]
}
};
await fs.writeFile(targetPath, JSON.stringify(exampleConfig, null, 2) + "\n", "utf-8");
}
};
// src/core/git.ts
import path2 from "path";
import fs2 from "fs/promises";
import chalk from "chalk";
import inquirer from "inquirer";
var GitWorktreeManager = class {
git;
configManager;
constructor(baseDir) {
this.git = simpleGit(baseDir || process.cwd());
this.configManager = new ConfigManager();
}
async createWorktree(branchName, baseBranch, skipDirCheck) {
await this.checkBranchNameCollision(branchName);
await this.configManager.loadProjectConfig();
const worktreeConfig = this.configManager.get("worktrees");
const directoryPrefix = worktreeConfig?.directoryPrefix || "";
const repoRoot = await this.getRepositoryRoot();
const worktreePath = path2.join(repoRoot, "..", `${directoryPrefix}${branchName}`);
if (!skipDirCheck) {
const dirExists = await this.checkDirectoryExists(worktreePath);
if (dirExists) {
const action = await this.handleExistingDirectory(worktreePath, branchName);
if (action === "cancel") {
throw new Error("\u30EF\u30FC\u30AF\u30C4\u30EA\u30FC\u306E\u4F5C\u6210\u304C\u30AD\u30E3\u30F3\u30BB\u30EB\u3055\u308C\u307E\u3057\u305F");
} else if (action === "rename") {
const branches = await this.getAllBranches();
const allBranches = [
...branches.local,
...branches.remote.map((r) => r.replace(/^[^/]+\//, ""))
];
const alternativeName = this.generateAlternativeBranchName(branchName, allBranches);
console.log(chalk.yellow(`
\u65B0\u3057\u3044\u30D6\u30E9\u30F3\u30C1\u540D: ${alternativeName}`));
return this.createWorktree(alternativeName, baseBranch, true);
} else if (action === "delete") {
await fs2.rm(worktreePath, { recursive: true, force: true });
console.log(
chalk.gray(`\u{1F5D1}\uFE0F \u65E2\u5B58\u30C7\u30A3\u30EC\u30AF\u30C8\u30EA\u3092\u524A\u9664\u3057\u307E\u3057\u305F: ${path2.basename(worktreePath)}`)
);
}
}
}
if (!baseBranch) {
const status = await this.git.status();
baseBranch = status.current || "main";
}
await this.git.raw(["worktree", "add", "-b", branchName, worktreePath, baseBranch]);
return path2.resolve(worktreePath);
}
async attachWorktree(existingBranch, skipDirCheck) {
await this.configManager.loadProjectConfig();
const worktreeConfig = this.configManager.get("worktrees");
const directoryPrefix = worktreeConfig?.directoryPrefix || "";
const repoRoot = await this.getRepositoryRoot();
const safeBranchName = existingBranch.replace(/\//g, "-");
const worktreePath = path2.join(repoRoot, "..", `${directoryPrefix}${safeBranchName}`);
if (!skipDirCheck) {
const dirExists = await this.checkDirectoryExists(worktreePath);
if (dirExists) {
const action = await this.handleExistingDirectory(worktreePath, safeBranchName);
if (action === "cancel") {
throw new Error("\u30EF\u30FC\u30AF\u30C4\u30EA\u30FC\u306E\u4F5C\u6210\u304C\u30AD\u30E3\u30F3\u30BB\u30EB\u3055\u308C\u307E\u3057\u305F");
} else if (action === "rename") {
const branches = await this.getAllBranches();
const allBranches = [
...branches.local,
...branches.remote.map((r) => r.replace(/^[^/]+\//, ""))
];
const alternativeName = this.generateAlternativeBranchName(safeBranchName, allBranches);
const newWorktreePath = path2.join(repoRoot, "..", `${directoryPrefix}${alternativeName}`);
console.log(chalk.yellow(`
\u65B0\u3057\u3044\u30C7\u30A3\u30EC\u30AF\u30C8\u30EA\u540D: ${alternativeName}`));
await this.git.raw(["worktree", "add", newWorktreePath, existingBranch]);
return path2.resolve(newWorktreePath);
} else if (action === "delete") {
await fs2.rm(worktreePath, { recursive: true, force: true });
console.log(
chalk.gray(`\u{1F5D1}\uFE0F \u65E2\u5B58\u30C7\u30A3\u30EC\u30AF\u30C8\u30EA\u3092\u524A\u9664\u3057\u307E\u3057\u305F: ${path2.basename(worktreePath)}`)
);
}
}
}
await this.git.raw(["worktree", "add", worktreePath, existingBranch]);
return path2.resolve(worktreePath);
}
async listWorktrees() {
const output = await this.git.raw(["worktree", "list", "--porcelain"]);
const worktrees = [];
const lines = output.split("\n").filter((line) => line.trim());
let currentWorktree = {};
for (const line of lines) {
if (line.startsWith("worktree ")) {
if (currentWorktree.path) {
worktrees.push(currentWorktree);
}
currentWorktree = {
path: line.substring(9),
detached: false,
prunable: false,
locked: false
};
} else if (line.startsWith("HEAD ")) {
currentWorktree.head = line.substring(5);
} else if (line.startsWith("branch ")) {
currentWorktree.branch = line.substring(7);
} else if (line === "detached") {
currentWorktree.detached = true;
} else if (line === "prunable") {
currentWorktree.prunable = true;
} else if (line.startsWith("locked")) {
currentWorktree.locked = true;
if (line.includes(" ")) {
currentWorktree.reason = line.substring(line.indexOf(" ") + 1);
}
}
}
if (currentWorktree.path) {
worktrees.push(currentWorktree);
}
return worktrees;
}
async deleteWorktree(branchName, force = false) {
const worktrees = await this.listWorktrees();
const worktree = worktrees.find((wt) => {
const branch = wt.branch?.replace("refs/heads/", "");
return branch === branchName;
});
if (!worktree) {
throw new Error(`\u30EF\u30FC\u30AF\u30C4\u30EA\u30FC '${branchName}' \u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093`);
}
const args = ["worktree", "remove"];
if (force) args.push("--force");
args.push(worktree.path);
await this.git.raw(args);
await this.cleanupEmptyDirectories(worktree.path);
try {
await this.git.branch(["-d", branchName]);
} catch (error) {
if (error instanceof Error && error.message.includes("not fully merged")) {
await this.git.branch(["-D", branchName]);
} else {
throw error;
}
}
}
async getCurrentBranch() {
const status = await this.git.status();
return status.current;
}
async isGitRepository() {
try {
await this.git.status();
return true;
} catch {
return false;
}
}
async getAllBranches() {
const localBranches = await this.git.branchLocal();
const remoteBranches = await this.git.branch(["-r"]);
return {
local: localBranches.all.filter((b) => !b.startsWith("remotes/")),
remote: remoteBranches.all.filter((b) => b.startsWith("remotes/")).map((b) => b.replace("remotes/", ""))
};
}
async listLocalBranches() {
const localBranches = await this.git.branchLocal();
return localBranches.all.filter((b) => !b.startsWith("remotes/"));
}
async fetchAll() {
await this.git.fetch(["--all"]);
}
async getLastCommit(worktreePath) {
try {
const gitInWorktree = simpleGit(worktreePath);
const log = await gitInWorktree.log({ maxCount: 1 });
if (log.latest) {
return {
date: log.latest.date,
message: log.latest.message,
hash: log.latest.hash.substring(0, 7)
};
}
return null;
} catch {
return null;
}
}
async getRepositoryRoot() {
try {
const output = await this.git.raw(["rev-parse", "--show-toplevel"]);
return output.trim();
} catch {
throw new Error("\u30EA\u30DD\u30B8\u30C8\u30EA\u30EB\u30FC\u30C8\u306E\u53D6\u5F97\u306B\u5931\u6557\u3057\u307E\u3057\u305F");
}
}
async isGitignored(filePath) {
try {
await this.git.raw(["check-ignore", filePath]);
return true;
} catch {
return false;
}
}
async checkBranchNameCollision(branchName) {
const branches = await this.getAllBranches();
const allBranches = [...branches.local, ...branches.remote.map((r) => r.replace(/^[^/]+\//, ""))];
if (allBranches.includes(branchName)) {
throw new Error(`\u30D6\u30E9\u30F3\u30C1 '${branchName}' \u306F\u65E2\u306B\u5B58\u5728\u3057\u307E\u3059`);
}
const conflictingBranches = allBranches.filter(
(existing) => existing.startsWith(branchName + "/")
);
if (conflictingBranches.length > 0) {
const examples = conflictingBranches.slice(0, 3).join(", ");
throw new Error(
`\u30D6\u30E9\u30F3\u30C1 '${branchName}' \u3092\u4F5C\u6210\u3067\u304D\u307E\u305B\u3093\u3002\u4EE5\u4E0B\u306E\u65E2\u5B58\u30D6\u30E9\u30F3\u30C1\u3068\u7AF6\u5408\u3057\u307E\u3059: ${examples}${conflictingBranches.length > 3 ? ` \u306A\u3069 (${conflictingBranches.length}\u4EF6)` : ""}`
);
}
const parentConflicts = allBranches.filter((existing) => branchName.startsWith(existing + "/"));
if (parentConflicts.length > 0) {
const examples = parentConflicts.slice(0, 3).join(", ");
throw new Error(
`\u30D6\u30E9\u30F3\u30C1 '${branchName}' \u3092\u4F5C\u6210\u3067\u304D\u307E\u305B\u3093\u3002\u4EE5\u4E0B\u306E\u65E2\u5B58\u30D6\u30E9\u30F3\u30C1\u306E\u30B5\u30D6\u30D6\u30E9\u30F3\u30C1\u306B\u306A\u308A\u307E\u3059: ${examples}${parentConflicts.length > 3 ? ` \u306A\u3069 (${parentConflicts.length}\u4EF6)` : ""}`
);
}
}
generateAlternativeBranchName(originalName, allBranches) {
let counter = 1;
let alternativeName = `${originalName}-${counter}`;
while (allBranches.includes(alternativeName) || allBranches.some(
(b) => b.startsWith(alternativeName + "/") || alternativeName.startsWith(b + "/")
)) {
counter++;
alternativeName = `${originalName}-${counter}`;
}
return alternativeName;
}
async cleanupEmptyDirectories(worktreePath) {
const repoRoot = await this.getRepositoryRoot();
const baseDir = path2.join(repoRoot, "..");
let currentDir = path2.dirname(worktreePath);
while (currentDir !== baseDir && currentDir !== path2.dirname(currentDir)) {
try {
const entries = await fs2.readdir(currentDir);
if (entries.length === 0) {
await fs2.rmdir(currentDir);
console.log(chalk.gray(`\u{1F9F9} Removed empty directory: ${path2.basename(currentDir)}`));
currentDir = path2.dirname(currentDir);
} else {
break;
}
} catch {
break;
}
}
}
async checkDirectoryExists(dirPath) {
try {
const stats = await fs2.stat(dirPath);
return stats.isDirectory();
} catch {
return false;
}
}
async handleExistingDirectory(dirPath, branchName) {
const repoRoot = await this.getRepositoryRoot();
const relativePath = path2.relative(repoRoot, dirPath);
console.log(chalk.yellow(`
\u26A0\uFE0F \u30C7\u30A3\u30EC\u30AF\u30C8\u30EA '${relativePath}' \u306F\u65E2\u306B\u5B58\u5728\u3057\u307E\u3059`));
const choices = [
{ name: "\u65E2\u5B58\u30C7\u30A3\u30EC\u30AF\u30C8\u30EA\u3092\u524A\u9664\u3057\u3066\u65B0\u898F\u4F5C\u6210", value: "delete" },
{ name: `\u5225\u306E\u540D\u524D\u3092\u4F7F\u7528\uFF08${branchName}-2\u306A\u3069\uFF09`, value: "rename" },
{ name: "\u30AD\u30E3\u30F3\u30BB\u30EB", value: "cancel" }
];
const answer = await inquirer.prompt([
{
type: "list",
name: "action",
message: "\u3069\u306E\u3088\u3046\u306B\u51E6\u7406\u3057\u307E\u3059\u304B\uFF1F",
choices
}
]);
return answer.action;
}
};
// src/mcp/server.ts
import { readFileSync } from "fs";
import { fileURLToPath } from "url";
import { dirname, join } from "path";
var isTestEnvironment = process.env.NODE_ENV === "test";
var __dirname2 = dirname(fileURLToPath(import.meta.url));
var packageJson = JSON.parse(readFileSync(join(__dirname2, "../../package.json"), "utf-8"));
var CreateWorktreeArgsSchema = z2.object({
branchName: z2.string().describe("\u4F5C\u6210\u3059\u308B\u30D6\u30E9\u30F3\u30C1\u540D"),
baseBranch: z2.string().optional().describe("\u30D9\u30FC\u30B9\u30D6\u30E9\u30F3\u30C1\uFF08\u7701\u7565\u6642\u306F\u73FE\u5728\u306E\u30D6\u30E9\u30F3\u30C1\uFF09")
});
var DeleteWorktreeArgsSchema = z2.object({
branchName: z2.string().describe("\u524A\u9664\u3059\u308B\u30D6\u30E9\u30F3\u30C1\u540D"),
force: z2.boolean().optional().describe("\u5F37\u5236\u524A\u9664\u30D5\u30E9\u30B0")
});
var ExecInWorktreeArgsSchema = z2.object({
branchName: z2.string().describe("\u5B9F\u884C\u5BFE\u8C61\u306E\u30D6\u30E9\u30F3\u30C1\u540D"),
command: z2.string().describe("\u5B9F\u884C\u3059\u308B\u30B3\u30DE\u30F3\u30C9")
});
var server = new Server(
{
name: "maestro",
version: packageJson.version
},
{
capabilities: {
tools: {}
}
}
);
var gitManager = new GitWorktreeManager();
var TOOLS = [
{
name: "create_orchestra_member",
description: "\u65B0\u3057\u3044\u6F14\u594F\u8005\uFF08Git worktree\uFF09\u3092\u62DB\u96C6\u3059\u308B",
inputSchema: {
type: "object",
properties: {
branchName: {
type: "string",
description: "\u4F5C\u6210\u3059\u308B\u30D6\u30E9\u30F3\u30C1\u540D"
},
baseBranch: {
type: "string",
description: "\u30D9\u30FC\u30B9\u30D6\u30E9\u30F3\u30C1\uFF08\u7701\u7565\u6642\u306F\u73FE\u5728\u306E\u30D6\u30E9\u30F3\u30C1\uFF09"
}
},
required: ["branchName"]
}
},
{
name: "list_orchestra_members",
description: "\u3059\u3079\u3066\u306E\u6F14\u594F\u8005\uFF08Git worktree\uFF09\u3092\u4E00\u89A7\u8868\u793A",
inputSchema: {
type: "object",
properties: {}
}
},
{
name: "delete_orchestra_member",
description: "\u6F14\u594F\u8005\uFF08Git worktree\uFF09\u3092\u89E3\u6563",
inputSchema: {
type: "object",
properties: {
branchName: {
type: "string",
description: "\u524A\u9664\u3059\u308B\u30D6\u30E9\u30F3\u30C1\u540D"
},
force: {
type: "boolean",
description: "\u5F37\u5236\u524A\u9664\u30D5\u30E9\u30B0"
}
},
required: ["branchName"]
}
},
{
name: "exec_in_orchestra_member",
description: "\u6F14\u594F\u8005\u3067\u30B3\u30DE\u30F3\u30C9\u3092\u5B9F\u884C",
inputSchema: {
type: "object",
properties: {
branchName: {
type: "string",
description: "\u5B9F\u884C\u5BFE\u8C61\u306E\u30D6\u30E9\u30F3\u30C1\u540D"
},
command: {
type: "string",
description: "\u5B9F\u884C\u3059\u308B\u30B3\u30DE\u30F3\u30C9"
}
},
required: ["branchName", "command"]
}
}
];
if (!isTestEnvironment) {
server.setRequestHandler(ListToolsRequestSchema, async () => {
return {
tools: TOOLS
};
});
}
if (!isTestEnvironment) {
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
try {
switch (name) {
case "create_orchestra_member": {
const validatedArgs = CreateWorktreeArgsSchema.parse(args);
const worktreePath = await gitManager.createWorktree(
validatedArgs.branchName,
validatedArgs.baseBranch
);
return {
content: [
{
type: "text",
text: `\u2705 \u6F14\u594F\u8005 '${validatedArgs.branchName}' \u3092\u62DB\u96C6\u3057\u307E\u3057\u305F\uFF01
\u{1F4C1} ${worktreePath}`
}
]
};
}
case "list_orchestra_members": {
const worktrees = await gitManager.listWorktrees();
const orchestraMembers = worktrees.filter((wt) => !wt.path.endsWith("."));
const list = orchestraMembers.map((wt) => {
const branchName = wt.branch?.replace("refs/heads/", "") || wt.branch;
return `\u2022 ${branchName} (${wt.path})`;
}).join("\n");
return {
content: [
{
type: "text",
text: orchestraMembers.length > 0 ? `\u{1F3BC} \u30AA\u30FC\u30B1\u30B9\u30C8\u30E9\u7DE8\u6210(worktree):
${list}
\u5408\u8A08: ${orchestraMembers.length} \u540D\u306E\u6F14\u594F\u8005` : "\u6F14\u594F\u8005\u304C\u5B58\u5728\u3057\u307E\u305B\u3093"
}
]
};
}
case "delete_orchestra_member": {
const validatedArgs = DeleteWorktreeArgsSchema.parse(args);
await gitManager.deleteWorktree(validatedArgs.branchName, validatedArgs.force);
return {
content: [
{
type: "text",
text: `\u2705 \u6F14\u594F\u8005 '${validatedArgs.branchName}' \u3092\u89E3\u6563\u3057\u307E\u3057\u305F`
}
]
};
}
case "exec_in_orchestra_member": {
const validatedArgs = ExecInWorktreeArgsSchema.parse(args);
const { execa } = await import("execa");
const worktrees = await gitManager.listWorktrees();
const targetWorktree = worktrees.find((wt) => {
const branch = wt.branch?.replace("refs/heads/", "");
return branch === validatedArgs.branchName || wt.branch === validatedArgs.branchName;
});
if (!targetWorktree) {
throw new Error(`\u6F14\u594F\u8005 '${validatedArgs.branchName}' \u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093`);
}
const result = await execa("sh", ["-c", validatedArgs.command], {
cwd: targetWorktree.path
});
return {
content: [
{
type: "text",
text: `\u{1F4CD} ${validatedArgs.branchName} \u3067\u5B9F\u884C: ${validatedArgs.command}
${result.stdout}`
}
]
};
}
default:
throw new Error(`\u4E0D\u660E\u306A\u30C4\u30FC\u30EB: ${name}`);
}
} catch (error) {
return {
content: [
{
type: "text",
text: `\u274C \u30A8\u30E9\u30FC: ${error instanceof Error ? error.message : "\u4E0D\u660E\u306A\u30A8\u30E9\u30FC"}`
}
]
};
}
});
}
async function main() {
const transport = new StdioServerTransport();
await server.connect(transport);
console.error("\u{1F3BC} Maestro MCP server started");
}
if (!isTestEnvironment) {
main().catch((error) => {
console.error("Fatal error:", error);
process.exit(1);
});
}
export {
server
};
//# sourceMappingURL=server.js.map