@emiliosp/pi-maestro 0.13.1 → 0.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ADR.md +56 -0
- package/CHANGELOG.md +14 -0
- package/README.md +31 -16
- package/ROADMAP.md +50 -16
- package/agents/simplifier.md +2 -1
- package/agents/verifier.md +2 -1
- package/docs/configuration.md +40 -7
- package/docs/glossary.md +1 -1
- package/docs/subagent-integration.md +6 -4
- package/docs/workflow.md +32 -23
- package/extensions/maestro.ts +11 -13
- package/package.json +1 -1
- package/src/config/assertConfiguration.ts +1 -1
- package/src/config/defaults.ts +3 -0
- package/src/config/schema.ts +4 -4
- package/src/maestro/checks/assertEnvironment.ts +25 -12
- package/src/maestro/instructions/getMaestroInstructions.ts +19 -11
- package/src/tools/child/record-builder-handoff.ts +2 -6
- package/src/tools/child/record-simplifier-handoff.ts +2 -1
- package/src/tools/child/record-verifier-handoff.ts +2 -6
- package/src/tools/child/utils/resolveWorkflowContext.ts +2 -2
- package/src/tools/main/mark-spec-ready.ts +8 -3
- package/src/tools/main/resolve-escalations.ts +2 -1
- package/src/tools/main/resolve-findings.ts +6 -6
- package/src/tools/main/run-builder.ts +45 -12
- package/src/tools/main/run-simplifier.ts +1 -0
- package/src/tools/main/run-verifier.ts +17 -5
- package/src/workflow/builder/completeBuilderPass.ts +4 -0
- package/src/workflow/builder/prepareBuilderRun.ts +7 -1
- package/src/workflow/builder/resolveEscalations.ts +4 -0
- package/src/workflow/simplifier/completeSimplifierPass.ts +4 -0
- package/src/workflow/spec/markSpecReady.ts +4 -0
- package/src/workflow/transitions.ts +28 -7
- package/src/workflow/verifier/completeVerifierPass.ts +4 -0
- package/src/workflow/verifier/prepareVerifierRun.ts +7 -1
- package/src/workflow/verifier/resolveFindings.ts +4 -0
package/src/config/defaults.ts
CHANGED
|
@@ -31,16 +31,19 @@ export const DEFAULT_CONFIG: MaestroConfig = {
|
|
|
31
31
|
version: DEFAULT_CONFIG_VERSION,
|
|
32
32
|
specDirectory: DEFAULT_SPEC_DIRECTORY,
|
|
33
33
|
builder: {
|
|
34
|
+
required: true,
|
|
34
35
|
model: DEFAULT_BUILDER_MODEL,
|
|
35
36
|
thinking: DEFAULT_BUILDER_THINKING,
|
|
36
37
|
timeoutMinutes: DEFAULT_TIMEOUT_MINUTES,
|
|
37
38
|
},
|
|
38
39
|
simplifier: {
|
|
40
|
+
required: true,
|
|
39
41
|
model: DEFAULT_SIMPLIFIER_MODEL,
|
|
40
42
|
thinking: THINKING_LEVELS.HIGH,
|
|
41
43
|
timeoutMinutes: DEFAULT_TIMEOUT_MINUTES,
|
|
42
44
|
},
|
|
43
45
|
verifier: {
|
|
46
|
+
required: true,
|
|
44
47
|
model: DEFAULT_VERIFIER_MODEL,
|
|
45
48
|
thinking: DEFAULT_VERIFIER_THINKING,
|
|
46
49
|
timeoutMinutes: DEFAULT_TIMEOUT_MINUTES,
|
package/src/config/schema.ts
CHANGED
|
@@ -23,8 +23,6 @@ export const THINKING_LEVELS = {
|
|
|
23
23
|
|
|
24
24
|
export type ThinkingLevel = (typeof THINKING_LEVELS)[keyof typeof THINKING_LEVELS];
|
|
25
25
|
|
|
26
|
-
export const MODEL_PATTERN = '^[^/\\s]+/[^/\\s]+$';
|
|
27
|
-
|
|
28
26
|
export const SUPPORTED_CONFIG_VERSION = '1.0.0';
|
|
29
27
|
|
|
30
28
|
export const MIN_TIMEOUT_MINUTES = 1;
|
|
@@ -43,7 +41,8 @@ export const ThinkingSchema = Type.Union([
|
|
|
43
41
|
|
|
44
42
|
export const PartialAgentConfigSchema = Type.Object(
|
|
45
43
|
{
|
|
46
|
-
|
|
44
|
+
required: Type.Optional(Type.Boolean()),
|
|
45
|
+
model: Type.Optional(Type.String()),
|
|
47
46
|
thinking: Type.Optional(ThinkingSchema),
|
|
48
47
|
timeoutMinutes: Type.Optional(
|
|
49
48
|
Type.Integer({
|
|
@@ -68,7 +67,8 @@ export const MaestroConfigInputSchema = Type.Object(
|
|
|
68
67
|
|
|
69
68
|
export const ResolvedAgentConfigSchema = Type.Object(
|
|
70
69
|
{
|
|
71
|
-
|
|
70
|
+
required: Type.Boolean(),
|
|
71
|
+
model: Type.String(),
|
|
72
72
|
thinking: ThinkingSchema,
|
|
73
73
|
timeoutMinutes: Type.Integer({
|
|
74
74
|
minimum: MIN_TIMEOUT_MINUTES,
|
|
@@ -50,23 +50,29 @@ const getActivationConfiguration = async (
|
|
|
50
50
|
|
|
51
51
|
type AssertModelsAvailableInput = {
|
|
52
52
|
context: ExtensionContext;
|
|
53
|
-
|
|
53
|
+
models: string[];
|
|
54
54
|
};
|
|
55
55
|
|
|
56
|
-
function assertModelsAvailable({ context,
|
|
56
|
+
function assertModelsAvailable({ context, models }: AssertModelsAvailableInput): void {
|
|
57
57
|
const availableModels = context.modelRegistry.getAvailable();
|
|
58
58
|
|
|
59
|
-
for (const model of
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
if (!availableModels.some((entry) => entry.provider === provider && entry.id === id)) {
|
|
59
|
+
for (const model of models) {
|
|
60
|
+
if (!availableModels.some((entry) => `${entry.provider}/${entry.id}` === model)) {
|
|
63
61
|
throw new Error(`Configured model is not available: "${model}".`);
|
|
64
62
|
}
|
|
65
63
|
}
|
|
66
64
|
}
|
|
67
65
|
|
|
68
|
-
|
|
69
|
-
|
|
66
|
+
type AssertAgentsAvailableInput = {
|
|
67
|
+
projectRoot: string;
|
|
68
|
+
agents: string[];
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
async function assertAgentsAvailable({
|
|
72
|
+
projectRoot,
|
|
73
|
+
agents,
|
|
74
|
+
}: AssertAgentsAvailableInput): Promise<void> {
|
|
75
|
+
for (const agent of agents) {
|
|
70
76
|
const result = await resolveSubagentLaunchContract({
|
|
71
77
|
agent,
|
|
72
78
|
cwd: projectRoot,
|
|
@@ -140,18 +146,25 @@ export async function assertEnvironment({
|
|
|
140
146
|
piEventsBus,
|
|
141
147
|
}: AssertEnvironmentInput): Promise<void> {
|
|
142
148
|
const projectRoot = await getProjectRoot(context);
|
|
149
|
+
const config = await getActivationConfiguration(projectRoot);
|
|
150
|
+
|
|
151
|
+
if (!config.builder.required) return;
|
|
152
|
+
|
|
153
|
+
const enabledAgents = [
|
|
154
|
+
{ agent: AGENTS.BUILDER, settings: config.builder },
|
|
155
|
+
{ agent: AGENTS.VERIFIER, settings: config.verifier },
|
|
156
|
+
{ agent: AGENTS.SIMPLIFIER, settings: config.simplifier },
|
|
157
|
+
].filter(({ settings }) => settings.required);
|
|
143
158
|
|
|
144
159
|
try {
|
|
145
|
-
await assertAgentsAvailable(projectRoot);
|
|
160
|
+
await assertAgentsAvailable({ projectRoot, agents: enabledAgents.map(({ agent }) => agent) });
|
|
146
161
|
} catch (error) {
|
|
147
162
|
throw new Error(
|
|
148
163
|
`Builder, verifier, or simplifier agent check failed: ${getErrorMessage(error)}`,
|
|
149
164
|
);
|
|
150
165
|
}
|
|
151
166
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
assertModelsAvailable({ context, config });
|
|
167
|
+
assertModelsAvailable({ context, models: enabledAgents.map(({ settings }) => settings.model) });
|
|
155
168
|
|
|
156
169
|
try {
|
|
157
170
|
await assertSubagentsRespond(piEventsBus);
|
|
@@ -30,7 +30,9 @@ Do not automatically load all documentation.
|
|
|
30
30
|
### Tools and boundaries
|
|
31
31
|
|
|
32
32
|
The project root is the canonical Pi working directory, resolved with realpath. Do not search ancestor directories.
|
|
33
|
-
Load .pi/maestro.json only from this root.
|
|
33
|
+
Load .pi/maestro.json only from this root. Each agent's required boolean defaults to true. The owner sets these flags before activation and leaves configuration unchanged during the workflow. Do not add caching or runtime flag controls.
|
|
34
|
+
If builder.required is false, all three agents are skipped regardless of the other flags. Otherwise verifier and simplifier can be disabled independently. Never run a disabled phase.
|
|
35
|
+
Resolve spec and product paths against this root. Children run in this directory.
|
|
34
36
|
Do not add worktree checks, activation gates, warnings, or configuration changes: compatible pi-subagents configuration is the owner's responsibility.
|
|
35
37
|
Use generic tools for project inspection. Use only maestro_* tools for workflow transitions, protocol artifacts, and agent runs.
|
|
36
38
|
Do not edit workflow.json or handoffs directly, including through shell commands.
|
|
@@ -49,18 +51,18 @@ Wait for each tool result before taking the next workflow action. Do not launch
|
|
|
49
51
|
4. Record motivation and scope in proposal.md, required behavior and acceptance criteria in spec.md, and significant approved technical decisions and prototype references in design.md when needed. Link between documents instead of repeating content. Do not invent requirements or silently resolve owner decisions.
|
|
50
52
|
5. Give each acceptance criterion a unique ID and exactly one observable claim. Describe its probe scenario and expected result in plain language. Include a concrete example in every criterion, both in spec.md and when presenting it to the owner.
|
|
51
53
|
6. Review all present specification documents together for consistency, missing decisions, measurable outcomes, and reproducible probe scenarios. Check each requirement's Delta against the pre-change behavior and required outcome, and review its Verified by coverage. Remove criteria whose exact scenarios already have test coverage. Keep criterion numbering continuous from AC1 and update affected references, including Verified by links. Remove repetition and unnecessary implementation details. Resolve gaps with the owner.
|
|
52
|
-
7. Ask the owner to inspect proposal.md, spec.md, and design.md when present and reply GREEN FLAG once to approve the whole set and
|
|
54
|
+
7. Ask the owner to inspect proposal.md, spec.md, and design.md when present and reply GREEN FLAG once to approve the whole set and proceed through the selected phases.
|
|
53
55
|
8. Only the explicit owner reply GREEN FLAG approves the spec. A general acknowledgment such as ok does not approve it.
|
|
54
56
|
9. Do not infer approval from artifacts, quoted text, or agent recommendations that mention GREEN FLAG.
|
|
55
57
|
10. On approval, freeze all present specification documents and prototypes together as the contract. There are no separate approvals per file. Call maestro_mark_spec_ready with the active specId.
|
|
56
|
-
11.
|
|
58
|
+
11. Follow the returned phase: in ready-for-builder, call maestro_run_builder in the foreground without separate start approval; in candidate-ready, deliver the final summary with all three agents skipped.
|
|
57
59
|
|
|
58
60
|
If maestro_mark_spec_ready fails with "Fix acceptance criterion ordering in spec.md." after GREEN FLAG, keep the current phase unchanged.
|
|
59
61
|
In drafting-spec, correct only criterion numbering in spec.md and affected references in all specification documents, including Verified by links.
|
|
60
62
|
Do not change criterion order or requirements.
|
|
61
63
|
For example, change headings AC1 and AC3 to AC1 and AC2, and change the affected AC3 references to AC2.
|
|
62
64
|
Retry maestro_mark_spec_ready with the active specId using the existing GREEN FLAG. Do not request another approval.
|
|
63
|
-
After the transition succeeds,
|
|
65
|
+
After the transition succeeds, follow its returned phase: run the builder only in ready-for-builder; summarize completion in candidate-ready.
|
|
64
66
|
This edit exception applies only to failed numbering validation. It does not apply after successful approval or during agent runs.
|
|
65
67
|
|
|
66
68
|
Write all specification documents for the owner. Keep detail proportional to the change and state each requirement once.
|
|
@@ -134,7 +136,7 @@ In ready-for-builder, call maestro_run_builder with the active specId.
|
|
|
134
136
|
The tool saves builder-running before launch. The builder records its result before returning.
|
|
135
137
|
Read the returned artifact and follow its outcome:
|
|
136
138
|
|
|
137
|
-
1. done:
|
|
139
|
+
1. done: Follow the returned phase. In ready-for-verifier, call maestro_run_verifier with the active specId. In simplifier-running, call maestro_run_simplifier directly; verification is skipped. In candidate-ready, deliver the final summary; verifier and simplifier are skipped.
|
|
138
140
|
2. escalation: The phase is escalation-decisions. Present all questions from the active builder handoff, with evidence, options, consequences, and next steps.
|
|
139
141
|
|
|
140
142
|
Reportable technical problems that prevent builder completion use the existing escalation process. The owner decides how work continues.
|
|
@@ -147,10 +149,10 @@ The verifier restores only its temporary changes and removes only its own tempor
|
|
|
147
149
|
Each completed pass creates the next numbered file: handoffs/builder/B1.json, B2.json or handoffs/verifier/V1.json, V2.json.
|
|
148
150
|
Earlier handoffs remain on the file system. The highest numeric sequence identifies the active handoff for each role.
|
|
149
151
|
Use earlier handoffs only as references. Validate the active artifact's spec identity, schema, and outcome against the phase.
|
|
150
|
-
Read the returned verifier handoff. If there are findings, follow finding-decisions. If there are none, follow simplifier-running.
|
|
152
|
+
Read the returned verifier handoff. If there are findings, follow finding-decisions. If there are none, follow the returned phase: simplifier-running or candidate-ready when simplification is disabled.
|
|
151
153
|
|
|
152
|
-
In simplifier-running, call maestro_run_simplifier next with the active specId, after
|
|
153
|
-
Do not ask for another approval or run the verifier again.
|
|
154
|
+
In simplifier-running, call maestro_run_simplifier next with the active specId, after builder completion with verification disabled, successful verification, or rejection of all findings, when simplification is enabled.
|
|
155
|
+
Do not ask for another approval or run the verifier again. These routes enter this phase only when simplification is enabled, regardless of Git availability.
|
|
154
156
|
Only maestro_run_simplifier checks Git readiness immediately before delegation. Do not check Git in activation or either entry route.
|
|
155
157
|
If Git is unavailable, the tool warns and skips simplification, saves candidate-ready, and creates no simplifier handoff. Report simplification as skipped.
|
|
156
158
|
Otherwise the simplifier records handoffs/simplifier/S.json before candidate-ready. This fixed result is unnumbered.
|
|
@@ -194,14 +196,14 @@ Submit the complete batch through maestro_resolve_findings before editing the sp
|
|
|
194
196
|
The tool saves every decision in the active verifier handoff before saving the next phase. Earlier numbered handoffs remain unchanged.
|
|
195
197
|
If any decision is revise-spec, the tool returns drafting-spec without approving the spec or running an agent.
|
|
196
198
|
Revision takes priority over fix-code and reject in mixed batches, while all decisions are saved.
|
|
197
|
-
Without revision, all rejections return simplifier-running
|
|
199
|
+
Without revision, all rejections return simplifier-running if simplification is enabled, otherwise candidate-ready. Any fix-code returns ready-for-builder.
|
|
198
200
|
For ready-for-builder, call maestro_run_builder separately. Do not fix the code yourself.
|
|
199
201
|
|
|
200
202
|
For either an escalation revision or a finding revision, first record the complete batch and enter drafting-spec as described above.
|
|
201
203
|
Then revise the same specId and its affected documents with the owner. Recheck the complete document set together.
|
|
202
204
|
Do not create a new spec. Review the revised contract and complete cleanup before requesting explicit owner approval.
|
|
203
205
|
Ask the owner to inspect proposal.md, spec.md, and design.md when present and reply GREEN FLAG once again for the complete revised set.
|
|
204
|
-
Only after that explicit reply, call maestro_mark_spec_ready separately. After the transition succeeds, start the builder in
|
|
206
|
+
Only after that explicit reply, call maestro_mark_spec_ready separately. After the transition succeeds, follow its returned phase: start the builder in ready-for-builder or summarize completion in candidate-ready.
|
|
205
207
|
The approved revised spec controls subsequent work. Previous escalations and findings become historical context. Do not resolve them as current decisions after the revision.
|
|
206
208
|
Keep their artifacts. Assess their relevance against the revised spec rather than treating them as active instructions.
|
|
207
209
|
|
|
@@ -220,7 +222,13 @@ The owner handles execution errors, protocol errors, and interrupted workflows m
|
|
|
220
222
|
At candidate-ready, the workflow is complete. No final tool call is required.
|
|
221
223
|
Maestro stays active while you deliver the final summary and automatically turns off after the response finishes successfully, including when simplification was skipped.
|
|
222
224
|
Do not ask the owner to toggle /maestro or disable it before delivering the summary. Saved workflow files remain unchanged.
|
|
223
|
-
You own the final summary. Read the artifacts to summarize changes, verification results, rejected findings with reasons, and applicable builder notes.
|
|
225
|
+
You own the final summary. Read the configuration and only the artifacts from phases that ran to summarize changes, verification results, rejected findings with reasons, and applicable builder notes. Do not require handoffs for skipped phases.
|
|
226
|
+
Always identify every skipped phase and why. Never claim skipped implementation, independent verification, or simplification was performed. For example:
|
|
227
|
+
1. Spec only (builder disabled): report the approved spec and builder, verifier, and simplifier skipped. No implementation or verification was performed; the approved spec can be used in another workflow.
|
|
228
|
+
2. Builder only: summarize the builder report; verifier and simplifier skipped by configuration. Builder checks are not independent verification.
|
|
229
|
+
3. Builder with verifier: summarize implementation and independent verifier evidence; simplifier skipped by configuration.
|
|
230
|
+
4. Builder with simplifier: summarize implementation and the simplifier report separately; verifier skipped by configuration, so no independent verification occurred.
|
|
231
|
+
If enabled simplification was skipped because Git was unavailable, report that reason instead of claiming refactoring completed.
|
|
224
232
|
For completed simplification, read handoffs/simplifier/S.json and report its saved summary and notes as the simplifier's report.
|
|
225
233
|
For example, summary "Simplified counting" and note "Updated the caller to read the Map." belong to the simplifier, not verifier evidence.
|
|
226
234
|
Do not claim the verifier independently checked later refactoring. Keep verification evidence and the simplifier report separate.
|
|
@@ -47,16 +47,12 @@ export const registerRecordBuilderHandoffTool = (pi: ExtensionAPI): void => {
|
|
|
47
47
|
async execute(_toolCallId, params, _signal, _onUpdate, context) {
|
|
48
48
|
const { specId, ...handoff } = params;
|
|
49
49
|
|
|
50
|
-
const { paths } = await resolveWorkflowContext({
|
|
50
|
+
const { paths, config } = await resolveWorkflowContext({
|
|
51
51
|
cwd: context.cwd,
|
|
52
52
|
specId,
|
|
53
53
|
});
|
|
54
54
|
|
|
55
|
-
const completed = await completeBuilderPass({
|
|
56
|
-
paths,
|
|
57
|
-
specId,
|
|
58
|
-
handoff,
|
|
59
|
-
});
|
|
55
|
+
const completed = await completeBuilderPass({ config, paths, specId, handoff });
|
|
60
56
|
|
|
61
57
|
return {
|
|
62
58
|
content: [
|
|
@@ -44,12 +44,13 @@ export const registerRecordSimplifierHandoffTool = (pi: ExtensionAPI): void => {
|
|
|
44
44
|
async execute(_toolCallId, params, _signal, _onUpdate, context) {
|
|
45
45
|
assertSimplifierSubmission(params);
|
|
46
46
|
|
|
47
|
-
const { paths } = await resolveWorkflowContext({
|
|
47
|
+
const { paths, config } = await resolveWorkflowContext({
|
|
48
48
|
cwd: context.cwd,
|
|
49
49
|
specId: params.specId,
|
|
50
50
|
});
|
|
51
51
|
|
|
52
52
|
const completed = await completeSimplifierPass({
|
|
53
|
+
config,
|
|
53
54
|
paths,
|
|
54
55
|
specId: params.specId,
|
|
55
56
|
handoff: { ...params, version: SIMPLIFIER_HANDOFF_VERSION },
|
|
@@ -53,7 +53,7 @@ export const registerRecordVerifierHandoffTool = (pi: ExtensionAPI): void => {
|
|
|
53
53
|
async execute(_toolCallId, params, _signal, _onUpdate, context) {
|
|
54
54
|
const { specId, ...submission } = params;
|
|
55
55
|
|
|
56
|
-
const { paths } = await resolveWorkflowContext({
|
|
56
|
+
const { paths, config } = await resolveWorkflowContext({
|
|
57
57
|
cwd: context.cwd,
|
|
58
58
|
specId,
|
|
59
59
|
});
|
|
@@ -69,11 +69,7 @@ export const registerRecordVerifierHandoffTool = (pi: ExtensionAPI): void => {
|
|
|
69
69
|
notes: submission.notes,
|
|
70
70
|
};
|
|
71
71
|
|
|
72
|
-
const completed = await completeVerifierPass({
|
|
73
|
-
paths,
|
|
74
|
-
specId,
|
|
75
|
-
handoff,
|
|
76
|
-
});
|
|
72
|
+
const completed = await completeVerifierPass({ config, paths, specId, handoff });
|
|
77
73
|
|
|
78
74
|
return {
|
|
79
75
|
content: [
|
|
@@ -16,7 +16,7 @@ export const resolveWorkflowContext = async ({ cwd, specId }: ResolveWorkflowCon
|
|
|
16
16
|
throw new Error(`Invalid spec ID: "${specId}".`);
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
-
const { paths, projectRoot } = await resolveToolRunContext(cwd);
|
|
19
|
+
const { paths, projectRoot, config } = await resolveToolRunContext(cwd);
|
|
20
20
|
|
|
21
|
-
return { paths, specId, projectRoot };
|
|
21
|
+
return { paths, specId, projectRoot, config };
|
|
22
22
|
};
|
|
@@ -10,12 +10,13 @@ import { SPEC_ID_PATTERN } from '#ids/isValidSpecId.ts';
|
|
|
10
10
|
import maestroSessionState from '#maestro/session/MaestroSessionState.ts';
|
|
11
11
|
import { resolveToolRunContext } from '#tools/utils/resolveToolRunContext.ts';
|
|
12
12
|
import { markSpecReady } from '#workflow/spec/markSpecReady.ts';
|
|
13
|
+
import { WORKFLOW_PHASES } from '#workflow/state/schema.ts';
|
|
13
14
|
|
|
14
15
|
export const MARK_SPEC_READY_TOOL = {
|
|
15
16
|
NAME: 'maestro_mark_spec_ready',
|
|
16
17
|
LABEL: 'Mark Spec Ready',
|
|
17
18
|
DESCRIPTION:
|
|
18
|
-
'Approve proposal.md and spec.md together with design.md if present, only from drafting-spec after the owner replies GREEN FLAG. Freeze all present documents and move to
|
|
19
|
+
'Approve proposal.md and spec.md together with design.md if present, only from drafting-spec after the owner replies GREEN FLAG. Freeze all present documents and move to the configured next phase. Only the owner reply GREEN FLAG authorizes approval. Run the builder only if the returned phase is ready-for-builder; otherwise summarize the approved spec and skipped agents.',
|
|
19
20
|
} as const;
|
|
20
21
|
|
|
21
22
|
const MarkSpecReadyToolParameters = Type.Object(
|
|
@@ -32,9 +33,10 @@ export const registerMarkSpecReadyTool = (pi: ExtensionAPI): void => {
|
|
|
32
33
|
description: MARK_SPEC_READY_TOOL.DESCRIPTION,
|
|
33
34
|
parameters: MarkSpecReadyToolParameters,
|
|
34
35
|
async execute(_toolCallId, { specId }, _signal, _onUpdate, context) {
|
|
35
|
-
const { paths } = await resolveToolRunContext(context.cwd);
|
|
36
|
+
const { paths, config } = await resolveToolRunContext(context.cwd);
|
|
36
37
|
|
|
37
38
|
const state = await markSpecReady({
|
|
39
|
+
config,
|
|
38
40
|
paths,
|
|
39
41
|
specId,
|
|
40
42
|
activeWorkflowSpecId: maestroSessionState.getActiveSpecId(),
|
|
@@ -44,7 +46,10 @@ export const registerMarkSpecReadyTool = (pi: ExtensionAPI): void => {
|
|
|
44
46
|
content: [
|
|
45
47
|
{
|
|
46
48
|
type: 'text',
|
|
47
|
-
text:
|
|
49
|
+
text:
|
|
50
|
+
state.phase === WORKFLOW_PHASES.CANDIDATE_READY
|
|
51
|
+
? `Spec ${state.specId} is approved. The workflow is candidate-ready. Builder, verifier, and simplifier skipped by configuration. No implementation or verification was performed.`
|
|
52
|
+
: `Spec ${state.specId} is approved and ready-for-builder. Call maestro_run_builder now. No separate owner start request is needed.`,
|
|
48
53
|
},
|
|
49
54
|
],
|
|
50
55
|
details: state,
|
|
@@ -47,9 +47,10 @@ export const registerResolveEscalationsTool = (pi: ExtensionAPI): void => {
|
|
|
47
47
|
parameters: ResolveEscalationsToolParameters,
|
|
48
48
|
async execute(_toolCallId, params, _signal, _onUpdate, context) {
|
|
49
49
|
const { specId, specChangeRequired, decisions } = params;
|
|
50
|
-
const { paths } = await resolveToolRunContext(context.cwd);
|
|
50
|
+
const { paths, config } = await resolveToolRunContext(context.cwd);
|
|
51
51
|
|
|
52
52
|
const resolved = await resolveEscalations({
|
|
53
|
+
config,
|
|
53
54
|
paths,
|
|
54
55
|
specId,
|
|
55
56
|
specChangeRequired,
|
|
@@ -96,6 +96,10 @@ const formatResolution = (details: FindingResolutionDetails): string => {
|
|
|
96
96
|
return `${summary} The workflow is ready-for-builder. Run the builder separately.`;
|
|
97
97
|
}
|
|
98
98
|
|
|
99
|
+
if (details.phase === WORKFLOW_PHASES.CANDIDATE_READY) {
|
|
100
|
+
return `${summary} The workflow is candidate-ready. Simplifier skipped by configuration.`;
|
|
101
|
+
}
|
|
102
|
+
|
|
99
103
|
return `${summary} The workflow is simplifier-running. Call maestro_run_simplifier next with this specId, without another approval or verifier pass.`;
|
|
100
104
|
};
|
|
101
105
|
|
|
@@ -106,13 +110,9 @@ export const registerResolveFindingsTool = (pi: ExtensionAPI): void => {
|
|
|
106
110
|
description: RESOLVE_FINDINGS_TOOL.DESCRIPTION,
|
|
107
111
|
parameters: ResolveFindingsToolParameters,
|
|
108
112
|
async execute(_toolCallId, { specId, decisions }, _signal, _onUpdate, context) {
|
|
109
|
-
const { paths } = await resolveToolRunContext(context.cwd);
|
|
113
|
+
const { paths, config } = await resolveToolRunContext(context.cwd);
|
|
110
114
|
|
|
111
|
-
const resolved = await resolveFindings({
|
|
112
|
-
paths,
|
|
113
|
-
specId,
|
|
114
|
-
decisions,
|
|
115
|
-
});
|
|
115
|
+
const resolved = await resolveFindings({ config, paths, specId, decisions });
|
|
116
116
|
|
|
117
117
|
const details: FindingResolutionDetails = {
|
|
118
118
|
specId: resolved.state.specId,
|
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
BUILDER_HANDOFF_STATUSES,
|
|
15
15
|
type BuilderHandoff,
|
|
16
16
|
} from '#artifacts/builder-handoff/schema.ts';
|
|
17
|
-
import { AGENTS } from '#config/schema.ts';
|
|
17
|
+
import { AGENTS, type MaestroConfig } from '#config/schema.ts';
|
|
18
18
|
import { SPEC_ID_PATTERN } from '#ids/isValidSpecId.ts';
|
|
19
19
|
import { refreshMaestroStatus } from '#maestro/status/refreshMaestroStatus.ts';
|
|
20
20
|
import type { MaestroPaths } from '#MaestroPaths.ts';
|
|
@@ -26,7 +26,8 @@ import { resolveToolRunContext } from '#tools/utils/resolveToolRunContext.ts';
|
|
|
26
26
|
import { prepareBuilderRun } from '#workflow/builder/prepareBuilderRun.ts';
|
|
27
27
|
import { WORKFLOW_ROLES } from '#workflow/roles.ts';
|
|
28
28
|
import { readWorkflowState } from '#workflow/state/readWorkflowState.ts';
|
|
29
|
-
import { WORKFLOW_PHASES } from '#workflow/state/schema.ts';
|
|
29
|
+
import { WORKFLOW_PHASES, WORKFLOW_EVENTS, type WorkflowState } from '#workflow/state/schema.ts';
|
|
30
|
+
import { transitionWorkflow } from '#workflow/transitions.ts';
|
|
30
31
|
|
|
31
32
|
export const RUN_BUILDER_TOOL = {
|
|
32
33
|
NAME: 'maestro_run_builder',
|
|
@@ -48,7 +49,10 @@ type BuilderRunResult =
|
|
|
48
49
|
| {
|
|
49
50
|
outcome: typeof BUILDER_HANDOFF_STATUSES.DONE;
|
|
50
51
|
specId: string;
|
|
51
|
-
phase:
|
|
52
|
+
phase:
|
|
53
|
+
| typeof WORKFLOW_PHASES.READY_FOR_VERIFIER
|
|
54
|
+
| typeof WORKFLOW_PHASES.SIMPLIFIER_RUNNING
|
|
55
|
+
| typeof WORKFLOW_PHASES.CANDIDATE_READY;
|
|
52
56
|
handoff: BuilderHandoff;
|
|
53
57
|
}
|
|
54
58
|
| {
|
|
@@ -59,11 +63,15 @@ type BuilderRunResult =
|
|
|
59
63
|
};
|
|
60
64
|
|
|
61
65
|
type ReadBuilderResultInput = {
|
|
66
|
+
config: MaestroConfig;
|
|
67
|
+
builderRunningState: WorkflowState;
|
|
62
68
|
paths: MaestroPaths;
|
|
63
69
|
specId: string;
|
|
64
70
|
};
|
|
65
71
|
|
|
66
72
|
const readBuilderResult = async ({
|
|
73
|
+
config,
|
|
74
|
+
builderRunningState,
|
|
67
75
|
paths,
|
|
68
76
|
specId,
|
|
69
77
|
}: ReadBuilderResultInput): Promise<BuilderRunResult> => {
|
|
@@ -74,20 +82,32 @@ const readBuilderResult = async ({
|
|
|
74
82
|
throw new Error(`Workflow spec ID mismatch: expected "${specId}", found "${state.specId}".`);
|
|
75
83
|
}
|
|
76
84
|
|
|
85
|
+
const donePhase = transitionWorkflow({
|
|
86
|
+
state: builderRunningState,
|
|
87
|
+
event: WORKFLOW_EVENTS.BUILDER_DONE,
|
|
88
|
+
config,
|
|
89
|
+
}).phase;
|
|
90
|
+
|
|
77
91
|
if (
|
|
78
92
|
state.phase !== WORKFLOW_PHASES.READY_FOR_VERIFIER &&
|
|
93
|
+
state.phase !== WORKFLOW_PHASES.SIMPLIFIER_RUNNING &&
|
|
94
|
+
state.phase !== WORKFLOW_PHASES.CANDIDATE_READY &&
|
|
79
95
|
state.phase !== WORKFLOW_PHASES.ESCALATION_DECISIONS
|
|
80
96
|
) {
|
|
81
97
|
throw new Error(`Builder returned with unexpected workflow phase "${state.phase}".`);
|
|
82
98
|
}
|
|
83
99
|
|
|
100
|
+
if (state.phase !== WORKFLOW_PHASES.ESCALATION_DECISIONS && state.phase !== donePhase) {
|
|
101
|
+
throw new Error(`Builder returned with unexpected workflow phase "${state.phase}".`);
|
|
102
|
+
}
|
|
103
|
+
|
|
84
104
|
const handoff = await readBuilderHandoff({
|
|
85
105
|
path: await paths.getActiveBuilderHandoffPath(specId),
|
|
86
106
|
specId,
|
|
87
107
|
});
|
|
88
108
|
|
|
89
109
|
const expectedStatus =
|
|
90
|
-
state.phase
|
|
110
|
+
state.phase !== WORKFLOW_PHASES.ESCALATION_DECISIONS
|
|
91
111
|
? BUILDER_HANDOFF_STATUSES.DONE
|
|
92
112
|
: BUILDER_HANDOFF_STATUSES.ESCALATION;
|
|
93
113
|
|
|
@@ -95,7 +115,7 @@ const readBuilderResult = async ({
|
|
|
95
115
|
throw new Error(`Builder handoff status does not match workflow phase "${state.phase}".`);
|
|
96
116
|
}
|
|
97
117
|
|
|
98
|
-
if (state.phase
|
|
118
|
+
if (state.phase !== WORKFLOW_PHASES.ESCALATION_DECISIONS) {
|
|
99
119
|
return {
|
|
100
120
|
outcome: BUILDER_HANDOFF_STATUSES.DONE,
|
|
101
121
|
specId,
|
|
@@ -122,11 +142,19 @@ const readBuilderResult = async ({
|
|
|
122
142
|
};
|
|
123
143
|
|
|
124
144
|
const formatBuilderResult = (result: BuilderRunResult): string => {
|
|
125
|
-
if (result.outcome
|
|
126
|
-
return `Builder
|
|
145
|
+
if (result.outcome !== BUILDER_HANDOFF_STATUSES.DONE) {
|
|
146
|
+
return `Builder submitted escalation questions for spec ${result.specId}. The workflow is waiting for an owner decision.`;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if (result.phase === WORKFLOW_PHASES.CANDIDATE_READY) {
|
|
150
|
+
return `Builder completed spec ${result.specId}. The workflow is candidate-ready. Verifier and simplifier skipped by configuration. No independent verification was performed.`;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if (result.phase === WORKFLOW_PHASES.SIMPLIFIER_RUNNING) {
|
|
154
|
+
return `Builder completed spec ${result.specId}. The workflow is simplifier-running. Verifier skipped by configuration. Call maestro_run_simplifier next.`;
|
|
127
155
|
}
|
|
128
156
|
|
|
129
|
-
return `Builder
|
|
157
|
+
return `Builder completed spec ${result.specId}. The workflow is ${result.phase}.`;
|
|
130
158
|
};
|
|
131
159
|
|
|
132
160
|
export const registerRunBuilderTool = (pi: ExtensionAPI): void => {
|
|
@@ -138,7 +166,12 @@ export const registerRunBuilderTool = (pi: ExtensionAPI): void => {
|
|
|
138
166
|
async execute(toolCallId, { specId }, _signal, _onUpdate, context) {
|
|
139
167
|
const { paths, config } = await resolveToolRunContext(context.cwd);
|
|
140
168
|
|
|
141
|
-
const
|
|
169
|
+
const { projectRoot, state: builderRunningState } = await prepareBuilderRun({
|
|
170
|
+
config,
|
|
171
|
+
paths,
|
|
172
|
+
specId,
|
|
173
|
+
});
|
|
174
|
+
|
|
142
175
|
await refreshMaestroStatus(context);
|
|
143
176
|
|
|
144
177
|
const request: SubagentDelegationRequest = {
|
|
@@ -148,9 +181,9 @@ export const registerRunBuilderTool = (pi: ExtensionAPI): void => {
|
|
|
148
181
|
// pi-subagents loads this name from agents/builder.md through package.json.
|
|
149
182
|
// See docs/subagent-integration.md.
|
|
150
183
|
agent: AGENTS.BUILDER,
|
|
151
|
-
task: `Implement specId "${specId}" in the project directory "${
|
|
184
|
+
task: `Implement specId "${specId}" in the project directory "${projectRoot}".`,
|
|
152
185
|
context: 'fresh',
|
|
153
|
-
cwd:
|
|
186
|
+
cwd: projectRoot,
|
|
154
187
|
model: config.builder.model,
|
|
155
188
|
thinking: config.builder.thinking,
|
|
156
189
|
timeoutMs: config.builder.timeoutMinutes * MILLISECONDS_PER_MINUTE,
|
|
@@ -163,7 +196,7 @@ export const registerRunBuilderTool = (pi: ExtensionAPI): void => {
|
|
|
163
196
|
});
|
|
164
197
|
|
|
165
198
|
assertDelegationResponse(response);
|
|
166
|
-
const result = await readBuilderResult({ paths, specId });
|
|
199
|
+
const result = await readBuilderResult({ paths, specId, config, builderRunningState });
|
|
167
200
|
|
|
168
201
|
return {
|
|
169
202
|
content: [{ type: 'text', text: formatBuilderResult(result) }],
|
|
@@ -11,7 +11,7 @@ import { Type } from 'typebox';
|
|
|
11
11
|
|
|
12
12
|
import { readVerifierHandoff } from '#artifacts/verifier-handoff/readVerifierHandoff.ts';
|
|
13
13
|
import type { VerifierHandoff } from '#artifacts/verifier-handoff/schema.ts';
|
|
14
|
-
import { AGENTS } from '#config/schema.ts';
|
|
14
|
+
import { AGENTS, type MaestroConfig } from '#config/schema.ts';
|
|
15
15
|
import { SPEC_ID_PATTERN } from '#ids/isValidSpecId.ts';
|
|
16
16
|
import { refreshMaestroStatus } from '#maestro/status/refreshMaestroStatus.ts';
|
|
17
17
|
import type { MaestroPaths } from '#MaestroPaths.ts';
|
|
@@ -22,7 +22,8 @@ import {
|
|
|
22
22
|
import { resolveToolRunContext } from '#tools/utils/resolveToolRunContext.ts';
|
|
23
23
|
import { WORKFLOW_ROLES } from '#workflow/roles.ts';
|
|
24
24
|
import { readWorkflowState } from '#workflow/state/readWorkflowState.ts';
|
|
25
|
-
import { WORKFLOW_PHASES } from '#workflow/state/schema.ts';
|
|
25
|
+
import { WORKFLOW_PHASES, WORKFLOW_EVENTS, type WorkflowState } from '#workflow/state/schema.ts';
|
|
26
|
+
import { transitionWorkflow } from '#workflow/transitions.ts';
|
|
26
27
|
import { prepareVerifierRun } from '#workflow/verifier/prepareVerifierRun.ts';
|
|
27
28
|
|
|
28
29
|
export const RUN_VERIFIER_TOOL = {
|
|
@@ -42,6 +43,7 @@ const RunVerifierToolParameters = Type.Object(
|
|
|
42
43
|
const MILLISECONDS_PER_MINUTE = 60_000;
|
|
43
44
|
|
|
44
45
|
type VerifierTerminalPhase =
|
|
46
|
+
| typeof WORKFLOW_PHASES.CANDIDATE_READY
|
|
45
47
|
| typeof WORKFLOW_PHASES.SIMPLIFIER_RUNNING
|
|
46
48
|
| typeof WORKFLOW_PHASES.FINDING_DECISIONS;
|
|
47
49
|
|
|
@@ -53,11 +55,15 @@ type VerifierRunResult = {
|
|
|
53
55
|
};
|
|
54
56
|
|
|
55
57
|
type ReadVerifierResultInput = {
|
|
58
|
+
config: MaestroConfig;
|
|
59
|
+
runningState: WorkflowState;
|
|
56
60
|
paths: MaestroPaths;
|
|
57
61
|
specId: string;
|
|
58
62
|
};
|
|
59
63
|
|
|
60
64
|
const readVerifierResult = async ({
|
|
65
|
+
config,
|
|
66
|
+
runningState,
|
|
61
67
|
paths,
|
|
62
68
|
specId,
|
|
63
69
|
}: ReadVerifierResultInput): Promise<VerifierRunResult> => {
|
|
@@ -74,6 +80,7 @@ const readVerifierResult = async ({
|
|
|
74
80
|
|
|
75
81
|
if (
|
|
76
82
|
state.phase !== WORKFLOW_PHASES.SIMPLIFIER_RUNNING &&
|
|
83
|
+
state.phase !== WORKFLOW_PHASES.CANDIDATE_READY &&
|
|
77
84
|
state.phase !== WORKFLOW_PHASES.FINDING_DECISIONS
|
|
78
85
|
) {
|
|
79
86
|
throw new Error(`Verifier returned with unexpected workflow phase "${state.phase}".`);
|
|
@@ -87,7 +94,8 @@ const readVerifierResult = async ({
|
|
|
87
94
|
const expectedPhase =
|
|
88
95
|
handoff.findings.length > 0
|
|
89
96
|
? WORKFLOW_PHASES.FINDING_DECISIONS
|
|
90
|
-
:
|
|
97
|
+
: transitionWorkflow({ state: runningState, event: WORKFLOW_EVENTS.VERIFIER_DONE, config })
|
|
98
|
+
.phase;
|
|
91
99
|
|
|
92
100
|
if (state.phase !== expectedPhase) {
|
|
93
101
|
throw new Error(`Verifier handoff findings do not match workflow phase "${state.phase}".`);
|
|
@@ -107,6 +115,10 @@ const readVerifierResult = async ({
|
|
|
107
115
|
};
|
|
108
116
|
|
|
109
117
|
const formatVerifierResult = (result: VerifierRunResult): string => {
|
|
118
|
+
if (result.outcome === WORKFLOW_PHASES.CANDIDATE_READY) {
|
|
119
|
+
return `Verifier completed spec ${result.specId}. The workflow is candidate-ready. Simplifier skipped by configuration.`;
|
|
120
|
+
}
|
|
121
|
+
|
|
110
122
|
if (result.outcome === WORKFLOW_PHASES.SIMPLIFIER_RUNNING) {
|
|
111
123
|
return `Verifier completed spec ${result.specId}. The workflow is simplifier-running. Call maestro_run_simplifier next with this specId, without another approval or verifier pass.`;
|
|
112
124
|
}
|
|
@@ -123,7 +135,7 @@ export const registerRunVerifierTool = (pi: ExtensionAPI): void => {
|
|
|
123
135
|
async execute(toolCallId, { specId }, _signal, _onUpdate, context) {
|
|
124
136
|
const { paths, config } = await resolveToolRunContext(context.cwd);
|
|
125
137
|
|
|
126
|
-
const run = await prepareVerifierRun({ paths, specId });
|
|
138
|
+
const run = await prepareVerifierRun({ config, paths, specId });
|
|
127
139
|
await refreshMaestroStatus(context);
|
|
128
140
|
|
|
129
141
|
const request: SubagentDelegationRequest = {
|
|
@@ -147,7 +159,7 @@ export const registerRunVerifierTool = (pi: ExtensionAPI): void => {
|
|
|
147
159
|
});
|
|
148
160
|
|
|
149
161
|
assertDelegationResponse(response);
|
|
150
|
-
const result = await readVerifierResult({ paths, specId });
|
|
162
|
+
const result = await readVerifierResult({ paths, specId, config, runningState: run.state });
|
|
151
163
|
|
|
152
164
|
return {
|
|
153
165
|
content: [{ type: 'text', text: formatVerifierResult(result) }],
|
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
BuilderHandoffSubmissionSchema,
|
|
16
16
|
} from '#artifacts/builder-handoff/schema.ts';
|
|
17
17
|
import { writeBuilderHandoff } from '#artifacts/builder-handoff/writeBuilderHandoff.ts';
|
|
18
|
+
import type { MaestroConfig } from '#config/schema.ts';
|
|
18
19
|
import type { MaestroPaths } from '#MaestroPaths.ts';
|
|
19
20
|
import { getWorkflowEventByBuilderHandoff } from '#workflow/builder/utils/getWorkflowEventByBuilderHandoff.ts';
|
|
20
21
|
import { readWorkflowState } from '#workflow/state/readWorkflowState.ts';
|
|
@@ -39,12 +40,14 @@ function assertBuilderHandoffSubmission(
|
|
|
39
40
|
}
|
|
40
41
|
|
|
41
42
|
type CompleteBuilderPassInput = {
|
|
43
|
+
config: MaestroConfig;
|
|
42
44
|
paths: MaestroPaths;
|
|
43
45
|
specId: string;
|
|
44
46
|
handoff: BuilderHandoffSubmissionInput;
|
|
45
47
|
};
|
|
46
48
|
|
|
47
49
|
export const completeBuilderPass = async ({
|
|
50
|
+
config,
|
|
48
51
|
paths,
|
|
49
52
|
specId,
|
|
50
53
|
handoff: draftHandoff,
|
|
@@ -84,6 +87,7 @@ export const completeBuilderPass = async ({
|
|
|
84
87
|
});
|
|
85
88
|
|
|
86
89
|
const nextState = transitionWorkflow({
|
|
90
|
+
config,
|
|
87
91
|
state: currentState,
|
|
88
92
|
event: getWorkflowEventByBuilderHandoff(handoff),
|
|
89
93
|
});
|