@emiliosp/pi-maestro 0.1.1 → 0.2.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.
@@ -1,6 +1,6 @@
1
1
  /**
2
- * Objective: Launch and validate one foreground verifier pass for the owner session.
3
- * Used: When the owner invokes the launch-verifier tool.
2
+ * Objective: Run and validate one foreground verifier pass for the owner session.
3
+ * Used: When the owner invokes the run-verifier tool.
4
4
  */
5
5
 
6
6
  import { randomUUID } from 'node:crypto';
@@ -13,11 +13,12 @@ import { AGENTS } from '#config/schema.ts';
13
13
  import { getRepositoryStatus } from '#git/repository/getRepositoryStatus.ts';
14
14
  import { SPEC_ID_PATTERN } from '#ids/isValidSpecId.ts';
15
15
  import type { MaestroPaths } from '#MaestroPaths.ts';
16
+ import maestroSessionState from '#maestro/session/MaestroSessionState.ts';
16
17
  import {
17
18
  assertDelegationResponse,
18
19
  waitForDelegationResponse,
19
20
  } from '#tools/utils/pi-subagent-delegation.ts';
20
- import { resolveToolLaunchContext } from '#tools/utils/resolveToolLaunchContext.ts';
21
+ import { resolveToolRunContext } from '#tools/utils/resolveToolRunContext.ts';
21
22
  import { WORKFLOW_ROLES } from '#workflow/roles.ts';
22
23
  import { readWorkflowState } from '#workflow/state/readWorkflowState.ts';
23
24
  import { WORKFLOW_PHASES, type WorkflowState } from '#workflow/state/schema.ts';
@@ -27,18 +28,18 @@ import {
27
28
  } from '#workflow/verifier/completeVerifierPass.ts';
28
29
  import { hasProductChanges } from '#workflow/verifier/hasProductChanges.ts';
29
30
  import {
30
- prepareVerifierLaunch,
31
- type VerifierLaunch,
32
- } from '#workflow/verifier/prepareVerifierLaunch.ts';
31
+ prepareVerifierRun,
32
+ type VerifierRun,
33
+ } from '#workflow/verifier/prepareVerifierRun.ts';
33
34
 
