maestro-flow 0.3.13 → 0.3.14
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/.codex/skills/maestro-link-coordinate/SKILL.md +430 -224
- package/chains/milestone-fork-merge.json +6 -6
- package/dist/src/hooks/auto-mode.d.ts +18 -0
- package/dist/src/hooks/auto-mode.d.ts.map +1 -0
- package/dist/src/hooks/auto-mode.js +28 -0
- package/dist/src/hooks/auto-mode.js.map +1 -0
- package/dist/src/hooks/context-monitor.d.ts.map +1 -1
- package/dist/src/hooks/context-monitor.js +14 -3
- package/dist/src/hooks/context-monitor.js.map +1 -1
- package/dist/src/hooks/coordinator-tracker.d.ts +1 -0
- package/dist/src/hooks/coordinator-tracker.d.ts.map +1 -1
- package/dist/src/hooks/coordinator-tracker.js +19 -9
- package/dist/src/hooks/coordinator-tracker.js.map +1 -1
- package/package.json +1 -1
- package/workflows/maestro-coordinate.codex.md +9 -9
- package/workflows/maestro-coordinate.md +9 -9
- package/workflows/maestro.md +2 -2
- package/.codex/skills/maestro-chain/SKILL.md +0 -233
|
@@ -1,224 +1,430 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: maestro-link-coordinate
|
|
3
|
-
description:
|
|
4
|
-
argument-hint: "\"intent text\" [--list] [-c [sessionId]] [--chain <name>] [
|
|
5
|
-
allowed-tools: Read, Write, Bash, Glob, Grep
|
|
6
|
-
---
|
|
7
|
-
|
|
8
|
-
<purpose>
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
|
22
|
-
|
|
23
|
-
|
|
|
24
|
-
|
|
|
25
|
-
|
|
|
26
|
-
|
|
|
27
|
-
|
|
|
28
|
-
|
|
|
29
|
-
|
|
|
30
|
-
|
|
|
31
|
-
| | +--
|
|
32
|
-
|
|
|
33
|
-
|
|
|
34
|
-
|
|
|
35
|
-
| +--
|
|
36
|
-
| +--
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
$
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
**
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
}
|
|
113
|
-
//
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
{
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
})
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
1
|
+
---
|
|
2
|
+
name: maestro-link-coordinate
|
|
3
|
+
description: Chain-graph walker with in-process flow control. Loads chain JSON, walks nodes in main process, dispatches command nodes via spawn_agents_on_csv. Decision nodes resolved in-process between waves.
|
|
4
|
+
argument-hint: "\"intent text\" [--list] [-c [sessionId]] [--chain <name>] [-y]"
|
|
5
|
+
allowed-tools: spawn_agents_on_csv, Read, Write, Edit, Bash, Glob, Grep, AskUserQuestion
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
<purpose>
|
|
9
|
+
In-process chain-graph coordinator. Unlike the CLI-delegated version (maestro coordinate start/next),
|
|
10
|
+
this coordinator loads chain graph JSON directly and drives flow in the main process:
|
|
11
|
+
|
|
12
|
+
- Command nodes → spawn via `spawn_agents_on_csv` (one command = one wave, always solo)
|
|
13
|
+
- Decision nodes → resolve in-process using expression evaluation against accumulated context
|
|
14
|
+
- Gate/terminal nodes → handle in-process
|
|
15
|
+
|
|
16
|
+
Coordinator responsibilities: load graph → walk nodes → build skill_call → spawn → read result →
|
|
17
|
+
evaluate decision → advance → persist state → repeat until terminal.
|
|
18
|
+
|
|
19
|
+
```
|
|
20
|
+
+-------------------------------------------------------------------+
|
|
21
|
+
| maestro-link-coordinate (in-process walker) |
|
|
22
|
+
+-------------------------------------------------------------------+
|
|
23
|
+
| |
|
|
24
|
+
| Phase 1: Load Chain Graph |
|
|
25
|
+
| +-- Parse flags (--chain, -y, -c, --list) |
|
|
26
|
+
| +-- Load chain JSON from chains/ directory |
|
|
27
|
+
| +-- Initialize session state |
|
|
28
|
+
| |
|
|
29
|
+
| Phase 2: Walk Loop |
|
|
30
|
+
| +-- while (current_node != terminal): |
|
|
31
|
+
| | +-- command → build skill_call → spawn_agents_on_csv |
|
|
32
|
+
| | | → read result → update context → follow next|
|
|
33
|
+
| | +-- decision → evaluate expr against ctx.result |
|
|
34
|
+
| | | → match edge → follow target |
|
|
35
|
+
| | +-- gate → evaluate condition → on_pass / on_fail |
|
|
36
|
+
| | +-- terminal → exit loop |
|
|
37
|
+
| +-- Persist state after each node |
|
|
38
|
+
| |
|
|
39
|
+
| Phase 3: Completion Report |
|
|
40
|
+
| +-- Per-node results with outcomes |
|
|
41
|
+
| +-- Final status and resume hint |
|
|
42
|
+
+-------------------------------------------------------------------+
|
|
43
|
+
```
|
|
44
|
+
</purpose>
|
|
45
|
+
|
|
46
|
+
<context>
|
|
47
|
+
$ARGUMENTS — user intent text, or flags.
|
|
48
|
+
|
|
49
|
+
**Flags**:
|
|
50
|
+
- `--list` — List all available chain graphs (scan chains/ directory)
|
|
51
|
+
- `-c / --continue [sessionId]` — Resume from last completed node
|
|
52
|
+
- `--chain <name>` — Force a specific chain graph
|
|
53
|
+
- `-y / --yes` — Auto mode: no confirmations between nodes
|
|
54
|
+
|
|
55
|
+
**Session state**: `.workflow/.maestro-coordinate/{session-id}/`
|
|
56
|
+
**Chain graphs**: `chains/` and `chains/singles/` directories (JSON files)
|
|
57
|
+
</context>
|
|
58
|
+
|
|
59
|
+
<invariants>
|
|
60
|
+
1. **ALL command-node execution via spawn_agents_on_csv**: Coordinator NEVER executes skills directly. Every command node dispatches through `spawn_agents_on_csv`.
|
|
61
|
+
2. **Coordinator = graph walker + prompt assembler**: Load graph → walk → build skill_call → spawn → read result → evaluate decisions → persist. Nothing else.
|
|
62
|
+
3. **One command per wave**: Each command node runs as a solo wave (result needed for subsequent decisions).
|
|
63
|
+
4. **Decision nodes are in-process**: Coordinator evaluates `node.eval` against `ctx.result` directly. No sub-agent or CLI delegation.
|
|
64
|
+
5. **Context flows forward**: Each command result is captured and available to subsequent decision expressions and command args.
|
|
65
|
+
6. **max_visits enforced**: Track visit count per node; bail with failure if exceeded.
|
|
66
|
+
7. **Resume from node**: `-c` loads saved state and continues from last incomplete node.
|
|
67
|
+
</invariants>
|
|
68
|
+
|
|
69
|
+
<execution>
|
|
70
|
+
|
|
71
|
+
### Phase 1: Load Chain Graph
|
|
72
|
+
|
|
73
|
+
```javascript
|
|
74
|
+
const args = $ARGUMENTS.trim();
|
|
75
|
+
const listMode = /\b--list\b/.test(args);
|
|
76
|
+
const autoYes = /\b(-y|--yes)\b/.test(args);
|
|
77
|
+
const resumeMode = /\b(-c|--continue)\b/.test(args);
|
|
78
|
+
const resumeId = args.match(/(?:-c|--continue)\s+(\S+)/)?.[1] || null;
|
|
79
|
+
const forcedChain = args.match(/--chain\s+(\S+)/)?.[1] || null;
|
|
80
|
+
const intent = args
|
|
81
|
+
.replace(/\b(-y|--yes|--list|-c|--continue)\b/g, '')
|
|
82
|
+
.replace(/(?:-c|--continue)\s+\S+/g, '')
|
|
83
|
+
.replace(/--chain\s+\S+/g, '')
|
|
84
|
+
.trim();
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
**`--list`**: Scan `chains/*.json` and `chains/singles/*.json`, display names + descriptions, stop.
|
|
88
|
+
|
|
89
|
+
**`-c` (resume)**:
|
|
90
|
+
1. Glob `.workflow/.maestro-coordinate/MLC-*/state.json`, pick most recent (or by `resumeId`)
|
|
91
|
+
2. Load state → find first node with `status !== "completed"` → set as `current_node`
|
|
92
|
+
3. Jump to **Phase 2**
|
|
93
|
+
|
|
94
|
+
**Fresh session**:
|
|
95
|
+
1. Resolve chain: `--chain` direct or classify from intent using `chains/_intent-map.json`
|
|
96
|
+
2. Load chain JSON: try `chains/{name}.json` then `chains/singles/{name}.json`
|
|
97
|
+
3. Read `.workflow/state.json` for project context (phase, milestone)
|
|
98
|
+
4. Initialize session:
|
|
99
|
+
|
|
100
|
+
```javascript
|
|
101
|
+
const sessionId = `MLC-${dateStr}-${timeStr}`;
|
|
102
|
+
const sessionDir = `.workflow/.maestro-coordinate/${sessionId}`;
|
|
103
|
+
|
|
104
|
+
const state = {
|
|
105
|
+
id: sessionId, intent, chain: graph.id, auto_mode: autoYes,
|
|
106
|
+
status: "in_progress", started_at: new Date().toISOString(),
|
|
107
|
+
current_node: graph.entry,
|
|
108
|
+
context: {
|
|
109
|
+
phase: resolvedPhase ?? null, description: intent,
|
|
110
|
+
result: null // last command result, used by decision eval
|
|
111
|
+
},
|
|
112
|
+
visit_counts: {}, // nodeId → number
|
|
113
|
+
history: [], // { node_id, type, outcome, summary, timestamp }
|
|
114
|
+
};
|
|
115
|
+
Write(`${sessionDir}/state.json`, JSON.stringify(state, null, 2));
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
**`--dry-run`**: Display node walk order with types, stop.
|
|
119
|
+
|
|
120
|
+
**Confirm** (skip if `autoYes`): Display chain summary, prompt `Proceed?`.
|
|
121
|
+
|
|
122
|
+
### Phase 2: Walk Loop
|
|
123
|
+
|
|
124
|
+
```javascript
|
|
125
|
+
while (state.status === 'in_progress') {
|
|
126
|
+
const nodeId = state.current_node;
|
|
127
|
+
const node = graph.nodes[nodeId];
|
|
128
|
+
|
|
129
|
+
if (!node) {
|
|
130
|
+
state.status = 'failed';
|
|
131
|
+
state.history.push({ node_id: nodeId, type: 'error', outcome: 'failed',
|
|
132
|
+
summary: `Node "${nodeId}" not found in graph`, timestamp: now() });
|
|
133
|
+
break;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// max_visits guard
|
|
137
|
+
state.visit_counts[nodeId] = (state.visit_counts[nodeId] ?? 0) + 1;
|
|
138
|
+
if (node.max_visits && state.visit_counts[nodeId] > node.max_visits) {
|
|
139
|
+
state.status = 'failed';
|
|
140
|
+
state.history.push({ node_id: nodeId, type: node.type, outcome: 'max_visits_exceeded',
|
|
141
|
+
summary: `Exceeded max_visits (${node.max_visits})`, timestamp: now() });
|
|
142
|
+
break;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
switch (node.type) {
|
|
146
|
+
case 'command': handleCommand(state, graph, nodeId, node); break;
|
|
147
|
+
case 'decision': handleDecision(state, nodeId, node); break;
|
|
148
|
+
case 'gate': handleGate(state, nodeId, node); break;
|
|
149
|
+
case 'terminal': handleTerminal(state, nodeId, node); break;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Persist after every node
|
|
153
|
+
Write(`${sessionDir}/state.json`, JSON.stringify(state, null, 2));
|
|
154
|
+
}
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
#### handleCommand — spawn via CSV
|
|
158
|
+
|
|
159
|
+
```javascript
|
|
160
|
+
function handleCommand(state, graph, nodeId, node) {
|
|
161
|
+
// 1. Build skill_call
|
|
162
|
+
const skillCall = buildSkillCall(node, state.context, state.auto_mode);
|
|
163
|
+
|
|
164
|
+
// 2. Write single-row CSV
|
|
165
|
+
const csvPath = `${sessionDir}/wave-${nodeId}.csv`;
|
|
166
|
+
const csv = `id,skill_call,topic\n"${nodeId}","${skillCall.replace(/"/g, '""')}","Chain \\"${state.chain}\\" node ${nodeId}"`;
|
|
167
|
+
Write(csvPath, csv);
|
|
168
|
+
|
|
169
|
+
// 3. Spawn
|
|
170
|
+
spawn_agents_on_csv({
|
|
171
|
+
csv_path: csvPath,
|
|
172
|
+
id_column: "id",
|
|
173
|
+
instruction: AGENT_INSTRUCTION,
|
|
174
|
+
max_workers: 1,
|
|
175
|
+
max_runtime_seconds: 1800,
|
|
176
|
+
output_csv_path: `${sessionDir}/wave-${nodeId}-results.csv`,
|
|
177
|
+
output_schema: RESULT_SCHEMA
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
// 4. Read result
|
|
181
|
+
const results = readCSV(`${sessionDir}/wave-${nodeId}-results.csv`);
|
|
182
|
+
const result = results[0];
|
|
183
|
+
const outcome = result?.status === 'completed' ? 'success' : 'failed';
|
|
184
|
+
|
|
185
|
+
// 5. Update context with result (for downstream decision eval)
|
|
186
|
+
state.context.result = parseResultContext(result);
|
|
187
|
+
|
|
188
|
+
// 6. Record history
|
|
189
|
+
state.history.push({
|
|
190
|
+
node_id: nodeId, type: 'command', outcome,
|
|
191
|
+
summary: result?.summary ?? '', timestamp: now()
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
// 7. Advance or fail
|
|
195
|
+
if (outcome === 'failed') {
|
|
196
|
+
if (node.on_failure) {
|
|
197
|
+
state.current_node = node.on_failure;
|
|
198
|
+
} else {
|
|
199
|
+
state.status = 'failed';
|
|
200
|
+
}
|
|
201
|
+
} else {
|
|
202
|
+
state.current_node = node.next;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
#### handleDecision — in-process expr evaluation
|
|
208
|
+
|
|
209
|
+
```javascript
|
|
210
|
+
function handleDecision(state, nodeId, node) {
|
|
211
|
+
// Resolve eval expression from context
|
|
212
|
+
const evalKey = node.eval; // e.g. "ctx.result.verification_status"
|
|
213
|
+
const value = resolveExpr(evalKey, state.context);
|
|
214
|
+
|
|
215
|
+
// Match edge
|
|
216
|
+
let target = null;
|
|
217
|
+
let matchedLabel = null;
|
|
218
|
+
for (const edge of node.edges) {
|
|
219
|
+
if (edge.value !== undefined && String(edge.value) === String(value)) {
|
|
220
|
+
target = edge.target;
|
|
221
|
+
matchedLabel = edge.label ?? edge.description ?? String(edge.value);
|
|
222
|
+
break;
|
|
223
|
+
}
|
|
224
|
+
if (edge.match && new RegExp(edge.match).test(String(value))) {
|
|
225
|
+
target = edge.target;
|
|
226
|
+
matchedLabel = edge.label ?? edge.description ?? edge.match;
|
|
227
|
+
break;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
// Default fallback
|
|
231
|
+
if (!target) {
|
|
232
|
+
const defaultEdge = node.edges.find(e => e.default);
|
|
233
|
+
if (defaultEdge) {
|
|
234
|
+
target = defaultEdge.target;
|
|
235
|
+
matchedLabel = defaultEdge.label ?? defaultEdge.description ?? 'default';
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
state.history.push({
|
|
240
|
+
node_id: nodeId, type: 'decision',
|
|
241
|
+
outcome: target ? 'resolved' : 'no_match',
|
|
242
|
+
summary: `${evalKey} = "${value}" → ${matchedLabel ?? 'none'}`,
|
|
243
|
+
timestamp: now()
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
if (target) {
|
|
247
|
+
state.current_node = target;
|
|
248
|
+
} else {
|
|
249
|
+
state.status = 'failed';
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
#### handleGate — condition check
|
|
255
|
+
|
|
256
|
+
```javascript
|
|
257
|
+
function handleGate(state, nodeId, node) {
|
|
258
|
+
const passed = resolveExpr(node.condition, state.context);
|
|
259
|
+
state.history.push({
|
|
260
|
+
node_id: nodeId, type: 'gate',
|
|
261
|
+
outcome: passed ? 'passed' : 'blocked',
|
|
262
|
+
summary: `${node.condition} → ${passed}`,
|
|
263
|
+
timestamp: now()
|
|
264
|
+
});
|
|
265
|
+
state.current_node = passed ? node.on_pass : node.on_fail;
|
|
266
|
+
}
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
#### handleTerminal
|
|
270
|
+
|
|
271
|
+
```javascript
|
|
272
|
+
function handleTerminal(state, nodeId, node) {
|
|
273
|
+
state.status = node.status === 'success' ? 'completed' : 'failed';
|
|
274
|
+
state.history.push({
|
|
275
|
+
node_id: nodeId, type: 'terminal',
|
|
276
|
+
outcome: node.status ?? 'completed',
|
|
277
|
+
summary: node.summary ?? 'Chain walk complete',
|
|
278
|
+
timestamp: now()
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
### Shared Utilities
|
|
284
|
+
|
|
285
|
+
```javascript
|
|
286
|
+
const AUTO_FLAG_MAP = {
|
|
287
|
+
'maestro-analyze': '-y', 'maestro-brainstorm': '-y',
|
|
288
|
+
'maestro-ui-design': '-y', 'maestro-plan': '--auto',
|
|
289
|
+
'maestro-spec-generate': '-y', 'quality-test': '--auto-fix',
|
|
290
|
+
'quality-retrospective': '--auto-yes', 'maestro-roadmap': '-y',
|
|
291
|
+
};
|
|
292
|
+
|
|
293
|
+
function buildSkillCall(node, ctx, autoMode) {
|
|
294
|
+
let args = (node.args ?? '')
|
|
295
|
+
.replace(/{phase}/g, ctx.phase ?? '')
|
|
296
|
+
.replace(/{description}/g, ctx.description ?? '')
|
|
297
|
+
.replace(/{issue_id}/g, ctx.issue_id ?? '')
|
|
298
|
+
.replace(/{milestone_num}/g, ctx.milestone_num ?? '');
|
|
299
|
+
if (autoMode) {
|
|
300
|
+
const flag = node.auto_flag ?? AUTO_FLAG_MAP[node.cmd];
|
|
301
|
+
if (flag && !args.includes(flag)) args = args ? `${args} ${flag}` : flag;
|
|
302
|
+
}
|
|
303
|
+
return `$${node.cmd} ${args}`.trim();
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
function resolveExpr(expr, ctx) {
|
|
307
|
+
// expr is "ctx.result.verification_status" or "all_phases_completed"
|
|
308
|
+
// Navigate dot-path from context root
|
|
309
|
+
if (!expr) return undefined;
|
|
310
|
+
const path = expr.replace(/^ctx\./, '').split('.');
|
|
311
|
+
let val = ctx;
|
|
312
|
+
for (const key of path) {
|
|
313
|
+
if (val == null) return undefined;
|
|
314
|
+
val = val[key];
|
|
315
|
+
}
|
|
316
|
+
return val;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
function parseResultContext(result) {
|
|
320
|
+
// Extract structured fields from agent result for decision eval
|
|
321
|
+
if (!result) return {};
|
|
322
|
+
try {
|
|
323
|
+
const parsed = typeof result.findings === 'string'
|
|
324
|
+
? JSON.parse(result.findings) : result.findings;
|
|
325
|
+
return { ...parsed, _raw_summary: result.summary, _status: result.status };
|
|
326
|
+
} catch {
|
|
327
|
+
return { _raw_summary: result.summary ?? '', _status: result.status ?? 'unknown' };
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
### Sub-Agent Instruction Template
|
|
333
|
+
|
|
334
|
+
```
|
|
335
|
+
你是 CSV job 子 agent。
|
|
336
|
+
|
|
337
|
+
先原样执行这一段技能调用:
|
|
338
|
+
{skill_call}
|
|
339
|
+
|
|
340
|
+
然后基于结果完成这一行任务说明:
|
|
341
|
+
{topic}
|
|
342
|
+
|
|
343
|
+
限制:
|
|
344
|
+
- 不要修改 .workflow/.maestro-coordinate/ 下的 state 文件
|
|
345
|
+
- skill 内部有自己的 session 管理,按 skill SKILL.md 执行即可
|
|
346
|
+
|
|
347
|
+
最后必须调用 `report_agent_job_result`,返回 JSON:
|
|
348
|
+
{"status":"completed|failed","skill_call":"{skill_call}","summary":"一句话结果","findings":"JSON 结构化结果(含 decision 所需字段)","artifacts":"产物路径或空字符串","error":"失败原因或空字符串"}
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
**findings 字段规约**:sub-agent 必须在 findings 中返回 decision node 需要的字段。例如:
|
|
352
|
+
- `maestro-verify` → `{"verification_status": "passed|failed", ...}`
|
|
353
|
+
- `quality-review` → `{"review_verdict": "PASS|BLOCK", ...}`
|
|
354
|
+
- `quality-test` → `{"uat_status": "passed|failed", ...}`
|
|
355
|
+
- `maestro-milestone-audit` → `{"audit_verdict": "PASS|BLOCK", ...}`
|
|
356
|
+
|
|
357
|
+
Coordinator 将 `findings` 解析后写入 `ctx.result`,供后续 decision node 的 `eval` 表达式读取。
|
|
358
|
+
|
|
359
|
+
### Result Schema
|
|
360
|
+
|
|
361
|
+
```javascript
|
|
362
|
+
const RESULT_SCHEMA = {
|
|
363
|
+
type: "object",
|
|
364
|
+
properties: {
|
|
365
|
+
status: { type: "string", enum: ["completed", "failed"] },
|
|
366
|
+
skill_call: { type: "string" },
|
|
367
|
+
summary: { type: "string" },
|
|
368
|
+
findings: { type: "string" },
|
|
369
|
+
artifacts: { type: "string" },
|
|
370
|
+
error: { type: "string" }
|
|
371
|
+
},
|
|
372
|
+
required: ["status", "skill_call", "summary", "findings", "artifacts", "error"]
|
|
373
|
+
};
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
### Phase 3: Completion Report
|
|
377
|
+
|
|
378
|
+
```javascript
|
|
379
|
+
state.completed_at = new Date().toISOString();
|
|
380
|
+
Write(`${sessionDir}/state.json`, JSON.stringify(state, null, 2));
|
|
381
|
+
```
|
|
382
|
+
|
|
383
|
+
Display:
|
|
384
|
+
```
|
|
385
|
+
=== LINK-COORDINATE COMPLETE ===
|
|
386
|
+
Session: {sessionId}
|
|
387
|
+
Chain: {chain.name} ({chain.id})
|
|
388
|
+
Status: {completed|failed}
|
|
389
|
+
|
|
390
|
+
NODE WALK:
|
|
391
|
+
[✓] plan (command) — success — Plan generated
|
|
392
|
+
[→] check_verify (decision) — ctx.result.verification_status = "passed" → review
|
|
393
|
+
[✓] review (command) — success — No blockers
|
|
394
|
+
[→] check_review (decision) — ctx.result.review_verdict = "PASS" → test
|
|
395
|
+
[✓] test (command) — success — All tests passing
|
|
396
|
+
|
|
397
|
+
Nodes: {completed}/{total} | Visits: {total_visits}
|
|
398
|
+
State: .workflow/.maestro-coordinate/{sessionId}/state.json
|
|
399
|
+
Resume: $maestro-link-coordinate -c {sessionId}
|
|
400
|
+
```
|
|
401
|
+
|
|
402
|
+
</execution>
|
|
403
|
+
|
|
404
|
+
<error_codes>
|
|
405
|
+
| Code | Severity | Description | Recovery |
|
|
406
|
+
|------|----------|-------------|----------|
|
|
407
|
+
| E001 | error | No intent and no --list/--chain | Suggest --list |
|
|
408
|
+
| E002 | error | Chain graph JSON not found | List available chains |
|
|
409
|
+
| E003 | error | Command node spawn failed | Check wave result CSV, resume with -c |
|
|
410
|
+
| E004 | error | Decision node: no matching edge | Show eval value and available edges |
|
|
411
|
+
| E005 | error | max_visits exceeded on node | Show loop path, suggest --chain with simpler graph |
|
|
412
|
+
| E006 | error | Resume session not found | List available sessions |
|
|
413
|
+
| W001 | warning | Decision eval returned undefined | Fall through to default edge |
|
|
414
|
+
</error_codes>
|
|
415
|
+
|
|
416
|
+
<success_criteria>
|
|
417
|
+
- [ ] Chain graph loaded from chains/ directory (multi-path resolution)
|
|
418
|
+
- [ ] Session state initialized with graph entry node
|
|
419
|
+
- [ ] Every command node dispatched via spawn_agents_on_csv — coordinator never executes skills
|
|
420
|
+
- [ ] Decision nodes resolved in-process via expr evaluation against ctx.result
|
|
421
|
+
- [ ] Gate nodes evaluated in-process with pass/fail routing
|
|
422
|
+
- [ ] max_visits tracked per node, exceeded → failure
|
|
423
|
+
- [ ] Context flows forward: command result → ctx.result → decision eval → next command args
|
|
424
|
+
- [ ] State persisted after every node for resumability
|
|
425
|
+
- [ ] -c resumes from last incomplete node
|
|
426
|
+
- [ ] --list displays available chains without starting a session
|
|
427
|
+
- [ ] -y propagates auto_flag to command skill_calls
|
|
428
|
+
- [ ] Completion report shows per-node walk with outcomes
|
|
429
|
+
- [ ] findings from sub-agent parsed into ctx.result for decision routing
|
|
430
|
+
</success_criteria>
|