beads-orchestration 2.0.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 +214 -0
- package/SKILL.md +263 -0
- package/bootstrap.py +928 -0
- package/package.json +37 -0
- package/scripts/cli.js +64 -0
- package/scripts/postinstall.js +71 -0
- package/skills/create-beads-orchestration/SKILL.md +263 -0
- package/skills/subagents-discipline/SKILL.md +158 -0
- package/templates/CLAUDE.md +326 -0
- package/templates/agents/architect.md +121 -0
- package/templates/agents/code-reviewer.md +248 -0
- package/templates/agents/detective.md +101 -0
- package/templates/agents/discovery.md +492 -0
- package/templates/agents/merge-supervisor.md +119 -0
- package/templates/agents/scout.md +100 -0
- package/templates/agents/scribe.md +96 -0
- package/templates/beads-workflow-injection-api.md +116 -0
- package/templates/beads-workflow-injection-git.md +108 -0
- package/templates/beads-workflow-injection.md +111 -0
- package/templates/frontend-reviews-requirement.md +61 -0
- package/templates/hooks/block-orchestrator-tools.sh +98 -0
- package/templates/hooks/clarify-vague-request.sh +39 -0
- package/templates/hooks/enforce-bead-for-supervisor.sh +32 -0
- package/templates/hooks/enforce-branch-before-edit.sh +47 -0
- package/templates/hooks/enforce-concise-response.sh +41 -0
- package/templates/hooks/enforce-sequential-dispatch.sh +63 -0
- package/templates/hooks/inject-discipline-reminder.sh +28 -0
- package/templates/hooks/log-dispatch-prompt.sh +39 -0
- package/templates/hooks/memory-capture.sh +104 -0
- package/templates/hooks/remind-inprogress.sh +14 -0
- package/templates/hooks/session-start.sh +121 -0
- package/templates/hooks/validate-completion.sh +131 -0
- package/templates/hooks/validate-epic-close.sh +84 -0
- package/templates/mcp.json.template +12 -0
- package/templates/memory/recall.sh +121 -0
- package/templates/settings.json +74 -0
- package/templates/skills/react-best-practices/SKILL.md +487 -0
- package/templates/skills/subagents-discipline/SKILL.md +127 -0
- package/templates/ui-constraints.md +76 -0
package/bootstrap.py
ADDED
|
@@ -0,0 +1,928 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Bootstrap script for beads-based orchestration.
|
|
4
|
+
|
|
5
|
+
Creates:
|
|
6
|
+
- .beads/ directory with beads CLI
|
|
7
|
+
- .claude/agents/ with agent templates (copied, not generated)
|
|
8
|
+
- .claude/hooks/ with hook scripts
|
|
9
|
+
- .claude/settings.json with hook configuration
|
|
10
|
+
- .mcp.json with provider-delegator configuration (only with --external-providers)
|
|
11
|
+
|
|
12
|
+
Usage:
|
|
13
|
+
python bootstrap.py [--project-name NAME] [--project-dir DIR] [--with-kanban-ui]
|
|
14
|
+
|
|
15
|
+
Modes:
|
|
16
|
+
Default: All agents use Claude Task() directly (claude-only)
|
|
17
|
+
--external-providers: Sets up provider_delegator MCP for Codex/Gemini delegation
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
import os
|
|
21
|
+
import sys
|
|
22
|
+
import json
|
|
23
|
+
import shutil
|
|
24
|
+
import stat
|
|
25
|
+
import subprocess
|
|
26
|
+
try:
|
|
27
|
+
import tomllib
|
|
28
|
+
except ImportError:
|
|
29
|
+
tomllib = None
|
|
30
|
+
from pathlib import Path
|
|
31
|
+
from datetime import datetime
|
|
32
|
+
import random
|
|
33
|
+
|
|
34
|
+
# Get the directory where this script lives (lean-orchestration repo)
|
|
35
|
+
SCRIPT_DIR = Path(__file__).parent.resolve()
|
|
36
|
+
TEMPLATES_DIR = SCRIPT_DIR / "templates"
|
|
37
|
+
|
|
38
|
+
# ============================================================================
|
|
39
|
+
# CONFIGURATION
|
|
40
|
+
# ============================================================================
|
|
41
|
+
|
|
42
|
+
CORE_AGENTS = ["scout", "detective", "architect", "scribe", "discovery", "merge-supervisor", "code-reviewer"]
|
|
43
|
+
|
|
44
|
+
# NOTE: Supervisors are NOT bootstrapped - they are created dynamically by the
|
|
45
|
+
# discovery agent which fetches specialists from the external agents directory
|
|
46
|
+
# and injects the beads workflow.
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
# ============================================================================
|
|
50
|
+
# PROJECT NAME INFERENCE
|
|
51
|
+
# ============================================================================
|
|
52
|
+
|
|
53
|
+
def infer_project_name(project_dir: Path) -> str:
|
|
54
|
+
"""Auto-infer project name from package files or directory name."""
|
|
55
|
+
|
|
56
|
+
# Try package.json (Node.js)
|
|
57
|
+
package_json = project_dir / "package.json"
|
|
58
|
+
if package_json.exists():
|
|
59
|
+
try:
|
|
60
|
+
data = json.loads(package_json.read_text())
|
|
61
|
+
if name := data.get("name"):
|
|
62
|
+
return name.replace("-", " ").replace("_", " ").title()
|
|
63
|
+
except (json.JSONDecodeError, KeyError):
|
|
64
|
+
pass
|
|
65
|
+
|
|
66
|
+
# Try pyproject.toml (Python)
|
|
67
|
+
if tomllib:
|
|
68
|
+
pyproject = project_dir / "pyproject.toml"
|
|
69
|
+
if pyproject.exists():
|
|
70
|
+
try:
|
|
71
|
+
data = tomllib.loads(pyproject.read_text())
|
|
72
|
+
if name := data.get("project", {}).get("name"):
|
|
73
|
+
return name.replace("-", " ").replace("_", " ").title()
|
|
74
|
+
if name := data.get("tool", {}).get("poetry", {}).get("name"):
|
|
75
|
+
return name.replace("-", " ").replace("_", " ").title()
|
|
76
|
+
except Exception:
|
|
77
|
+
pass
|
|
78
|
+
|
|
79
|
+
# Try Cargo.toml (Rust)
|
|
80
|
+
cargo = project_dir / "Cargo.toml"
|
|
81
|
+
if cargo.exists():
|
|
82
|
+
try:
|
|
83
|
+
data = tomllib.loads(cargo.read_text())
|
|
84
|
+
if name := data.get("package", {}).get("name"):
|
|
85
|
+
return name.replace("-", " ").replace("_", " ").title()
|
|
86
|
+
except Exception:
|
|
87
|
+
pass
|
|
88
|
+
|
|
89
|
+
# Try go.mod (Go)
|
|
90
|
+
go_mod = project_dir / "go.mod"
|
|
91
|
+
if go_mod.exists():
|
|
92
|
+
try:
|
|
93
|
+
content = go_mod.read_text()
|
|
94
|
+
for line in content.splitlines():
|
|
95
|
+
if line.startswith("module "):
|
|
96
|
+
module_path = line.split()[1]
|
|
97
|
+
name = module_path.split("/")[-1]
|
|
98
|
+
return name.replace("-", " ").replace("_", " ").title()
|
|
99
|
+
except Exception:
|
|
100
|
+
pass
|
|
101
|
+
|
|
102
|
+
# Fallback to directory name
|
|
103
|
+
return project_dir.name.replace("-", " ").replace("_", " ").title()
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
# ============================================================================
|
|
107
|
+
# PLACEHOLDER REPLACEMENT
|
|
108
|
+
# ============================================================================
|
|
109
|
+
|
|
110
|
+
def replace_placeholders(content: str, replacements: dict) -> str:
|
|
111
|
+
"""Replace all placeholders in content."""
|
|
112
|
+
for placeholder, value in replacements.items():
|
|
113
|
+
content = content.replace(placeholder, value)
|
|
114
|
+
return content
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def copy_and_replace(source: Path, dest: Path, replacements: dict) -> None:
|
|
118
|
+
"""Copy file and replace placeholders."""
|
|
119
|
+
content = source.read_text()
|
|
120
|
+
updated = replace_placeholders(content, replacements)
|
|
121
|
+
dest.parent.mkdir(parents=True, exist_ok=True)
|
|
122
|
+
dest.write_text(updated)
|
|
123
|
+
|
|
124
|
+
# Preserve executable permissions for shell scripts
|
|
125
|
+
if source.suffix == '.sh':
|
|
126
|
+
dest.chmod(dest.stat().st_mode | stat.S_IEXEC | stat.S_IXGRP | stat.S_IXOTH)
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
# ============================================================================
|
|
130
|
+
# CODEX DELEGATOR SETUP (SHARED LOCATION)
|
|
131
|
+
# ============================================================================
|
|
132
|
+
|
|
133
|
+
# Shared location for provider-delegator (installed once, used by all projects)
|
|
134
|
+
SHARED_MCP_DIR = Path.home() / ".claude" / "mcp-servers" / "provider-delegator"
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def setup_provider_delegator() -> Path:
|
|
138
|
+
"""Set up provider-delegator in shared location (~/.claude/mcp-servers/provider-delegator/).
|
|
139
|
+
|
|
140
|
+
This installs once and is reused by all projects.
|
|
141
|
+
Returns path to venv python.
|
|
142
|
+
"""
|
|
143
|
+
print("\n[0/8] Setting up provider-delegator (shared)...")
|
|
144
|
+
|
|
145
|
+
source_dir = SCRIPT_DIR / "mcp-provider-delegator"
|
|
146
|
+
venv_dir = SHARED_MCP_DIR / ".venv"
|
|
147
|
+
venv_python = venv_dir / "bin" / "python"
|
|
148
|
+
|
|
149
|
+
# Check if already installed in shared location
|
|
150
|
+
if venv_python.exists():
|
|
151
|
+
print(f" - Already installed at {SHARED_MCP_DIR}")
|
|
152
|
+
return venv_python
|
|
153
|
+
|
|
154
|
+
# Verify source exists
|
|
155
|
+
if not source_dir.exists():
|
|
156
|
+
print(f" ERROR: mcp-provider-delegator not found at {source_dir}")
|
|
157
|
+
print(" Make sure you cloned the full lean-orchestration repo")
|
|
158
|
+
return None
|
|
159
|
+
|
|
160
|
+
# Check if uv is available
|
|
161
|
+
if not shutil.which("uv"):
|
|
162
|
+
print(" ERROR: 'uv' not found. Install with: curl -LsSf https://astral.sh/uv/install.sh | sh")
|
|
163
|
+
return None
|
|
164
|
+
|
|
165
|
+
# Create shared directory
|
|
166
|
+
print(f" - Installing to {SHARED_MCP_DIR}")
|
|
167
|
+
SHARED_MCP_DIR.mkdir(parents=True, exist_ok=True)
|
|
168
|
+
|
|
169
|
+
# Copy source to shared location
|
|
170
|
+
print(" - Copying source files...")
|
|
171
|
+
for item in source_dir.iterdir():
|
|
172
|
+
if item.name == ".venv":
|
|
173
|
+
continue # Skip any existing venv in source
|
|
174
|
+
dest = SHARED_MCP_DIR / item.name
|
|
175
|
+
if item.is_dir():
|
|
176
|
+
if dest.exists():
|
|
177
|
+
shutil.rmtree(dest)
|
|
178
|
+
shutil.copytree(item, dest)
|
|
179
|
+
else:
|
|
180
|
+
shutil.copy2(item, dest)
|
|
181
|
+
|
|
182
|
+
# Create venv using uv
|
|
183
|
+
print(" - Creating venv with uv...")
|
|
184
|
+
result = subprocess.run(
|
|
185
|
+
["uv", "venv", str(venv_dir)],
|
|
186
|
+
cwd=SHARED_MCP_DIR,
|
|
187
|
+
capture_output=True,
|
|
188
|
+
text=True
|
|
189
|
+
)
|
|
190
|
+
if result.returncode != 0:
|
|
191
|
+
print(f" ERROR: Failed to create venv: {result.stderr}")
|
|
192
|
+
return None
|
|
193
|
+
|
|
194
|
+
# Install dependencies
|
|
195
|
+
print(" - Installing dependencies...")
|
|
196
|
+
result = subprocess.run(
|
|
197
|
+
["uv", "pip", "install", "-e", "."],
|
|
198
|
+
cwd=SHARED_MCP_DIR,
|
|
199
|
+
capture_output=True,
|
|
200
|
+
text=True,
|
|
201
|
+
env={**os.environ, "VIRTUAL_ENV": str(venv_dir)}
|
|
202
|
+
)
|
|
203
|
+
if result.returncode != 0:
|
|
204
|
+
print(f" ERROR: Failed to install dependencies: {result.stderr}")
|
|
205
|
+
return None
|
|
206
|
+
|
|
207
|
+
print(f" DONE: provider-delegator installed at {SHARED_MCP_DIR}")
|
|
208
|
+
return venv_python
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
# ============================================================================
|
|
212
|
+
# BEADS INSTALLATION
|
|
213
|
+
# ============================================================================
|
|
214
|
+
|
|
215
|
+
def install_beads(project_dir: Path, claude_only: bool = False) -> bool:
|
|
216
|
+
"""Install beads CLI and initialize .beads directory."""
|
|
217
|
+
step = "[1/7]" if claude_only else "[1/8]"
|
|
218
|
+
print(f"\n{step} Installing beads...")
|
|
219
|
+
|
|
220
|
+
beads_dir = project_dir / ".beads"
|
|
221
|
+
|
|
222
|
+
# Check if beads is already installed globally
|
|
223
|
+
beads_installed = shutil.which("bd") is not None
|
|
224
|
+
|
|
225
|
+
if not beads_installed:
|
|
226
|
+
print(" - beads CLI (bd) not found, installing...")
|
|
227
|
+
|
|
228
|
+
# Try installation methods in order of preference
|
|
229
|
+
installed = False
|
|
230
|
+
|
|
231
|
+
# Method 1: Homebrew (macOS)
|
|
232
|
+
if shutil.which("brew") and sys.platform == "darwin":
|
|
233
|
+
print(" - Trying Homebrew...")
|
|
234
|
+
result = subprocess.run(
|
|
235
|
+
["brew", "install", "steveyegge/beads/bd"],
|
|
236
|
+
capture_output=True,
|
|
237
|
+
text=True
|
|
238
|
+
)
|
|
239
|
+
if result.returncode == 0:
|
|
240
|
+
installed = True
|
|
241
|
+
print(" - Installed via Homebrew")
|
|
242
|
+
|
|
243
|
+
# Method 2: npm (cross-platform)
|
|
244
|
+
if not installed and shutil.which("npm"):
|
|
245
|
+
print(" - Trying npm...")
|
|
246
|
+
result = subprocess.run(
|
|
247
|
+
["npm", "install", "-g", "@beads/bd"],
|
|
248
|
+
capture_output=True,
|
|
249
|
+
text=True
|
|
250
|
+
)
|
|
251
|
+
if result.returncode == 0:
|
|
252
|
+
installed = True
|
|
253
|
+
print(" - Installed via npm")
|
|
254
|
+
|
|
255
|
+
# Method 3: curl install script (Linux/macOS/FreeBSD)
|
|
256
|
+
if not installed and sys.platform != "win32":
|
|
257
|
+
print(" - Trying curl install script...")
|
|
258
|
+
result = subprocess.run(
|
|
259
|
+
["bash", "-c", "curl -fsSL https://raw.githubusercontent.com/steveyegge/beads/main/scripts/install.sh | bash"],
|
|
260
|
+
capture_output=True,
|
|
261
|
+
text=True
|
|
262
|
+
)
|
|
263
|
+
if result.returncode == 0:
|
|
264
|
+
installed = True
|
|
265
|
+
print(" - Installed via curl script")
|
|
266
|
+
|
|
267
|
+
# Method 4: Go install (if Go is available)
|
|
268
|
+
if not installed and shutil.which("go"):
|
|
269
|
+
print(" - Trying go install...")
|
|
270
|
+
result = subprocess.run(
|
|
271
|
+
["go", "install", "github.com/steveyegge/beads/cmd/bd@latest"],
|
|
272
|
+
capture_output=True,
|
|
273
|
+
text=True
|
|
274
|
+
)
|
|
275
|
+
if result.returncode == 0:
|
|
276
|
+
installed = True
|
|
277
|
+
print(" - Installed via go install")
|
|
278
|
+
|
|
279
|
+
if not installed:
|
|
280
|
+
print("\n ERROR: Could not install beads CLI (bd)")
|
|
281
|
+
print(" The beads workflow requires the bd command.")
|
|
282
|
+
print(" Please install manually: https://github.com/steveyegge/beads#-installation")
|
|
283
|
+
print("\n Installation options:")
|
|
284
|
+
print(" macOS: brew install steveyegge/beads/bd")
|
|
285
|
+
print(" npm: npm install -g @beads/bd")
|
|
286
|
+
print(" Go: go install github.com/steveyegge/beads/cmd/bd@latest")
|
|
287
|
+
return False
|
|
288
|
+
else:
|
|
289
|
+
print(" - beads CLI already installed")
|
|
290
|
+
|
|
291
|
+
beads_installed = True
|
|
292
|
+
|
|
293
|
+
# Initialize .beads in project
|
|
294
|
+
if not beads_dir.exists():
|
|
295
|
+
print(" - Initializing .beads directory...")
|
|
296
|
+
|
|
297
|
+
# Try bd init first
|
|
298
|
+
if shutil.which("bd"):
|
|
299
|
+
result = subprocess.run(
|
|
300
|
+
["bd", "init"],
|
|
301
|
+
cwd=project_dir,
|
|
302
|
+
capture_output=True,
|
|
303
|
+
text=True
|
|
304
|
+
)
|
|
305
|
+
if result.returncode == 0:
|
|
306
|
+
print(" - Initialized via 'bd init'")
|
|
307
|
+
else:
|
|
308
|
+
# Manual init as fallback
|
|
309
|
+
_manual_beads_init(beads_dir)
|
|
310
|
+
else:
|
|
311
|
+
_manual_beads_init(beads_dir)
|
|
312
|
+
else:
|
|
313
|
+
print(" - .beads already exists")
|
|
314
|
+
|
|
315
|
+
# Configure custom 'inreview' status for parallel work workflow
|
|
316
|
+
if shutil.which("bd"):
|
|
317
|
+
print(" - Configuring custom 'inreview' status...")
|
|
318
|
+
result = subprocess.run(
|
|
319
|
+
["bd", "config", "set", "status.custom", "inreview"],
|
|
320
|
+
cwd=project_dir,
|
|
321
|
+
capture_output=True,
|
|
322
|
+
text=True
|
|
323
|
+
)
|
|
324
|
+
if result.returncode == 0:
|
|
325
|
+
print(" - Added 'inreview' custom status")
|
|
326
|
+
else:
|
|
327
|
+
print(f" - Warning: Could not add custom status: {result.stderr}")
|
|
328
|
+
|
|
329
|
+
print(" DONE: beads setup complete")
|
|
330
|
+
return True
|
|
331
|
+
|
|
332
|
+
|
|
333
|
+
def _manual_beads_init(beads_dir: Path):
|
|
334
|
+
"""Manually create .beads directory structure."""
|
|
335
|
+
beads_dir.mkdir(exist_ok=True)
|
|
336
|
+
(beads_dir / "issues.jsonl").touch()
|
|
337
|
+
# Create minimal config
|
|
338
|
+
config = {
|
|
339
|
+
"version": "1",
|
|
340
|
+
"mode": "normal"
|
|
341
|
+
}
|
|
342
|
+
(beads_dir / "config.json").write_text(json.dumps(config, indent=2))
|
|
343
|
+
print(" - Created .beads manually")
|
|
344
|
+
|
|
345
|
+
|
|
346
|
+
def setup_memory(project_dir: Path) -> None:
|
|
347
|
+
"""Create .beads/memory/ directory with knowledge store and recall script."""
|
|
348
|
+
memory_dir = project_dir / ".beads" / "memory"
|
|
349
|
+
memory_dir.mkdir(parents=True, exist_ok=True)
|
|
350
|
+
|
|
351
|
+
# Create empty knowledge store
|
|
352
|
+
knowledge_file = memory_dir / "knowledge.jsonl"
|
|
353
|
+
if not knowledge_file.exists():
|
|
354
|
+
knowledge_file.touch()
|
|
355
|
+
print(" - Created .beads/memory/knowledge.jsonl")
|
|
356
|
+
|
|
357
|
+
# Copy recall script
|
|
358
|
+
recall_src = TEMPLATES_DIR / "memory" / "recall.sh"
|
|
359
|
+
recall_dest = memory_dir / "recall.sh"
|
|
360
|
+
if recall_src.exists():
|
|
361
|
+
shutil.copy2(recall_src, recall_dest)
|
|
362
|
+
recall_dest.chmod(recall_dest.stat().st_mode | stat.S_IEXEC | stat.S_IXGRP | stat.S_IXOTH)
|
|
363
|
+
print(" - Copied .beads/memory/recall.sh")
|
|
364
|
+
else:
|
|
365
|
+
print(" - WARNING: recall.sh template not found")
|
|
366
|
+
|
|
367
|
+
|
|
368
|
+
# ============================================================================
|
|
369
|
+
# RAMS INSTALLATION (Accessibility Review)
|
|
370
|
+
# ============================================================================
|
|
371
|
+
|
|
372
|
+
def install_rams() -> bool:
|
|
373
|
+
"""Install RAMS accessibility review tool if not already installed."""
|
|
374
|
+
print("\n Checking RAMS (accessibility review tool)...")
|
|
375
|
+
|
|
376
|
+
# Check if rams is already installed
|
|
377
|
+
if shutil.which("rams"):
|
|
378
|
+
print(" - RAMS already installed")
|
|
379
|
+
return True
|
|
380
|
+
|
|
381
|
+
print(" - RAMS not found, installing...")
|
|
382
|
+
|
|
383
|
+
# Install via curl
|
|
384
|
+
if sys.platform != "win32":
|
|
385
|
+
result = subprocess.run(
|
|
386
|
+
["bash", "-c", "curl -fsSL https://rams.ai/install | bash"],
|
|
387
|
+
capture_output=True,
|
|
388
|
+
text=True
|
|
389
|
+
)
|
|
390
|
+
if result.returncode == 0:
|
|
391
|
+
print(" - RAMS installed successfully")
|
|
392
|
+
return True
|
|
393
|
+
else:
|
|
394
|
+
print(f" - Warning: Could not install RAMS: {result.stderr}")
|
|
395
|
+
print(" - Frontend supervisors will still work but RAMS review enforcement may fail")
|
|
396
|
+
print(" - Install manually: curl -fsSL https://rams.ai/install | bash")
|
|
397
|
+
return False
|
|
398
|
+
|
|
399
|
+
print(" - Warning: RAMS installation not supported on Windows")
|
|
400
|
+
return False
|
|
401
|
+
|
|
402
|
+
|
|
403
|
+
# ============================================================================
|
|
404
|
+
# WEB INTERFACE GUIDELINES INSTALLATION
|
|
405
|
+
# ============================================================================
|
|
406
|
+
|
|
407
|
+
def install_web_interface_guidelines() -> bool:
|
|
408
|
+
"""Install Web Interface Guidelines review tool if not already installed."""
|
|
409
|
+
print("\n Checking Web Interface Guidelines (design review tool)...")
|
|
410
|
+
|
|
411
|
+
# Check if wig is already installed
|
|
412
|
+
if shutil.which("wig"):
|
|
413
|
+
print(" - Web Interface Guidelines already installed")
|
|
414
|
+
return True
|
|
415
|
+
|
|
416
|
+
print(" - Web Interface Guidelines not found, installing...")
|
|
417
|
+
|
|
418
|
+
# Install via curl
|
|
419
|
+
if sys.platform != "win32":
|
|
420
|
+
result = subprocess.run(
|
|
421
|
+
["bash", "-c", "curl -fsSL https://vercel.com/design/guidelines/install | bash"],
|
|
422
|
+
capture_output=True,
|
|
423
|
+
text=True
|
|
424
|
+
)
|
|
425
|
+
if result.returncode == 0:
|
|
426
|
+
print(" - Web Interface Guidelines installed successfully")
|
|
427
|
+
return True
|
|
428
|
+
else:
|
|
429
|
+
print(f" - Warning: Could not install Web Interface Guidelines: {result.stderr}")
|
|
430
|
+
print(" - Frontend supervisors will still work but WIG review enforcement may fail")
|
|
431
|
+
print(" - Install manually: curl -fsSL https://vercel.com/design/guidelines/install | bash")
|
|
432
|
+
return False
|
|
433
|
+
|
|
434
|
+
print(" - Warning: Web Interface Guidelines installation not supported on Windows")
|
|
435
|
+
return False
|
|
436
|
+
|
|
437
|
+
|
|
438
|
+
# ============================================================================
|
|
439
|
+
# AGENTS (TEMPLATE COPYING)
|
|
440
|
+
# ============================================================================
|
|
441
|
+
|
|
442
|
+
def copy_agents(project_dir: Path, project_name: str, claude_only: bool = False, with_kanban_ui: bool = False) -> list:
|
|
443
|
+
"""Copy core agent templates from templates/ directory.
|
|
444
|
+
|
|
445
|
+
NOTE: Supervisors are NOT copied here - they are created dynamically
|
|
446
|
+
by the discovery agent based on detected tech stack.
|
|
447
|
+
"""
|
|
448
|
+
step = "[2/7]" if claude_only else "[2/8]"
|
|
449
|
+
print(f"\n{step} Copying core agent templates...")
|
|
450
|
+
|
|
451
|
+
agents_dir = project_dir / ".claude" / "agents"
|
|
452
|
+
agents_dir.mkdir(parents=True, exist_ok=True)
|
|
453
|
+
|
|
454
|
+
agents_template_dir = TEMPLATES_DIR / "agents"
|
|
455
|
+
|
|
456
|
+
copied = []
|
|
457
|
+
|
|
458
|
+
# Replacements for templates
|
|
459
|
+
replacements = {
|
|
460
|
+
"[Project]": project_name,
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
# Copy core agents ONLY (not supervisors)
|
|
464
|
+
for agent_file in agents_template_dir.glob("*.md"):
|
|
465
|
+
dest = agents_dir / agent_file.name
|
|
466
|
+
copy_and_replace(agent_file, dest, replacements)
|
|
467
|
+
copied.append(agent_file.name)
|
|
468
|
+
print(f" - Copied {agent_file.name}")
|
|
469
|
+
|
|
470
|
+
# Copy beads workflow injection snippet (used by discovery agent)
|
|
471
|
+
# Select API version (with git fallback) or git-only version based on flag
|
|
472
|
+
if with_kanban_ui:
|
|
473
|
+
beads_workflow_src = TEMPLATES_DIR / "beads-workflow-injection-api.md"
|
|
474
|
+
workflow_type = "API + git fallback"
|
|
475
|
+
else:
|
|
476
|
+
beads_workflow_src = TEMPLATES_DIR / "beads-workflow-injection-git.md"
|
|
477
|
+
workflow_type = "git only"
|
|
478
|
+
beads_workflow_dest = project_dir / ".claude" / "beads-workflow-injection.md"
|
|
479
|
+
if beads_workflow_src.exists():
|
|
480
|
+
shutil.copy2(beads_workflow_src, beads_workflow_dest)
|
|
481
|
+
print(f" - Copied beads-workflow-injection.md ({workflow_type})")
|
|
482
|
+
|
|
483
|
+
# Copy UI constraints (used by discovery agent for frontend supervisors)
|
|
484
|
+
ui_constraints_src = TEMPLATES_DIR / "ui-constraints.md"
|
|
485
|
+
ui_constraints_dest = project_dir / ".claude" / "ui-constraints.md"
|
|
486
|
+
if ui_constraints_src.exists():
|
|
487
|
+
shutil.copy2(ui_constraints_src, ui_constraints_dest)
|
|
488
|
+
print(" - Copied ui-constraints.md")
|
|
489
|
+
|
|
490
|
+
# Copy frontend reviews requirement (RAMS + Web Interface Guidelines)
|
|
491
|
+
frontend_reviews_src = TEMPLATES_DIR / "frontend-reviews-requirement.md"
|
|
492
|
+
frontend_reviews_dest = project_dir / ".claude" / "frontend-reviews-requirement.md"
|
|
493
|
+
if frontend_reviews_src.exists():
|
|
494
|
+
shutil.copy2(frontend_reviews_src, frontend_reviews_dest)
|
|
495
|
+
print(" - Copied frontend-reviews-requirement.md")
|
|
496
|
+
|
|
497
|
+
print(f" DONE: {len(copied)} core agents copied")
|
|
498
|
+
print(" NOTE: Supervisors will be created by discovery agent based on tech stack")
|
|
499
|
+
return copied
|
|
500
|
+
|
|
501
|
+
|
|
502
|
+
# ============================================================================
|
|
503
|
+
# SKILLS (TEMPLATE COPYING)
|
|
504
|
+
# ============================================================================
|
|
505
|
+
|
|
506
|
+
def copy_skills(project_dir: Path, claude_only: bool = False) -> list:
|
|
507
|
+
"""Copy skill templates from templates/ directory.
|
|
508
|
+
|
|
509
|
+
Skills are copied so discovery agent can install them when tech stack is detected.
|
|
510
|
+
"""
|
|
511
|
+
step = "[3/7]" if claude_only else "[3/8]"
|
|
512
|
+
print(f"\n{step} Copying skill templates...")
|
|
513
|
+
|
|
514
|
+
skills_template_dir = TEMPLATES_DIR / "skills"
|
|
515
|
+
if not skills_template_dir.exists():
|
|
516
|
+
print(" - No skill templates found, skipping")
|
|
517
|
+
return []
|
|
518
|
+
|
|
519
|
+
skills_dir = project_dir / ".claude" / "skills"
|
|
520
|
+
skills_dir.mkdir(parents=True, exist_ok=True)
|
|
521
|
+
|
|
522
|
+
copied = []
|
|
523
|
+
|
|
524
|
+
for skill_dir in skills_template_dir.iterdir():
|
|
525
|
+
if skill_dir.is_dir():
|
|
526
|
+
dest_dir = skills_dir / skill_dir.name
|
|
527
|
+
if dest_dir.exists():
|
|
528
|
+
shutil.rmtree(dest_dir)
|
|
529
|
+
shutil.copytree(skill_dir, dest_dir)
|
|
530
|
+
copied.append(skill_dir.name)
|
|
531
|
+
print(f" - Copied {skill_dir.name}/ skill")
|
|
532
|
+
|
|
533
|
+
print(f" DONE: {len(copied)} skill templates copied")
|
|
534
|
+
return copied
|
|
535
|
+
|
|
536
|
+
|
|
537
|
+
# ============================================================================
|
|
538
|
+
# HOOKS (TEMPLATE COPYING)
|
|
539
|
+
# ============================================================================
|
|
540
|
+
|
|
541
|
+
def copy_hooks(project_dir: Path, claude_only: bool = False) -> list:
|
|
542
|
+
"""Copy hook templates from templates/ directory.
|
|
543
|
+
|
|
544
|
+
Args:
|
|
545
|
+
project_dir: Target project directory
|
|
546
|
+
claude_only: If True, skip provider delegation enforcement hooks
|
|
547
|
+
"""
|
|
548
|
+
step = "[4/7]" if claude_only else "[4/8]"
|
|
549
|
+
print(f"\n{step} Copying hook templates...")
|
|
550
|
+
|
|
551
|
+
hooks_dir = project_dir / ".claude" / "hooks"
|
|
552
|
+
hooks_dir.mkdir(parents=True, exist_ok=True)
|
|
553
|
+
|
|
554
|
+
hooks_template_dir = TEMPLATES_DIR / "hooks"
|
|
555
|
+
copied = []
|
|
556
|
+
|
|
557
|
+
# Hooks to skip in claude-only mode (none currently - all hooks apply to both modes)
|
|
558
|
+
skip_in_claude_only = set()
|
|
559
|
+
|
|
560
|
+
for hook_file in hooks_template_dir.glob("*.sh"):
|
|
561
|
+
# Skip provider enforcement hooks in claude-only mode
|
|
562
|
+
if claude_only and hook_file.name in skip_in_claude_only:
|
|
563
|
+
print(f" - Skipped {hook_file.name} (claude-only mode)")
|
|
564
|
+
continue
|
|
565
|
+
|
|
566
|
+
dest = hooks_dir / hook_file.name
|
|
567
|
+
shutil.copy2(hook_file, dest)
|
|
568
|
+
dest.chmod(dest.stat().st_mode | stat.S_IEXEC | stat.S_IXGRP | stat.S_IXOTH)
|
|
569
|
+
copied.append(hook_file.name)
|
|
570
|
+
print(f" - Copied {hook_file.name}")
|
|
571
|
+
|
|
572
|
+
print(f" DONE: {len(copied)} hooks copied")
|
|
573
|
+
return copied
|
|
574
|
+
|
|
575
|
+
|
|
576
|
+
# ============================================================================
|
|
577
|
+
# SETTINGS
|
|
578
|
+
# ============================================================================
|
|
579
|
+
|
|
580
|
+
def copy_settings(project_dir: Path, claude_only: bool = False) -> None:
|
|
581
|
+
"""Copy settings.json template, optionally removing provider enforcement hooks.
|
|
582
|
+
|
|
583
|
+
Args:
|
|
584
|
+
project_dir: Target project directory
|
|
585
|
+
claude_only: If True, remove provider delegation enforcement from settings
|
|
586
|
+
"""
|
|
587
|
+
step = "[5/7]" if claude_only else "[5/8]"
|
|
588
|
+
print(f"\n{step} Copying settings...")
|
|
589
|
+
|
|
590
|
+
settings_template = TEMPLATES_DIR / "settings.json"
|
|
591
|
+
settings_dest = project_dir / ".claude" / "settings.json"
|
|
592
|
+
|
|
593
|
+
# Settings are the same for both modes now (no provider-specific hooks)
|
|
594
|
+
shutil.copy2(settings_template, settings_dest)
|
|
595
|
+
if claude_only:
|
|
596
|
+
print(" - Copied settings.json (claude-only mode)")
|
|
597
|
+
else:
|
|
598
|
+
print(" - Copied settings.json")
|
|
599
|
+
|
|
600
|
+
print(" DONE: settings configured")
|
|
601
|
+
|
|
602
|
+
|
|
603
|
+
# ============================================================================
|
|
604
|
+
# CLAUDE.MD
|
|
605
|
+
# ============================================================================
|
|
606
|
+
|
|
607
|
+
def copy_claude_md(project_dir: Path, project_name: str, claude_only: bool = False) -> None:
|
|
608
|
+
"""Copy CLAUDE.md template with project name replacement."""
|
|
609
|
+
step = "[6/7]" if claude_only else "[6/8]"
|
|
610
|
+
print(f"\n{step} Copying CLAUDE.md...")
|
|
611
|
+
|
|
612
|
+
claude_template = TEMPLATES_DIR / "CLAUDE.md"
|
|
613
|
+
claude_dest = project_dir / "CLAUDE.md"
|
|
614
|
+
|
|
615
|
+
replacements = {"[Project]": project_name}
|
|
616
|
+
copy_and_replace(claude_template, claude_dest, replacements)
|
|
617
|
+
|
|
618
|
+
print(" - Copied CLAUDE.md")
|
|
619
|
+
print(" DONE: CLAUDE.md copied")
|
|
620
|
+
|
|
621
|
+
|
|
622
|
+
# ============================================================================
|
|
623
|
+
# GITIGNORE
|
|
624
|
+
# ============================================================================
|
|
625
|
+
|
|
626
|
+
def setup_gitignore(project_dir: Path, claude_only: bool = False) -> None:
|
|
627
|
+
"""Ensure .beads is in .gitignore. .claude/ is tracked (not ignored)."""
|
|
628
|
+
step = "[7/7]" if claude_only else "[7/8]"
|
|
629
|
+
print(f"\n{step} Setting up .gitignore...")
|
|
630
|
+
|
|
631
|
+
gitignore_path = project_dir / ".gitignore"
|
|
632
|
+
# Only ignore .beads/ (ephemeral task data) and .mcp.json (user-specific paths)
|
|
633
|
+
# .claude/ is tracked so it survives git operations
|
|
634
|
+
entries_to_add = [".beads/", ".mcp.json"]
|
|
635
|
+
|
|
636
|
+
if gitignore_path.exists():
|
|
637
|
+
content = gitignore_path.read_text()
|
|
638
|
+
lines = content.splitlines()
|
|
639
|
+
|
|
640
|
+
# Check which entries are missing
|
|
641
|
+
missing = []
|
|
642
|
+
for entry in entries_to_add:
|
|
643
|
+
# Check for exact match or without trailing slash
|
|
644
|
+
entry_no_slash = entry.rstrip("/")
|
|
645
|
+
if entry not in lines and entry_no_slash not in lines:
|
|
646
|
+
missing.append(entry)
|
|
647
|
+
|
|
648
|
+
if missing:
|
|
649
|
+
# Append missing entries
|
|
650
|
+
with open(gitignore_path, "a") as f:
|
|
651
|
+
# Add newline if file doesn't end with one
|
|
652
|
+
if content and not content.endswith("\n"):
|
|
653
|
+
f.write("\n")
|
|
654
|
+
f.write("\n# Beads task tracking (ephemeral)\n")
|
|
655
|
+
for entry in missing:
|
|
656
|
+
f.write(f"{entry}\n")
|
|
657
|
+
print(f" - Added {entry} to .gitignore")
|
|
658
|
+
else:
|
|
659
|
+
print(" - .beads/ and .mcp.json already in .gitignore")
|
|
660
|
+
else:
|
|
661
|
+
# Create new .gitignore
|
|
662
|
+
content = """# Beads task tracking (ephemeral)
|
|
663
|
+
.beads/
|
|
664
|
+
|
|
665
|
+
# MCP config (user-specific paths)
|
|
666
|
+
.mcp.json
|
|
667
|
+
"""
|
|
668
|
+
gitignore_path.write_text(content)
|
|
669
|
+
print(" - Created .gitignore with .beads/ and .mcp.json")
|
|
670
|
+
|
|
671
|
+
print(" DONE: .gitignore configured")
|
|
672
|
+
print(" NOTE: .claude/ is tracked (not ignored) to prevent accidental loss")
|
|
673
|
+
|
|
674
|
+
|
|
675
|
+
# ============================================================================
|
|
676
|
+
# MCP CONFIG
|
|
677
|
+
# ============================================================================
|
|
678
|
+
|
|
679
|
+
def create_mcp_config(project_dir: Path, venv_python: Path) -> None:
|
|
680
|
+
"""Add provider-delegator to .mcp.json, preserving existing servers."""
|
|
681
|
+
print("\n[8/8] Configuring MCP...")
|
|
682
|
+
|
|
683
|
+
mcp_dest = project_dir / ".mcp.json"
|
|
684
|
+
|
|
685
|
+
# Load existing config or start fresh
|
|
686
|
+
if mcp_dest.exists():
|
|
687
|
+
try:
|
|
688
|
+
existing = json.loads(mcp_dest.read_text())
|
|
689
|
+
print(" - Found existing .mcp.json, merging...")
|
|
690
|
+
except json.JSONDecodeError:
|
|
691
|
+
print(" - Warning: Invalid .mcp.json, creating new one")
|
|
692
|
+
existing = {}
|
|
693
|
+
else:
|
|
694
|
+
existing = {}
|
|
695
|
+
|
|
696
|
+
# Ensure mcpServers key exists
|
|
697
|
+
if "mcpServers" not in existing:
|
|
698
|
+
existing["mcpServers"] = {}
|
|
699
|
+
|
|
700
|
+
# Add/update provider_delegator
|
|
701
|
+
existing["mcpServers"]["provider_delegator"] = {
|
|
702
|
+
"type": "stdio",
|
|
703
|
+
"command": str(venv_python),
|
|
704
|
+
"args": ["-m", "mcp_provider_delegator.server"],
|
|
705
|
+
"env": {
|
|
706
|
+
"AGENT_TEMPLATES_PATH": ".claude/agents"
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
mcp_dest.write_text(json.dumps(existing, indent=2))
|
|
711
|
+
|
|
712
|
+
server_count = len(existing["mcpServers"])
|
|
713
|
+
print(f" - Added provider-delegator to .mcp.json ({server_count} total servers)")
|
|
714
|
+
print(f" Command: {venv_python}")
|
|
715
|
+
print(f" Agents: .claude/agents (relative)")
|
|
716
|
+
print(" DONE: MCP config updated")
|
|
717
|
+
|
|
718
|
+
|
|
719
|
+
# ============================================================================
|
|
720
|
+
# VERIFICATION
|
|
721
|
+
# ============================================================================
|
|
722
|
+
|
|
723
|
+
def verify_installation(project_dir: Path, claude_only: bool = False) -> bool:
|
|
724
|
+
"""Verify all components were installed correctly."""
|
|
725
|
+
checks = {
|
|
726
|
+
".claude/hooks": "Hooks directory",
|
|
727
|
+
".claude/agents": "Agents directory",
|
|
728
|
+
".claude/settings.json": "Settings file",
|
|
729
|
+
".beads": "Beads directory",
|
|
730
|
+
"CLAUDE.md": "CLAUDE.md",
|
|
731
|
+
".gitignore": ".gitignore",
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
# Only check for .mcp.json in external providers mode
|
|
735
|
+
if not claude_only:
|
|
736
|
+
checks[".mcp.json"] = "MCP config"
|
|
737
|
+
|
|
738
|
+
print("\n=== Verification ===")
|
|
739
|
+
all_good = True
|
|
740
|
+
|
|
741
|
+
for path, description in checks.items():
|
|
742
|
+
full_path = project_dir / path
|
|
743
|
+
if full_path.exists():
|
|
744
|
+
print(f" - {description}")
|
|
745
|
+
else:
|
|
746
|
+
print(f" X {description} MISSING")
|
|
747
|
+
all_good = False
|
|
748
|
+
|
|
749
|
+
# Count files
|
|
750
|
+
hooks_dir = project_dir / ".claude/hooks"
|
|
751
|
+
if hooks_dir.exists():
|
|
752
|
+
hook_count = len(list(hooks_dir.glob("*.sh")))
|
|
753
|
+
print(f" - Hooks: {hook_count}")
|
|
754
|
+
|
|
755
|
+
agents_dir = project_dir / ".claude/agents"
|
|
756
|
+
if agents_dir.exists():
|
|
757
|
+
agent_count = len(list(agents_dir.glob("*.md")))
|
|
758
|
+
print(f" - Agents: {agent_count}")
|
|
759
|
+
|
|
760
|
+
skills_dir = project_dir / ".claude/skills"
|
|
761
|
+
if skills_dir.exists():
|
|
762
|
+
skill_count = len(list(skills_dir.iterdir()))
|
|
763
|
+
if skill_count > 0:
|
|
764
|
+
print(f" - Skills: {skill_count}")
|
|
765
|
+
|
|
766
|
+
return all_good
|
|
767
|
+
|
|
768
|
+
|
|
769
|
+
# ============================================================================
|
|
770
|
+
# MAIN
|
|
771
|
+
# ============================================================================
|
|
772
|
+
|
|
773
|
+
def main():
|
|
774
|
+
import argparse
|
|
775
|
+
|
|
776
|
+
parser = argparse.ArgumentParser(description="Bootstrap beads-based orchestration")
|
|
777
|
+
parser.add_argument("--project-name", default=None, help="Project name (auto-inferred if not provided)")
|
|
778
|
+
parser.add_argument("--project-dir", default=".", help="Project directory")
|
|
779
|
+
parser.add_argument("--external-providers", action="store_true",
|
|
780
|
+
help="Use Codex/Gemini for delegation (default: Claude-only)")
|
|
781
|
+
parser.add_argument("--with-kanban-ui", action="store_true",
|
|
782
|
+
help="Use Beads Kanban UI API for worktree creation (with git fallback)")
|
|
783
|
+
args = parser.parse_args()
|
|
784
|
+
|
|
785
|
+
project_dir = Path(args.project_dir).resolve()
|
|
786
|
+
claude_only = not args.external_providers # Default is now claude-only
|
|
787
|
+
with_kanban_ui = args.with_kanban_ui
|
|
788
|
+
|
|
789
|
+
# Ensure project directory exists
|
|
790
|
+
project_dir.mkdir(parents=True, exist_ok=True)
|
|
791
|
+
|
|
792
|
+
# Auto-infer project name if not provided
|
|
793
|
+
if args.project_name:
|
|
794
|
+
project_name = args.project_name
|
|
795
|
+
else:
|
|
796
|
+
project_name = infer_project_name(project_dir)
|
|
797
|
+
print(f"Auto-inferred project name: {project_name}")
|
|
798
|
+
|
|
799
|
+
mode_str = "CLAUDE-ONLY" if claude_only else "EXTERNAL PROVIDERS"
|
|
800
|
+
worktree_str = "API + git fallback" if with_kanban_ui else "git only"
|
|
801
|
+
print(f"\nBootstrapping beads orchestration for: {project_name}")
|
|
802
|
+
print(f"Directory: {project_dir}")
|
|
803
|
+
print(f"Mode: {mode_str}")
|
|
804
|
+
print(f"Worktrees: {worktree_str}")
|
|
805
|
+
print("=" * 60)
|
|
806
|
+
|
|
807
|
+
# Verify templates exist
|
|
808
|
+
if not TEMPLATES_DIR.exists():
|
|
809
|
+
print(f"\nERROR: Templates directory not found: {TEMPLATES_DIR}")
|
|
810
|
+
print("Make sure you cloned the full lean-orchestration repo")
|
|
811
|
+
sys.exit(1)
|
|
812
|
+
|
|
813
|
+
venv_python = None
|
|
814
|
+
|
|
815
|
+
# Step 0: Setup bundled provider-delegator (skip in claude-only mode)
|
|
816
|
+
if not claude_only:
|
|
817
|
+
venv_python = setup_provider_delegator()
|
|
818
|
+
if not venv_python:
|
|
819
|
+
print("\nERROR: Failed to setup provider-delegator. Aborting.")
|
|
820
|
+
sys.exit(1)
|
|
821
|
+
|
|
822
|
+
# Run remaining steps with provider support
|
|
823
|
+
if not install_beads(project_dir, claude_only=False):
|
|
824
|
+
print("\nERROR: Beads CLI is required. Aborting bootstrap.")
|
|
825
|
+
sys.exit(1)
|
|
826
|
+
|
|
827
|
+
# Install frontend review tools (optional, won't block)
|
|
828
|
+
install_rams()
|
|
829
|
+
install_web_interface_guidelines()
|
|
830
|
+
|
|
831
|
+
copy_agents(project_dir, project_name, claude_only=False, with_kanban_ui=with_kanban_ui)
|
|
832
|
+
copy_skills(project_dir, claude_only=False)
|
|
833
|
+
copy_hooks(project_dir, claude_only=False)
|
|
834
|
+
copy_settings(project_dir, claude_only=False)
|
|
835
|
+
copy_claude_md(project_dir, project_name, claude_only=False)
|
|
836
|
+
setup_memory(project_dir)
|
|
837
|
+
setup_gitignore(project_dir, claude_only=False)
|
|
838
|
+
create_mcp_config(project_dir, venv_python)
|
|
839
|
+
else:
|
|
840
|
+
# Claude-only mode: skip provider setup
|
|
841
|
+
print("\n[0/7] Skipping provider-delegator setup (claude-only mode)")
|
|
842
|
+
|
|
843
|
+
if not install_beads(project_dir, claude_only=True):
|
|
844
|
+
print("\nERROR: Beads CLI is required. Aborting bootstrap.")
|
|
845
|
+
sys.exit(1)
|
|
846
|
+
|
|
847
|
+
# Install frontend review tools (optional, won't block)
|
|
848
|
+
install_rams()
|
|
849
|
+
install_web_interface_guidelines()
|
|
850
|
+
|
|
851
|
+
copy_agents(project_dir, project_name, claude_only=True, with_kanban_ui=with_kanban_ui)
|
|
852
|
+
copy_skills(project_dir, claude_only=True)
|
|
853
|
+
copy_hooks(project_dir, claude_only=True)
|
|
854
|
+
copy_settings(project_dir, claude_only=True)
|
|
855
|
+
copy_claude_md(project_dir, project_name, claude_only=True)
|
|
856
|
+
setup_memory(project_dir)
|
|
857
|
+
setup_gitignore(project_dir, claude_only=True)
|
|
858
|
+
|
|
859
|
+
# Verify
|
|
860
|
+
if not verify_installation(project_dir, claude_only):
|
|
861
|
+
print("\nWARNING: Installation incomplete - check errors above")
|
|
862
|
+
|
|
863
|
+
print("\n" + "=" * 60)
|
|
864
|
+
print("BOOTSTRAP COMPLETE")
|
|
865
|
+
print("=" * 60)
|
|
866
|
+
|
|
867
|
+
if claude_only:
|
|
868
|
+
print(f"""
|
|
869
|
+
Mode: CLAUDE-ONLY (all agents use Claude Task)
|
|
870
|
+
|
|
871
|
+
Next steps:
|
|
872
|
+
|
|
873
|
+
1. Restart Claude Code to load new hooks and agents
|
|
874
|
+
|
|
875
|
+
2. **REQUIRED: Run discovery to create supervisors**
|
|
876
|
+
Discovery will scan your codebase and fetch specialist agents:
|
|
877
|
+
|
|
878
|
+
Task(
|
|
879
|
+
subagent_type="discovery",
|
|
880
|
+
prompt="Detect tech stack and create supervisors for {project_name}"
|
|
881
|
+
)
|
|
882
|
+
|
|
883
|
+
3. Create your first bead:
|
|
884
|
+
bd create "First task"
|
|
885
|
+
|
|
886
|
+
4. Dispatch work to supervisors:
|
|
887
|
+
Task(subagent_type="<supervisor-name>", prompt="BEAD_ID: BD-001\\n\\nImplement...")
|
|
888
|
+
|
|
889
|
+
NOTE: All agents (scout, detective, architect, etc.) run via Claude Task().
|
|
890
|
+
No external providers (Codex/Gemini) are configured.
|
|
891
|
+
""")
|
|
892
|
+
else:
|
|
893
|
+
print(f"""
|
|
894
|
+
Mode: EXTERNAL PROVIDERS (Codex → Gemini → Claude fallback)
|
|
895
|
+
|
|
896
|
+
Next steps:
|
|
897
|
+
|
|
898
|
+
1. Restart Claude Code to load new hooks and agents
|
|
899
|
+
|
|
900
|
+
2. **REQUIRED: Run discovery to create supervisors**
|
|
901
|
+
Discovery will scan your codebase and fetch specialist agents:
|
|
902
|
+
|
|
903
|
+
Task(
|
|
904
|
+
subagent_type="discovery",
|
|
905
|
+
prompt="Detect tech stack and create supervisors for {project_name}"
|
|
906
|
+
)
|
|
907
|
+
|
|
908
|
+
This will:
|
|
909
|
+
- Scan package.json, requirements.txt, Dockerfile, etc.
|
|
910
|
+
- Fetch matching specialists from external agents directory
|
|
911
|
+
- Inject beads workflow at the beginning of each agent
|
|
912
|
+
- Write supervisors to .claude/agents/
|
|
913
|
+
|
|
914
|
+
3. Create your first bead:
|
|
915
|
+
bd create "First task"
|
|
916
|
+
|
|
917
|
+
4. Dispatch work to supervisors:
|
|
918
|
+
Task(subagent_type="<supervisor-name>", prompt="BEAD_ID: BD-001\\n\\nImplement...")
|
|
919
|
+
|
|
920
|
+
NOTE: Read-only agents (scout, detective, architect, scribe, code-reviewer)
|
|
921
|
+
are delegated via provider_delegator MCP (Codex → Gemini fallback).
|
|
922
|
+
Supervisors are sourced from https://github.com/ayush-that/sub-agents.directory
|
|
923
|
+
with beads workflow injected.
|
|
924
|
+
""")
|
|
925
|
+
|
|
926
|
+
|
|
927
|
+
if __name__ == "__main__":
|
|
928
|
+
main()
|