@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.
@@ -3,15 +3,13 @@
3
3
  * Used: When Maestro initializes for a repository.
4
4
  */
5
5
 
6
- import { lstat, readFile, realpath } from 'node:fs/promises';
7
- import { dirname, isAbsolute, join, relative, resolve } from 'node:path';
6
+ import { readFile, realpath } from 'node:fs/promises';
7
+ import { isAbsolute, join, resolve } from 'node:path';
8
8
  import { assertConfiguration } from '#config/assertConfiguration.ts';
9
9
  import { CONFIG_FILE_PATH, DEFAULT_CONFIG } from '#config/defaults.ts';
10
10
  import type { MaestroConfig, PartialMaestroConfig } from '#config/schema.ts';
11
- import { isErrnoException } from '#utils/is-errno-exception.ts';
12
11
  import { pathExists } from '#utils/path-exists.ts';
13
12
  import { isPathStrictlyWithin } from '#utils/path-strictly-within.ts';
14
- import { isPathWithinOrEqual } from '#utils/path-within-or-equal.ts';
15
13
 
16
14
  const resolveConfiguration = (input: PartialMaestroConfig): MaestroConfig => {
17
15
  const resolved: MaestroConfig = {
@@ -35,43 +33,6 @@ const resolveConfiguration = (input: PartialMaestroConfig): MaestroConfig => {
35
33
  return resolved;
36
34
  };
37
35
 
38
- type ExistingAncestor = {
39
- path: string;
40
- realPath: string;
41
- };
42
-
43
- const findExistingAncestor = async (
44
- path: string,
45
- ): Promise<ExistingAncestor> => {
46
- let anchestor = path;
47
-
48
- while (true) {
49
- try {
50
- // lstat instead of access, because access follows symlinks
51
- await lstat(anchestor);
52
- } catch (error) {
53
- if (!isErrnoException(error) || error.code !== 'ENOENT') {
54
- throw error;
55
- }
56
-
57
- const parent = dirname(anchestor);
58
-
59
- if (parent === anchestor) {
60
- throw new Error(`Cannot resolve an existing ancestor for "${path}".`);
61
- }
62
-
63
- anchestor = parent;
64
- continue;
65
- }
66
-
67
- try {
68
- return { path: anchestor, realPath: await realpath(anchestor) };
69
- } catch {
70
- throw new Error(`Cannot resolve symlink "${anchestor}".`);
71
- }
72
- }
73
- };
74
-
75
36
  type AssertSafeDirectoryInput = {
76
37
  value: string;
77
38
  name: string;
@@ -96,11 +57,11 @@ type ResolveSafeDirectoryInput = {
96
57
  name: string;
97
58
  };
98
59
 
99
- const resolveSafeDirectory = async ({
60
+ const resolveSafeDirectory = ({
100
61
  repositoryRoot,
101
62
  directory,
102
63
  name,
103
- }: ResolveSafeDirectoryInput): Promise<string> => {
64
+ }: ResolveSafeDirectoryInput): string => {
104
65
  assertSafeDirectoryInput({ value: directory, name });
105
66
 
106
67
  const requestedDirectory = resolve(repositoryRoot, directory);
@@ -118,31 +79,7 @@ const resolveSafeDirectory = async ({
118
79
  throw new Error(`${name} must stay inside the Git root.`);
119
80
  }
120
81
 
121
- // The directory could not exist at the check time. We find the existing anchestor and do the check on that.
122
- const ancestor = await findExistingAncestor(requestedDirectory);
123
-
124
- if (
125
- !isPathWithinOrEqual({
126
- parent: repositoryRoot,
127
- candidate: ancestor.realPath,
128
- })
129
- ) {
130
- throw new Error(`${name} resolves outside the Git root through a symlink.`);
131
- }
132
-
133
- const unresolvedSuffix = relative(ancestor.path, requestedDirectory);
134
- const resolvedDirectory = resolve(ancestor.realPath, unresolvedSuffix);
135
-
136
- if (
137
- !isPathStrictlyWithin({
138
- parent: repositoryRoot,
139
- candidate: resolvedDirectory,
140
- })
141
- ) {
142
- throw new Error(`${name} resolves outside the Git root through a symlink.`);
143
- }
144
-
145
- return resolvedDirectory;
82
+ return requestedDirectory;
146
83
  };
147
84
 
148
85
  type ResolveDirectoriesInput = {
@@ -150,13 +87,11 @@ type ResolveDirectoriesInput = {
150
87
  config: MaestroConfig;
151
88
  };
152
89
 
153
- const resolveDirectories = async ({
154
- repositoryRoot: configuredRepositoryRoot,
90
+ const resolveDirectories = ({
91
+ repositoryRoot,
155
92
  config,
156
- }: ResolveDirectoriesInput): Promise<MaestroConfig> => {
157
- const repositoryRoot = await realpath(configuredRepositoryRoot);
158
-
159
- const specDirectory = await resolveSafeDirectory({
93
+ }: ResolveDirectoriesInput): MaestroConfig => {
94
+ const specDirectory = resolveSafeDirectory({
160
95
  repositoryRoot,
161
96
  directory: config.specDirectory,
162
97
  name: 'specDirectory',
@@ -186,7 +121,7 @@ export const loadConfiguration = async (
186
121
  const targetPath = join(repositoryRoot, CONFIG_FILE_PATH);
187
122
 
188
123
  if (!(await pathExists(targetPath))) {
189
- return await resolveDirectories({ repositoryRoot, config: DEFAULT_CONFIG });
124
+ return resolveDirectories({ repositoryRoot, config: DEFAULT_CONFIG });
190
125
  }
191
126
 
192
127
  const parsed = await readConfigurationFile(targetPath);
@@ -194,5 +129,5 @@ export const loadConfiguration = async (
194
129
  assertConfiguration(parsed);
195
130
  const config = resolveConfiguration(parsed);
196
131
 
197
- return await resolveDirectories({ repositoryRoot, config });
132
+ return resolveDirectories({ repositoryRoot, config });
198
133
  };
@@ -15,11 +15,11 @@ A builder escalation is an owner decision checkpoint, not only a technical failu
15
15
 
16
16
  If the approved contract must change from escalation-decision or findings-decision, the owner revises and approves the same specId, then calls maestro_mark_spec_ready. If a builder reports failed for a technical reason, Maestro reports the error and stops the workflow. The owner is responsible for the follow-up; there is no retry or spec revision from builder-failed.
17
17
 
18
- Use the deterministic maestro_* tools for workflow mutations, Git operations, artifact changes, and agent launches. Do not perform these mutations through generic tools.
18
+ Use the deterministic maestro_* tools for workflow mutations, Git operations, artifact changes, and agent runs. Do not perform these mutations through generic tools.
19
19
 
20
20
  While Maestro mode is active, use generic tools to inspect and discuss the repository, but do not edit normal product files. Builder and verifier work through their dedicated handoff tools and do not communicate with the owner directly. Do not treat their conclusions as owner decisions.
21
21
 
22
- The verifier uses the parent of the verifier-running checkpoint as its candidate and must restore product changes before its handoff. The verifier handoff tool owns the protocol commit. The verifier handoff and finding resolution operations own the checks required to reach candidate-ready.
22
+ The verifier uses the verifier-running checkpoint itself as its fixed candidate and must restore product changes before its handoff. The verifier handoff tool owns the protocol commit. The verifier handoff and finding resolution operations own the checks required to reach candidate-ready.
23
23
 
24
24
  The workflow ends at candidate-ready. No final tool call, checkpoint, or owner commit is required. You own the final summary. Use inspection tools to read the existing artifacts and Git information. Summarize the changes, verification results, rejected findings with their reasons, and applicable builder notes. Include the current branch and final HEAD after the protocol commit as the Pull Request facts. Do not present owner rejections as passed verification.
25
25
 
@@ -9,6 +9,7 @@ class MaestroSessionState {
9
9
  private active = false;
10
10
  private activeSpecId: string | null = null;
11
11
  private specSha256: string | null = null;
12
+ private verifierCheckpointCommit: string | null = null;
12
13
 
13
14
  public isActive = (): boolean => this.active;
14
15
 
@@ -34,6 +35,17 @@ class MaestroSessionState {
34
35
  this.specSha256 = await getFileSha256(specPath);
35
36
  };
36
37
 
38
+ public getVerifierCheckpointCommit = (): string | null =>
39
+ this.verifierCheckpointCommit;
40
+
41
+ public setVerifierCheckpointCommit = (commit: string): void => {
42
+ this.verifierCheckpointCommit = commit;
43
+ };
44
+
45
+ public clearVerifierCheckpointCommit = (): void => {
46
+ this.verifierCheckpointCommit = null;
47
+ };
48
+
37
49
  public activate = (): void => {
38
50
  this.active = true;
39
51
  };
@@ -41,6 +53,7 @@ class MaestroSessionState {
41
53
  public clearActiveSpecId = (): void => {
42
54
  this.activeSpecId = null;
43
55
  this.specSha256 = null;
56
+ this.clearVerifierCheckpointCommit();
44
57
  };
45
58
 
46
59
  public deactivate = (): void => {
@@ -6,7 +6,7 @@
6
6
  import type { ExtensionContext } from '@earendil-works/pi-coding-agent';
7
7
  import maestroSessionState from '#maestro/session/MaestroSessionState.ts';
8
8
  import { formatMaestroStatus } from '#maestro/status/formatMaestroStatus.ts';
9
- import { resolveToolLaunchContext } from '#tools/utils/resolveToolLaunchContext.ts';
9
+ import { resolveToolRunContext } from '#tools/utils/resolveToolRunContext.ts';
10
10
  import { readWorkflowState } from '#workflow/state/readWorkflowState.ts';
11
11
 
12
12
  export const MAESTRO_STATUS_KEY = 'maestro';
@@ -32,7 +32,7 @@ export const refreshMaestroStatus = async (
32
32
  }
33
33
 
34
34
  try {
35
- const { paths } = await resolveToolLaunchContext(context.cwd);
35
+ const { paths } = await resolveToolRunContext(context.cwd);
36
36
 
37
37
  const state = await readWorkflowState(paths.getWorkflowPath(specId));
38
38
 
@@ -11,10 +11,9 @@ import {
11
11
  VerifierFindingSchema,
12
12
  } from '#artifacts/verifier-handoff/schema.ts';
13
13
  import { createWorkflowCheckpointCommit } from '#git/commits/createWorkflowCheckpointCommit.ts';
14
- import { getParentCommit } from '#git/history/getParentCommit.ts';
15
- import { getHeadCommit } from '#git/repository/getHeadCommit.ts';
16
14
  import { getRepositoryStatus } from '#git/repository/getRepositoryStatus.ts';
17
15
  import { SPEC_ID_PATTERN } from '#ids/isValidSpecId.ts';
16
+ import maestroSessionState from '#maestro/session/MaestroSessionState.ts';
18
17
  import { resolveWorkflowContext } from '#tools/child/utils/resolveWorkflowContext.ts';
19
18
  import { readWorkflowState } from '#workflow/state/readWorkflowState.ts';
20
19
  import {
@@ -55,6 +54,14 @@ const isVerifierPassRejection = (
55
54
  result: VerifierPassResult,
56
55
  ): result is VerifierPassRejection => 'error' in result;
57
56
 
57
+ function assertVerifierCheckpointCommit(
58
+ commit: string | null,
59
+ ): asserts commit is string {
60
+ if (commit === null) {
61
+ throw new Error('Verifier handoff requires a live verifier checkpoint.');
62
+ }
63
+ }
64
+
58
65
  export const registerRecordVerifierHandoffTool = (pi: ExtensionAPI): void => {
59
66
  pi.registerTool({
60
67
  name: VERIFIER_HANDOFF_TOOL.NAME,
@@ -73,12 +80,9 @@ export const registerRecordVerifierHandoffTool = (pi: ExtensionAPI): void => {
73
80
  paths.getWorkflowPath(specId),
74
81
  );
75
82
 
76
- const headCommit = await getHeadCommit(repositoryRoot);
83
+ const candidateCommit = maestroSessionState.getVerifierCheckpointCommit();
77
84
 
78
- const candidateCommit = await getParentCommit({
79
- repositoryRoot,
80
- commit: headCommit,
81
- });
85
+ assertVerifierCheckpointCommit(candidateCommit);
82
86
 
83
87
  const handoff = {
84
88
  version: VERIFIER_HANDOFF_VERSION,
@@ -4,7 +4,7 @@
4
4
  */
5
5
 
6
6
  import { isValidSpecId } from '#ids/isValidSpecId.ts';
7
- import { resolveToolLaunchContext } from '#tools/utils/resolveToolLaunchContext.ts';
7
+ import { resolveToolRunContext } from '#tools/utils/resolveToolRunContext.ts';
8
8
 
9
9
  export type ResolveWorkflowContextInput = {
10
10
  cwd: string;
@@ -19,7 +19,7 @@ export const resolveWorkflowContext = async ({
19
19
  throw new Error(`Invalid spec ID: "${specId}".`);
20
20
  }
21
21
 
22
- const { paths, repositoryRoot } = await resolveToolLaunchContext(cwd);
22
+ const { paths, repositoryRoot } = await resolveToolRunContext(cwd);
23
23
 
24
24
  return { paths, specId, repositoryRoot };
25
25
  };
@@ -7,7 +7,7 @@ import type { ExtensionAPI } from '@earendil-works/pi-coding-agent';
7
7
  import { Type } from 'typebox';
8
8
  import maestroSessionState from '#maestro/session/MaestroSessionState.ts';
9
9
  import { createSpec } from '#specs/create.ts';
10
- import { resolveToolLaunchContext } from '#tools/utils/resolveToolLaunchContext.ts';
10
+ import { resolveToolRunContext } from '#tools/utils/resolveToolRunContext.ts';
11
11
 
12
12
  export const CREATE_SPEC_TOOL = {
13
13
  NAME: 'maestro_create_spec',
@@ -30,7 +30,7 @@ export const registerCreateSpecTool = (pi: ExtensionAPI): void => {
30
30
  description: CREATE_SPEC_TOOL.DESCRIPTION,
31
31
  parameters: CreateSpecToolParameters,
32
32
  async execute(_toolCallId, { title }, _signal, _onUpdate, context) {
33
- const { paths } = await resolveToolLaunchContext(context.cwd);
33
+ const { paths } = await resolveToolRunContext(context.cwd);
34
34
 
35
35
  const created = await createSpec({
36
36
  paths,
@@ -7,14 +7,14 @@ import type { ExtensionAPI } from '@earendil-works/pi-coding-agent';
7
7
  import { Type } from 'typebox';
8
8
  import { SPEC_ID_PATTERN } from '#ids/isValidSpecId.ts';
9
9
  import maestroSessionState from '#maestro/session/MaestroSessionState.ts';
10
- import { resolveToolLaunchContext } from '#tools/utils/resolveToolLaunchContext.ts';
10
+ import { resolveToolRunContext } from '#tools/utils/resolveToolRunContext.ts';
11
11
  import { markSpecReady } from '#workflow/spec/markSpecReady.ts';
12
12
 
13
13
  export const MARK_SPEC_READY_TOOL = {
14
14
  NAME: 'maestro_mark_spec_ready',
15
15
  LABEL: 'Mark Spec Ready',
16
16
  DESCRIPTION:
17
- 'Approve the current spec.md after explicit owner approval and move the workflow to ready-for-builder. The owner must commit spec.md and workflow.json before launching the builder.',
17
+ 'Approve the current spec.md after explicit owner approval and move the workflow to ready-for-builder. The owner must commit spec.md and workflow.json before running the builder.',
18
18
  } as const;
19
19
 
20
20
  const MarkSpecReadyToolParameters = Type.Object(
@@ -31,7 +31,7 @@ export const registerMarkSpecReadyTool = (pi: ExtensionAPI): void => {
31
31
  description: MARK_SPEC_READY_TOOL.DESCRIPTION,
32
32
  parameters: MarkSpecReadyToolParameters,
33
33
  async execute(_toolCallId, { specId }, _signal, _onUpdate, context) {
34
- const { paths } = await resolveToolLaunchContext(context.cwd);
34
+ const { paths } = await resolveToolRunContext(context.cwd);
35
35
 
36
36
  const state = await markSpecReady({
37
37
  paths,
@@ -43,7 +43,7 @@ export const registerMarkSpecReadyTool = (pi: ExtensionAPI): void => {
43
43
  content: [
44
44
  {
45
45
  type: 'text',
46
- text: `Spec ${state.specId} is approved at workflow revision ${state.revision} and ready-for-builder. Commit spec.md and workflow.json before launching the builder.`,
46
+ text: `Spec ${state.specId} is approved at workflow revision ${state.revision} and ready-for-builder. Commit spec.md and workflow.json before running the builder.`,
47
47
  },
48
48
  ],
49
49
  details: state,
@@ -10,14 +10,14 @@ import {
10
10
  EscalationResolutionSchema,
11
11
  } from '#artifacts/escalation/schema.ts';
12
12
  import { SPEC_ID_PATTERN } from '#ids/isValidSpecId.ts';
13
- import { resolveToolLaunchContext } from '#tools/utils/resolveToolLaunchContext.ts';
13
+ import { resolveToolRunContext } from '#tools/utils/resolveToolRunContext.ts';
14
14
  import { resolveBuilderEscalation } from '#workflow/escalation/resolveBuilderEscalation.ts';
15
15
 
16
16
  export const RESOLVE_ESCALATION_TOOL = {
17
17
  NAME: 'maestro_resolve_escalation',
18
18
  LABEL: 'Resolve Escalation',
19
19
  DESCRIPTION:
20
- 'Record the explicit owner decision for the current builder escalation when the approved contract remains valid. Commit the resolution and workflow transition on the current branch, then return ready-for-builder without launching the builder. If the contract must change, edit spec.md and use maestro_mark_spec_ready instead.',
20
+ 'Record the explicit owner decision for the current builder escalation when the approved contract remains valid. Commit the resolution and workflow transition on the current branch, then return ready-for-builder without running the builder. If the contract must change, edit spec.md and use maestro_mark_spec_ready instead.',
21
21
  } as const;
22
22
 
23
23
  const ResolveEscalationToolParameters = Type.Object(
@@ -37,7 +37,7 @@ export const registerResolveEscalationTool = (pi: ExtensionAPI): void => {
37
37
  parameters: ResolveEscalationToolParameters,
38
38
  async execute(_toolCallId, params, _signal, _onUpdate, context) {
39
39
  const { specId, escalationId, ...resolution } = params;
40
- const { paths } = await resolveToolLaunchContext(context.cwd);
40
+ const { paths } = await resolveToolRunContext(context.cwd);
41
41
 
42
42
  const resolved = await resolveBuilderEscalation({
43
43
  paths,
@@ -50,7 +50,7 @@ export const registerResolveEscalationTool = (pi: ExtensionAPI): void => {
50
50
  content: [
51
51
  {
52
52
  type: 'text',
53
- text: `Escalation ${resolved.escalation.id} resolved. Spec ${resolved.state.specId} is ready-for-builder. Launch the builder separately.`,
53
+ text: `Escalation ${resolved.escalation.id} resolved. Spec ${resolved.state.specId} is ready-for-builder. Run the builder separately.`,
54
54
  },
55
55
  ],
56
56
  details: {
@@ -7,7 +7,7 @@ import { StringEnum } from '@earendil-works/pi-ai';
7
7
  import type { ExtensionAPI } from '@earendil-works/pi-coding-agent';
8
8
  import { Type } from 'typebox';
9
9
  import { SPEC_ID_PATTERN } from '#ids/isValidSpecId.ts';
10
- import { resolveToolLaunchContext } from '#tools/utils/resolveToolLaunchContext.ts';
10
+ import { resolveToolRunContext } from '#tools/utils/resolveToolRunContext.ts';
11
11
  import {
12
12
  FINDING_DECISIONS,
13
13
  resolveFindings,
@@ -73,7 +73,7 @@ const formatResolution = (details: FindingResolutionDetails): string => {
73
73
  const requiringFix = formatFindingIds(details.findingsRequiringFixIds);
74
74
 
75
75
  if (details.phase === WORKFLOW_PHASES.READY_FOR_BUILDER) {
76
- return `Recorded rejections for ${rejected}. Findings requiring code fixes: ${requiringFix}. The workflow is ready-for-builder. Launch the builder separately.`;
76
+ return `Recorded rejections for ${rejected}. Findings requiring code fixes: ${requiringFix}. The workflow is ready-for-builder. Run the builder separately.`;
77
77
  }
78
78
 
79
79
  return `Recorded rejections for ${rejected}. Findings requiring code fixes: ${requiringFix}. The workflow is candidate-ready.`;
@@ -92,7 +92,7 @@ export const registerResolveFindingsTool = (pi: ExtensionAPI): void => {
92
92
  _onUpdate,
93
93
  context,
94
94
  ) {
95
- const { paths } = await resolveToolLaunchContext(context.cwd);
95
+ const { paths } = await resolveToolRunContext(context.cwd);
96
96
 
97
97
  const resolved = await resolveFindings({
98
98
  paths,
@@ -1,6 +1,6 @@
1
1
  /**
2
- * Objective: Launch and validate one foreground builder pass for the owner session.
3
- * Used: When the owner invokes the launch-builder tool.
2
+ * Objective: Run and validate one foreground builder pass for the owner session.
3
+ * Used: When the owner invokes the run-builder tool.
4
4
  */
5
5
 
6
6
  import { randomUUID } from 'node:crypto';
@@ -22,20 +22,20 @@ import {
22
22
  assertDelegationResponse,
23
23
  waitForDelegationResponse,
24
24
  } from '#tools/utils/pi-subagent-delegation.ts';
25
- import { resolveToolLaunchContext } from '#tools/utils/resolveToolLaunchContext.ts';
26
- import { prepareBuilderLaunch } from '#workflow/builder/prepareBuilderLauncher.ts';
25
+ import { resolveToolRunContext } from '#tools/utils/resolveToolRunContext.ts';
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
29
  import { WORKFLOW_PHASES, type WorkflowState } from '#workflow/state/schema.ts';
30
30
 
31
- export const LAUNCH_BUILDER_TOOL = {
32
- NAME: 'maestro_launch_builder',
33
- LABEL: 'Launch Builder',
31
+ export const RUN_BUILDER_TOOL = {
32
+ NAME: 'maestro_run_builder',
33
+ LABEL: 'Run Builder',
34
34
  DESCRIPTION:
35
- 'Launch the builder in the current checkout for an owner-approved spec. The builder runs in the foreground and cannot be retried after a committed failure.',
35
+ 'Run the builder in the current checkout for an owner-approved spec. The builder runs in the foreground and cannot be retried after a committed failure.',
36
36
  } as const;
37
37
 
38
- const LaunchBuilderToolParameters = Type.Object(
38
+ const RunBuilderToolParameters = Type.Object(
39
39
  {
40
40
  specId: Type.String({ pattern: SPEC_ID_PATTERN.source }),
41
41
  },
@@ -44,7 +44,7 @@ const LaunchBuilderToolParameters = Type.Object(
44
44
 
45
45
  const MILLISECONDS_PER_MINUTE = 60_000;
46
46
 
47
- type BuilderLaunchResult =
47
+ type BuilderRunResult =
48
48
  | {
49
49
  outcome: typeof BUILDER_HANDOFF_STATUSES.DONE;
50
50
  specId: string;
@@ -75,7 +75,7 @@ type ReadCommittedBuilderResultInput = {
75
75
  const readCommittedBuilderResult = async ({
76
76
  paths,
77
77
  specId,
78
- }: ReadCommittedBuilderResultInput): Promise<BuilderLaunchResult> => {
78
+ }: ReadCommittedBuilderResultInput): Promise<BuilderRunResult> => {
79
79
  try {
80
80
  const repositoryStatus = await getRepositoryStatus(
81
81
  paths.getRepositoryRoot(),
@@ -95,7 +95,7 @@ const readCommittedBuilderResult = async ({
95
95
  );
96
96
  }
97
97
 
98
- return await buildLaunchResult({ paths, specId, state });
98
+ return await buildRunResult({ paths, specId, state });
99
99
  } catch (error) {
100
100
  const message = error instanceof Error ? error.message : String(error);
101
101
 
@@ -117,7 +117,7 @@ const readTerminalBuilderResult = async ({
117
117
  specId,
118
118
  revision,
119
119
  phase,
120
- }: ReadTerminalBuilderResultInput): Promise<BuilderLaunchResult> => {
120
+ }: ReadTerminalBuilderResultInput): Promise<BuilderRunResult> => {
121
121
  const handoff = await readBuilderHandoff({
122
122
  path: paths.getBuilderHandoffPath(specId),
123
123
  specId,
@@ -154,17 +154,17 @@ const readTerminalBuilderResult = async ({
154
154
  };
155
155
  };
156
156
 
157
- type BuildLaunchResultInput = {
157
+ type BuildRunResultInput = {
158
158
  paths: MaestroPaths;
159
159
  specId: string;
160
160
  state: WorkflowState;
161
161
  };
162
162
 
163
- const buildLaunchResult = async ({
163
+ const buildRunResult = async ({
164
164
  paths,
165
165
  specId,
166
166
  state,
167
- }: BuildLaunchResultInput): Promise<BuilderLaunchResult> => {
167
+ }: BuildRunResultInput): Promise<BuilderRunResult> => {
168
168
  if (
169
169
  state.phase === WORKFLOW_PHASES.READY_FOR_VERIFIER ||
170
170
  state.phase === WORKFLOW_PHASES.BUILDER_FAILED
@@ -210,7 +210,7 @@ const buildLaunchResult = async ({
210
210
  );
211
211
  };
212
212
 
213
- const formatBuilderResult = (result: BuilderLaunchResult): string => {
213
+ const formatBuilderResult = (result: BuilderRunResult): string => {
214
214
  if (result.outcome === BUILDER_HANDOFF_STATUSES.DONE) {
215
215
  return `Builder completed spec ${result.specId}. The workflow is ready-for-verifier.`;
216
216
  }
@@ -222,16 +222,16 @@ const formatBuilderResult = (result: BuilderLaunchResult): string => {
222
222
  return `Builder opened escalation ${result.escalation.id} for spec ${result.specId}. The workflow is waiting for an owner decision.`;
223
223
  };
224
224
 
225
- export const registerLaunchBuilderTool = (pi: ExtensionAPI): void => {
225
+ export const registerRunBuilderTool = (pi: ExtensionAPI): void => {
226
226
  pi.registerTool({
227
- name: LAUNCH_BUILDER_TOOL.NAME,
228
- label: LAUNCH_BUILDER_TOOL.LABEL,
229
- description: LAUNCH_BUILDER_TOOL.DESCRIPTION,
230
- parameters: LaunchBuilderToolParameters,
227
+ name: RUN_BUILDER_TOOL.NAME,
228
+ label: RUN_BUILDER_TOOL.LABEL,
229
+ description: RUN_BUILDER_TOOL.DESCRIPTION,
230
+ parameters: RunBuilderToolParameters,
231
231
  async execute(toolCallId, { specId }, _signal, _onUpdate, context) {
232
- const { paths, config } = await resolveToolLaunchContext(context.cwd);
232
+ const { paths, config } = await resolveToolRunContext(context.cwd);
233
233
 
234
- const launch = await prepareBuilderLaunch({ paths, specId });
234
+ const run = await prepareBuilderRun({ paths, specId });
235
235
 
236
236
  const request: SubagentDelegationRequest = {
237
237
  requestId: randomUUID(),
@@ -240,9 +240,9 @@ export const registerLaunchBuilderTool = (pi: ExtensionAPI): void => {
240
240
  // pi-subagents loads this name from agents/builder.md through package.json.
241
241
  // See docs/subagent-integration.md.
242
242
  agent: AGENTS.BUILDER,
243
- task: `Implement specId "${specId}" in the current checkout "${launch.repositoryRoot}". Read all applicable AGENTS.md files before working.`,
243
+ task: `Implement specId "${specId}" in the current checkout "${run.repositoryRoot}". Read all applicable AGENTS.md files before working.`,
244
244
  context: 'fresh',
245
- cwd: launch.repositoryRoot,
245
+ cwd: run.repositoryRoot,
246
246
  model: config.builder.model,
247
247
  thinking: config.builder.thinking,
248
248
  timeoutMs: config.builder.timeoutMinutes * MILLISECONDS_PER_MINUTE,