34
- export const LAUNCH_VERIFIER_TOOL = {
35
- NAME: 'maestro_launch_verifier',
36
- LABEL: 'Launch Verifier',
35
+ export const RUN_VERIFIER_TOOL = {
36
+ NAME: 'maestro_run_verifier',
37
+ LABEL: 'Run Verifier',
37
38
  DESCRIPTION:
38
- 'Launch the verifier in the current checkout for a completed builder candidate. The verifier runs in the foreground.',
39
+ 'Run the verifier in the current checkout for a completed builder candidate. The verifier runs in the foreground.',
39
40
  } as const;
40
41
 
41
- const LaunchVerifierToolParameters = Type.Object(
42
+ const RunVerifierToolParameters = Type.Object(
42
43
  {
43
44
  specId: Type.String({ pattern: SPEC_ID_PATTERN.source }),
44
45
  },
@@ -78,7 +79,7 @@ type ProductChangesResult = {
78
79
  checkpointCommit: string;
79
80
  };
80
81
 
81
- type VerifierLaunchResult =
82
+ type VerifierRunResult =
82
83
  | CandidateReadyResult
83
84
  | FindingsResult
84
85
  | ProductChangesResult;
@@ -86,7 +87,7 @@ type VerifierLaunchResult =
86
87
  type ReadTerminalVerifierResultInput = {
87
88
  paths: MaestroPaths;
88
89
  specId: string;
89
- launch: VerifierLaunch;
90
+ run: VerifierRun;
90
91
  state: WorkflowState;
91
92
  productChanges: boolean;
92
93
  };
@@ -94,7 +95,7 @@ type ReadTerminalVerifierResultInput = {
94
95
  const readTerminalVerifierResult = async ({
95
96
  paths,
96
97
  specId,
97
- launch,
98
+ run,
98
99
  state,
99
100
  productChanges,
100
101
  }: ReadTerminalVerifierResultInput): Promise<
@@ -115,7 +116,7 @@ const readTerminalVerifierResult = async ({
115
116
  );
116
117
  }
117
118
 
118
- const repositoryStatus = await getRepositoryStatus(launch.repositoryRoot);
119
+ const repositoryStatus = await getRepositoryStatus(run.repositoryRoot);
119
120
 
120
121
  if (!repositoryStatus.clean) {
121
122
  throw new Error(
@@ -143,8 +144,8 @@ const readTerminalVerifierResult = async ({
143
144
  const details = {
144
145
  specId,
145
146
  revision: state.revision,
146
- candidateCommit: launch.candidateCommit,
147
- checkpointCommit: launch.checkpointCommit,
147
+ candidateCommit: run.candidateCommit,
148
+ checkpointCommit: run.checkpointCommit,
148
149
  handoff,
149
150
  };
150
151
 
@@ -158,14 +159,14 @@ const readTerminalVerifierResult = async ({
158
159
  type ReadVerifierResultInput = {
159
160
  paths: MaestroPaths;
160
161
  specId: string;
161
- launch: VerifierLaunch;
162
+ run: VerifierRun;
162
163
  };
163
164
 
164
165
  const readVerifierResult = async ({
165
166
  paths,
166
167
  specId,
167
- launch,
168
- }: ReadVerifierResultInput): Promise<VerifierLaunchResult> => {
168
+ run,
169
+ }: ReadVerifierResultInput): Promise<VerifierRunResult> => {
169
170
  try {
170
171
  const state = await readWorkflowState(paths.getWorkflowPath(specId));
171
172
 
@@ -176,8 +177,8 @@ const readVerifierResult = async ({
176
177
  }
177
178
 
178
179
  const productChanges = await hasProductChanges({
179
- repositoryRoot: launch.repositoryRoot,
180
- candidateCommit: launch.candidateCommit,
180
+ repositoryRoot: run.repositoryRoot,
181
+ candidateCommit: run.candidateCommit,
181
182
  workflowPath: paths.getWorkflowPath(specId),
182
183
  handoffPath: paths.getVerifierHandoffPath(specId),
183
184
  });
@@ -191,8 +192,8 @@ const readVerifierResult = async ({
191
192
  specId,
192
193
  revision: state.revision,
193
194
  phase: state.phase,
194
- candidateCommit: launch.candidateCommit,
195
- checkpointCommit: launch.checkpointCommit,
195
+ candidateCommit: run.candidateCommit,
196
+ checkpointCommit: run.checkpointCommit,
196
197
  };
197
198
  }
198
199
 
@@ -204,7 +205,7 @@ const readVerifierResult = async ({
204
205
  return await readTerminalVerifierResult({
205
206
  paths,
206
207
  specId,
207
- launch,
208
+ run,
208
209
  state,
209
210
  productChanges,
210
211
  });
@@ -215,7 +216,7 @@ const readVerifierResult = async ({
215
216
  }
216
217
  };
217
218
 
218
- const formatVerifierResult = (result: VerifierLaunchResult): string => {
219
+ const formatVerifierResult = (result: VerifierRunResult): string => {
219
220
  if (result.outcome === WORKFLOW_PHASES.CANDIDATE_READY) {
220
221
  return `Verifier completed spec ${result.specId}. The workflow is candidate-ready.`;
221
222
  }
@@ -227,44 +228,48 @@ const formatVerifierResult = (result: VerifierLaunchResult): string => {
227
228
  return `${result.message} The workflow remains verifier-running.`;
228
229
  };
229
230
 
230
- export const registerLaunchVerifierTool = (pi: ExtensionAPI): void => {
231
+ export const registerRunVerifierTool = (pi: ExtensionAPI): void => {
231
232
  pi.registerTool({
232
- name: LAUNCH_VERIFIER_TOOL.NAME,
233
- label: LAUNCH_VERIFIER_TOOL.LABEL,
234
- description: LAUNCH_VERIFIER_TOOL.DESCRIPTION,
235
- parameters: LaunchVerifierToolParameters,
233
+ name: RUN_VERIFIER_TOOL.NAME,
234
+ label: RUN_VERIFIER_TOOL.LABEL,
235
+ description: RUN_VERIFIER_TOOL.DESCRIPTION,
236
+ parameters: RunVerifierToolParameters,
236
237
  async execute(toolCallId, { specId }, _signal, _onUpdate, context) {
237
- const { paths, config } = await resolveToolLaunchContext(context.cwd);
238
-
239
- const launch = await prepareVerifierLaunch({ paths, specId });
240
-
241
- const request: SubagentDelegationRequest = {
242
- requestId: randomUUID(),
243
- ownerRunId: toolCallId,
244
- nodeId: WORKFLOW_ROLES.VERIFIER,
245
- // pi-subagents loads this name from agents/verifier.md through package.json.
246
- agent: AGENTS.VERIFIER,
247
- task: `Verify specId "${specId}" in the current checkout "${launch.repositoryRoot}". Read all applicable AGENTS.md files before working.`,
248
- context: 'fresh',
249
- cwd: launch.repositoryRoot,
250
- model: config.verifier.model,
251
- thinking: config.verifier.thinking,
252
- timeoutMs: config.verifier.timeoutMinutes * MILLISECONDS_PER_MINUTE,
253
- result: { kind: 'text' },
254
- };
255
-
256
- const response = await waitForDelegationResponse({
257
- piEventsBus: pi.events,
258
- request,
259
- });
260
-
261
- assertDelegationResponse(response);
262
- const result = await readVerifierResult({ paths, specId, launch });
263
-
264
- return {
265
- content: [{ type: 'text', text: formatVerifierResult(result) }],
266
- details: result,
267
- };
238
+ const { paths, config } = await resolveToolRunContext(context.cwd);
239
+
240
+ const run = await prepareVerifierRun({ paths, specId });
241
+
242
+ try {
243
+ const request: SubagentDelegationRequest = {
244
+ requestId: randomUUID(),
245
+ ownerRunId: toolCallId,
246
+ nodeId: WORKFLOW_ROLES.VERIFIER,
247
+ // pi-subagents loads this name from agents/verifier.md through package.json.
248
+ agent: AGENTS.VERIFIER,
249
+ task: `Verify specId "${specId}" at candidate checkpoint "${run.candidateCommit}" in the current checkout "${run.repositoryRoot}". Read all applicable AGENTS.md files before working.`,
250
+ context: 'fresh',
251
+ cwd: run.repositoryRoot,
252
+ model: config.verifier.model,
253
+ thinking: config.verifier.thinking,
254
+ timeoutMs: config.verifier.timeoutMinutes * MILLISECONDS_PER_MINUTE,
255
+ result: { kind: 'text' },
256
+ };
257
+
258
+ const response = await waitForDelegationResponse({
259
+ piEventsBus: pi.events,
260
+ request,
261
+ });
262
+
263
+ assertDelegationResponse(response);
264
+ const result = await readVerifierResult({ paths, specId, run });
265
+
266
+ return {
267
+ content: [{ type: 'text', text: formatVerifierResult(result) }],
268
+ details: result,
269
+ };
270
+ } finally {
271
+ maestroSessionState.clearVerifierCheckpointCommit();
272
+ }
268
273
  },
269
274
  });
270
275
  };
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Objective: Coordinate foreground pi-subagents delegation responses.
3
- * Used: By Pi tools that launch pi-subagents agents.
3
+ * Used: By Pi tools that run pi-subagents agents.
4
4
  */
5
5
 
6
6
  import type { ExtensionAPI } from '@earendil-works/pi-coding-agent';
@@ -8,15 +8,15 @@ import type { MaestroConfig } from '#config/schema.ts';
8
8
  import { findRepositoryRoot } from '#git/repository/findRepositoryRoot.ts';
9
9
  import { MaestroPaths } from '#MaestroPaths.ts';
10
10
 
11
- export type ToolLaunchContext = {
11
+ export type ToolRunContext = {
12
12
  repositoryRoot: string;
13
13
  config: MaestroConfig;
14
14
  paths: MaestroPaths;
15
15
  };
16
16
 
17
- export const resolveToolLaunchContext = async (
17
+ export const resolveToolRunContext = async (
18
18
  cwd: string,
19
- ): Promise<ToolLaunchContext> => {
19
+ ): Promise<ToolRunContext> => {
20
20
  const repositoryRoot = await findRepositoryRoot(cwd);
21
21
  const config = await loadConfiguration(repositoryRoot);
22
22
 
@@ -26,6 +26,8 @@ export async function assertBuilderProtocolUnchanged({
26
26
  const actualSpecSha256 = await getFileSha256(specPath);
27
27
 
28
28
  if (actualSpecSha256 !== expectedSpecSha256) {
29
- throw new Error(`Builder changed spec.md after launch: "${specPath}".`);
29
+ throw new Error(
30
+ `Builder changed spec.md after the run starts: "${specPath}".`,
31
+ );
30
32
  }
31
33
  }
@@ -1,6 +1,6 @@
1
1
  /**
2
- * Objective: Prepare a committed builder launch checkpoint in the current checkout.
3
- * Used: Before Maestro launches a builder pass.
2
+ * Objective: Prepare a committed builder run checkpoint in the current checkout.
3
+ * Used: Before Maestro runs a builder pass.
4
4
  */
5
5
 
6
6
  import { rm } from 'node:fs/promises';
@@ -14,22 +14,22 @@ import { WORKFLOW_EVENTS, WORKFLOW_PHASES } from '#workflow/state/schema.ts';
14
14
  import { writeWorkflowState } from '#workflow/state/writeWorkflowState.ts';
15
15
  import { transitionWorkflow } from '#workflow/transitions.ts';
16
16
 
17
- export type BuilderLaunch = {
17
+ export type BuilderRun = {
18
18
  specId: string;
19
19
  revision: number;
20
20
  repositoryRoot: string;
21
21
  checkpointCommit: string;
22
22
  };
23
23
 
24
- type AssertBuilderLaunchBaseInput = {
24
+ type AssertBuilderRunBaseInput = {
25
25
  paths: MaestroPaths;
26
26
  specId: string;
27
27
  };
28
28
 
29
- async function assertBuilderLaunchBase({
29
+ async function assertBuilderRunBase({
30
30
  paths,
31
31
  specId,
32
- }: AssertBuilderLaunchBaseInput): Promise<void> {
32
+ }: AssertBuilderRunBaseInput): Promise<void> {
33
33
  const state = await readWorkflowState(paths.getWorkflowPath(specId));
34
34
 
35
35
  if (state.specId !== specId) {
@@ -39,13 +39,13 @@ async function assertBuilderLaunchBase({
39
39
  }
40
40
 
41
41
  if (state.phase !== WORKFLOW_PHASES.READY_FOR_BUILDER) {
42
- throw new Error(`Builder launch is not valid from phase "${state.phase}".`);
42
+ throw new Error(`Builder run is not valid from phase "${state.phase}".`);
43
43
  }
44
44
 
45
45
  const status = await getRepositoryStatus(paths.getRepositoryRoot());
46
46
 
47
47
  if (!status.clean) {
48
- throw new Error('Builder launch requires a clean current checkout.');
48
+ throw new Error('Builder run requires a clean current checkout.');
49
49
  }
50
50
 
51
51
  if (!(await pathExists(paths.getSpecFilePath(specId)))) {
@@ -53,24 +53,24 @@ async function assertBuilderLaunchBase({
53
53
  }
54
54
  }
55
55
 
56
- type PrepareBuilderLaunchInput = {
56
+ type PrepareBuilderRunInput = {
57
57
  paths: MaestroPaths;
58
58
  specId: string;
59
59
  };
60
60
 
61
- export const prepareBuilderLaunch = async ({
61
+ export const prepareBuilderRun = async ({
62
62
  paths,
63
63
  specId,
64
- }: PrepareBuilderLaunchInput): Promise<BuilderLaunch> => {
64
+ }: PrepareBuilderRunInput): Promise<BuilderRun> => {
65
65
  const activeSpecId = maestroSessionState.getActiveSpecId();
66
66
 
67
67
  if (activeSpecId !== specId) {
68
68
  throw new Error(
69
- `Builder launch requires active Maestro spec "${specId}", found "${activeSpecId ?? 'none'}".`,
69
+ `Builder run requires active Maestro spec "${specId}", found "${activeSpecId ?? 'none'}".`,
70
70
  );
71
71
  }
72
72
 
73
- await assertBuilderLaunchBase({ paths, specId });
73
+ await assertBuilderRunBase({ paths, specId });
74
74
 
75
75
  const workflowPath = paths.getWorkflowPath(specId);
76
76
  const handoffPath = paths.getBuilderHandoffPath(specId);
@@ -83,7 +83,7 @@ export const prepareBuilderLaunch = async ({
83
83
 
84
84
  const nextState = transitionWorkflow({
85
85
  state: currentState,
86
- event: WORKFLOW_EVENTS.LAUNCH_BUILDER,
86
+ event: WORKFLOW_EVENTS.RUN_BUILDER,
87
87
  });
88
88
 
89
89
  await writeWorkflowState({
@@ -23,13 +23,12 @@ export const WORKFLOW_PHASES = {
23
23
 
24
24
  export const WORKFLOW_EVENTS = {
25
25
  MARK_SPEC_READY: 'mark-spec-ready',
26
- LAUNCH_BUILDER: 'launch-builder',
26
+ RUN_BUILDER: 'run-builder',
27
27
  OPEN_ESCALATION: 'open-escalation',
28
28
  RESOLVE_ESCALATION: 'resolve-escalation',
29
29
  BUILDER_FAILED: 'builder-failed',
30
30
  BUILDER_DONE: 'builder-done',
31
- LAUNCH_VERIFIER: 'launch-verifier',
32
- RETRY_VERIFIER: 'retry-verifier',
31
+ RUN_VERIFIER: 'run-verifier',
33
32
  VERIFIER_FOUND_FINDINGS: 'verifier-found-findings',
34
33
  VERIFIER_APPROVED: 'verifier-approved',
35
34
  REJECT_FINDINGS: 'reject-findings',
@@ -26,7 +26,7 @@ export const WORKFLOW_TRANSITIONS: Record<WorkflowEvent, WorkflowTransition> = {
26
26
  ],
27
27
  to: WORKFLOW_PHASES.READY_FOR_BUILDER,
28
28
  },
29
- [WORKFLOW_EVENTS.LAUNCH_BUILDER]: {
29
+ [WORKFLOW_EVENTS.RUN_BUILDER]: {
30
30
  from: [WORKFLOW_PHASES.READY_FOR_BUILDER],
31
31
  to: WORKFLOW_PHASES.BUILDER_RUNNING,
32
32
  },
@@ -46,14 +46,10 @@ export const WORKFLOW_TRANSITIONS: Record<WorkflowEvent, WorkflowTransition> = {
46
46
  from: [WORKFLOW_PHASES.BUILDER_RUNNING],
47
47
  to: WORKFLOW_PHASES.READY_FOR_VERIFIER,
48
48
  },
49
- [WORKFLOW_EVENTS.LAUNCH_VERIFIER]: {
49
+ [WORKFLOW_EVENTS.RUN_VERIFIER]: {
50
50
  from: [WORKFLOW_PHASES.READY_FOR_VERIFIER],
51
51
  to: WORKFLOW_PHASES.VERIFIER_RUNNING,
52
52
  },
53
- [WORKFLOW_EVENTS.RETRY_VERIFIER]: {
54
- from: [WORKFLOW_PHASES.VERIFIER_RUNNING],
55
- to: WORKFLOW_PHASES.VERIFIER_RUNNING,
56
- },
57
53
  [WORKFLOW_EVENTS.VERIFIER_FOUND_FINDINGS]: {
58
54
  from: [WORKFLOW_PHASES.VERIFIER_RUNNING],
59
55
  to: WORKFLOW_PHASES.FINDINGS_DECISION,
@@ -1,21 +1,20 @@
1
1
  /**
2
- * Objective: Prepare a committed verifier launch checkpoint in the current checkout.
2
+ * Objective: Prepare a committed verifier run checkpoint in the current checkout.
3
3
  * Used: When Maestro starts a verifier pass.
4
4
  */
5
5
 
6
6
  import { rm } from 'node:fs/promises';
7
7
  import { createWorkflowCheckpointCommit } from '#git/commits/createWorkflowCheckpointCommit.ts';
8
- import { getParentCommit } from '#git/history/getParentCommit.ts';
9
- import { getHeadCommit } from '#git/repository/getHeadCommit.ts';
10
8
  import { getRepositoryStatus } from '#git/repository/getRepositoryStatus.ts';
11
9
  import type { MaestroPaths } from '#MaestroPaths.ts';
10
+ import maestroSessionState from '#maestro/session/MaestroSessionState.ts';
12
11
  import { pathExists } from '#utils/path-exists.ts';
13
12
  import { readWorkflowState } from '#workflow/state/readWorkflowState.ts';
14
13
  import { WORKFLOW_EVENTS, WORKFLOW_PHASES } from '#workflow/state/schema.ts';
15
14
  import { writeWorkflowState } from '#workflow/state/writeWorkflowState.ts';
16
15
  import { transitionWorkflow } from '#workflow/transitions.ts';
17
16
 
18
- export type VerifierLaunch = {
17
+ export type VerifierRun = {
19
18
  specId: string;
20
19
  repositoryRoot: string;
21
20
  candidateCommit: string;
@@ -23,15 +22,15 @@ export type VerifierLaunch = {
23
22
  revision: number;
24
23
  };
25
24
 
26
- type PrepareVerifierLaunchInput = {
25
+ type PrepareVerifierRunInput = {
27
26
  paths: MaestroPaths;
28
27
  specId: string;
29
28
  };
30
29
 
31
- export const prepareVerifierLaunch = async ({
30
+ export const prepareVerifierRun = async ({
32
31
  paths,
33
32
  specId,
34
- }: PrepareVerifierLaunchInput): Promise<VerifierLaunch> => {
33
+ }: PrepareVerifierRunInput): Promise<VerifierRun> => {
35
34
  const repositoryRoot = paths.getRepositoryRoot();
36
35
  const workflowPath = paths.getWorkflowPath(specId);
37
36
  const handoffPath = paths.getVerifierHandoffPath(specId);
@@ -45,7 +44,7 @@ export const prepareVerifierLaunch = async ({
45
44
 
46
45
  if (currentState.phase !== WORKFLOW_PHASES.READY_FOR_VERIFIER) {
47
46
  throw new Error(
48
- `Verifier launch requires ready-for-verifier state, found "${currentState.phase}".`,
47
+ `Verifier run requires ready-for-verifier state, found "${currentState.phase}".`,
49
48
  );
50
49
  }
51
50
 
@@ -53,13 +52,13 @@ export const prepareVerifierLaunch = async ({
53
52
 
54
53
  if (!status.clean) {
55
54
  throw new Error(
56
- 'Verifier launch requires a committed ready-for-verifier candidate.',
55
+ 'Verifier run requires a committed ready-for-verifier candidate.',
57
56
  );
58
57
  }
59
58
 
60
59
  const nextState = transitionWorkflow({
61
60
  state: currentState,
62
- event: WORKFLOW_EVENTS.LAUNCH_VERIFIER,
61
+ event: WORKFLOW_EVENTS.RUN_VERIFIER,
63
62
  });
64
63
 
65
64
  const handoffExists = await pathExists(handoffPath);
@@ -80,28 +79,17 @@ export const prepareVerifierLaunch = async ({
80
79
  expectedPaths.push(handoffPath);
81
80
  }
82
81
 
83
- const previousHead = await getHeadCommit(repositoryRoot);
84
-
85
82
  const checkpointCommit = await createWorkflowCheckpointCommit({
86
83
  repositoryRoot,
87
84
  expectedPaths,
88
85
  });
89
86
 
90
- const candidateCommit = await getParentCommit({
91
- repositoryRoot,
92
- commit: checkpointCommit,
93
- });
94
-
95
- if (candidateCommit !== previousHead) {
96
- throw new Error(
97
- 'Verifier candidate changed while creating its checkpoint.',
98
- );
99
- }
87
+ maestroSessionState.setVerifierCheckpointCommit(checkpointCommit);
100
88
 
101
89
  return {
102
90
  specId,
103
91
  repositoryRoot,
104
- candidateCommit,
92
+ candidateCommit: checkpointCommit,
105
93
  checkpointCommit,
106
94
  revision: nextState.revision,
107
95
  };