ensemble-claude 0.3.0__py3-none-any.whl

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.
Files changed (46) hide show
  1. ensemble/__init__.py +5 -0
  2. ensemble/ack.py +86 -0
  3. ensemble/cli.py +31 -0
  4. ensemble/commands/__init__.py +1 -0
  5. ensemble/commands/_init_impl.py +208 -0
  6. ensemble/commands/_launch_impl.py +217 -0
  7. ensemble/commands/init.py +35 -0
  8. ensemble/commands/launch.py +32 -0
  9. ensemble/config.py +218 -0
  10. ensemble/dashboard.py +168 -0
  11. ensemble/helpers.py +79 -0
  12. ensemble/lock.py +77 -0
  13. ensemble/logger.py +80 -0
  14. ensemble/notes.py +221 -0
  15. ensemble/queue.py +166 -0
  16. ensemble/templates/__init__.py +75 -0
  17. ensemble/templates/agents/conductor.md +239 -0
  18. ensemble/templates/agents/dispatch.md +351 -0
  19. ensemble/templates/agents/integrator.md +138 -0
  20. ensemble/templates/agents/learner.md +133 -0
  21. ensemble/templates/agents/reviewer.md +84 -0
  22. ensemble/templates/agents/security-reviewer.md +136 -0
  23. ensemble/templates/agents/worker.md +184 -0
  24. ensemble/templates/commands/go-light.md +49 -0
  25. ensemble/templates/commands/go.md +101 -0
  26. ensemble/templates/commands/improve.md +116 -0
  27. ensemble/templates/commands/review.md +74 -0
  28. ensemble/templates/commands/status.md +56 -0
  29. ensemble/templates/scripts/dashboard-update.sh +78 -0
  30. ensemble/templates/scripts/launch.sh +137 -0
  31. ensemble/templates/scripts/pane-setup.sh +111 -0
  32. ensemble/templates/scripts/setup.sh +163 -0
  33. ensemble/templates/scripts/worktree-create.sh +89 -0
  34. ensemble/templates/scripts/worktree-merge.sh +194 -0
  35. ensemble/templates/workflows/default.yaml +78 -0
  36. ensemble/templates/workflows/heavy.yaml +149 -0
  37. ensemble/templates/workflows/simple.yaml +41 -0
  38. ensemble/templates/workflows/worktree.yaml +202 -0
  39. ensemble/utils.py +60 -0
  40. ensemble/workflow.py +127 -0
  41. ensemble/worktree.py +322 -0
  42. ensemble_claude-0.3.0.dist-info/METADATA +144 -0
  43. ensemble_claude-0.3.0.dist-info/RECORD +46 -0
  44. ensemble_claude-0.3.0.dist-info/WHEEL +4 -0
  45. ensemble_claude-0.3.0.dist-info/entry_points.txt +2 -0
  46. ensemble_claude-0.3.0.dist-info/licenses/LICENSE +21 -0
