@sulhadin/orchestrator 2.0.0 → 3.0.0-beta
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/README.md +49 -74
- package/bin/index.js +136 -82
- package/package.json +1 -1
- package/template/.claude/agents/conductor.md +146 -0
- package/template/.claude/agents/reviewer.md +88 -0
- package/template/.claude/commands/orchestra/blueprint.md +23 -0
- package/template/.claude/commands/orchestra/help.md +49 -0
- package/template/.claude/commands/orchestra/hotfix.md +13 -0
- package/template/.claude/commands/orchestra/pm.md +7 -0
- package/template/.claude/commands/orchestra/start.md +13 -0
- package/template/.claude/commands/orchestra/status.md +11 -0
- package/template/.claude/conductor.md +146 -0
- package/template/.claude/rules/acceptance-check.orchestra.md +13 -0
- package/template/.claude/rules/code-standards.orchestra.md +15 -0
- package/template/.claude/rules/commit-format.orchestra.md +14 -0
- package/template/.claude/rules/phase-limits.orchestra.md +21 -0
- package/template/.claude/rules/stuck-detection.orchestra.md +25 -0
- package/template/.claude/rules/testing-standards.orchestra.md +10 -0
- package/template/.claude/rules/verification-gate.orchestra.md +24 -0
- package/template/.claude/skills/fullstack-infrastructure.orchestra.md +810 -0
- package/template/.orchestra/README.md +10 -14
- package/template/.orchestra/config.yml +36 -0
- package/template/.orchestra/knowledge.md +4 -23
- package/template/.orchestra/roles/adaptive.md +14 -87
- package/template/.orchestra/roles/architect.md +17 -407
- package/template/.orchestra/roles/backend-engineer.md +13 -357
- package/template/.orchestra/roles/frontend-engineer.md +14 -419
- package/template/.orchestra/roles/orchestrator.md +48 -0
- package/template/.orchestra/roles/product-manager.md +73 -590
- package/template/CLAUDE.md +39 -139
- package/template/.orchestra/agents/worker.md +0 -557
- package/template/.orchestra/roles/code-reviewer.md +0 -265
- package/template/.orchestra/roles/owner.md +0 -290
- /package/template/{.orchestra/skills/accessibility.md → .claude/skills/accessibility.orchestra.md} +0 -0
- /package/template/{.orchestra/skills/auth-setup.md → .claude/skills/auth-setup.orchestra.md} +0 -0
- /package/template/{.orchestra/skills/best-practices.md → .claude/skills/best-practices.orchestra.md} +0 -0
- /package/template/{.orchestra/skills/code-optimizer.md → .claude/skills/code-optimizer.orchestra.md} +0 -0
- /package/template/{.orchestra/skills/core-web-vitals.md → .claude/skills/core-web-vitals.orchestra.md} +0 -0
- /package/template/{.orchestra/skills/crud-api.md → .claude/skills/crud-api.orchestra.md} +0 -0
- /package/template/{.orchestra/skills/debug.md → .claude/skills/debug.orchestra.md} +0 -0
- /package/template/{.orchestra/skills/deployment.md → .claude/skills/deployment.orchestra.md} +0 -0
- /package/template/{.orchestra/skills/frontend-design.md → .claude/skills/frontend-design.orchestra.md} +0 -0
- /package/template/{.orchestra/skills/react-best-practices.md → .claude/skills/react-best-practices.orchestra.md} +0 -0
- /package/template/{.orchestra/skills/review.md → .claude/skills/review.orchestra.md} +0 -0
- /package/template/{.orchestra/skills/testing.md → .claude/skills/testing.orchestra.md} +0 -0
- /package/template/{.orchestra/skills/web-quality-audit.md → .claude/skills/web-quality-audit.orchestra.md} +0 -0
package/README.md
CHANGED
|
@@ -1,111 +1,86 @@
|
|
|
1
1
|
# Orchestra
|
|
2
2
|
|
|
3
|
-
AI team orchestration for [Claude Code](https://docs.anthropic.com/en/docs/claude-code). Two terminals — PM plans,
|
|
3
|
+
AI team orchestration for [Claude Code](https://docs.anthropic.com/en/docs/claude-code). Two terminals — PM plans, conductor builds.
|
|
4
4
|
|
|
5
5
|
## Install
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
|
-
npx @sulhadin/orchestrator
|
|
9
|
-
```
|
|
10
|
-
|
|
11
|
-
Skip permission prompts:
|
|
12
|
-
|
|
13
|
-
```bash
|
|
14
|
-
npx @sulhadin/orchestrator@latest --dangerously-skip-permissions
|
|
8
|
+
npx @sulhadin/orchestrator
|
|
15
9
|
```
|
|
16
10
|
|
|
17
11
|
## Two Terminals
|
|
18
12
|
|
|
19
|
-
### Terminal 1:
|
|
13
|
+
### Terminal 1: `/orchestra pm` — Planning
|
|
20
14
|
|
|
21
|
-
PM is your strategic partner. Discuss ideas, challenge scope, create milestones.
|
|
15
|
+
PM is your strategic partner. Discuss ideas, challenge scope, create milestones.
|
|
22
16
|
|
|
23
17
|
```
|
|
24
|
-
You:
|
|
25
|
-
PM: "No active milestones.
|
|
18
|
+
You: /orchestra pm
|
|
19
|
+
PM: "No active milestones. What's on your mind?"
|
|
26
20
|
|
|
27
21
|
You: "I want user authentication with JWT"
|
|
28
|
-
PM: *discusses, challenges,
|
|
29
|
-
|
|
30
|
-
You: "Create the milestone"
|
|
31
|
-
PM: *creates prd.md, grooming.md, milestone.md, phases/*
|
|
32
|
-
"🎯 M1-user-auth ready. Run #start in another terminal."
|
|
33
|
-
|
|
34
|
-
You: "Let's also plan a dashboard"
|
|
35
|
-
PM: *plans M2 while worker executes M1*
|
|
22
|
+
PM: *discusses, challenges, creates milestone with phases*
|
|
36
23
|
```
|
|
37
24
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
### Terminal 2: `#start` — Execution
|
|
25
|
+
### Terminal 2: `/orchestra start` — Execution
|
|
41
26
|
|
|
42
|
-
|
|
27
|
+
Conductor picks up milestones and executes them autonomously.
|
|
43
28
|
|
|
44
29
|
```
|
|
45
|
-
You:
|
|
30
|
+
You: /orchestra start
|
|
46
31
|
|
|
47
32
|
📋 Starting M1-user-auth
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
⚙️ #backend ✅ phase-1 done (feat(db): add auth tables)
|
|
55
|
-
|
|
56
|
-
⚙️ #backend ▶ phase-2: API endpoints + tests...
|
|
57
|
-
⚙️ #backend ✅ phase-2 done (feat(auth): add login endpoint)
|
|
58
|
-
|
|
59
|
-
🎨 #frontend ▶ phase-3: Login UI...
|
|
60
|
-
🎨 #frontend ✅ phase-3 done (feat(auth): add login page)
|
|
61
|
-
|
|
62
|
-
🔍 #reviewer ▶ reviewing unpushed commits...
|
|
63
|
-
🔍 #reviewer ✅ approved
|
|
64
|
-
|
|
65
|
-
🚦 Push to origin? → yes
|
|
33
|
+
🏗️ architect ▶ RFC... ✅ done
|
|
34
|
+
⚙️ backend ▶ DB schema... ✅ done
|
|
35
|
+
⚙️ backend ▶ API endpoints... ✅ done
|
|
36
|
+
🎨 frontend ▶ Login UI... ✅ done
|
|
37
|
+
🔍 reviewer ▶ reviewing... ✅ approved
|
|
38
|
+
🚦 Push? → yes
|
|
66
39
|
✅ M1-user-auth done.
|
|
67
40
|
|
|
68
41
|
📋 Starting M2-dashboard...
|
|
69
42
|
```
|
|
70
43
|
|
|
71
|
-
Close the terminal, reopen, type `#start` — it resumes from where it left off.
|
|
72
|
-
|
|
73
44
|
## Commands
|
|
74
45
|
|
|
75
|
-
| Command |
|
|
76
|
-
|
|
77
|
-
|
|
|
78
|
-
|
|
|
79
|
-
|
|
|
80
|
-
|
|
|
81
|
-
|
|
|
82
|
-
|
|
|
83
|
-
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
46
|
+
| Command | What it does |
|
|
47
|
+
|---------|-------------|
|
|
48
|
+
| `/orchestra pm` | Plan features, create milestones |
|
|
49
|
+
| `/orchestra start` | Execute milestones (asks at approval gates) |
|
|
50
|
+
| `/orchestra start --auto` | Fully autonomous |
|
|
51
|
+
| `/orchestra hotfix {desc}` | Ultra-fast fix: implement → verify → commit → push |
|
|
52
|
+
| `/orchestra status` | Milestone status report |
|
|
53
|
+
| `/orchestra blueprint {name}` | Generate milestones from template |
|
|
54
|
+
| `/orchestra help` | Show all commands |
|
|
55
|
+
|
|
56
|
+
## Architecture
|
|
57
|
+
|
|
58
|
+
```
|
|
59
|
+
.claude/ ← Claude Code integration
|
|
60
|
+
├── agents/conductor.md ← Autonomous executor
|
|
61
|
+
├── agents/reviewer.md ← Independent code review
|
|
62
|
+
├── skills/*.orchestra.md ← 14 domain checklists
|
|
63
|
+
├── rules/*.orchestra.md ← Discipline rules
|
|
64
|
+
└── commands/orchestra/ ← /orchestra commands
|
|
65
|
+
|
|
66
|
+
.orchestra/ ← Project data + config
|
|
67
|
+
├── config.yml ← Pipeline settings (customize per stack)
|
|
68
|
+
├── roles/ ← Role identities (slim)
|
|
69
|
+
├── blueprints/ ← Project templates
|
|
70
|
+
├── knowledge.md ← Project knowledge log
|
|
71
|
+
└── milestones/ ← Your work
|
|
72
|
+
```
|
|
98
73
|
|
|
99
74
|
## Documentation
|
|
100
75
|
|
|
101
76
|
See [docs/](https://github.com/sulhadin/orchestrator/blob/main/docs/README.md) for full documentation:
|
|
102
77
|
|
|
103
|
-
- [Getting Started](https://github.com/sulhadin/orchestrator/blob/main/docs/getting-started.md)
|
|
104
|
-
- [Commands](https://github.com/sulhadin/orchestrator/blob/main/docs/commands.md)
|
|
105
|
-
- [Roles](https://github.com/sulhadin/orchestrator/blob/main/docs/roles.md)
|
|
106
|
-
- [Features](https://github.com/sulhadin/orchestrator/blob/main/docs/features.md)
|
|
107
|
-
- [Blueprints](https://github.com/sulhadin/orchestrator/blob/main/docs/blueprints.md)
|
|
108
|
-
- [Skills](https://github.com/sulhadin/orchestrator/blob/main/docs/skills.md)
|
|
78
|
+
- [Getting Started](https://github.com/sulhadin/orchestrator/blob/main/docs/getting-started.md)
|
|
79
|
+
- [Commands](https://github.com/sulhadin/orchestrator/blob/main/docs/commands.md)
|
|
80
|
+
- [Roles](https://github.com/sulhadin/orchestrator/blob/main/docs/roles.md)
|
|
81
|
+
- [Features](https://github.com/sulhadin/orchestrator/blob/main/docs/features.md)
|
|
82
|
+
- [Blueprints](https://github.com/sulhadin/orchestrator/blob/main/docs/blueprints.md)
|
|
83
|
+
- [Skills](https://github.com/sulhadin/orchestrator/blob/main/docs/skills.md)
|
|
109
84
|
|
|
110
85
|
## License
|
|
111
86
|
|
package/bin/index.js
CHANGED
|
@@ -9,7 +9,9 @@ const templateDir = path.join(__dirname, "..", "template");
|
|
|
9
9
|
const ORCHESTRA_SECTION_START = "<!-- orchestra -->";
|
|
10
10
|
const ORCHESTRA_SECTION_END = "<!-- /orchestra -->";
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
// User directories to preserve during upgrade
|
|
13
|
+
const ORCHESTRA_USER_DIRS = ["milestones", "blueprints"];
|
|
14
|
+
const CLAUDE_USER_DIRS = ["skills", "rules"];
|
|
13
15
|
|
|
14
16
|
const ALLOW_PERMISSIONS = [
|
|
15
17
|
"Bash(*)",
|
|
@@ -25,10 +27,7 @@ const ALLOW_PERMISSIONS = [
|
|
|
25
27
|
|
|
26
28
|
function parseArgs() {
|
|
27
29
|
const args = process.argv.slice(2);
|
|
28
|
-
const flags = {
|
|
29
|
-
skipPermissions: false,
|
|
30
|
-
help: false,
|
|
31
|
-
};
|
|
30
|
+
const flags = { skipPermissions: false, help: false };
|
|
32
31
|
|
|
33
32
|
for (const arg of args) {
|
|
34
33
|
switch (arg) {
|
|
@@ -53,7 +52,7 @@ function showHelp() {
|
|
|
53
52
|
console.log(" Usage: npx @sulhadin/orchestrator [options]");
|
|
54
53
|
console.log("");
|
|
55
54
|
console.log(" Options:");
|
|
56
|
-
console.log(" --dangerously-skip-permissions Auto-allow all tool permissions
|
|
55
|
+
console.log(" --dangerously-skip-permissions Auto-allow all tool permissions");
|
|
57
56
|
console.log(" --help, -h Show this help");
|
|
58
57
|
console.log("");
|
|
59
58
|
console.log(" Examples:");
|
|
@@ -66,13 +65,10 @@ function copyDirRecursive(src, dest) {
|
|
|
66
65
|
if (!fs.existsSync(dest)) {
|
|
67
66
|
fs.mkdirSync(dest, { recursive: true });
|
|
68
67
|
}
|
|
69
|
-
|
|
70
68
|
const entries = fs.readdirSync(src, { withFileTypes: true });
|
|
71
|
-
|
|
72
69
|
for (const entry of entries) {
|
|
73
70
|
const srcPath = path.join(src, entry.name);
|
|
74
71
|
const destPath = path.join(dest, entry.name);
|
|
75
|
-
|
|
76
72
|
if (entry.isDirectory()) {
|
|
77
73
|
copyDirRecursive(srcPath, destPath);
|
|
78
74
|
} else {
|
|
@@ -83,9 +79,7 @@ function copyDirRecursive(src, dest) {
|
|
|
83
79
|
|
|
84
80
|
function rmDirRecursive(dir) {
|
|
85
81
|
if (!fs.existsSync(dir)) return;
|
|
86
|
-
|
|
87
82
|
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
88
|
-
|
|
89
83
|
for (const entry of entries) {
|
|
90
84
|
const fullPath = path.join(dir, entry.name);
|
|
91
85
|
if (entry.isDirectory()) {
|
|
@@ -94,21 +88,47 @@ function rmDirRecursive(dir) {
|
|
|
94
88
|
fs.unlinkSync(fullPath);
|
|
95
89
|
}
|
|
96
90
|
}
|
|
97
|
-
|
|
98
91
|
fs.rmdirSync(dir);
|
|
99
92
|
}
|
|
100
93
|
|
|
101
94
|
function extractOrchestraSection(content) {
|
|
102
95
|
const startIdx = content.indexOf(ORCHESTRA_SECTION_START);
|
|
103
96
|
if (startIdx === -1) return null;
|
|
104
|
-
|
|
105
97
|
const endIdx = content.indexOf(ORCHESTRA_SECTION_END, startIdx);
|
|
106
|
-
if (endIdx === -1)
|
|
107
|
-
return content.slice(startIdx);
|
|
108
|
-
}
|
|
98
|
+
if (endIdx === -1) return content.slice(startIdx);
|
|
109
99
|
return content.slice(startIdx, endIdx + ORCHESTRA_SECTION_END.length);
|
|
110
100
|
}
|
|
111
101
|
|
|
102
|
+
/**
|
|
103
|
+
* Smart merge for user directories (skills, rules, blueprints):
|
|
104
|
+
* - .orchestra.md files = system files → always updated from template
|
|
105
|
+
* - other .md files = user files → preserved
|
|
106
|
+
*/
|
|
107
|
+
function smartMergeDir(backupPath, restorePath, templateDirPath) {
|
|
108
|
+
const templateFiles = fs.existsSync(templateDirPath)
|
|
109
|
+
? fs.readdirSync(templateDirPath)
|
|
110
|
+
: [];
|
|
111
|
+
const backupFiles = fs.readdirSync(backupPath).filter((f) => f !== ".gitkeep");
|
|
112
|
+
let restored = 0;
|
|
113
|
+
|
|
114
|
+
for (const file of backupFiles) {
|
|
115
|
+
// User-created files: not in template AND not .orchestra.md
|
|
116
|
+
const isOrchestraFile = file.endsWith(".orchestra.md");
|
|
117
|
+
if (!templateFiles.includes(file) && !isOrchestraFile) {
|
|
118
|
+
const srcFile = path.join(backupPath, file);
|
|
119
|
+
const destFile = path.join(restorePath, file);
|
|
120
|
+
if (fs.statSync(srcFile).isDirectory()) {
|
|
121
|
+
copyDirRecursive(srcFile, destFile);
|
|
122
|
+
} else {
|
|
123
|
+
fs.copyFileSync(srcFile, destFile);
|
|
124
|
+
}
|
|
125
|
+
restored++;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return restored;
|
|
130
|
+
}
|
|
131
|
+
|
|
112
132
|
function setupPermissions() {
|
|
113
133
|
const claudeDir = path.join(targetDir, ".claude");
|
|
114
134
|
const settingsPath = path.join(claudeDir, "settings.local.json");
|
|
@@ -126,10 +146,7 @@ function setupPermissions() {
|
|
|
126
146
|
}
|
|
127
147
|
}
|
|
128
148
|
|
|
129
|
-
if (!settings.permissions) {
|
|
130
|
-
settings.permissions = {};
|
|
131
|
-
}
|
|
132
|
-
|
|
149
|
+
if (!settings.permissions) settings.permissions = {};
|
|
133
150
|
const existing = settings.permissions.allow || [];
|
|
134
151
|
const merged = [...new Set([...existing, ...ALLOW_PERMISSIONS])];
|
|
135
152
|
settings.permissions.allow = merged;
|
|
@@ -138,7 +155,7 @@ function setupPermissions() {
|
|
|
138
155
|
|
|
139
156
|
const added = merged.length - existing.length;
|
|
140
157
|
if (added > 0) {
|
|
141
|
-
console.log(" [+] Permissions configured (" + added + " rules added
|
|
158
|
+
console.log(" [+] Permissions configured (" + added + " rules added)");
|
|
142
159
|
} else {
|
|
143
160
|
console.log(" [~] Permissions already configured");
|
|
144
161
|
}
|
|
@@ -146,7 +163,6 @@ function setupPermissions() {
|
|
|
146
163
|
|
|
147
164
|
function run() {
|
|
148
165
|
const flags = parseArgs();
|
|
149
|
-
|
|
150
166
|
if (flags.help) {
|
|
151
167
|
showHelp();
|
|
152
168
|
return;
|
|
@@ -158,87 +174,118 @@ function run() {
|
|
|
158
174
|
console.log("");
|
|
159
175
|
|
|
160
176
|
const orchestraSrc = path.join(templateDir, ".orchestra");
|
|
177
|
+
const claudeSrc = path.join(templateDir, ".claude");
|
|
161
178
|
const orchestraDest = path.join(targetDir, ".orchestra");
|
|
179
|
+
const claudeDest = path.join(targetDir, ".claude");
|
|
162
180
|
const isUpgrade = fs.existsSync(orchestraDest);
|
|
163
181
|
|
|
164
182
|
if (isUpgrade) {
|
|
183
|
+
// ── Backup user data ──
|
|
165
184
|
const backups = {};
|
|
166
185
|
|
|
167
|
-
|
|
186
|
+
// .orchestra/ user dirs (milestones, blueprints)
|
|
187
|
+
for (const dir of ORCHESTRA_USER_DIRS) {
|
|
168
188
|
const dirPath = path.join(orchestraDest, dir);
|
|
169
189
|
const backupPath = path.join(targetDir, ".orchestra-backup-" + dir);
|
|
190
|
+
if (fs.existsSync(dirPath)) {
|
|
191
|
+
const files = fs.readdirSync(dirPath).filter((f) => f !== ".gitkeep");
|
|
192
|
+
if (files.length > 0) {
|
|
193
|
+
copyDirRecursive(dirPath, backupPath);
|
|
194
|
+
backups["orchestra:" + dir] = { backupPath, type: "orchestra", dir };
|
|
195
|
+
console.log(" [~] Backed up .orchestra/" + dir + "/ (" + files.length + " items)");
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
170
199
|
|
|
200
|
+
// .claude/ user dirs (skills, rules — user may have added custom ones)
|
|
201
|
+
for (const dir of CLAUDE_USER_DIRS) {
|
|
202
|
+
const dirPath = path.join(claudeDest, dir);
|
|
203
|
+
const backupPath = path.join(targetDir, ".claude-backup-" + dir);
|
|
171
204
|
if (fs.existsSync(dirPath)) {
|
|
172
205
|
const files = fs.readdirSync(dirPath).filter((f) => f !== ".gitkeep");
|
|
173
206
|
if (files.length > 0) {
|
|
174
207
|
copyDirRecursive(dirPath, backupPath);
|
|
175
|
-
backups[dir] = backupPath;
|
|
176
|
-
console.log(" [~] Backed up " + dir + "/ (" + files.length + " items)");
|
|
208
|
+
backups["claude:" + dir] = { backupPath, type: "claude", dir };
|
|
209
|
+
console.log(" [~] Backed up .claude/" + dir + "/ (" + files.length + " items)");
|
|
177
210
|
}
|
|
178
211
|
}
|
|
179
212
|
}
|
|
180
213
|
|
|
181
|
-
//
|
|
182
|
-
const
|
|
214
|
+
// knowledge.md
|
|
215
|
+
const knowledgePath = path.join(orchestraDest, "knowledge.md");
|
|
183
216
|
const knowledgeBackup = path.join(targetDir, ".orchestra-backup-knowledge.md");
|
|
184
217
|
let hasKnowledge = false;
|
|
185
|
-
if (fs.existsSync(
|
|
186
|
-
const content = fs.readFileSync(
|
|
187
|
-
|
|
188
|
-
const
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
if (hasEntries) {
|
|
192
|
-
fs.copyFileSync(knowledgeSrc, knowledgeBackup);
|
|
218
|
+
if (fs.existsSync(knowledgePath)) {
|
|
219
|
+
const content = fs.readFileSync(knowledgePath, "utf-8");
|
|
220
|
+
const marker = "<!-- New entries go here";
|
|
221
|
+
const idx = content.indexOf(marker);
|
|
222
|
+
if (idx !== -1 && content.slice(idx + marker.length).trim().length > 10) {
|
|
223
|
+
fs.copyFileSync(knowledgePath, knowledgeBackup);
|
|
193
224
|
hasKnowledge = true;
|
|
194
|
-
console.log(" [~] Backed up knowledge.md
|
|
225
|
+
console.log(" [~] Backed up knowledge.md");
|
|
195
226
|
}
|
|
196
227
|
}
|
|
197
228
|
|
|
229
|
+
// config.yml (user may have customized)
|
|
230
|
+
const configPath = path.join(orchestraDest, "config.yml");
|
|
231
|
+
const configBackup = path.join(targetDir, ".orchestra-backup-config.yml");
|
|
232
|
+
let hasConfig = false;
|
|
233
|
+
if (fs.existsSync(configPath)) {
|
|
234
|
+
fs.copyFileSync(configPath, configBackup);
|
|
235
|
+
hasConfig = true;
|
|
236
|
+
console.log(" [~] Backed up config.yml");
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// ── Clean install ──
|
|
198
240
|
rmDirRecursive(orchestraDest);
|
|
199
241
|
console.log(" [~] Removed old .orchestra/");
|
|
200
242
|
|
|
243
|
+
// Remove old .claude/ orchestra files (agents, commands) but keep user files
|
|
244
|
+
const claudeOrchestraDirs = ["agents", "commands"];
|
|
245
|
+
for (const dir of claudeOrchestraDirs) {
|
|
246
|
+
const dirPath = path.join(claudeDest, dir);
|
|
247
|
+
if (fs.existsSync(dirPath)) {
|
|
248
|
+
// Only remove orchestra files, keep user's custom agents/commands
|
|
249
|
+
const files = fs.readdirSync(dirPath);
|
|
250
|
+
for (const file of files) {
|
|
251
|
+
if (file === "conductor.md" || file === "reviewer.md" || file === "orchestra") {
|
|
252
|
+
const fullPath = path.join(dirPath, file);
|
|
253
|
+
if (fs.statSync(fullPath).isDirectory()) {
|
|
254
|
+
rmDirRecursive(fullPath);
|
|
255
|
+
} else {
|
|
256
|
+
fs.unlinkSync(fullPath);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
201
263
|
copyDirRecursive(orchestraSrc, orchestraDest);
|
|
202
|
-
console.log(" [+] .orchestra/ installed
|
|
264
|
+
console.log(" [+] .orchestra/ installed");
|
|
203
265
|
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
266
|
+
if (fs.existsSync(claudeSrc)) {
|
|
267
|
+
copyDirRecursive(claudeSrc, claudeDest);
|
|
268
|
+
console.log(" [+] .claude/ orchestra files installed");
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// ── Restore user data ──
|
|
272
|
+
for (const [key, { backupPath, type, dir }] of Object.entries(backups)) {
|
|
273
|
+
const baseDest = type === "orchestra" ? orchestraDest : claudeDest;
|
|
274
|
+
const restorePath = path.join(baseDest, dir);
|
|
275
|
+
const templateDirPath = type === "orchestra"
|
|
276
|
+
? path.join(orchestraSrc, dir)
|
|
277
|
+
: path.join(claudeSrc, dir);
|
|
207
278
|
|
|
208
279
|
if (dir === "milestones") {
|
|
209
|
-
|
|
210
|
-
if (fs.existsSync(restorePath)) {
|
|
211
|
-
rmDirRecursive(restorePath);
|
|
212
|
-
}
|
|
280
|
+
if (fs.existsSync(restorePath)) rmDirRecursive(restorePath);
|
|
213
281
|
copyDirRecursive(backupPath, restorePath);
|
|
214
|
-
console.log(" [+] Restored
|
|
282
|
+
console.log(" [+] Restored milestones/");
|
|
215
283
|
} else {
|
|
216
|
-
|
|
217
|
-
// Template files are already in place from clean install.
|
|
218
|
-
// Only copy back files that DON'T exist in template (user-created).
|
|
219
|
-
const templateFiles = fs.existsSync(templateDirPath)
|
|
220
|
-
? fs.readdirSync(templateDirPath)
|
|
221
|
-
: [];
|
|
222
|
-
const backupFiles = fs.readdirSync(backupPath).filter((f) => f !== ".gitkeep");
|
|
223
|
-
let restored = 0;
|
|
224
|
-
|
|
225
|
-
for (const file of backupFiles) {
|
|
226
|
-
if (!templateFiles.includes(file)) {
|
|
227
|
-
const srcFile = path.join(backupPath, file);
|
|
228
|
-
const destFile = path.join(restorePath, file);
|
|
229
|
-
if (fs.statSync(srcFile).isDirectory()) {
|
|
230
|
-
copyDirRecursive(srcFile, destFile);
|
|
231
|
-
} else {
|
|
232
|
-
fs.copyFileSync(srcFile, destFile);
|
|
233
|
-
}
|
|
234
|
-
restored++;
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
|
|
284
|
+
const restored = smartMergeDir(backupPath, restorePath, templateDirPath);
|
|
238
285
|
if (restored > 0) {
|
|
239
|
-
console.log(" [+] Restored " + restored + " user
|
|
286
|
+
console.log(" [+] Restored " + restored + " user files in " + dir + "/");
|
|
240
287
|
}
|
|
241
|
-
console.log(" [~] Updated
|
|
288
|
+
console.log(" [~] Updated system files in " + dir + "/");
|
|
242
289
|
}
|
|
243
290
|
|
|
244
291
|
rmDirRecursive(backupPath);
|
|
@@ -250,16 +297,26 @@ function run() {
|
|
|
250
297
|
fs.unlinkSync(knowledgeBackup);
|
|
251
298
|
console.log(" [+] Restored knowledge.md");
|
|
252
299
|
}
|
|
300
|
+
|
|
301
|
+
// Restore config.yml (user's customizations take priority)
|
|
302
|
+
if (hasConfig && fs.existsSync(configBackup)) {
|
|
303
|
+
fs.copyFileSync(configBackup, path.join(orchestraDest, "config.yml"));
|
|
304
|
+
fs.unlinkSync(configBackup);
|
|
305
|
+
console.log(" [+] Restored config.yml (user customizations preserved)");
|
|
306
|
+
}
|
|
253
307
|
} else {
|
|
308
|
+
// ── Fresh install ──
|
|
254
309
|
copyDirRecursive(orchestraSrc, orchestraDest);
|
|
255
310
|
console.log(" [+] .orchestra/ installed");
|
|
311
|
+
|
|
312
|
+
if (fs.existsSync(claudeSrc)) {
|
|
313
|
+
copyDirRecursive(claudeSrc, claudeDest);
|
|
314
|
+
console.log(" [+] .claude/ installed");
|
|
315
|
+
}
|
|
256
316
|
}
|
|
257
317
|
|
|
258
|
-
// Handle CLAUDE.md
|
|
259
|
-
const templateClaudeMd = fs.readFileSync(
|
|
260
|
-
path.join(templateDir, "CLAUDE.md"),
|
|
261
|
-
"utf-8"
|
|
262
|
-
);
|
|
318
|
+
// ── Handle CLAUDE.md ──
|
|
319
|
+
const templateClaudeMd = fs.readFileSync(path.join(templateDir, "CLAUDE.md"), "utf-8");
|
|
263
320
|
const targetClaudeMdPath = path.join(targetDir, "CLAUDE.md");
|
|
264
321
|
|
|
265
322
|
if (fs.existsSync(targetClaudeMdPath)) {
|
|
@@ -274,15 +331,11 @@ function run() {
|
|
|
274
331
|
if (existingContent.includes(ORCHESTRA_SECTION_START)) {
|
|
275
332
|
const existingSection = extractOrchestraSection(existingContent);
|
|
276
333
|
if (existingSection) {
|
|
277
|
-
existingContent = existingContent.replace(
|
|
278
|
-
existingSection,
|
|
279
|
-
orchestraSection
|
|
280
|
-
);
|
|
334
|
+
existingContent = existingContent.replace(existingSection, orchestraSection);
|
|
281
335
|
}
|
|
282
336
|
console.log(" [~] CLAUDE.md updated (Orchestra section replaced)");
|
|
283
337
|
} else {
|
|
284
|
-
existingContent =
|
|
285
|
-
existingContent.trimEnd() + "\n\n" + orchestraSection + "\n";
|
|
338
|
+
existingContent = existingContent.trimEnd() + "\n\n" + orchestraSection + "\n";
|
|
286
339
|
console.log(" [+] CLAUDE.md updated (Orchestra section appended)");
|
|
287
340
|
}
|
|
288
341
|
|
|
@@ -292,7 +345,7 @@ function run() {
|
|
|
292
345
|
console.log(" [+] CLAUDE.md created");
|
|
293
346
|
}
|
|
294
347
|
|
|
295
|
-
// Handle permissions
|
|
348
|
+
// ── Handle permissions ──
|
|
296
349
|
if (flags.skipPermissions) {
|
|
297
350
|
setupPermissions();
|
|
298
351
|
}
|
|
@@ -302,7 +355,8 @@ function run() {
|
|
|
302
355
|
console.log("");
|
|
303
356
|
console.log(" Next steps:");
|
|
304
357
|
console.log(" 1. Open Claude Code in your project");
|
|
305
|
-
console.log(' 2.
|
|
358
|
+
console.log(' 2. Type "/orchestra pm" to start planning');
|
|
359
|
+
console.log(' 3. Type "/orchestra start" in another terminal to execute');
|
|
306
360
|
console.log("");
|
|
307
361
|
}
|
|
308
362
|
|