@emiliosp/pi-maestro 0.10.1 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ADR.md +53 -9
- package/CHANGELOG.md +73 -33
- package/MISSION.md +4 -2
- package/README.md +29 -14
- package/ROADMAP.md +114 -85
- package/TECH_STACK.md +12 -11
- package/agents/simplifier.md +214 -0
- package/agents/verifier.md +1 -16
- package/docs/configuration.md +27 -16
- package/docs/glossary.md +41 -36
- package/docs/subagent-integration.md +23 -13
- package/docs/workflow.md +102 -71
- package/extensions/maestro-subagent.ts +3 -1
- package/extensions/maestro.ts +10 -0
- package/package.json +1 -1
- package/src/MaestroPaths.ts +4 -0
- package/src/artifacts/simplifier-handoff/assertSimplifierHandoff.ts +56 -0
- package/src/artifacts/simplifier-handoff/readSimplifierHandoff.ts +29 -0
- package/src/artifacts/simplifier-handoff/schema.ts +26 -0
- package/src/config/assertConfiguration.ts +1 -1
- package/src/config/defaults.ts +7 -0
- package/src/config/loadConfiguration.ts +1 -0
- package/src/config/schema.ts +3 -0
- package/src/maestro/checks/assertEnvironment.ts +7 -3
- package/src/maestro/instructions/getMaestroInstructions.ts +16 -6
- package/src/maestro/status/formatMaestroStatus.ts +3 -0
- package/src/tools/child/record-simplifier-handoff.ts +73 -0
- package/src/tools/main/resolve-findings.ts +1 -1
- package/src/tools/main/run-simplifier.ts +182 -0
- package/src/tools/main/run-verifier.ts +5 -5
- package/src/workflow/roles.ts +2 -1
- package/src/workflow/simplifier/completeSimplifierPass.ts +57 -0
- package/src/workflow/state/schema.ts +4 -0
- package/src/workflow/transitions.ts +9 -1
- package/src/workflow/utils/getAgentRole.ts +5 -1
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Objective: Compose the Maestro extension for builder and
|
|
2
|
+
* Objective: Compose the Maestro extension for builder, verifier, and simplifier subagent sessions.
|
|
3
3
|
* Used: When Pi starts a Maestro subagent session.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import type { ExtensionAPI } from '@earendil-works/pi-coding-agent';
|
|
7
7
|
import { registerRecordBuilderHandoffTool } from '#tools/child/record-builder-handoff.ts';
|
|
8
|
+
import { registerRecordSimplifierHandoffTool } from '#tools/child/record-simplifier-handoff.ts';
|
|
8
9
|
import { registerRecordVerifierHandoffTool } from '#tools/child/record-verifier-handoff.ts';
|
|
9
10
|
|
|
10
11
|
export default (pi: ExtensionAPI): void => {
|
|
11
12
|
registerRecordBuilderHandoffTool(pi);
|
|
12
13
|
registerRecordVerifierHandoffTool(pi);
|
|
14
|
+
registerRecordSimplifierHandoffTool(pi);
|
|
13
15
|
};
|
package/extensions/maestro.ts
CHANGED
|
@@ -29,6 +29,10 @@ import {
|
|
|
29
29
|
RUN_BUILDER_TOOL,
|
|
30
30
|
registerRunBuilderTool,
|
|
31
31
|
} from '#tools/main/run-builder.ts';
|
|
32
|
+
import {
|
|
33
|
+
RUN_SIMPLIFIER_TOOL,
|
|
34
|
+
registerRunSimplifierTool,
|
|
35
|
+
} from '#tools/main/run-simplifier.ts';
|
|
32
36
|
import {
|
|
33
37
|
RUN_VERIFIER_TOOL,
|
|
34
38
|
registerRunVerifierTool,
|
|
@@ -41,6 +45,7 @@ const MAIN_TOOL_NAMES: readonly string[] = [
|
|
|
41
45
|
RESOLVE_ESCALATIONS_TOOL.NAME,
|
|
42
46
|
RUN_VERIFIER_TOOL.NAME,
|
|
43
47
|
RESOLVE_FINDINGS_TOOL.NAME,
|
|
48
|
+
RUN_SIMPLIFIER_TOOL.NAME,
|
|
44
49
|
];
|
|
45
50
|
|
|
46
51
|
export default (pi: ExtensionAPI): void => {
|
|
@@ -50,6 +55,7 @@ export default (pi: ExtensionAPI): void => {
|
|
|
50
55
|
registerResolveEscalationsTool(pi);
|
|
51
56
|
registerRunVerifierTool(pi);
|
|
52
57
|
registerResolveFindingsTool(pi);
|
|
58
|
+
registerRunSimplifierTool(pi);
|
|
53
59
|
|
|
54
60
|
const syncMainTools = (): void => {
|
|
55
61
|
const foreignTools = pi
|
|
@@ -104,6 +110,10 @@ export default (pi: ExtensionAPI): void => {
|
|
|
104
110
|
'accent',
|
|
105
111
|
`Verifier (${config.verifier.model} ${config.verifier.thinking})`,
|
|
106
112
|
),
|
|
113
|
+
context.ui.theme.fg(
|
|
114
|
+
'accent',
|
|
115
|
+
`Simplifier (${config.simplifier.model} ${config.simplifier.thinking})`,
|
|
116
|
+
),
|
|
107
117
|
].join('\n'),
|
|
108
118
|
'info',
|
|
109
119
|
);
|
package/package.json
CHANGED
package/src/MaestroPaths.ts
CHANGED
|
@@ -186,6 +186,10 @@ export class MaestroPaths {
|
|
|
186
186
|
});
|
|
187
187
|
}
|
|
188
188
|
|
|
189
|
+
public getSimplifierHandoffPath(specId: string): string {
|
|
190
|
+
return resolve(this.getSpecPath(specId), 'handoffs/simplifier/S.json');
|
|
191
|
+
}
|
|
192
|
+
|
|
189
193
|
public getPrototypesPath(specId: string): string {
|
|
190
194
|
return resolve(this.getSpecPath(specId), PATHS.PROTOTYPES_PATH);
|
|
191
195
|
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Objective: Validate the simplifier result and its project-relative file paths.
|
|
3
|
+
* Used: Before reading or saving a simplifier handoff.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { isAbsolute, resolve } from 'node:path';
|
|
7
|
+
import { Value } from 'typebox/value';
|
|
8
|
+
import {
|
|
9
|
+
type SimplifierHandoff,
|
|
10
|
+
SimplifierHandoffSchema,
|
|
11
|
+
} from '#artifacts/simplifier-handoff/schema.ts';
|
|
12
|
+
import { isPathStrictlyWithin } from '#utils/path-strictly-within.ts';
|
|
13
|
+
|
|
14
|
+
type AssertSimplifierHandoffInput<Handoff = unknown> = {
|
|
15
|
+
handoff: Handoff;
|
|
16
|
+
specId: string;
|
|
17
|
+
projectRoot: string;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
function assertSimplifierHandoffSchema(
|
|
21
|
+
input: unknown,
|
|
22
|
+
): asserts input is SimplifierHandoff {
|
|
23
|
+
const [error] = Value.Errors(SimplifierHandoffSchema, input);
|
|
24
|
+
|
|
25
|
+
if (error !== undefined) {
|
|
26
|
+
throw new Error(`Invalid simplifier handoff: ${error.message}.`);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function assertSimplifierHandoff(
|
|
31
|
+
input: AssertSimplifierHandoffInput,
|
|
32
|
+
): asserts input is AssertSimplifierHandoffInput<SimplifierHandoff> {
|
|
33
|
+
assertSimplifierHandoffSchema(input.handoff);
|
|
34
|
+
const { handoff } = input;
|
|
35
|
+
|
|
36
|
+
if (handoff.specId !== input.specId) {
|
|
37
|
+
throw new Error(
|
|
38
|
+
`Simplifier handoff spec ID mismatch: expected "${input.specId}", found "${handoff.specId}".`,
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
for (const file of handoff.refactoredFiles) {
|
|
43
|
+
if (
|
|
44
|
+
file.includes('\0') ||
|
|
45
|
+
isAbsolute(file) ||
|
|
46
|
+
!isPathStrictlyWithin({
|
|
47
|
+
parent: input.projectRoot,
|
|
48
|
+
path: resolve(input.projectRoot, file),
|
|
49
|
+
})
|
|
50
|
+
) {
|
|
51
|
+
throw new Error(
|
|
52
|
+
`Refactored file path must be relative and stay inside the project root: "${file}".`,
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Objective: Read the fixed simplifier handoff and validate its identity.
|
|
3
|
+
* Used: After foreground simplifier execution completes.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { assertSimplifierHandoff } from '#artifacts/simplifier-handoff/assertSimplifierHandoff.ts';
|
|
7
|
+
import { readJsonFile } from '#utils/read-json.ts';
|
|
8
|
+
|
|
9
|
+
type ReadSimplifierHandoffInput = {
|
|
10
|
+
path: string;
|
|
11
|
+
specId: string;
|
|
12
|
+
projectRoot: string;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export const readSimplifierHandoff = async ({
|
|
16
|
+
path,
|
|
17
|
+
specId,
|
|
18
|
+
projectRoot,
|
|
19
|
+
}: ReadSimplifierHandoffInput) => {
|
|
20
|
+
const input = {
|
|
21
|
+
handoff: await readJsonFile({ path, description: 'Simplifier handoff' }),
|
|
22
|
+
specId,
|
|
23
|
+
projectRoot,
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
assertSimplifierHandoff(input);
|
|
27
|
+
|
|
28
|
+
return input.handoff;
|
|
29
|
+
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Objective: Define the fixed simplifier result contract.
|
|
3
|
+
* Used: When a simplifier submits or Maestro reads its handoff.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { type Static, Type } from 'typebox';
|
|
7
|
+
import { SPEC_ID_PATTERN } from '#ids/isValidSpecId.ts';
|
|
8
|
+
|
|
9
|
+
export const SIMPLIFIER_HANDOFF_VERSION = '1.0.0';
|
|
10
|
+
|
|
11
|
+
export const SimplifierSubmissionFields = {
|
|
12
|
+
specId: Type.String({ pattern: SPEC_ID_PATTERN.source }),
|
|
13
|
+
refactoredFiles: Type.Array(Type.String({ minLength: 1 })),
|
|
14
|
+
summary: Type.String({ minLength: 1 }),
|
|
15
|
+
notes: Type.Array(Type.String()),
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export const SimplifierHandoffSchema = Type.Object(
|
|
19
|
+
{
|
|
20
|
+
version: Type.Literal(SIMPLIFIER_HANDOFF_VERSION),
|
|
21
|
+
...SimplifierSubmissionFields,
|
|
22
|
+
},
|
|
23
|
+
{ additionalProperties: false },
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
export type SimplifierHandoff = Static<typeof SimplifierHandoffSchema>;
|
|
@@ -41,7 +41,7 @@ const formatError = (error: SchemaValidationError): string => {
|
|
|
41
41
|
return `Invalid model identifier at "${path}". Must use "provider/model" format.`;
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
-
if (path === 'builder' || path === 'verifier') {
|
|
44
|
+
if (path === 'builder' || path === 'verifier' || path === 'simplifier') {
|
|
45
45
|
return `Invalid ${path} configuration: expected an object.`;
|
|
46
46
|
}
|
|
47
47
|
|
package/src/config/defaults.ts
CHANGED
|
@@ -21,6 +21,8 @@ export const DEFAULT_VERIFIER_MODEL = 'openai-codex/gpt-6.1-sol';
|
|
|
21
21
|
|
|
22
22
|
export const DEFAULT_VERIFIER_THINKING = THINKING_LEVELS.HIGH;
|
|
23
23
|
|
|
24
|
+
export const DEFAULT_SIMPLIFIER_MODEL = 'openai-codex/gpt-6.1-sol';
|
|
25
|
+
|
|
24
26
|
export const DEFAULT_TIMEOUT_MINUTES = 60;
|
|
25
27
|
|
|
26
28
|
export const CONFIG_DIRECTORY_NAME = '.pi';
|
|
@@ -37,6 +39,11 @@ export const DEFAULT_CONFIG: MaestroConfig = {
|
|
|
37
39
|
thinking: DEFAULT_BUILDER_THINKING,
|
|
38
40
|
timeoutMinutes: DEFAULT_TIMEOUT_MINUTES,
|
|
39
41
|
},
|
|
42
|
+
simplifier: {
|
|
43
|
+
model: DEFAULT_SIMPLIFIER_MODEL,
|
|
44
|
+
thinking: THINKING_LEVELS.HIGH,
|
|
45
|
+
timeoutMinutes: DEFAULT_TIMEOUT_MINUTES,
|
|
46
|
+
},
|
|
40
47
|
verifier: {
|
|
41
48
|
model: DEFAULT_VERIFIER_MODEL,
|
|
42
49
|
thinking: DEFAULT_VERIFIER_THINKING,
|
|
@@ -16,6 +16,7 @@ const resolveConfiguration = (input: PartialMaestroConfig): MaestroConfig => ({
|
|
|
16
16
|
specDirectory: input.specDirectory ?? DEFAULT_CONFIG.specDirectory,
|
|
17
17
|
builder: { ...DEFAULT_CONFIG.builder, ...input.builder },
|
|
18
18
|
verifier: { ...DEFAULT_CONFIG.verifier, ...input.verifier },
|
|
19
|
+
simplifier: { ...DEFAULT_CONFIG.simplifier, ...input.simplifier },
|
|
19
20
|
});
|
|
20
21
|
|
|
21
22
|
type ResolveSpecDirectoryInput = {
|
package/src/config/schema.ts
CHANGED
|
@@ -8,6 +8,7 @@ import { type Static, Type } from 'typebox';
|
|
|
8
8
|
export const AGENTS = {
|
|
9
9
|
BUILDER: 'maestro.builder',
|
|
10
10
|
VERIFIER: 'maestro.verifier',
|
|
11
|
+
SIMPLIFIER: 'maestro.simplifier',
|
|
11
12
|
} as const;
|
|
12
13
|
|
|
13
14
|
export const THINKING_LEVELS = {
|
|
@@ -61,6 +62,7 @@ export const MaestroConfigInputSchema = Type.Object(
|
|
|
61
62
|
specDirectory: Type.Optional(Type.String({ minLength: 1 })),
|
|
62
63
|
builder: Type.Optional(PartialAgentConfigSchema),
|
|
63
64
|
verifier: Type.Optional(PartialAgentConfigSchema),
|
|
65
|
+
simplifier: Type.Optional(PartialAgentConfigSchema),
|
|
64
66
|
},
|
|
65
67
|
{ additionalProperties: false },
|
|
66
68
|
);
|
|
@@ -83,6 +85,7 @@ export const ResolvedMaestroConfigSchema = Type.Object(
|
|
|
83
85
|
specDirectory: Type.String({ minLength: 1 }),
|
|
84
86
|
builder: ResolvedAgentConfigSchema,
|
|
85
87
|
verifier: ResolvedAgentConfigSchema,
|
|
88
|
+
simplifier: ResolvedAgentConfigSchema,
|
|
86
89
|
},
|
|
87
90
|
{ additionalProperties: false },
|
|
88
91
|
);
|
|
@@ -63,7 +63,11 @@ function assertModelsAvailable({
|
|
|
63
63
|
}: AssertModelsAvailableInput): void {
|
|
64
64
|
const availableModels = context.modelRegistry.getAvailable();
|
|
65
65
|
|
|
66
|
-
for (const model of [
|
|
66
|
+
for (const model of [
|
|
67
|
+
config.builder.model,
|
|
68
|
+
config.verifier.model,
|
|
69
|
+
config.simplifier.model,
|
|
70
|
+
]) {
|
|
67
71
|
const [provider, id] = model.split('/');
|
|
68
72
|
|
|
69
73
|
if (
|
|
@@ -77,7 +81,7 @@ function assertModelsAvailable({
|
|
|
77
81
|
}
|
|
78
82
|
|
|
79
83
|
async function assertAgentsAvailable(projectRoot: string): Promise<void> {
|
|
80
|
-
for (const agent of [AGENTS.BUILDER, AGENTS.VERIFIER]) {
|
|
84
|
+
for (const agent of [AGENTS.BUILDER, AGENTS.VERIFIER, AGENTS.SIMPLIFIER]) {
|
|
81
85
|
const result = await resolveSubagentLaunchContract({
|
|
82
86
|
agent,
|
|
83
87
|
cwd: projectRoot,
|
|
@@ -171,7 +175,7 @@ export async function assertEnvironment({
|
|
|
171
175
|
await assertAgentsAvailable(projectRoot);
|
|
172
176
|
} catch (error) {
|
|
173
177
|
throw new Error(
|
|
174
|
-
`Builder or
|
|
178
|
+
`Builder, verifier, or simplifier agent check failed: ${getErrorMessage(error)}`,
|
|
175
179
|
);
|
|
176
180
|
}
|
|
177
181
|
|
|
@@ -15,8 +15,8 @@ const MAESTRO_INSTRUCTIONS = `## Maestro mode
|
|
|
15
15
|
|
|
16
16
|
You are Maestro. Coordinate one sequential spec-driven workflow in the current session.
|
|
17
17
|
The owner decides requirements, scope, technical decisions in the spec, spec approval, escalation answers, and finding decisions.
|
|
18
|
-
The builder implements the approved spec. The verifier independently checks the candidate. Do not perform their work yourself.
|
|
19
|
-
The approved spec.md is the contract for all
|
|
18
|
+
The builder implements the approved spec. The verifier independently checks the candidate. The simplifier refactors accepted code. Do not perform their work yourself.
|
|
19
|
+
The approved spec.md is the contract for all roles. Do not infer owner approval from an agent's recommendation.
|
|
20
20
|
|
|
21
21
|
### Maestro documentation
|
|
22
22
|
|
|
@@ -37,7 +37,7 @@ The spec, prototype, and experiment permissions below are the only exceptions fo
|
|
|
37
37
|
|
|
38
38
|
Use the specId and paths returned by maestro_create_spec. Read workflow.json before selecting the next action. Its validated phase is the source of truth.
|
|
39
39
|
Use tool schemas for arguments and tool results for outcomes. Do not infer a completed transition from an agent's final message.
|
|
40
|
-
Builder and
|
|
40
|
+
Builder, verifier, and simplifier runs are foreground operations with fresh contexts. Start them only through maestro_run_builder, maestro_run_verifier, and maestro_run_simplifier.
|
|
41
41
|
Wait for each tool result before taking the next workflow action. Do not launch parallel or background runs.
|
|
42
42
|
|
|
43
43
|
### Prepare and approve the spec
|
|
@@ -93,7 +93,7 @@ Edit the active spec.md and its prototypes/ directory only in drafting-spec.
|
|
|
93
93
|
For either revision origin, record the complete owner decision batch through the corresponding resolution tool before editing in drafting-spec.
|
|
94
94
|
The numbering-only recovery procedure above also permits spec.md corrections after failed numbering validation in drafting-spec.
|
|
95
95
|
Do not edit the spec or prototypes in other phases.
|
|
96
|
-
The approved spec is frozen. Never change it during builder or
|
|
96
|
+
The approved spec is frozen. Never change it during builder, verifier, or simplifier execution.
|
|
97
97
|
Use any available tool for these permitted edits. Do not change other workflow artifacts.
|
|
98
98
|
|
|
99
99
|
Only in those same circumstances, run tests and checks to answer specification questions.
|
|
@@ -128,7 +128,14 @@ The verifier restores only its temporary changes and removes only its own tempor
|
|
|
128
128
|
Each completed pass creates the next numbered file: handoffs/builder/B1.json, B2.json or handoffs/verifier/V1.json, V2.json.
|
|
129
129
|
Earlier handoffs remain on the file system. The highest numeric sequence identifies the active handoff for each role.
|
|
130
130
|
Use earlier handoffs only as references. Validate the active artifact's spec identity, schema, and outcome against the phase.
|
|
131
|
-
Read the returned verifier handoff. If there are findings, follow finding-decisions. If there are none, follow
|
|
131
|
+
Read the returned verifier handoff. If there are findings, follow finding-decisions. If there are none, follow simplifier-running.
|
|
132
|
+
|
|
133
|
+
In simplifier-running, call maestro_run_simplifier next with the active specId, after either successful verification or rejection of all findings.
|
|
134
|
+
Do not ask for another approval or run the verifier again. Both routes enter this phase regardless of Git availability.
|
|
135
|
+
Only maestro_run_simplifier checks Git readiness immediately before delegation. Do not check Git in activation or either entry route.
|
|
136
|
+
If Git is unavailable, the tool warns and skips simplification, saves candidate-ready, and creates no simplifier handoff. Report simplification as skipped.
|
|
137
|
+
Otherwise the simplifier records handoffs/simplifier/S.json before candidate-ready. This fixed result is unnumbered.
|
|
138
|
+
Simplifier execution or protocol errors stop the workflow under the same error policy as other runs.
|
|
132
139
|
|
|
133
140
|
### Explain findings and escalations
|
|
134
141
|
|
|
@@ -168,7 +175,7 @@ Submit the complete batch through maestro_resolve_findings before editing the sp
|
|
|
168
175
|
The tool saves every decision in the active verifier handoff before saving the next phase. Earlier numbered handoffs remain unchanged.
|
|
169
176
|
If any decision is revise-spec, the tool returns drafting-spec without approving the spec or running an agent.
|
|
170
177
|
Revision takes priority over fix-code and reject in mixed batches, while all decisions are saved.
|
|
171
|
-
Without revision, all rejections return
|
|
178
|
+
Without revision, all rejections return simplifier-running and any fix-code returns ready-for-builder.
|
|
172
179
|
For ready-for-builder, call maestro_run_builder separately. Do not fix the code yourself.
|
|
173
180
|
|
|
174
181
|
For either an escalation revision or a finding revision, first record the complete batch and enter drafting-spec as described above.
|
|
@@ -193,6 +200,9 @@ The owner handles execution errors, protocol errors, and interrupted workflows m
|
|
|
193
200
|
|
|
194
201
|
At candidate-ready, the workflow is complete. No final tool call is required.
|
|
195
202
|
You own the final summary. Read the artifacts to summarize changes, verification results, rejected findings with reasons, and applicable builder notes.
|
|
203
|
+
For completed simplification, read handoffs/simplifier/S.json and report its saved summary and notes as the simplifier's report.
|
|
204
|
+
For example, summary "Simplified counting" and note "Updated the caller to read the Map." belong to the simplifier, not verifier evidence.
|
|
205
|
+
Do not claim the verifier independently checked later refactoring. Keep verification evidence and the simplifier report separate.
|
|
196
206
|
Do not describe rejected findings as passed verification.
|
|
197
207
|
Do not change files, run verification again, or modify the concluded workflow to prepare this summary.
|
|
198
208
|
The owner controls later review and changes. Later changes are outside this verification.`;
|
|
@@ -18,6 +18,7 @@ export const WORKFLOW_PHASE_LABELS = {
|
|
|
18
18
|
[WORKFLOW_PHASES.READY_FOR_VERIFIER]: 'Ready for verifier',
|
|
19
19
|
[WORKFLOW_PHASES.VERIFIER_RUNNING]: 'Verifier running',
|
|
20
20
|
[WORKFLOW_PHASES.FINDING_DECISIONS]: 'Owner decision needed: findings',
|
|
21
|
+
[WORKFLOW_PHASES.SIMPLIFIER_RUNNING]: 'Simplifier running',
|
|
21
22
|
[WORKFLOW_PHASES.CANDIDATE_READY]: 'Completed',
|
|
22
23
|
} as const;
|
|
23
24
|
|
|
@@ -29,6 +30,7 @@ export const WORKFLOW_PHASE_ICONS = {
|
|
|
29
30
|
[WORKFLOW_PHASES.READY_FOR_VERIFIER]: '📋',
|
|
30
31
|
[WORKFLOW_PHASES.VERIFIER_RUNNING]: '🔍',
|
|
31
32
|
[WORKFLOW_PHASES.FINDING_DECISIONS]: '💬',
|
|
33
|
+
[WORKFLOW_PHASES.SIMPLIFIER_RUNNING]: '🧹',
|
|
32
34
|
[WORKFLOW_PHASES.CANDIDATE_READY]: '✅',
|
|
33
35
|
} as const;
|
|
34
36
|
|
|
@@ -40,6 +42,7 @@ export const WORKFLOW_PHASE_COLORS = {
|
|
|
40
42
|
[WORKFLOW_PHASES.READY_FOR_VERIFIER]: 'accent',
|
|
41
43
|
[WORKFLOW_PHASES.VERIFIER_RUNNING]: 'accent',
|
|
42
44
|
[WORKFLOW_PHASES.FINDING_DECISIONS]: 'warning',
|
|
45
|
+
[WORKFLOW_PHASES.SIMPLIFIER_RUNNING]: 'accent',
|
|
43
46
|
[WORKFLOW_PHASES.CANDIDATE_READY]: 'success',
|
|
44
47
|
} as const satisfies Record<WorkflowPhase, ThemeColor>;
|
|
45
48
|
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Objective: Register the simplifier's fixed result recording tool.
|
|
3
|
+
* Used: When a child simplifier completes its refactoring pass.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { ExtensionAPI } from '@earendil-works/pi-coding-agent';
|
|
7
|
+
import { type Static, Type } from 'typebox';
|
|
8
|
+
import { Value } from 'typebox/value';
|
|
9
|
+
import {
|
|
10
|
+
SIMPLIFIER_HANDOFF_VERSION,
|
|
11
|
+
SimplifierSubmissionFields,
|
|
12
|
+
} from '#artifacts/simplifier-handoff/schema.ts';
|
|
13
|
+
import { resolveWorkflowContext } from '#tools/child/utils/resolveWorkflowContext.ts';
|
|
14
|
+
import { completeSimplifierPass } from '#workflow/simplifier/completeSimplifierPass.ts';
|
|
15
|
+
|
|
16
|
+
export const SIMPLIFIER_HANDOFF_TOOL = {
|
|
17
|
+
NAME: 'maestro_record_simplifier_handoff',
|
|
18
|
+
LABEL: 'Record Simplifier Handoff',
|
|
19
|
+
DESCRIPTION:
|
|
20
|
+
'Save the simplifier result at handoffs/simplifier/S.json and complete the workflow. Stop after success.',
|
|
21
|
+
} as const;
|
|
22
|
+
|
|
23
|
+
const SimplifierHandoffToolParameters = Type.Object(
|
|
24
|
+
SimplifierSubmissionFields,
|
|
25
|
+
{ additionalProperties: false },
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
function assertSimplifierSubmission(
|
|
29
|
+
input: unknown,
|
|
30
|
+
): asserts input is Static<typeof SimplifierHandoffToolParameters> {
|
|
31
|
+
const [error] = Value.Errors(SimplifierHandoffToolParameters, input);
|
|
32
|
+
|
|
33
|
+
if (error !== undefined) {
|
|
34
|
+
throw new Error(`Invalid simplifier submission: ${error.message}.`);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export const registerRecordSimplifierHandoffTool = (pi: ExtensionAPI): void => {
|
|
39
|
+
pi.registerTool({
|
|
40
|
+
name: SIMPLIFIER_HANDOFF_TOOL.NAME,
|
|
41
|
+
label: SIMPLIFIER_HANDOFF_TOOL.LABEL,
|
|
42
|
+
description: SIMPLIFIER_HANDOFF_TOOL.DESCRIPTION,
|
|
43
|
+
parameters: SimplifierHandoffToolParameters,
|
|
44
|
+
async execute(_toolCallId, params, _signal, _onUpdate, context) {
|
|
45
|
+
assertSimplifierSubmission(params);
|
|
46
|
+
|
|
47
|
+
const { paths } = await resolveWorkflowContext({
|
|
48
|
+
cwd: context.cwd,
|
|
49
|
+
specId: params.specId,
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
const completed = await completeSimplifierPass({
|
|
53
|
+
paths,
|
|
54
|
+
specId: params.specId,
|
|
55
|
+
handoff: { ...params, version: SIMPLIFIER_HANDOFF_VERSION },
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
return {
|
|
59
|
+
content: [
|
|
60
|
+
{
|
|
61
|
+
type: 'text',
|
|
62
|
+
text: 'Simplifier handoff and candidate-ready phase saved. Stop now.',
|
|
63
|
+
},
|
|
64
|
+
],
|
|
65
|
+
details: {
|
|
66
|
+
specId: params.specId,
|
|
67
|
+
handoffPath: completed.handoffPath,
|
|
68
|
+
phase: completed.state.phase,
|
|
69
|
+
},
|
|
70
|
+
};
|
|
71
|
+
},
|
|
72
|
+
});
|
|
73
|
+
};
|
|
@@ -97,7 +97,7 @@ const formatResolution = (details: FindingResolutionDetails): string => {
|
|
|
97
97
|
return `${summary} The workflow is ready-for-builder. Run the builder separately.`;
|
|
98
98
|
}
|
|
99
99
|
|
|
100
|
-
return `${summary} The workflow is
|
|
100
|
+
return `${summary} The workflow is simplifier-running. Call maestro_run_simplifier next with this specId, without another approval or verifier pass.`;
|
|
101
101
|
};
|
|
102
102
|
|
|
103
103
|
export const registerResolveFindingsTool = (pi: ExtensionAPI): void => {
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Objective: Run the simplifier in Git projects or explicitly skip unavailable Git.
|
|
3
|
+
* Used: After verification succeeds or the owner rejects all findings.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { randomUUID } from 'node:crypto';
|
|
7
|
+
import type { ExtensionAPI } from '@earendil-works/pi-coding-agent';
|
|
8
|
+
import type { SubagentDelegationRequest } from 'pi-subagents/delegation';
|
|
9
|
+
import { Type } from 'typebox';
|
|
10
|
+
import { readSimplifierHandoff } from '#artifacts/simplifier-handoff/readSimplifierHandoff.ts';
|
|
11
|
+
import { AGENTS } from '#config/schema.ts';
|
|
12
|
+
import { SPEC_ID_PATTERN } from '#ids/isValidSpecId.ts';
|
|
13
|
+
import type { MaestroPaths } from '#MaestroPaths.ts';
|
|
14
|
+
import { refreshMaestroStatus } from '#maestro/status/refreshMaestroStatus.ts';
|
|
15
|
+
import {
|
|
16
|
+
assertDelegationResponse,
|
|
17
|
+
waitForDelegationResponse,
|
|
18
|
+
} from '#tools/utils/pi-subagent-delegation.ts';
|
|
19
|
+
import { resolveToolRunContext } from '#tools/utils/resolveToolRunContext.ts';
|
|
20
|
+
import { WORKFLOW_ROLES } from '#workflow/roles.ts';
|
|
21
|
+
import { readWorkflowState } from '#workflow/state/readWorkflowState.ts';
|
|
22
|
+
import { WORKFLOW_EVENTS, WORKFLOW_PHASES } from '#workflow/state/schema.ts';
|
|
23
|
+
import { writeWorkflowState } from '#workflow/state/writeWorkflowState.ts';
|
|
24
|
+
import { transitionWorkflow } from '#workflow/transitions.ts';
|
|
25
|
+
|
|
26
|
+
export const RUN_SIMPLIFIER_TOOL = {
|
|
27
|
+
NAME: 'maestro_run_simplifier',
|
|
28
|
+
LABEL: 'Run Simplifier',
|
|
29
|
+
DESCRIPTION:
|
|
30
|
+
'Run the simplifier in the foreground from simplifier-running. If Git readiness cannot be confirmed, warn and complete without delegation.',
|
|
31
|
+
} as const;
|
|
32
|
+
|
|
33
|
+
const RunSimplifierToolParameters = Type.Object(
|
|
34
|
+
{ specId: Type.String({ pattern: SPEC_ID_PATTERN.source }) },
|
|
35
|
+
{ additionalProperties: false },
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
const MILLISECONDS_PER_MINUTE = 60_000;
|
|
39
|
+
|
|
40
|
+
const GIT_SKIP_WARNING = 'Simplifier skipped: Git repository unavailable.';
|
|
41
|
+
|
|
42
|
+
type GitReadinessInput = {
|
|
43
|
+
pi: ExtensionAPI;
|
|
44
|
+
projectRoot: string;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const isGitReady = async ({
|
|
48
|
+
pi,
|
|
49
|
+
projectRoot,
|
|
50
|
+
}: GitReadinessInput): Promise<boolean> => {
|
|
51
|
+
try {
|
|
52
|
+
const result = await pi.exec(
|
|
53
|
+
'git',
|
|
54
|
+
['rev-parse', '--is-inside-work-tree'],
|
|
55
|
+
{ cwd: projectRoot },
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
return result.code === 0 && result.stdout.trim() === 'true';
|
|
59
|
+
} catch {
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
type ReadSimplifierResultInput = {
|
|
65
|
+
paths: MaestroPaths;
|
|
66
|
+
specId: string;
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
const readSimplifierResult = async ({
|
|
70
|
+
paths,
|
|
71
|
+
specId,
|
|
72
|
+
}: ReadSimplifierResultInput) => {
|
|
73
|
+
try {
|
|
74
|
+
const state = await readWorkflowState(paths.getWorkflowPath(specId));
|
|
75
|
+
|
|
76
|
+
if (state.specId !== specId) {
|
|
77
|
+
throw new Error(
|
|
78
|
+
`Workflow spec ID mismatch: expected "${specId}", found "${state.specId}".`,
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (state.phase !== WORKFLOW_PHASES.CANDIDATE_READY) {
|
|
83
|
+
throw new Error(
|
|
84
|
+
`The simplifier returned without recording a valid terminal handoff. Workflow phase is "${state.phase}".`,
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const handoff = await readSimplifierHandoff({
|
|
89
|
+
path: paths.getSimplifierHandoffPath(specId),
|
|
90
|
+
specId,
|
|
91
|
+
projectRoot: paths.getProjectRoot(),
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
return { specId, phase: state.phase, outcome: state.phase, handoff };
|
|
95
|
+
} catch (error) {
|
|
96
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
97
|
+
throw new Error(`Simplifier protocol error: ${message}`, { cause: error });
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
export const registerRunSimplifierTool = (pi: ExtensionAPI): void => {
|
|
102
|
+
pi.registerTool({
|
|
103
|
+
name: RUN_SIMPLIFIER_TOOL.NAME,
|
|
104
|
+
label: RUN_SIMPLIFIER_TOOL.LABEL,
|
|
105
|
+
description: RUN_SIMPLIFIER_TOOL.DESCRIPTION,
|
|
106
|
+
parameters: RunSimplifierToolParameters,
|
|
107
|
+
async execute(toolCallId, { specId }, _signal, _onUpdate, context) {
|
|
108
|
+
const { paths, config, projectRoot } = await resolveToolRunContext(
|
|
109
|
+
context.cwd,
|
|
110
|
+
);
|
|
111
|
+
|
|
112
|
+
const workflowPath = paths.getWorkflowPath(specId);
|
|
113
|
+
const state = await readWorkflowState(workflowPath);
|
|
114
|
+
|
|
115
|
+
if (state.specId !== specId) {
|
|
116
|
+
throw new Error(
|
|
117
|
+
`Workflow spec ID mismatch: expected "${specId}", found "${state.specId}".`,
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (state.phase !== WORKFLOW_PHASES.SIMPLIFIER_RUNNING) {
|
|
122
|
+
throw new Error(
|
|
123
|
+
`Simplifier run requires simplifier-running state, found "${state.phase}".`,
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
await refreshMaestroStatus(context);
|
|
128
|
+
|
|
129
|
+
if (!(await isGitReady({ pi, projectRoot }))) {
|
|
130
|
+
const nextState = transitionWorkflow({
|
|
131
|
+
state,
|
|
132
|
+
event: WORKFLOW_EVENTS.SIMPLIFIER_SKIPPED,
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
await writeWorkflowState({ path: workflowPath, state: nextState });
|
|
136
|
+
context.ui.notify(GIT_SKIP_WARNING, 'warning');
|
|
137
|
+
|
|
138
|
+
return {
|
|
139
|
+
content: [{ type: 'text', text: GIT_SKIP_WARNING }],
|
|
140
|
+
details: {
|
|
141
|
+
specId,
|
|
142
|
+
phase: nextState.phase,
|
|
143
|
+
skipped: true,
|
|
144
|
+
warning: GIT_SKIP_WARNING,
|
|
145
|
+
},
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const request: SubagentDelegationRequest = {
|
|
150
|
+
requestId: randomUUID(),
|
|
151
|
+
ownerRunId: toolCallId,
|
|
152
|
+
nodeId: WORKFLOW_ROLES.SIMPLIFIER,
|
|
153
|
+
agent: AGENTS.SIMPLIFIER,
|
|
154
|
+
task: `Simplify accepted code for specId "${specId}" in the project directory "${projectRoot}". Discover all current staged, unstaged, and untracked code changes with Git, including pre-existing changes.`,
|
|
155
|
+
context: 'fresh',
|
|
156
|
+
cwd: projectRoot,
|
|
157
|
+
model: config.simplifier.model,
|
|
158
|
+
thinking: config.simplifier.thinking,
|
|
159
|
+
timeoutMs: config.simplifier.timeoutMinutes * MILLISECONDS_PER_MINUTE,
|
|
160
|
+
result: { kind: 'text' },
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
const response = await waitForDelegationResponse({
|
|
164
|
+
piEventsBus: pi.events,
|
|
165
|
+
request,
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
assertDelegationResponse(response);
|
|
169
|
+
const result = await readSimplifierResult({ paths, specId });
|
|
170
|
+
|
|
171
|
+
return {
|
|
172
|
+
content: [
|
|
173
|
+
{
|
|
174
|
+
type: 'text',
|
|
175
|
+
text: `Simplifier completed spec ${specId}. The workflow is candidate-ready. Report the saved simplifier summary and notes as its report, not verifier evidence.`,
|
|
176
|
+
},
|
|
177
|
+
],
|
|
178
|
+
details: result,
|
|
179
|
+
};
|
|
180
|
+
},
|
|
181
|
+
});
|
|
182
|
+
};
|