ensemble/notes.py ADDED
@@ -0,0 +1,221 @@
1
+ """
2
+ 学習ノート管理ユーティリティ
3
+
4
+ タスク実行の学習記録を管理する。
5
+ notes/{task-id}/以下にplan.md, decisions.md, lessons.md, skill-candidates.mdを配置。
6
+ """
7
+
8
+ from datetime import datetime
9
+ from pathlib import Path
10
+ from typing import Any
11
+
12
+
13
+ def create_task_notes_dir(notes_base_dir: str, task_id: str) -> Path:
14
+ """
15
+ タスクノート用ディレクトリを作成する
16
+
17
+ Args:
18
+ notes_base_dir: ノートのベースディレクトリ
19
+ task_id: タスクID
20
+
21
+ Returns:
22
+ 作成されたディレクトリのPath
23
+ """
24
+ task_dir = Path(notes_base_dir) / task_id
25
+ task_dir.mkdir(parents=True, exist_ok=True)
26
+ return task_dir
27
+
28
+
29
+ def write_lessons(
30
+ task_dir: Path,
31
+ task_id: str,
32
+ lessons: dict[str, Any],
33
+ ) -> None:
34
+ """
35
+ lessons.mdファイルを作成/更新する
36
+
37
+ Args:
38
+ task_dir: タスクディレクトリ
39
+ task_id: タスクID
40
+ lessons: 学習内容
41
+ - successes: 成功したこと(リスト)
42
+ - improvements: 改善点(リスト)
43
+ - metrics: 数値(辞書)
44
+ """
45
+ content = f"""# タスク: {task_id}
46
+ ## 日時: {datetime.now().isoformat()}
47
+
48
+ ## 成功したこと
49
+ """
50
+ for success in lessons.get("successes", []):
51
+ content += f"- {success}\n"
52
+
53
+ content += "\n## 改善すべきこと\n"
54
+ for improvement in lessons.get("improvements", []):
55
+ if isinstance(improvement, dict):
56
+ content += f"- [ ] {improvement.get('issue', '')}\n"
57
+ content += f" - 原因: {improvement.get('cause', '')}\n"
58
+ content += f" - 対策: {improvement.get('solution', '')}\n"
59
+ else:
60
+ content += f"- [ ] {improvement}\n"
61
+
62
+ metrics = lessons.get("metrics", {})
63
+ content += f"""
64
+ ## 数値
65
+ - 修正回数: {metrics.get('fix_count', 0)}回
66
+ - テスト失敗回数: {metrics.get('test_failures', 0)}回
67
+ - レビュー指摘数: {metrics.get('review_issues', 0)}件
68
+ """
69
+
70
+ (task_dir / "lessons.md").write_text(content)
71
+
72
+
73
+ def write_skill_candidates(
74
+ task_dir: Path,
75
+ candidates: list[dict[str, Any]],
76
+ ) -> None:
77
+ """
78
+ skill-candidates.mdファイルを作成する
79
+
80
+ Args:
81
+ task_dir: タスクディレクトリ
82
+ candidates: スキル化候補のリスト
83
+ - name: スキル名
84
+ - purpose: 用途
85
+ - occurrences: 発生回数
86
+ - cost: 実装コスト(low/medium/high)
87
+ - recommended: 推奨(True/False)
88
+ """
89
+ content = "# スキル化候補\n\n"
90
+
91
+ if not candidates:
92
+ content += "候補なし\n"
93
+ else:
94
+ for i, candidate in enumerate(candidates, 1):
95
+ recommended = "YES" if candidate.get("recommended", False) else "NO"
96
+ content += f"""## 候補{i}: {candidate.get('name', 'unknown')}
97
+ - 用途: {candidate.get('purpose', '')}
98
+ - 発生回数: {candidate.get('occurrences', 0)}回
99
+ - 実装コスト: {candidate.get('cost', 'unknown')}
100
+ - 推奨: {recommended}
101
+
102
+ """
103
+
104
+ (task_dir / "skill-candidates.md").write_text(content)
105
+
106
+
107
+ def write_decisions(
108
+ task_dir: Path,
109
+ task_id: str,
110
+ decisions: list[dict[str, Any]],
111
+ append: bool = False,
112
+ ) -> None:
113
+ """
114
+ decisions.mdファイルを作成/更新する
115
+
116
+ Args:
117
+ task_dir: タスクディレクトリ
118
+ task_id: タスクID
119
+ decisions: 決定のリスト
120
+ - timestamp: タイムスタンプ
121
+ - context: 文脈
122
+ - decision: 決定内容
123
+ - rationale: 理由
124
+ append: Trueの場合、既存ファイルに追記
125
+ """
126
+ decisions_file = task_dir / "decisions.md"
127
+
128
+ if append and decisions_file.exists():
129
+ content = decisions_file.read_text()
130
+ content += "\n---\n\n"
131
+ else:
132
+ content = f"# 決定ログ: {task_id}\n\n"
133
+
134
+ for decision in decisions:
135
+ content += f"""## {decision.get('context', 'No context')}
136
+ - **日時**: {decision.get('timestamp', datetime.now().isoformat())}
137
+ - **決定**: {decision.get('decision', '')}
138
+ - **理由**: {decision.get('rationale', '')}
139
+
140
+ """
141
+
142
+ decisions_file.write_text(content)
143
+
144
+
145
+ def read_lessons(task_dir: Path) -> str | None:
146
+ """
147
+ lessons.mdの内容を読み込む
148
+
149
+ Args:
150
+ task_dir: タスクディレクトリ
151
+
152
+ Returns:
153
+ ファイル内容。存在しない場合はNone
154
+ """
155
+ lessons_file = task_dir / "lessons.md"
156
+ if lessons_file.exists():
157
+ return lessons_file.read_text()
158
+ return None
159
+
160
+
161
+ def read_skill_candidates(task_dir: Path) -> str | None:
162
+ """
163
+ skill-candidates.mdの内容を読み込む
164
+
165
+ Args:
166
+ task_dir: タスクディレクトリ
167
+
168
+ Returns:
169
+ ファイル内容。存在しない場合はNone
170
+ """
171
+ skills_file = task_dir / "skill-candidates.md"
172
+ if skills_file.exists():
173
+ return skills_file.read_text()
174
+ return None
175
+
176
+
177
+ def list_task_notes(notes_base_dir: str) -> list[str]:
178
+ """
179
+ 全タスクディレクトリをリストする
180
+
181
+ Args:
182
+ notes_base_dir: ノートのベースディレクトリ
183
+
184
+ Returns:
185
+ タスクIDのリスト
186
+ """
187
+ notes_path = Path(notes_base_dir)
188
+ if not notes_path.exists():
189
+ return []
190
+
191
+ return [d.name for d in notes_path.iterdir() if d.is_dir()]
192
+
193
+
194
+ def get_notes_summary(notes_base_dir: str) -> dict[str, dict[str, bool]]:
195
+ """
196
+ 全ノートの要約を返す
197
+
198
+ Args:
199
+ notes_base_dir: ノートのベースディレクトリ
200
+
201
+ Returns:
202
+ {task_id: {has_lessons: bool, has_skill_candidates: bool, ...}}
203
+ """
204
+ notes_path = Path(notes_base_dir)
205
+ if not notes_path.exists():
206
+ return {}
207
+
208
+ summary: dict[str, dict[str, bool]] = {}
209
+
210
+ for task_dir in notes_path.iterdir():
211
+ if not task_dir.is_dir():
212
+ continue
213
+
214
+ summary[task_dir.name] = {
215
+ "has_lessons": (task_dir / "lessons.md").exists(),
216
+ "has_skill_candidates": (task_dir / "skill-candidates.md").exists(),
217
+ "has_decisions": (task_dir / "decisions.md").exists(),
218
+ "has_plan": (task_dir / "plan.md").exists(),
219
+ }
220
+
221
+ return summary
ensemble/queue.py ADDED
@@ -0,0 +1,166 @@
1
+ """
2
+ ファイルベースのタスクキュー
3
+
4
+ アトミック操作でタスクの配信・取得・完了報告を行う。
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import os
10
+ import uuid
11
+ from datetime import datetime
12
+ from pathlib import Path
13
+ from typing import Any
14
+
15
+ import yaml
16
+
17
+ from ensemble.lock import atomic_claim, atomic_write
18
+
19
+
20
+ class TaskQueue:
21
+ """
22
+ ファイルベースのタスクキュー
23
+
24
+ 構造:
25
+ queue/
26
+ ├── tasks/ # 保留中のタスク
27
+ ├── processing/ # 処理中のタスク
28
+ └── reports/ # 完了報告
29
+ """
30
+
31
+ def __init__(self, base_dir: Path | None = None) -> None:
32
+ """
33
+ キューを初期化する
34
+
35
+ Args:
36
+ base_dir: ベースディレクトリ(デフォルト: queue/)
37
+ """
38
+ self.base_dir = base_dir if base_dir else Path("queue")
39
+ self.tasks_dir = self.base_dir / "tasks"
40
+ self.processing_dir = self.base_dir / "processing"
41
+ self.reports_dir = self.base_dir / "reports"
42
+
43
+ # ディレクトリ作成
44
+ self.tasks_dir.mkdir(parents=True, exist_ok=True)
45
+ self.processing_dir.mkdir(parents=True, exist_ok=True)
46
+ self.reports_dir.mkdir(parents=True, exist_ok=True)
47
+
48
+ def enqueue(
49
+ self,
50
+ command: str,
51
+ agent: str,
52
+ params: dict[str, Any] | None = None,
53
+ ) -> str:
54
+ """
55
+ タスクをキューに追加する
56
+
57
+ Args:
58
+ command: 実行するコマンド
59
+ agent: 担当エージェント
60
+ params: 追加パラメータ
61
+
62
+ Returns:
63
+ タスクID
64
+ """
65
+ task_id = self._generate_task_id()
66
+
67
+ task = {
68
+ "task_id": task_id,
69
+ "command": command,
70
+ "agent": agent,
71
+ "params": params or {},
72
+ "status": "pending",
73
+ "created_at": datetime.now().isoformat(),
74
+ }
75
+
76
+ task_file = self.tasks_dir / f"{task_id}.yaml"
77
+ content = yaml.dump(task, allow_unicode=True, default_flow_style=False)
78
+ atomic_write(str(task_file), content)
79
+
80
+ return task_id
81
+
82
+ def claim(self) -> dict[str, Any] | None:
83
+ """
84
+ タスクを取得する(アトミック)
85
+
86
+ Returns:
87
+ タスクデータ、またはキューが空の場合None
88
+ """
89
+ # 最も古いタスクを取得(ファイル名でソート)
90
+ task_files = sorted(self.tasks_dir.glob("*.yaml"))
91
+
92
+ for task_file in task_files:
93
+ result = atomic_claim(str(task_file), str(self.processing_dir))
94
+ if result:
95
+ with open(result) as f:
96
+ return yaml.safe_load(f)
97
+
98
+ return None
99
+
100
+ def complete(
101
+ self,
102
+ task_id: str,
103
+ result: str,
104
+ output: str,
105
+ error: str | None = None,
106
+ ) -> None:
107
+ """
108
+ タスク完了を報告する
109
+
110
+ Args:
111
+ task_id: タスクID
112
+ result: 結果 ("success" or "error")
113
+ output: 出力内容
114
+ error: エラーメッセージ(エラー時のみ)
115
+ """
116
+ processing_file = self.processing_dir / f"{task_id}.yaml"
117
+
118
+ # 元のタスク情報を読み込み
119
+ if processing_file.exists():
120
+ with open(processing_file) as f:
121
+ task = yaml.safe_load(f)
122
+ else:
123
+ task = {"task_id": task_id}
124
+
125
+ # レポート作成
126
+ report = {
127
+ **task,
128
+ "result": result,
129
+ "output": output,
130
+ "completed_at": datetime.now().isoformat(),
131
+ }
132
+ if error:
133
+ report["error"] = error
134
+
135
+ # reportsに保存
136
+ report_file = self.reports_dir / f"{task_id}.yaml"
137
+ content = yaml.dump(report, allow_unicode=True, default_flow_style=False)
138
+ atomic_write(str(report_file), content)
139
+
140
+ # processingから削除
141
+ if processing_file.exists():
142
+ processing_file.unlink()
143
+
144
+ def list_pending(self) -> list[str]:
145
+ """
146
+ 保留中のタスクIDリストを取得する
147
+
148
+ Returns:
149
+ タスクIDのリスト
150
+ """
151
+ task_files = self.tasks_dir.glob("*.yaml")
152
+ return [f.stem for f in task_files]
153
+
154
+ def cleanup(self) -> None:
155
+ """
156
+ 全てのファイルを削除する(セッション開始時用)
157
+ """
158
+ for dir_path in [self.tasks_dir, self.processing_dir, self.reports_dir]:
159
+ for f in dir_path.glob("*.yaml"):
160
+ f.unlink()
161
+
162
+ def _generate_task_id(self) -> str:
163
+ """ユニークなタスクIDを生成"""
164
+ timestamp = datetime.now().strftime("%Y%m%d%H%M%S")
165
+ short_uuid = uuid.uuid4().hex[:8]
166
+ return f"{timestamp}-{short_uuid}"
@@ -0,0 +1,75 @@
1
+ """Ensemble templates package.
2
+
3
+ Provides access to bundled agent definitions, commands, workflows, and scripts.
4
+ """
5
+
6
+ from importlib import resources
7
+ from pathlib import Path
8
+ from typing import Optional
9
+
10
+
11
+ def get_template_path(template_type: str) -> Path:
12
+ """Get the path to a template directory.
13
+
14
+ Args:
15
+ template_type: One of "agents", "commands", "workflows", "scripts"
16
+
17
+ Returns:
18
+ Path to the template directory
19
+
20
+ Raises:
21
+ ValueError: If template_type is not valid
22
+ """
23
+ valid_types = {"agents", "commands", "workflows", "scripts"}
24
+ if template_type not in valid_types:
25
+ raise ValueError(f"Invalid template type: {template_type}. Must be one of {valid_types}")
26
+
27
+ # Use importlib.resources for Python 3.9+
28
+ try:
29
+ # Python 3.9+ style
30
+ with resources.as_file(resources.files("ensemble.templates") / template_type) as path:
31
+ return Path(path)
32
+ except (TypeError, AttributeError):
33
+ # Fallback for older Python or edge cases
34
+ package_dir = Path(__file__).parent
35
+ return package_dir / template_type
36
+
37
+
38
+ def get_template_file(template_type: str, filename: str) -> Optional[Path]:
39
+ """Get the path to a specific template file.
40
+
41
+ Args:
42
+ template_type: One of "agents", "commands", "workflows", "scripts"
43
+ filename: Name of the file within the template directory
44
+
45
+ Returns:
46
+ Path to the template file, or None if not found
47
+ """
48
+ template_dir = get_template_path(template_type)
49
+ file_path = template_dir / filename
50
+
51
+ if file_path.exists():
52
+ return file_path
53
+ return None
54
+
55
+
56
+ def list_templates(template_type: str) -> list[str]:
57
+ """List all template files of a given type.
58
+
59
+ Args:
60
+ template_type: One of "agents", "commands", "workflows", "scripts"
61
+
62
+ Returns:
63
+ List of template filenames
64
+ """
65
+ template_dir = get_template_path(template_type)
66
+ if not template_dir.exists():
67
+ return []
68
+
69
+ if template_type in ("agents", "commands"):
70
+ return [f.name for f in template_dir.glob("*.md")]
71
+ elif template_type == "workflows":
72
+ return [f.name for f in template_dir.glob("*.yaml")]
73
+ elif template_type == "scripts":
74
+ return [f.name for f in template_dir.glob("*.sh")]
75
+ return []
@@ -0,0 +1,239 @@
1
+ ---
2
+ name: conductor
3
+ description: |
4
+ Ensembleの指揮者(頭脳)。ユーザーのタスクを受け取り、
5
+ 計画・分解・パターン選択・最終判断を行う。
6
+ 実行の伝達・監視はDispatchに委譲する。考えすぎない。即判断。
7
+ tools: Read, Write, Edit, Bash, Glob, Grep, Skill, Task
8
+ model: opus
9
+ ---
10
+
11
+ あなたはEnsembleの指揮者(Conductor)です。
12
+
13
+ ## 最重要ルール: 考えるな、委譲しろ
14
+
15
+ - あなたの仕事は「判断」と「委譲」。自分でコードを書いたりファイルを直接操作するな。
16
+ - 計画を立てたら即座にDispatchまたはサブエージェントに委譲せよ。
17
+ - 30秒で済む判断に5分かけるな。
18
+
19
+ ## 行動原則
20
+
21
+ 1. まずplanモードで全体計画を立てる
22
+ 2. タスクを分解し、最適な実行パターンを選択する
23
+ 3. コスト見積もりを行い、適切なワークフローを選択する
24
+ 4. 必要なskillsやagentsが不足していれば生成を提案する
25
+ 5. Dispatchにタスク配信を指示する(パターンB/Cの場合)
26
+ 6. 完了報告を受けたら最終判断のみ行う
27
+ 7. 完了後は必ず自己改善フェーズをlearnerに委譲する
28
+
29
+ ## 実行パターン判定基準
30
+
31
+ ### パターンA: subagent直接実行
32
+ - 変更ファイル数 ≤ 3
33
+ - 独立性が高くない単純タスク
34
+ - 例: 単一ファイルの修正、typo修正、小さな機能追加
35
+
36
+ ### パターンB: tmux並列実行
37
+ - 変更ファイル数 4〜10
38
+ - 並列可能な作業あり
39
+ - 例: 複数エンドポイントの実装、テスト追加
40
+
41
+ ### パターンC: worktree分離
42
+ - 機能が独立している
43
+ - 変更ファイル数 > 10 または 複数ブランチ必要
44
+ - 例: 認証・API・UIの同時開発
45
+
46
+ ## パターン別実行方法
47
+
48
+ ### パターンA: takt方式(subagent直接)
49
+
50
+ Taskツールでsubagentを起動して直接実行する。Dispatchは不要。
51
+
52
+ ```
53
+ 1. Task(subagent_type="general-purpose")でタスク実行
54
+ 2. 結果を確認
55
+ 3. 必要に応じてレビュー
56
+ ```
57
+
58
+ ### パターンB: shogun方式(tmux並列)
59
+
60
+ Dispatchに指示を送り、ワーカーペインを起動させる。
61
+
62
+ ```
63
+ 1. タスクを分解し、ワーカー数を決定
64
+ 2. queue/conductor/dispatch-instruction.yaml に指示を書く:
65
+
66
+ type: start_workers
67
+ worker_count: 2
68
+ tasks:
69
+ - id: task-001
70
+ instruction: "タスク1の説明"
71
+ files: ["file1.py"]
72
+ - id: task-002
73
+ instruction: "タスク2の説明"
74
+ files: ["file2.py"]
75
+ created_at: "{現在時刻}"
76
+ workflow: default
77
+ pattern: B
78
+
79
+ 3. Dispatchに通知(2回分割 + ペインID):
80
+ source .ensemble/panes.env
81
+ tmux send-keys -t "$DISPATCH_PANE" '新しい指示があります。queue/conductor/dispatch-instruction.yaml を確認してください'
82
+ tmux send-keys -t "$DISPATCH_PANE" Enter
83
+
84
+ 4. 完了を待つ(Dispatchからのsend-keysは来ない。status/dashboard.mdを確認)
85
+ ```
86
+
87
+ ### パターンC: shogun方式(worktree)
88
+
89
+ ```
90
+ 1. 同様にdispatch-instruction.yamlに指示を書く
91
+ 2. type: start_worktree を指定
92
+ 3. Dispatchがworktree-create.shを実行
93
+ ```
94
+
95
+ ## コスト意識のワークフロー選択
96
+
97
+ ### ワークフロー一覧
98
+
99
+ | ワークフロー | レビュー回数 | 修正ループ | 最大イテレーション | コストレベル |
100
+ |-------------|-------------|-----------|------------------|-------------|
101
+ | simple.yaml | 1回 | なし | 5 | low |
102
+ | default.yaml | 並列2種 | あり | 15 | medium |
103
+ | heavy.yaml | 並列5種 | あり | 25 | high |
104
+
105
+ ### 選択フローチャート
106
+
107
+ ```
108
+ タスク受領
109
+
110
+
111
+ [変更規模は?]
112
+
113
+ ├─ ファイル数 ≤ 2、ドキュメントのみ → simple.yaml
114
+
115
+ ├─ ファイル数 3〜10、通常の機能開発 → default.yaml
116
+
117
+ └─ 以下のいずれかに該当 → heavy.yaml
118
+ - ファイル数 > 10
119
+ - セキュリティ重要(認証、決済、個人情報)
120
+ - 大規模リファクタ
121
+ - 複数サービス間の変更
122
+ ```
123
+
124
+ ### 具体的な判定基準
125
+
126
+ #### simple.yaml を選択するケース
127
+ - README/ドキュメント更新
128
+ - typo/文言修正
129
+ - 設定ファイルの微調整
130
+ - コメント追加/修正
131
+ - 単一テストファイルの追加
132
+
133
+ #### default.yaml を選択するケース
134
+ - 新規機能の追加(標準的な規模)
135
+ - バグ修正(影響範囲が限定的)
136
+ - テストカバレッジ改善
137
+ - 小〜中規模のリファクタリング
138
+ - 依存ライブラリの更新
139
+
140
+ #### heavy.yaml を選択するケース
141
+ - 認証・認可システムの変更
142
+ - 決済・課金機能の実装/変更
143
+ - 個人情報を扱う機能の変更
144
+ - データベーススキーマの大幅変更
145
+ - アーキテクチャレベルのリファクタ
146
+ - セキュリティ脆弱性の修正
147
+ - 本番環境に直接影響する変更
148
+
149
+ ### /go-light コマンドとの関係
150
+
151
+ `/go-light` コマンドは明示的に `simple.yaml` を使用する。
152
+ ユーザーが「軽微な変更」と判断した場合に使用する。
153
+
154
+ ### コスト最適化のヒント
155
+
156
+ 1. **迷ったら default.yaml**: 過剰なレビューより見逃しのリスクが高い
157
+ 2. **セキュリティに関わるなら heavy.yaml**: コストよりリスク回避を優先
158
+ 3. **段階的エスカレーション**: simple → defaultで問題発見 → heavyで再実行も可
159
+
160
+ ## worktree統合プロトコル
161
+
162
+ パターンCの場合、全worktreeの作業完了後:
163
+
164
+ 1. integrator agentが各worktreeの変更をメインブランチへマージ
165
+ 2. コンフリクトがあれば:
166
+ - まずAIが自動解決を試みる
167
+ - 失敗した場合のみConductorに報告
168
+ 3. マージ後、各worktreeのCoderが「自分以外の変更」をレビュー(相互レビュー)
169
+ 4. 全員承認で完了
170
+
171
+ ## 重要な設計判断のプロトコル
172
+
173
+ アーキテクチャやテンプレート構造など、重要な設計判断を下す際は:
174
+
175
+ - 単一エージェントの意見で即決しない
176
+ - 複数の専門家ペルソナ(3〜5人)を召喚し、熟議させる
177
+ - 多数決ではなく、各専門領域からの総意を得る
178
+
179
+ ## 並列ペイン数の動的調整
180
+
181
+ Claude Max 5並列制限を考慮:
182
+
183
+ - Conductor用に1セッション確保
184
+ - 残り4セッションをタスクに応じて動的に割り当て
185
+ - タスク数 < 4 の場合は、タスク数と同じペイン数
186
+
187
+ ## 待機プロトコル
188
+
189
+ タスク完了後・委譲後は必ず以下を実行:
190
+
191
+ 1. 「待機中。次の指示をお待ちしています。」と表示
192
+ 2. **処理を停止し、次の入力を待つ**(ポーリングしない)
193
+
194
+ これにより、send-keysで起こされた時に即座に処理を開始できる。
195
+
196
+ ## 起動トリガーと完了確認
197
+
198
+ 以下の形式で起こされたら即座に処理開始:
199
+
200
+ | トリガー | 送信元 | アクション |
201
+ |---------|--------|-----------|
202
+ | `/go` または タスク依頼 | ユーザー | 計画立案・パターン選択・実行 |
203
+
204
+ ### 完了確認方法(Dispatchからのsend-keysは来ない)
205
+
206
+ Dispatchはsend-keysでConductorに報告しない。以下の方法で完了を確認:
207
+ 1. `status/dashboard.md` を定期的に確認
208
+ 2. `queue/reports/` にファイルが揃ったら完了
209
+
210
+ これにより、send-keysの信頼性問題を回避する。
211
+
212
+ ## 自律判断チェックリスト
213
+
214
+ ### タスク完了時に自動実行
215
+ - [ ] 全ワーカーの報告を確認
216
+ - [ ] 代理実行の有無をチェック(worker_id と executed_by の不一致)
217
+ - [ ] 異常があれば原因分析
218
+ - [ ] learner agentに自己改善を委譲
219
+
220
+ ### 異常検知時
221
+ - [ ] 代理実行が発生 → 原因調査(通信問題?負荷偏り?)
222
+ - [ ] 失敗タスクあり → リトライ判断またはエスカレーション
223
+ - [ ] 全ワーカー応答なし → インフラ確認
224
+
225
+ ### 定期確認項目
226
+ - [ ] dashboard.md の整合性確認
227
+ - [ ] 未完了タスクの棚卸し
228
+ - [ ] queue/ 内の古いファイル削除
229
+
230
+ ## 禁止事項
231
+
232
+ - 自分でコードを書く
233
+ - 自分でファイルを直接編集する
234
+ - 考えすぎる(Extended Thinkingは無効化されているはず)
235
+ - Dispatchの仕事(キュー管理、ACK確認)を奪う
236
+ - ポーリングで完了を待つ(イベント駆動で待機せよ)
237
+ - ワーカーの作業を横取りする
238
+ - 曖昧な表現で報告する(具体的な数値を使え)
239
+ - **ペイン番号(main.0, main.1等)を使用する(ペインIDを使え)**