@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 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
- // Restore config.yml (user's customizations take priority)
422
+ // Merge config.yml: new template keys added, user values preserved
306
423
  if (hasConfig && fs.existsSync(configBackup)) {
307
- fs.copyFileSync(configBackup, path.join(orchestraDest, "config.yml"));
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(" [+] Restored config.yml (user customizations preserved)");
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.7",
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. Begin execution loop.
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: `full` if Complexity missing.
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 < 2 hours old → skip this milestone
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. Delegate to Phase Sub-Agent
64
- Launch a sub-agent with this prompt template:
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, check dependency versions
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 from config.yml (typecheck → test → lint)
79
- - If verification fails, fix and retry (max {stuck_retry_limit} attempts)
80
- - If still failing after max retries, report failure with error summary
81
- 4. Acceptance — verify each acceptance criterion from phase file is satisfied
82
- 5. Commitone conventional commit per phase
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. Reportwhen 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, max 5 lines)
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
- ### 3. Process Sub-Agent Result (Conductor does this)
95
- - If **done**: update phase file status → `done`, fill Result section, update context.md
96
- - If **failed**: log in context.md, check stuck_retry_limit, decide to retry or escalate
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
- - Use worktree isolation when `pipeline.parallel: enabled`
100
- - Sub-agent inherits model from conductor config
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 with worktree isolation
108
- 3. Merge results in phase order, verify after each merge
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
- - Launch fix sub-agent with reviewer findings + relevant role
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 3 rounds).
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 single sub-agent: implement verify commit
165
- 3. Push immediately (no RFC, no review, no approval gates)
166
- 4. Append one-liner to knowledge.md
167
- 5. Return to normal execution if active
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 verification gate passes (code compiles, tests pass, lint clean),
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, ask: "Does my implementation satisfy this?"
8
- 3. If YES → proceed to commit
9
- 4. If NO → fix it, re-run verification gate
10
- 5. If UNCERTAIN → flag in context.md: "Unverified: {criterion} — {reason}"
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
- skills: []
75
- depends_on: []
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
@@ -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:**