UNPKG

@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
#!/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