@emiliosp/pi-maestro 0.1.0 → 0.1.2
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/README.md +5 -5
- package/agents/verifier.md +1 -1
- package/docs/configuration.md +7 -18
- package/docs/subagent-integration.md +15 -14
- package/docs/workflow.md +64 -83
- package/extensions/maestro.ts +12 -12
- package/package.json +1 -1
- package/src/config/loadConfiguration.ts +11 -76
- package/src/maestro/instructions/getMaestroInstructions.ts +2 -2
- package/src/maestro/session/MaestroSessionState.ts +13 -0
- package/src/maestro/status/refreshMaestroStatus.ts +2 -2
- package/src/tools/child/record-verifier-handoff.ts +11 -7
- package/src/tools/child/utils/resolveWorkflowContext.ts +2 -2
- package/src/tools/main/create-spec.ts +2 -2
- package/src/tools/main/mark-spec-ready.ts +4 -4
- package/src/tools/main/resolve-escalation.ts +4 -4
- package/src/tools/main/resolve-findings.ts +3 -3
- package/src/tools/main/{launch-builder.ts → run-builder.ts} +26 -26
- package/src/tools/main/{launch-verifier.ts → run-verifier.ts} +67 -62
- package/src/tools/utils/pi-subagent-delegation.ts +1 -1
- package/src/tools/utils/{resolveToolLaunchContext.ts → resolveToolRunContext.ts} +3 -3
- package/src/workflow/builder/assertBuilderProtocolUnchanged.ts +3 -1
- package/src/workflow/builder/{prepareBuilderLauncher.ts → prepareBuilderRun.ts} +14 -14
- package/src/workflow/state/schema.ts +2 -3
- package/src/workflow/transitions.ts +2 -6
- package/src/workflow/verifier/{prepareVerifierLaunch.ts → prepareVerifierRun.ts} +11 -23
|
@@ -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 {
|
|
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
|
|
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
|
|
83
|
+
const candidateCommit = maestroSessionState.getVerifierCheckpointCommit();
|
|
77
84
|
|
|
78
|
-
|
|
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 {
|
|
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
|
|
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 {
|
|
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
|
|
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 {
|
|
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
|
|
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
|
|
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
|
|
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 {
|
|
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
|
|
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
|
|
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.
|
|
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 {
|
|
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.
|
|
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
|
|
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:
|
|
3
|
-
* Used: When the owner invokes the
|
|
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 {
|
|
26
|
-
import {
|
|
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
|
|
32
|
-
NAME: '
|
|
33
|
-
LABEL: '
|
|
31
|
+
export const RUN_BUILDER_TOOL = {
|
|
32
|
+
NAME: 'maestro_run_builder',
|
|
33
|
+
LABEL: 'Run Builder',
|
|
34
34
|
DESCRIPTION:
|
|
35
|
-
'
|
|
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
|
|
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
|
|
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<
|
|
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
|
|
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<
|
|
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
|
|
157
|
+
type BuildRunResultInput = {
|
|
158
158
|
paths: MaestroPaths;
|
|
159
159
|
specId: string;
|
|
160
160
|
state: WorkflowState;
|
|
161
161
|
};
|
|
162
162
|
|
|
163
|
-
const
|
|
163
|
+
const buildRunResult = async ({
|
|
164
164
|
paths,
|
|
165
165
|
specId,
|
|
166
166
|
state,
|
|
167
|
-
}:
|
|
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:
|
|
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
|
|
225
|
+
export const registerRunBuilderTool = (pi: ExtensionAPI): void => {
|
|
226
226
|
pi.registerTool({
|
|
227
|
-
name:
|
|
228
|
-
label:
|
|
229
|
-
description:
|
|
230
|
-
parameters:
|
|
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
|
|
232
|
+
const { paths, config } = await resolveToolRunContext(context.cwd);
|
|
233
233
|
|
|
234
|
-
const
|
|
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 "${
|
|
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:
|
|
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,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Objective:
|
|
3
|
-
* Used: When the owner invokes the
|
|
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 {
|
|
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
|
-
|
|
31
|
-
type
|
|
32
|
-
} from '#workflow/verifier/
|
|
31
|
+
prepareVerifierRun,
|
|
32
|
+
type VerifierRun,
|
|
33
|
+
} from '#workflow/verifier/prepareVerifierRun.ts';
|
|
33
34
|
|
|
34
|
-
export const
|
|
35
|
-
NAME: '
|
|
36
|
-
LABEL: '
|
|
35
|
+
export const RUN_VERIFIER_TOOL = {
|
|
36
|
+
NAME: 'maestro_run_verifier',
|
|
37
|
+
LABEL: 'Run Verifier',
|
|
37
38
|
DESCRIPTION:
|
|
38
|
-
'
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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:
|
|
147
|
-
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
|
-
|
|
162
|
+
run: VerifierRun;
|
|
162
163
|
};
|
|
163
164
|
|
|
164
165
|
const readVerifierResult = async ({
|
|
165
166
|
paths,
|
|
166
167
|
specId,
|
|
167
|
-
|
|
168
|
-
}: ReadVerifierResultInput): Promise<
|
|
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:
|
|
180
|
-
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:
|
|
195
|
-
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
|
-
|
|
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:
|
|
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
|
|
231
|
+
export const registerRunVerifierTool = (pi: ExtensionAPI): void => {
|
|
231
232
|
pi.registerTool({
|
|
232
|
-
name:
|
|
233
|
-
label:
|
|
234
|
-
description:
|
|
235
|
-
parameters:
|
|
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
|
|
238
|
-
|
|
239
|
-
const
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
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
|
};
|
|
@@ -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
|
|
11
|
+
export type ToolRunContext = {
|
|
12
12
|
repositoryRoot: string;
|
|
13
13
|
config: MaestroConfig;
|
|
14
14
|
paths: MaestroPaths;
|
|
15
15
|
};
|
|
16
16
|
|
|
17
|
-
export const
|
|
17
|
+
export const resolveToolRunContext = async (
|
|
18
18
|
cwd: string,
|
|
19
|
-
): Promise<
|
|
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(
|
|
29
|
+
throw new Error(
|
|
30
|
+
`Builder changed spec.md after the run starts: "${specPath}".`,
|
|
31
|
+
);
|
|
30
32
|
}
|
|
31
33
|
}
|