@sulhadin/orchestrator 3.0.0-beta.7 → 3.0.0-beta.9
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/bin/index.js +123 -3
- package/bin/merge-config.test.js +135 -0
- package/package.json +4 -2
- package/template/.claude/agents/conductor.md +90 -35
- package/template/.claude/rules/acceptance-check.orchestra.md +5 -7
- package/template/.claude/rules/code-standards.orchestra.md +1 -1
- package/template/.orchestra/config.yml +22 -0
- package/template/.orchestra/roles/product-manager.md +6 -5
- package/template/CLAUDE.md +1 -0
package/bin/index.js
CHANGED
|
@@ -95,6 +95,123 @@ function rmDirRecursive(dir) {
|
|
|
95
95
|
fs.rmdirSync(dir);
|
|
96
96
|
}
|
|
97
97
|
|
|
98
|
+
/**
|
|
99
|
+
* Simple YAML config merge: adds new keys from template, preserves user values.
|
|
100
|
+
* Works with flat and one-level nested YAML (no deep nesting needed for config.yml).
|
|
101
|
+
*/
|
|
102
|
+
function mergeConfigYaml(userContent, templateContent) {
|
|
103
|
+
const userLines = userContent.split("\n");
|
|
104
|
+
const templateLines = templateContent.split("\n");
|
|
105
|
+
|
|
106
|
+
// Parse YAML into { key: value } with awareness of sections
|
|
107
|
+
function parseYaml(lines) {
|
|
108
|
+
const result = {};
|
|
109
|
+
let currentSection = null;
|
|
110
|
+
for (const line of lines) {
|
|
111
|
+
// Skip comments and empty lines for parsing, but we'll preserve them
|
|
112
|
+
if (line.trim() === "" || line.trim().startsWith("#")) continue;
|
|
113
|
+
// Top-level section (no indent)
|
|
114
|
+
const sectionMatch = line.match(/^(\w[\w_-]*):\s*$/);
|
|
115
|
+
if (sectionMatch) {
|
|
116
|
+
currentSection = sectionMatch[1];
|
|
117
|
+
result[currentSection] = result[currentSection] || {};
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
// Nested key (2-space indent)
|
|
121
|
+
const nestedMatch = line.match(/^ (\w[\w_-]*):\s*(.+)?$/);
|
|
122
|
+
if (nestedMatch && currentSection) {
|
|
123
|
+
const key = nestedMatch[1];
|
|
124
|
+
const value = nestedMatch[2] || "";
|
|
125
|
+
if (!result[currentSection]) result[currentSection] = {};
|
|
126
|
+
// Check if this is a sub-section (value is empty, next lines are indented more)
|
|
127
|
+
if (!value) {
|
|
128
|
+
result[currentSection][key] = result[currentSection][key] || {};
|
|
129
|
+
} else {
|
|
130
|
+
result[currentSection][key] = value;
|
|
131
|
+
}
|
|
132
|
+
continue;
|
|
133
|
+
}
|
|
134
|
+
// Deeper nested key (4-space indent)
|
|
135
|
+
const deepMatch = line.match(/^ (\w[\w_-]*):\s*(.+)$/);
|
|
136
|
+
if (deepMatch && currentSection) {
|
|
137
|
+
// Find parent key (last nested key without value)
|
|
138
|
+
const parentKeys = Object.keys(result[currentSection] || {});
|
|
139
|
+
const parentKey = parentKeys.reverse().find(
|
|
140
|
+
(k) => typeof result[currentSection][k] === "object"
|
|
141
|
+
);
|
|
142
|
+
if (parentKey) {
|
|
143
|
+
result[currentSection][parentKey][deepMatch[1]] = deepMatch[2];
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
return result;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const userParsed = parseYaml(userLines);
|
|
151
|
+
const templateParsed = parseYaml(templateLines);
|
|
152
|
+
|
|
153
|
+
// Build merged config: start with template (has comments + structure), fill with user values
|
|
154
|
+
const merged = [];
|
|
155
|
+
let currentSection = null;
|
|
156
|
+
let currentSubSection = null;
|
|
157
|
+
|
|
158
|
+
for (const line of templateLines) {
|
|
159
|
+
const sectionMatch = line.match(/^(\w[\w_-]*):\s*$/);
|
|
160
|
+
if (sectionMatch) {
|
|
161
|
+
currentSection = sectionMatch[1];
|
|
162
|
+
currentSubSection = null;
|
|
163
|
+
merged.push(line);
|
|
164
|
+
continue;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const nestedMatch = line.match(/^ (\w[\w_-]*):\s*(.+)?$/);
|
|
168
|
+
if (nestedMatch && currentSection) {
|
|
169
|
+
const key = nestedMatch[1];
|
|
170
|
+
const templateValue = nestedMatch[2] || "";
|
|
171
|
+
|
|
172
|
+
if (!templateValue) {
|
|
173
|
+
// Sub-section header (e.g., "models:")
|
|
174
|
+
currentSubSection = key;
|
|
175
|
+
merged.push(line);
|
|
176
|
+
continue;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Has a value → this is a flat key, reset sub-section
|
|
180
|
+
currentSubSection = null;
|
|
181
|
+
|
|
182
|
+
// Check if user has this key
|
|
183
|
+
const userSection = userParsed[currentSection];
|
|
184
|
+
if (userSection && userSection[key] !== undefined && typeof userSection[key] !== "object") {
|
|
185
|
+
merged.push(` ${key}: ${userSection[key]}`);
|
|
186
|
+
continue;
|
|
187
|
+
}
|
|
188
|
+
// New key — use template value
|
|
189
|
+
merged.push(line);
|
|
190
|
+
continue;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const deepMatch = line.match(/^ (\w[\w_-]*):\s*(.+)$/);
|
|
194
|
+
if (deepMatch && currentSection && currentSubSection) {
|
|
195
|
+
const key = deepMatch[1];
|
|
196
|
+
const userSection = userParsed[currentSection];
|
|
197
|
+
if (userSection && typeof userSection[currentSubSection] === "object") {
|
|
198
|
+
const userValue = userSection[currentSubSection][key];
|
|
199
|
+
if (userValue !== undefined) {
|
|
200
|
+
merged.push(` ${key}: ${userValue}`);
|
|
201
|
+
continue;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
// New key — use template value
|
|
205
|
+
merged.push(line);
|
|
206
|
+
continue;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
merged.push(line);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
return merged.join("\n");
|
|
213
|
+
}
|
|
214
|
+
|
|
98
215
|
function extractOrchestraSection(content) {
|
|
99
216
|
const startIdx = content.indexOf(ORCHESTRA_SECTION_START);
|
|
100
217
|
if (startIdx === -1) return null;
|
|
@@ -302,11 +419,14 @@ function run() {
|
|
|
302
419
|
console.log(" [+] Restored knowledge.md");
|
|
303
420
|
}
|
|
304
421
|
|
|
305
|
-
//
|
|
422
|
+
// Merge config.yml: new template keys added, user values preserved
|
|
306
423
|
if (hasConfig && fs.existsSync(configBackup)) {
|
|
307
|
-
fs.
|
|
424
|
+
const userConfig = fs.readFileSync(configBackup, "utf-8");
|
|
425
|
+
const templateConfig = fs.readFileSync(path.join(orchestraDest, "config.yml"), "utf-8");
|
|
426
|
+
const mergedConfig = mergeConfigYaml(userConfig, templateConfig);
|
|
427
|
+
fs.writeFileSync(path.join(orchestraDest, "config.yml"), mergedConfig);
|
|
308
428
|
fs.unlinkSync(configBackup);
|
|
309
|
-
console.log(" [+]
|
|
429
|
+
console.log(" [+] Merged config.yml (user values preserved, new keys added)");
|
|
310
430
|
}
|
|
311
431
|
} else {
|
|
312
432
|
// ── Fresh install ──
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
const { describe, it } = require("node:test");
|
|
2
|
+
const assert = require("node:assert");
|
|
3
|
+
const fs = require("fs");
|
|
4
|
+
const path = require("path");
|
|
5
|
+
|
|
6
|
+
// Extract mergeConfigYaml from index.js
|
|
7
|
+
const src = fs.readFileSync(path.join(__dirname, "index.js"), "utf-8");
|
|
8
|
+
const match = src.match(/function mergeConfigYaml\([\s\S]*?^}/m);
|
|
9
|
+
eval(match[0]);
|
|
10
|
+
|
|
11
|
+
const userConfig = `pipeline:
|
|
12
|
+
models:
|
|
13
|
+
quick: haiku
|
|
14
|
+
standard: sonnet
|
|
15
|
+
complex: opus
|
|
16
|
+
rfc_approval: skip
|
|
17
|
+
push_approval: auto
|
|
18
|
+
review: required
|
|
19
|
+
parallel: disabled
|
|
20
|
+
|
|
21
|
+
thresholds:
|
|
22
|
+
re_review_lines: 50
|
|
23
|
+
phase_time_limit: 20
|
|
24
|
+
phase_tool_limit: 40
|
|
25
|
+
stuck_retry_limit: 5
|
|
26
|
+
|
|
27
|
+
verification:
|
|
28
|
+
typecheck: "yarn tsc --noEmit"
|
|
29
|
+
test: "yarn test"
|
|
30
|
+
lint: "yarn lint"
|
|
31
|
+
`;
|
|
32
|
+
|
|
33
|
+
const templateConfig = `# Orchestra Configuration
|
|
34
|
+
# Customize pipeline behavior, thresholds, and verification commands.
|
|
35
|
+
|
|
36
|
+
pipeline:
|
|
37
|
+
# Model selection per phase complexity
|
|
38
|
+
models:
|
|
39
|
+
trivial: haiku
|
|
40
|
+
quick: sonnet
|
|
41
|
+
standard: sonnet
|
|
42
|
+
complex: opus
|
|
43
|
+
rfc_approval: required
|
|
44
|
+
push_approval: required
|
|
45
|
+
review: required
|
|
46
|
+
parallel: disabled
|
|
47
|
+
default_pipeline: full
|
|
48
|
+
default_complexity: standard
|
|
49
|
+
max_rfc_rounds: 3
|
|
50
|
+
|
|
51
|
+
thresholds:
|
|
52
|
+
milestone_lock_timeout: 120
|
|
53
|
+
re_review_lines: 30
|
|
54
|
+
phase_time_limit: 15
|
|
55
|
+
phase_tool_limit: 40
|
|
56
|
+
stuck_retry_limit: 3
|
|
57
|
+
|
|
58
|
+
verification:
|
|
59
|
+
typecheck: "npx tsc --noEmit"
|
|
60
|
+
test: "npm test"
|
|
61
|
+
lint: "npm run lint"
|
|
62
|
+
`;
|
|
63
|
+
|
|
64
|
+
describe("mergeConfigYaml", () => {
|
|
65
|
+
const result = mergeConfigYaml(userConfig, templateConfig);
|
|
66
|
+
|
|
67
|
+
describe("new template keys are added", () => {
|
|
68
|
+
it("adds trivial model tier", () => {
|
|
69
|
+
assert.ok(result.includes("trivial: haiku"));
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it("adds default_pipeline", () => {
|
|
73
|
+
assert.ok(result.includes("default_pipeline: full"));
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it("adds default_complexity", () => {
|
|
77
|
+
assert.ok(result.includes("default_complexity: standard"));
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it("adds max_rfc_rounds", () => {
|
|
81
|
+
assert.ok(result.includes("max_rfc_rounds: 3"));
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it("adds milestone_lock_timeout", () => {
|
|
85
|
+
assert.ok(result.includes("milestone_lock_timeout: 120"));
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
describe("user values are preserved", () => {
|
|
90
|
+
it("keeps user models.quick value", () => {
|
|
91
|
+
assert.ok(result.includes("quick: haiku"));
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it("keeps user rfc_approval", () => {
|
|
95
|
+
assert.ok(result.includes("rfc_approval: skip"));
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it("keeps user push_approval", () => {
|
|
99
|
+
assert.ok(result.includes("push_approval: auto"));
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it("keeps user re_review_lines", () => {
|
|
103
|
+
assert.ok(result.includes("re_review_lines: 50"));
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it("keeps user phase_time_limit", () => {
|
|
107
|
+
assert.ok(result.includes("phase_time_limit: 20"));
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it("keeps user stuck_retry_limit", () => {
|
|
111
|
+
assert.ok(result.includes("stuck_retry_limit: 5"));
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it("keeps user verification commands", () => {
|
|
115
|
+
assert.ok(result.includes('typecheck: "yarn tsc --noEmit"'));
|
|
116
|
+
assert.ok(result.includes('test: "yarn test"'));
|
|
117
|
+
assert.ok(result.includes('lint: "yarn lint"'));
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
describe("template structure is preserved", () => {
|
|
122
|
+
it("keeps comments from template", () => {
|
|
123
|
+
assert.ok(result.includes("# Orchestra Configuration"));
|
|
124
|
+
assert.ok(result.includes("# Model selection per phase complexity"));
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it("maintains section order", () => {
|
|
128
|
+
const pipelineIdx = result.indexOf("pipeline:");
|
|
129
|
+
const thresholdsIdx = result.indexOf("thresholds:");
|
|
130
|
+
const verificationIdx = result.indexOf("verification:");
|
|
131
|
+
assert.ok(pipelineIdx < thresholdsIdx);
|
|
132
|
+
assert.ok(thresholdsIdx < verificationIdx);
|
|
133
|
+
});
|
|
134
|
+
});
|
|
135
|
+
});
|
package/package.json
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sulhadin/orchestrator",
|
|
3
|
-
"version": "3.0.0-beta.
|
|
3
|
+
"version": "3.0.0-beta.9",
|
|
4
4
|
"description": "AI Team Orchestration System — multi-role coordination for Claude Code",
|
|
5
5
|
"bin": "bin/index.js",
|
|
6
6
|
"scripts": {
|
|
7
|
+
"test": "node --test bin/**/*.test.js",
|
|
7
8
|
"template": "node bin/build-template.js",
|
|
8
9
|
"prepare": "husky"
|
|
9
10
|
},
|
|
@@ -23,7 +24,8 @@
|
|
|
23
24
|
"ai",
|
|
24
25
|
"agent",
|
|
25
26
|
"multi-agent",
|
|
26
|
-
"claude-code"
|
|
27
|
+
"claude-code",
|
|
28
|
+
"team"
|
|
27
29
|
],
|
|
28
30
|
"repository": {
|
|
29
31
|
"type": "git",
|
|
@@ -26,7 +26,12 @@ When started:
|
|
|
26
26
|
- Glob `.orchestra/milestones/*/milestone.md`
|
|
27
27
|
- Read each to check Status field
|
|
28
28
|
- `status: in-progress` → resume | `status: planning` → start | all `done` → report complete
|
|
29
|
-
6.
|
|
29
|
+
6. If milestone scope spans 3+ directories or 20+ files, launch scout sub-agent (haiku):
|
|
30
|
+
- Scan project structure within milestone's phase scopes
|
|
31
|
+
- Return max 30-line map: `path/file — one-line description`
|
|
32
|
+
- Write map to context.md under `## Codebase Map`
|
|
33
|
+
- For smaller scopes, skip — sub-agents can explore directly.
|
|
34
|
+
7. Begin execution loop.
|
|
30
35
|
|
|
31
36
|
## Pipeline Selection
|
|
32
37
|
|
|
@@ -38,13 +43,13 @@ Read `Complexity` from milestone.md + `pipeline` from config.yml:
|
|
|
38
43
|
| `standard` | Phases → Review → Push |
|
|
39
44
|
| `full` | Architect → Phases → Review → Push |
|
|
40
45
|
|
|
41
|
-
Default: `
|
|
46
|
+
Default: config.yml `pipeline.default_pipeline` (default `full`).
|
|
42
47
|
|
|
43
48
|
## Milestone Lock
|
|
44
49
|
|
|
45
50
|
Before starting a milestone:
|
|
46
51
|
1. Check milestone.md for `Locked-By` field
|
|
47
|
-
2. If locked and <
|
|
52
|
+
2. If locked and < config.yml `thresholds.milestone_lock_timeout` minutes → skip this milestone
|
|
48
53
|
3. If no lock or stale → write `Locked-By: {timestamp}`
|
|
49
54
|
4. On completion or failure → remove `Locked-By`
|
|
50
55
|
|
|
@@ -59,53 +64,103 @@ For each phase:
|
|
|
59
64
|
- Read phase file — extract role, skills, scope, acceptance criteria, depends_on
|
|
60
65
|
- Check phase status — skip if `done`, resume if `in-progress`
|
|
61
66
|
- Verify dependencies — all `depends_on` phases must be `done`
|
|
67
|
+
- Select model: read `complexity` from phase file (default: config.yml `pipeline.default_complexity`), map via `pipeline.models:`
|
|
62
68
|
|
|
63
|
-
### 2.
|
|
64
|
-
|
|
69
|
+
### 2. Pre-read & Compose Prompt (Conductor does this)
|
|
70
|
+
|
|
71
|
+
Before launching the sub-agent, conductor reads required files and inlines
|
|
72
|
+
their content into the prompt. This eliminates sub-agent startup Read calls.
|
|
73
|
+
Cache role/skills content in conductor context — don't re-read for consecutive
|
|
74
|
+
phases with the same role.
|
|
75
|
+
|
|
76
|
+
1. Read `.orchestra/roles/{role}.md` → role_content (skip if same role as previous phase)
|
|
77
|
+
2. Read skill files from phase → skills_content (skip already-read skills)
|
|
78
|
+
3. Read phase file → phase_content
|
|
79
|
+
4. Extract verification commands from config.yml (read once at startup, reuse)
|
|
80
|
+
5. Read codebase map from context.md (if exists) → codebase_map
|
|
81
|
+
|
|
82
|
+
### 3. Delegate to Phase Sub-Agent
|
|
83
|
+
|
|
84
|
+
Launch sub-agent with model from pre-flight step. Always use default
|
|
85
|
+
(general-purpose) subagent_type — role identity is provided in the prompt,
|
|
86
|
+
using named types like "backend-engineer" would load a conflicting agent definition.
|
|
87
|
+
Save the sub-agent ID for potential fix cycles via SendMessage.
|
|
88
|
+
|
|
89
|
+
Prompt structure: static content first (better prefix cache hit chance when
|
|
90
|
+
same role runs consecutive phases), dynamic content last.
|
|
65
91
|
|
|
66
92
|
```
|
|
67
93
|
You are executing a phase for Orchestra conductor.
|
|
68
|
-
|
|
69
|
-
**Role:** Read `.orchestra/roles/{role}.md` — adopt this identity.
|
|
70
|
-
**Skills:** Read these skill files: {skill_files_list}
|
|
71
|
-
**Phase file:** Read `{phase_file_path}` for objective, scope, and acceptance criteria.
|
|
72
|
-
**Config:** Read `.orchestra/config.yml` for verification commands.
|
|
73
94
|
Rules from `.claude/rules/*.orchestra.md` are automatically loaded.
|
|
74
95
|
|
|
96
|
+
**Verification commands (run in this order, stop at first failure):**
|
|
97
|
+
typecheck: {typecheck_cmd}
|
|
98
|
+
test: {test_cmd}
|
|
99
|
+
lint: {lint_cmd}
|
|
100
|
+
|
|
101
|
+
**Role:**
|
|
102
|
+
{role_content}
|
|
103
|
+
|
|
104
|
+
**Skills:**
|
|
105
|
+
{skills_content}
|
|
106
|
+
|
|
107
|
+
**Phase:**
|
|
108
|
+
{phase_content}
|
|
109
|
+
|
|
110
|
+
**Codebase map:**
|
|
111
|
+
{codebase_map}
|
|
112
|
+
|
|
113
|
+
**Previous phase summary:**
|
|
114
|
+
{previous_phase_result_summary — concise summary of decisions and artifacts
|
|
115
|
+
that affect this phase. Omit for first phase.}
|
|
116
|
+
|
|
75
117
|
## Your Task
|
|
76
|
-
1. Research — read existing code in scope
|
|
118
|
+
1. Research — read existing code in scope (use codebase map to target files)
|
|
77
119
|
2. Implement — write code + tests following role identity + skill checklists
|
|
78
|
-
3. Verify — run verification commands
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
4. Acceptance —
|
|
82
|
-
5.
|
|
120
|
+
3. Verify — run verification commands: typecheck → test → lint (in order, stop at first failure).
|
|
121
|
+
Fix and retry until all pass (max {stuck_retry_limit} attempts). Error logs stay in your
|
|
122
|
+
context — this is intentional, your context is ephemeral.
|
|
123
|
+
4. Acceptance — check each acceptance criterion from phase. Note any gaps.
|
|
124
|
+
5. Report — when all verification passes and acceptance is checked, report back.
|
|
125
|
+
Do NOT commit — conductor handles commit.
|
|
83
126
|
|
|
84
127
|
## Return Format
|
|
85
|
-
Report back with:
|
|
86
128
|
- status: done | failed
|
|
87
|
-
- commit_hash: (if committed)
|
|
88
129
|
- files_changed: [list]
|
|
89
130
|
- verification_retries: N
|
|
90
|
-
- error_summary: (if failed
|
|
131
|
+
- error_summary: (if failed after max retries, include last error)
|
|
91
132
|
- acceptance_notes: (any unverified criteria)
|
|
133
|
+
- notes: (workarounds flagged, effort concerns, anything conductor should know)
|
|
92
134
|
```
|
|
93
135
|
|
|
94
|
-
###
|
|
95
|
-
|
|
96
|
-
- If **
|
|
136
|
+
### 4. Process Sub-Agent Result (Conductor does this)
|
|
137
|
+
|
|
138
|
+
- If **done** (verification passed):
|
|
139
|
+
1. Conductor commits → update phase status → `done`, update context.md
|
|
140
|
+
2. Store sub-agent ID for potential review fix cycle
|
|
141
|
+
- If **failed** (verification failed after max retries):
|
|
142
|
+
1. Log in context.md: phase name, last error summary, retry count
|
|
143
|
+
2. Decide: retry with new sub-agent or escalate to user
|
|
144
|
+
|
|
145
|
+
**Note:** Conductor owns commit only. Sub-agents own implementation + verification.
|
|
97
146
|
|
|
98
147
|
### Sub-Agent Configuration
|
|
99
|
-
-
|
|
100
|
-
-
|
|
148
|
+
- Model selected per phase complexity via config.yml `pipeline.models:`
|
|
149
|
+
- Use Agent tool `isolation: "worktree"` when `pipeline.parallel: enabled`
|
|
101
150
|
- Each sub-agent starts with fresh context — no carryover from previous phases
|
|
151
|
+
- Sub-agent runs its own verification loop (tight feedback, errors stay in ephemeral context)
|
|
152
|
+
- Conductor stores sub-agent ID for potential review fix cycles
|
|
153
|
+
- Conductor passes previous phase result summary to next phase
|
|
102
154
|
|
|
103
155
|
## Parallel Execution
|
|
104
156
|
|
|
105
157
|
If config.yml `pipeline.parallel: enabled`:
|
|
106
158
|
1. Read all phase files and `depends_on` fields
|
|
107
|
-
2. Phases with `depends_on: []` launch as concurrent sub-agents
|
|
108
|
-
|
|
159
|
+
2. Phases with `depends_on: []` launch as concurrent sub-agents:
|
|
160
|
+
- Use Agent tool with `run_in_background: true` and `isolation: "worktree"` for each
|
|
161
|
+
- Track launched count, process each completion notification as it arrives
|
|
162
|
+
- Proceed to step 3 only when all launched sub-agents have completed
|
|
163
|
+
3. Merge results in phase order (sub-agents already verified in their worktrees)
|
|
109
164
|
4. If `depends_on` not set on any phase → sequential (backward compatible)
|
|
110
165
|
|
|
111
166
|
## Review
|
|
@@ -116,7 +171,8 @@ After all implementation phases (unless config says `review: skip`):
|
|
|
116
171
|
3. **approved** → push gate
|
|
117
172
|
4. **approved-with-comments** → push gate, log comments in context.md
|
|
118
173
|
5. **changes-requested** → fix cycle:
|
|
119
|
-
-
|
|
174
|
+
- Use SendMessage to continue the last phase's sub-agent with reviewer findings
|
|
175
|
+
(if sub-agent no longer available, launch new sub-agent with findings + role)
|
|
120
176
|
- If fix < config `re_review_lines` → proceed
|
|
121
177
|
- If fix >= config `re_review_lines` → abbreviated re-review
|
|
122
178
|
|
|
@@ -128,13 +184,13 @@ Read gate behavior from config.yml:
|
|
|
128
184
|
|
|
129
185
|
## Rejection Flow
|
|
130
186
|
|
|
131
|
-
- **RFC Rejected:** Ask feedback → architect revises → re-submit (max
|
|
187
|
+
- **RFC Rejected:** Ask feedback → architect revises → re-submit (max config.yml `pipeline.max_rfc_rounds`).
|
|
132
188
|
- **Push Rejected:** Ask feedback → create fix phase → re-submit.
|
|
133
189
|
|
|
134
190
|
## Milestone Completion
|
|
135
191
|
|
|
136
192
|
After push:
|
|
137
|
-
1. Update milestone.md `status: done`, remove `Locked-By
|
|
193
|
+
1. Update milestone.md `status: done`, remove `Locked-By`.
|
|
138
194
|
2. Append 5-line retrospective to knowledge.md:
|
|
139
195
|
```
|
|
140
196
|
## Retro: {id} — {title} ({date})
|
|
@@ -161,14 +217,13 @@ On resume: read context.md, continue from last completed phase.
|
|
|
161
217
|
|
|
162
218
|
When user types `/orchestra hotfix {description}`:
|
|
163
219
|
1. Auto-create hotfix milestone + single phase
|
|
164
|
-
2. Launch
|
|
165
|
-
3.
|
|
166
|
-
|
|
167
|
-
|
|
220
|
+
2. Launch implementation sub-agent (model: standard) — implements, verifies, reports
|
|
221
|
+
3. If done → conductor commits → push immediately (no RFC, no review, no gates)
|
|
222
|
+
5. Append one-liner to knowledge.md
|
|
223
|
+
6. Return to normal execution if active
|
|
168
224
|
|
|
169
225
|
## What Conductor Does NOT Do
|
|
170
226
|
|
|
171
|
-
- Does NOT implement code (sub-agents do)
|
|
172
|
-
- Does NOT run verification commands directly (sub-agents do)
|
|
227
|
+
- Does NOT implement code (implementation sub-agents do)
|
|
173
228
|
- Does NOT create milestones (PM does)
|
|
174
229
|
- Does NOT modify Orchestra system files (Orchestrator does)
|
|
@@ -1,13 +1,11 @@
|
|
|
1
1
|
# Acceptance Check
|
|
2
2
|
|
|
3
|
-
After
|
|
4
|
-
check whether you built **the right thing** — not just whether it works.
|
|
3
|
+
After implementation, check whether you built **the right thing** — not just whether it works.
|
|
5
4
|
|
|
6
5
|
1. Re-read the phase file's acceptance criteria
|
|
7
|
-
2. For EACH criterion
|
|
8
|
-
3. If YES → proceed
|
|
9
|
-
4. If NO → fix it
|
|
10
|
-
5. If UNCERTAIN → flag
|
|
6
|
+
2. For EACH criterion: "Does my implementation satisfy this?"
|
|
7
|
+
3. If YES → proceed
|
|
8
|
+
4. If NO → fix it
|
|
9
|
+
5. If UNCERTAIN → flag: "Unverified: {criterion} — {reason}"
|
|
11
10
|
|
|
12
11
|
This catches "code works but doesn't do what was asked."
|
|
13
|
-
Verification gate checks if code is correct. Acceptance check checks if code is complete.
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
**DRY** — Don't repeat yourself. Extract after second duplication, not before.
|
|
10
10
|
|
|
11
11
|
**Zero-tolerance rules:**
|
|
12
|
-
- No workarounds — if the right solution is hard, do it right. Flag effort in context.md.
|
|
12
|
+
- No workarounds — if the right solution is hard, do it right. Flag effort in report or context.md.
|
|
13
13
|
- No unused code — delete dead imports, functions, files. Don't comment out.
|
|
14
14
|
- No breaking changes without migration — update every consumer.
|
|
15
15
|
- Current library versions only — verify before using, don't assume from memory.
|
|
@@ -2,6 +2,16 @@
|
|
|
2
2
|
# Customize pipeline behavior, thresholds, and verification commands.
|
|
3
3
|
|
|
4
4
|
pipeline:
|
|
5
|
+
# Model selection per phase complexity
|
|
6
|
+
# trivial: version bumps, env vars, config-only changes
|
|
7
|
+
# quick: single-file fixes, simple CRUD
|
|
8
|
+
# standard: typical features
|
|
9
|
+
# complex: new subsystems, architectural changes
|
|
10
|
+
models:
|
|
11
|
+
trivial: haiku
|
|
12
|
+
quick: sonnet
|
|
13
|
+
standard: sonnet
|
|
14
|
+
complex: opus
|
|
5
15
|
# RFC approval gate: required | optional | skip
|
|
6
16
|
rfc_approval: required
|
|
7
17
|
|
|
@@ -15,7 +25,19 @@ pipeline:
|
|
|
15
25
|
# When enabled, phases with depends_on: [] run in parallel
|
|
16
26
|
parallel: disabled
|
|
17
27
|
|
|
28
|
+
# Default pipeline when milestone Complexity is missing
|
|
29
|
+
default_pipeline: full # quick | standard | full
|
|
30
|
+
|
|
31
|
+
# Default phase complexity when not set by PM
|
|
32
|
+
default_complexity: standard # trivial | quick | standard | complex
|
|
33
|
+
|
|
34
|
+
# Max RFC rejection rounds before escalating to user
|
|
35
|
+
max_rfc_rounds: 3
|
|
36
|
+
|
|
18
37
|
thresholds:
|
|
38
|
+
# Milestone lock timeout in minutes (stale locks are ignored)
|
|
39
|
+
milestone_lock_timeout: 120
|
|
40
|
+
|
|
19
41
|
# Fix cycle: re-review if fix exceeds this many lines
|
|
20
42
|
re_review_lines: 30
|
|
21
43
|
|
|
@@ -68,11 +68,12 @@ Cannot write: feature code, RFCs, architecture docs, review findings, system fil
|
|
|
68
68
|
|
|
69
69
|
```markdown
|
|
70
70
|
---
|
|
71
|
-
role: backend-engineer | frontend-engineer | architect | adaptive
|
|
72
|
-
status: pending | in-progress | done | failed
|
|
73
|
-
order: 1
|
|
74
|
-
|
|
75
|
-
|
|
71
|
+
role: backend-engineer | frontend-engineer | architect | adaptive
|
|
72
|
+
status: pending | in-progress | done | failed
|
|
73
|
+
order: 1
|
|
74
|
+
complexity: standard # trivial | quick | standard | complex — conductor uses this for model selection
|
|
75
|
+
skills: []
|
|
76
|
+
depends_on: []
|
|
76
77
|
---
|
|
77
78
|
|
|
78
79
|
## Objective
|
package/template/CLAUDE.md
CHANGED
|
@@ -23,6 +23,7 @@ Call `ask_user_questions` with:
|
|
|
23
23
|
7. Discussion — Just brainstorm, no role needed
|
|
24
24
|
|
|
25
25
|
If user skips or starts giving instructions directly — work with them normally.
|
|
26
|
+
Sub-agents spawned by conductor skip role selection — they already have a role assigned in their prompt.
|
|
26
27
|
Do NOT greet. Do NOT explain. The role selection IS your greeting.
|
|
27
28
|
|
|
28
29
|
**AFTER ROLE SELECTED:**
|