@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.
Files changed (36) hide show
  1. package/ADR.md +56 -0
  2. package/CHANGELOG.md +14 -0
  3. package/README.md +31 -16
  4. package/ROADMAP.md +50 -16
  5. package/agents/simplifier.md +2 -1
  6. package/agents/verifier.md +2 -1
  7. package/docs/configuration.md +40 -7
  8. package/docs/glossary.md +1 -1
  9. package/docs/subagent-integration.md +6 -4
  10. package/docs/workflow.md +32 -23
  11. package/extensions/maestro.ts +11 -13
  12. package/package.json +1 -1
  13. package/src/config/assertConfiguration.ts +1 -1
  14. package/src/config/defaults.ts +3 -0
  15. package/src/config/schema.ts +4 -4
  16. package/src/maestro/checks/assertEnvironment.ts +25 -12
  17. package/src/maestro/instructions/getMaestroInstructions.ts +19 -11
  18. package/src/tools/child/record-builder-handoff.ts +2 -6
  19. package/src/tools/child/record-simplifier-handoff.ts +2 -1
  20. package/src/tools/child/record-verifier-handoff.ts +2 -6
  21. package/src/tools/child/utils/resolveWorkflowContext.ts +2 -2
  22. package/src/tools/main/mark-spec-ready.ts +8 -3
  23. package/src/tools/main/resolve-escalations.ts +2 -1
  24. package/src/tools/main/resolve-findings.ts +6 -6
  25. package/src/tools/main/run-builder.ts +45 -12
  26. package/src/tools/main/run-simplifier.ts +1 -0
  27. package/src/tools/main/run-verifier.ts +17 -5
  28. package/src/workflow/builder/completeBuilderPass.ts +4 -0
  29. package/src/workflow/builder/prepareBuilderRun.ts +7 -1
  30. package/src/workflow/builder/resolveEscalations.ts +4 -0
  31. package/src/workflow/simplifier/completeSimplifierPass.ts +4 -0
  32. package/src/workflow/spec/markSpecReady.ts +4 -0
  33. package/src/workflow/transitions.ts +28 -7
  34. package/src/workflow/verifier/completeVerifierPass.ts +4 -0
  35. package/src/workflow/verifier/prepareVerifierRun.ts +7 -1
  36. package/src/workflow/verifier/resolveFindings.ts +4 -0
@@ -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,
@@ -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
- model: Type.Optional(Type.String({ pattern: MODEL_PATTERN })),
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
- model: Type.String({ pattern: MODEL_PATTERN }),
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
- config: Awaited<ReturnType<typeof loadConfiguration>>;
53
+ models: string[];
54
54
  };
55
55
 
56
- function assertModelsAvailable({ context, config }: AssertModelsAvailableInput): void {
56
+ function assertModelsAvailable({ context, models }: AssertModelsAvailableInput): void {
57
57
  const availableModels = context.modelRegistry.getAvailable();
58
58
 
59
- for (const model of [config.builder.model, config.verifier.model, config.simplifier.model]) {
60
- const [provider, id] = model.split('/');
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
- async function assertAgentsAvailable(projectRoot: string): Promise<void> {
69
- for (const agent of [AGENTS.BUILDER, AGENTS.VERIFIER, AGENTS.SIMPLIFIER]) {
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
- const config = await getActivationConfiguration(projectRoot);
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. Resolve spec and product paths against this root. Children run in this directory.
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 start the builder.
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. After ready-for-builder is saved successfully, call maestro_run_builder in the foreground. Do not request a separate start approval.
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, call maestro_run_builder in the foreground.
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: The phase is ready-for-verifier. Call maestro_run_verifier with the active specId.
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 either successful verification or rejection of all findings.
153
- Do not ask for another approval or run the verifier again. Both routes enter this phase regardless of Git availability.
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 and any fix-code returns ready-for-builder.
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 the foreground.
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 ready-for-builder. Only the owner reply GREEN FLAG authorizes approval. After this transition succeeds, call maestro_run_builder in the foreground.',
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: `Spec ${state.specId} is approved and ready-for-builder. Call maestro_run_builder now. No separate owner start request is needed.`,
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: typeof WORKFLOW_PHASES.READY_FOR_VERIFIER;
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 === WORKFLOW_PHASES.READY_FOR_VERIFIER
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 === WORKFLOW_PHASES.READY_FOR_VERIFIER) {
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 === BUILDER_HANDOFF_STATUSES.DONE) {
126
- return `Builder completed spec ${result.specId}. The workflow is ready-for-verifier.`;
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 submitted escalation questions for spec ${result.specId}. The workflow is waiting for an owner decision.`;
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 run = await prepareBuilderRun({ paths, specId });
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 "${run.projectRoot}".`,
184
+ task: `Implement specId "${specId}" in the project directory "${projectRoot}".`,
152
185
  context: 'fresh',
153
- cwd: run.projectRoot,
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) }],
@@ -118,6 +118,7 @@ export const registerRunSimplifierTool = (pi: ExtensionAPI): void => {
118
118
 
119
119
  if (!(await isGitReady({ pi, projectRoot }))) {
120
120
  const nextState = transitionWorkflow({
121
+ config,
121
122
  state,
122
123
  event: WORKFLOW_EVENTS.SIMPLIFIER_SKIPPED,
123
124
  });
@@ -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
- : WORKFLOW_PHASES.SIMPLIFIER_RUNNING;
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
  });