@emiliosp/pi-maestro 0.10.1 → 0.12.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 +94 -18
- package/CHANGELOG.md +87 -33
- package/MISSION.md +4 -4
- package/README.md +66 -22
- package/ROADMAP.md +141 -112
- package/TECH_STACK.md +12 -11
- package/agents/builder.md +53 -57
- package/agents/simplifier.md +232 -0
- package/agents/verifier.md +52 -79
- package/docs/configuration.md +27 -16
- package/docs/glossary.md +47 -39
- package/docs/subagent-integration.md +22 -13
- package/docs/workflow.md +207 -103
- package/extensions/maestro-subagent.ts +4 -1
- package/extensions/maestro.ts +15 -26
- package/package.json +38 -39
- package/src/MaestroPaths.ts +26 -35
- package/src/artifacts/builder-handoff/assertBuilderHandoff.ts +6 -15
- package/src/artifacts/builder-handoff/schema.ts +3 -6
- 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 +27 -0
- package/src/artifacts/verifier-handoff/assertVerifierHandoff.ts +4 -10
- package/src/artifacts/verifier-handoff/schema.ts +3 -6
- package/src/artifacts/verifier-handoff/writeVerifierHandoff.ts +1 -3
- package/src/config/assertConfiguration.ts +4 -7
- package/src/config/defaults.ts +8 -5
- package/src/config/loadConfiguration.ts +5 -11
- package/src/config/schema.ts +4 -2
- package/src/ids/createSpecId.ts +1 -2
- package/src/ids/isValidSpecId.ts +1 -2
- package/src/maestro/checks/assertEnvironment.ts +21 -48
- package/src/maestro/instructions/getMaestroInstructions.ts +59 -30
- package/src/maestro/status/formatMaestroStatus.ts +7 -13
- package/src/maestro/status/refreshMaestroStatus.ts +3 -7
- package/src/specs/create.ts +14 -4
- package/src/specs/template.ts +10 -14
- package/src/tools/child/record-builder-handoff.ts +1 -0
- package/src/tools/child/record-simplifier-handoff.ts +73 -0
- package/src/tools/child/record-verifier-handoff.ts +2 -3
- package/src/tools/child/utils/resolveWorkflowContext.ts +1 -4
- package/src/tools/main/create-spec.ts +3 -2
- package/src/tools/main/mark-spec-ready.ts +2 -1
- package/src/tools/main/resolve-escalations.ts +1 -0
- package/src/tools/main/resolve-findings.ts +7 -21
- package/src/tools/main/run-builder.ts +37 -57
- package/src/tools/main/run-simplifier.ts +172 -0
- package/src/tools/main/run-verifier.ts +34 -58
- package/src/tools/utils/pi-subagent-delegation.ts +13 -22
- package/src/tools/utils/resolveToolRunContext.ts +2 -3
- package/src/utils/is-errno-exception.ts +1 -3
- package/src/utils/path-exists.ts +1 -0
- package/src/utils/path-strictly-within.ts +3 -6
- package/src/utils/path-within-or-equal.ts +1 -4
- package/src/utils/read-json.ts +1 -4
- package/src/utils/write-json.ts +1 -4
- package/src/workflow/builder/completeBuilderPass.ts +3 -2
- package/src/workflow/builder/prepareBuilderRun.ts +3 -8
- package/src/workflow/builder/resolveEscalations.ts +2 -6
- package/src/workflow/builder/utils/getWorkflowEventByBuilderHandoff.ts +1 -3
- package/src/workflow/roles.ts +2 -1
- package/src/workflow/simplifier/completeSimplifierPass.ts +56 -0
- package/src/workflow/spec/markSpecReady.ts +10 -4
- package/src/workflow/state/readWorkflowState.ts +2 -7
- package/src/workflow/state/schema.ts +9 -10
- package/src/workflow/state/writeWorkflowState.ts +1 -4
- package/src/workflow/transitions.ts +12 -12
- package/src/workflow/utils/assertSpecAcceptanceCriteria.ts +2 -1
- package/src/workflow/utils/getAgentRole.ts +6 -4
- package/src/workflow/verifier/completeVerifierPass.ts +1 -0
- package/src/workflow/verifier/resolveFindings.ts +9 -29
- package/src/workflow/verifier/utils/getWorkflowEventByVerifierHandoff.ts +5 -20
- package/templates/design.md +20 -0
- package/templates/proposal.md +23 -0
- package/templates/spec.md +21 -50
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 = {
|
|
@@ -20,8 +21,7 @@ export const THINKING_LEVELS = {
|
|
|
20
21
|
MAX: 'max',
|
|
21
22
|
} as const;
|
|
22
23
|
|
|
23
|
-
export type ThinkingLevel =
|
|
24
|
-
(typeof THINKING_LEVELS)[keyof typeof THINKING_LEVELS];
|
|
24
|
+
export type ThinkingLevel = (typeof THINKING_LEVELS)[keyof typeof THINKING_LEVELS];
|
|
25
25
|
|
|
26
26
|
export const MODEL_PATTERN = '^[^/\\s]+/[^/\\s]+$';
|
|
27
27
|
|
|
@@ -61,6 +61,7 @@ export const MaestroConfigInputSchema = Type.Object(
|
|
|
61
61
|
specDirectory: Type.Optional(Type.String({ minLength: 1 })),
|
|
62
62
|
builder: Type.Optional(PartialAgentConfigSchema),
|
|
63
63
|
verifier: Type.Optional(PartialAgentConfigSchema),
|
|
64
|
+
simplifier: Type.Optional(PartialAgentConfigSchema),
|
|
64
65
|
},
|
|
65
66
|
{ additionalProperties: false },
|
|
66
67
|
);
|
|
@@ -83,6 +84,7 @@ export const ResolvedMaestroConfigSchema = Type.Object(
|
|
|
83
84
|
specDirectory: Type.String({ minLength: 1 }),
|
|
84
85
|
builder: ResolvedAgentConfigSchema,
|
|
85
86
|
verifier: ResolvedAgentConfigSchema,
|
|
87
|
+
simplifier: ResolvedAgentConfigSchema,
|
|
86
88
|
},
|
|
87
89
|
{ additionalProperties: false },
|
|
88
90
|
);
|
package/src/ids/createSpecId.ts
CHANGED
|
@@ -41,5 +41,4 @@ type CreateSpecIdInput = {
|
|
|
41
41
|
export const createSpecId = ({
|
|
42
42
|
title,
|
|
43
43
|
instant = Temporal.Now.instant(),
|
|
44
|
-
}: CreateSpecIdInput): string =>
|
|
45
|
-
`${formatUtcTimestamp(instant)}-${normalizeSlug(title)}`;
|
|
44
|
+
}: CreateSpecIdInput): string => `${formatUtcTimestamp(instant)}-${normalizeSlug(title)}`;
|
package/src/ids/isValidSpecId.ts
CHANGED
|
@@ -12,8 +12,7 @@ const isValidUtcTimestamp = (value: string): boolean => {
|
|
|
12
12
|
return false;
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
const [, yearText, monthText, dayText, hourText, minuteText, secondText] =
|
|
16
|
-
match;
|
|
15
|
+
const [, yearText, monthText, dayText, hourText, minuteText, secondText] = match;
|
|
17
16
|
|
|
18
17
|
try {
|
|
19
18
|
Temporal.PlainDateTime.from(
|
|
@@ -5,11 +5,10 @@
|
|
|
5
5
|
|
|
6
6
|
import { randomUUID } from 'node:crypto';
|
|
7
7
|
import { realpath } from 'node:fs/promises';
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
ExtensionContext,
|
|
11
|
-
} from '@earendil-works/pi-coding-agent';
|
|
8
|
+
|
|
9
|
+
import type { ExtensionAPI, ExtensionContext } from '@earendil-works/pi-coding-agent';
|
|
12
10
|
import { resolveSubagentLaunchContract } from 'pi-subagents/preflight';
|
|
11
|
+
|
|
13
12
|
import { loadConfiguration } from '#config/loadConfiguration.ts';
|
|
14
13
|
import { AGENTS } from '#config/schema.ts';
|
|
15
14
|
|
|
@@ -19,8 +18,7 @@ export const SUBAGENT_PING = {
|
|
|
19
18
|
VERSION: 1,
|
|
20
19
|
METHOD: 'ping',
|
|
21
20
|
TIMEOUT_MS: 2000,
|
|
22
|
-
INSTALLATION_ERROR:
|
|
23
|
-
'Maestro flow requires pi-subagents installed in the Pi environment.',
|
|
21
|
+
INSTALLATION_ERROR: 'Maestro flow requires pi-subagents installed in the Pi environment.',
|
|
24
22
|
} as const;
|
|
25
23
|
|
|
26
24
|
const getErrorMessage = (error: unknown): string =>
|
|
@@ -46,9 +44,7 @@ const getActivationConfiguration = async (
|
|
|
46
44
|
try {
|
|
47
45
|
return await loadConfiguration(projectRoot);
|
|
48
46
|
} catch (error) {
|
|
49
|
-
throw new Error(
|
|
50
|
-
`Maestro configuration check failed: ${getErrorMessage(error)}`,
|
|
51
|
-
);
|
|
47
|
+
throw new Error(`Maestro configuration check failed: ${getErrorMessage(error)}`);
|
|
52
48
|
}
|
|
53
49
|
};
|
|
54
50
|
|
|
@@ -57,27 +53,20 @@ type AssertModelsAvailableInput = {
|
|
|
57
53
|
config: Awaited<ReturnType<typeof loadConfiguration>>;
|
|
58
54
|
};
|
|
59
55
|
|
|
60
|
-
function assertModelsAvailable({
|
|
61
|
-
context,
|
|
62
|
-
config,
|
|
63
|
-
}: AssertModelsAvailableInput): void {
|
|
56
|
+
function assertModelsAvailable({ context, config }: AssertModelsAvailableInput): void {
|
|
64
57
|
const availableModels = context.modelRegistry.getAvailable();
|
|
65
58
|
|
|
66
|
-
for (const model of [config.builder.model, config.verifier.model]) {
|
|
59
|
+
for (const model of [config.builder.model, config.verifier.model, config.simplifier.model]) {
|
|
67
60
|
const [provider, id] = model.split('/');
|
|
68
61
|
|
|
69
|
-
if (
|
|
70
|
-
!availableModels.some(
|
|
71
|
-
(entry) => entry.provider === provider && entry.id === id,
|
|
72
|
-
)
|
|
73
|
-
) {
|
|
62
|
+
if (!availableModels.some((entry) => entry.provider === provider && entry.id === id)) {
|
|
74
63
|
throw new Error(`Configured model is not available: "${model}".`);
|
|
75
64
|
}
|
|
76
65
|
}
|
|
77
66
|
}
|
|
78
67
|
|
|
79
68
|
async function assertAgentsAvailable(projectRoot: string): Promise<void> {
|
|
80
|
-
for (const agent of [AGENTS.BUILDER, AGENTS.VERIFIER]) {
|
|
69
|
+
for (const agent of [AGENTS.BUILDER, AGENTS.VERIFIER, AGENTS.SIMPLIFIER]) {
|
|
81
70
|
const result = await resolveSubagentLaunchContract({
|
|
82
71
|
agent,
|
|
83
72
|
cwd: projectRoot,
|
|
@@ -98,17 +87,7 @@ const isReplyWithRequestId = (
|
|
|
98
87
|
): reply is Record<string, unknown> & { requestId: string } =>
|
|
99
88
|
isRecord(reply) && typeof reply.requestId === 'string';
|
|
100
89
|
|
|
101
|
-
|
|
102
|
-
version: typeof SUBAGENT_PING.VERSION;
|
|
103
|
-
requestId: string;
|
|
104
|
-
method: typeof SUBAGENT_PING.METHOD;
|
|
105
|
-
success: true;
|
|
106
|
-
data: { version: typeof SUBAGENT_PING.VERSION };
|
|
107
|
-
};
|
|
108
|
-
|
|
109
|
-
function assertSuccessfulPingReply(
|
|
110
|
-
reply: unknown,
|
|
111
|
-
): asserts reply is SuccessfulPingReply {
|
|
90
|
+
function assertSuccessfulPingReply(reply: unknown): void {
|
|
112
91
|
if (
|
|
113
92
|
!isReplyWithRequestId(reply) ||
|
|
114
93
|
reply.version !== SUBAGENT_PING.VERSION ||
|
|
@@ -121,22 +100,17 @@ function assertSuccessfulPingReply(
|
|
|
121
100
|
}
|
|
122
101
|
}
|
|
123
102
|
|
|
124
|
-
async function assertSubagentsRespond(
|
|
125
|
-
piEventsBus: ExtensionAPI['events'],
|
|
126
|
-
): Promise<void> {
|
|
103
|
+
async function assertSubagentsRespond(piEventsBus: ExtensionAPI['events']): Promise<void> {
|
|
127
104
|
const requestId = randomUUID();
|
|
128
105
|
const { promise, resolve, reject } = Promise.withResolvers<unknown>();
|
|
129
106
|
|
|
130
|
-
const unsubscribe = piEventsBus.on(
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
return;
|
|
135
|
-
}
|
|
107
|
+
const unsubscribe = piEventsBus.on(`${SUBAGENT_PING.REPLY_EVENT_PREFIX}${requestId}`, (reply) => {
|
|
108
|
+
if (isReplyWithRequestId(reply) && reply.requestId !== requestId) {
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
136
111
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
);
|
|
112
|
+
resolve(reply);
|
|
113
|
+
});
|
|
140
114
|
|
|
141
115
|
const timeout = setTimeout(
|
|
142
116
|
() => reject(new Error('Pi-subagents ping timed out after 2000 ms.')),
|
|
@@ -171,7 +145,7 @@ export async function assertEnvironment({
|
|
|
171
145
|
await assertAgentsAvailable(projectRoot);
|
|
172
146
|
} catch (error) {
|
|
173
147
|
throw new Error(
|
|
174
|
-
`Builder or
|
|
148
|
+
`Builder, verifier, or simplifier agent check failed: ${getErrorMessage(error)}`,
|
|
175
149
|
);
|
|
176
150
|
}
|
|
177
151
|
|
|
@@ -182,9 +156,8 @@ export async function assertEnvironment({
|
|
|
182
156
|
try {
|
|
183
157
|
await assertSubagentsRespond(piEventsBus);
|
|
184
158
|
} catch (error) {
|
|
185
|
-
throw new Error(
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
);
|
|
159
|
+
throw new Error(`${SUBAGENT_PING.INSTALLATION_ERROR} ${getErrorMessage(error)}`, {
|
|
160
|
+
cause: error,
|
|
161
|
+
});
|
|
189
162
|
}
|
|
190
163
|
}
|
|
@@ -5,24 +5,25 @@
|
|
|
5
5
|
|
|
6
6
|
import { fileURLToPath } from 'node:url';
|
|
7
7
|
|
|
8
|
-
const README_PATH = fileURLToPath(
|
|
9
|
-
new URL('../../../README.md', import.meta.url),
|
|
10
|
-
);
|
|
8
|
+
const README_PATH = fileURLToPath(new URL('../../../README.md', import.meta.url));
|
|
11
9
|
|
|
12
10
|
const DOCS_PATH = fileURLToPath(new URL('../../../docs/', import.meta.url));
|
|
13
11
|
|
|
12
|
+
const TEMPLATES_PATH = fileURLToPath(new URL('../../../templates/', import.meta.url));
|
|
13
|
+
|
|
14
14
|
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
|
|
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 proposal.md, spec.md, and design.md when present form one frozen contract for all roles. Do not infer owner approval from an agent's recommendation.
|
|
20
20
|
|
|
21
21
|
### Maestro documentation
|
|
22
22
|
|
|
23
23
|
The loaded Maestro package's documentation is separate from the owner's project:
|
|
24
24
|
README.md: ${README_PATH}
|
|
25
25
|
docs/: ${DOCS_PATH}
|
|
26
|
+
templates/: ${TEMPLATES_PATH}
|
|
26
27
|
Before answering questions about Maestro, use the available file-reading tools to read the relevant documentation on demand.
|
|
27
28
|
Do not automatically load all documentation.
|
|
28
29
|
|
|
@@ -37,43 +38,61 @@ The spec, prototype, and experiment permissions below are the only exceptions fo
|
|
|
37
38
|
|
|
38
39
|
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
40
|
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
|
|
41
|
+
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
42
|
Wait for each tool result before taking the next workflow action. Do not launch parallel or background runs.
|
|
42
43
|
|
|
43
44
|
### Prepare and approve the spec
|
|
44
45
|
|
|
45
|
-
1. Call maestro_create_spec with the change title. Use the
|
|
46
|
+
1. Call maestro_create_spec with the change title. It creates proposal.md and spec.md from the bundled templates. Use the returned document paths. Create design.md from the installed templates/design.md only when significant technical decisions need to be recorded; do not create an empty design.
|
|
46
47
|
2. Read the repository and applicable AGENTS.md files. Investigate the affected behavior before asking the owner for missing information. Inspect existing tests before writing acceptance criteria.
|
|
47
|
-
3. Ask one focused question at a time. Wait for the answer, then update the
|
|
48
|
-
4. Record
|
|
49
|
-
5. Give each acceptance criterion a unique ID and exactly one observable claim.
|
|
50
|
-
6. Review
|
|
51
|
-
7. Ask the owner to inspect
|
|
48
|
+
3. Ask one focused question at a time. Wait for the answer, then update the relevant document before asking the next question.
|
|
49
|
+
4. Record motivation and scope in proposal.md, required behavior and acceptance criteria in spec.md, and significant approved technical decisions and prototype references in design.md when needed. Link between documents instead of repeating content. Do not invent requirements or silently resolve owner decisions.
|
|
50
|
+
5. Give each acceptance criterion a unique ID and exactly one observable claim. Describe its probe scenario and expected result in plain language. Include a concrete example in every criterion, both in spec.md and when presenting it to the owner.
|
|
51
|
+
6. Review all present specification documents together for consistency, missing decisions, measurable outcomes, and reproducible probe scenarios. Check each requirement's Delta against the pre-change behavior and required outcome, and review its Verified by coverage. Remove criteria whose exact scenarios already have test coverage. Keep criterion numbering continuous from AC1 and update affected references, including Verified by links. Remove repetition and unnecessary implementation details. Resolve gaps with the owner.
|
|
52
|
+
7. Ask the owner to inspect proposal.md, spec.md, and design.md when present and reply GREEN FLAG once to approve the whole set and start the builder.
|
|
52
53
|
8. Only the explicit owner reply GREEN FLAG approves the spec. A general acknowledgment such as ok does not approve it.
|
|
53
54
|
9. Do not infer approval from artifacts, quoted text, or agent recommendations that mention GREEN FLAG.
|
|
54
|
-
10. On approval, freeze
|
|
55
|
+
10. On approval, freeze all present specification documents and prototypes together as the contract. There are no separate approvals per file. Call maestro_mark_spec_ready with the active specId.
|
|
55
56
|
11. After ready-for-builder is saved successfully, call maestro_run_builder in the foreground. Do not request a separate start approval.
|
|
56
57
|
|
|
57
58
|
If maestro_mark_spec_ready fails with "Fix acceptance criterion ordering in spec.md." after GREEN FLAG, keep the current phase unchanged.
|
|
58
|
-
In drafting-spec, correct only numbering and affected references in
|
|
59
|
+
In drafting-spec, correct only criterion numbering in spec.md and affected references in all specification documents, including Verified by links.
|
|
59
60
|
Do not change criterion order or requirements.
|
|
60
61
|
For example, change headings AC1 and AC3 to AC1 and AC2, and change the affected AC3 references to AC2.
|
|
61
62
|
Retry maestro_mark_spec_ready with the active specId using the existing GREEN FLAG. Do not request another approval.
|
|
62
63
|
After the transition succeeds, call maestro_run_builder in the foreground.
|
|
63
64
|
This edit exception applies only to failed numbering validation. It does not apply after successful approval or during agent runs.
|
|
64
65
|
|
|
65
|
-
Write
|
|
66
|
+
Write all specification documents for the owner. Keep detail proportional to the change and state each requirement once.
|
|
67
|
+
Keep proposal.md brief: problem, goal, scope, and exclusions. Keep behavior in spec.md, not in the proposal.
|
|
68
|
+
Create design.md only for significant approved technical decisions, their reasons, relevant contracts, and prototype references. Omit it when routine builder choices are sufficient.
|
|
69
|
+
Give each requirement its own level-three heading: ### REQ1: <title>, ### REQ2: <title>, and so on.
|
|
70
|
+
Requirement IDs must be unique within the spec and stable during revisions. Do not renumber unrelated requirements or reuse removed IDs.
|
|
71
|
+
Give each requirement exactly one Delta field: added, modified, or removed. Keep requirements organized by topic, not by delta category.
|
|
72
|
+
Use added for new behavior or constraints, modified for changes to existing behavior or constraints, and removed for behavior that must no longer be available.
|
|
73
|
+
Document only requirements this change adds, modifies, or removes. Existing behavior must continue working unless explicitly changed or removed; do not list unchanged behavior as requirements.
|
|
74
|
+
Do not force all three categories into a spec. Split requirements that mix distinct delta categories.
|
|
75
|
+
For modified or removed requirements, add a brief Before description only when it helps explain the change. Verify the previous behavior from repository evidence; do not invent it or repeat the whole old requirement.
|
|
76
|
+
State the complete required outcome in the requirement body, not only a difference. For removed behavior, specify the required absence and any agreed replacement or error behavior so it remains verifiable.
|
|
77
|
+
During spec revisions, keep the comparison anchored to the same pre-change behavior, not the previous draft or the builder's partial implementation.
|
|
78
|
+
Delta classifies requirement changes, not acceptance criteria.
|
|
79
|
+
Place each requirement-associated acceptance criterion immediately below its primary requirement using a level-four heading, such as #### AC1: <title>.
|
|
80
|
+
A relevant regression criterion without an associated changed requirement may stand alone under an optional ## Regression checks section with a level-three ### AC<number>: <title> heading.
|
|
81
|
+
Give standalone regression criteria the same probe, expected result, and concrete example fields. Do not invent a REQ or Delta field for them, force a requirement association, or enumerate all existing behavior. Omit the section when unnecessary.
|
|
82
|
+
Define each AC only once. Other requirements may link to it. Keep AC numbering continuous from AC1 across both nested and standalone criteria, not restarted per requirement or section.
|
|
83
|
+
Under each requirement, add Verified by with Markdown links to the acceptance criteria that verify it, using their actual heading anchors.
|
|
84
|
+
For coverage provided by an existing test, link its file relative to spec.md and give the exact test scenario name instead of adding a duplicate criterion.
|
|
85
|
+
A requirement may link to multiple criteria, existing tests, or both; the same criterion may verify multiple requirements.
|
|
86
|
+
Keep this mapping only under requirements, not repeated inside criteria. During review, follow every link and check that the combined scenarios verify the whole requirement.
|
|
87
|
+
Resolve missing, partial, or uncertain coverage before requesting approval. Keep links current when criterion headings or test references change.
|
|
66
88
|
Use the template topics as guidance. Omit empty subsections instead of filling them with Not applicable.
|
|
67
|
-
Keep behavior,
|
|
89
|
+
Keep observable behavior, constraints, edge cases, and recovery requirements in spec.md. Keep approved technical decisions in design.md and scope in proposal.md. Leave routine implementation choices to the builder.
|
|
68
90
|
Keep probes as starting conditions and actions or observations, with measurable expected results.
|
|
69
|
-
|
|
70
|
-
Use unchanged when a criterion explicitly checks existing behavior that the change must preserve, even if the action is valid or invalid.
|
|
71
|
-
The coverage label describes the scenario, not the test outcome. A failure scenario passes when the invalid action is correctly rejected.
|
|
72
|
-
Use exactly one label per criterion. Do not force all three categories into a spec.
|
|
91
|
+
Cover relevant valid actions, invalid actions, and regressions directly through the probe and expected result.
|
|
73
92
|
Omit a criterion only when an existing test covers the same starting conditions, action, and expected result.
|
|
74
93
|
Similar tests do not count. Keep the criterion if coverage is partial or uncertain.
|
|
75
94
|
For example, a test that rejects an empty title does not cover a title containing only spaces.
|
|
76
|
-
|
|
95
|
+
For requirement-associated criteria, keep required behavior documented even when the criterion is omitted, and link the covering test from the requirement's Verified by field. Omit a standalone regression criterion when an existing test already covers its exact scenario without creating a requirement for it. These rules also apply during spec revisions.
|
|
77
96
|
Each example must show specific starting conditions, an input or action, and the exact observable result expected from that scenario.
|
|
78
97
|
Use concrete values or states, not a restatement of the claim. Keep examples within the criterion's scope and approved behavior.
|
|
79
98
|
Every probe remains mandatory for builder and verifier.
|
|
@@ -85,15 +104,15 @@ Trace the relevant code and data flow across affected components, including tran
|
|
|
85
104
|
Use repository evidence to explain each option's feasibility, required changes, scope, and effects on existing behavior.
|
|
86
105
|
Cite the relevant files. Distinguish confirmed facts from assumptions and state what you could not verify, including deployed state.
|
|
87
106
|
Do not ask the owner questions that repository inspection can answer. Keep requirement choices and technical decisions with the owner.
|
|
88
|
-
In
|
|
107
|
+
In design.md when needed, keep only approved decisions, brief reasons, essential references, and limits that affect them. Keep resulting behavior requirements in spec.md and motivation in proposal.md. Follow the check and experiment permissions below.
|
|
89
108
|
|
|
90
109
|
### Spec edits, checks, and experiments
|
|
91
110
|
|
|
92
|
-
|
|
111
|
+
Create, edit, or remove the active proposal.md, spec.md, optional design.md, and prototypes only in drafting-spec. proposal.md and spec.md must be present before approval.
|
|
93
112
|
For either revision origin, record the complete owner decision batch through the corresponding resolution tool before editing in drafting-spec.
|
|
94
113
|
The numbering-only recovery procedure above also permits spec.md corrections after failed numbering validation in drafting-spec.
|
|
95
|
-
Do not edit the
|
|
96
|
-
The approved
|
|
114
|
+
Do not edit the specification documents or prototypes in other phases. Do not create a design.md after approval.
|
|
115
|
+
The approved document set is frozen. Never change any of it during builder, verifier, or simplifier execution.
|
|
97
116
|
Use any available tool for these permitted edits. Do not change other workflow artifacts.
|
|
98
117
|
|
|
99
118
|
Only in those same circumstances, run tests and checks to answer specification questions.
|
|
@@ -107,7 +126,7 @@ Do not change workflow.json, handoffs, or other protected workflow artifacts.
|
|
|
107
126
|
Get explicit owner approval before installing packages or adding or updating dependencies.
|
|
108
127
|
Before requesting spec approval or continuing the workflow, restore only your experiment changes and remove your temporary files.
|
|
109
128
|
If cleanup fails, report the remaining changes and stop. Preserve all pre-existing files and content.
|
|
110
|
-
After cleanup, summarize only experiment conclusions and limits that affect the contract in spec.md. Experiments do not replace builder or verifier work.
|
|
129
|
+
After cleanup, summarize only experiment conclusions and limits that affect the contract: behavior in spec.md, technical decisions in design.md when needed. Experiments do not replace builder or verifier work.
|
|
111
130
|
|
|
112
131
|
### Run the workflow
|
|
113
132
|
|
|
@@ -128,7 +147,14 @@ The verifier restores only its temporary changes and removes only its own tempor
|
|
|
128
147
|
Each completed pass creates the next numbered file: handoffs/builder/B1.json, B2.json or handoffs/verifier/V1.json, V2.json.
|
|
129
148
|
Earlier handoffs remain on the file system. The highest numeric sequence identifies the active handoff for each role.
|
|
130
149
|
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
|
|
150
|
+
Read the returned verifier handoff. If there are findings, follow finding-decisions. If there are none, follow simplifier-running.
|
|
151
|
+
|
|
152
|
+
In simplifier-running, call maestro_run_simplifier next with the active specId, after either successful verification or rejection of all findings.
|
|
153
|
+
Do not ask for another approval or run the verifier again. Both routes enter this phase regardless of Git availability.
|
|
154
|
+
Only maestro_run_simplifier checks Git readiness immediately before delegation. Do not check Git in activation or either entry route.
|
|
155
|
+
If Git is unavailable, the tool warns and skips simplification, saves candidate-ready, and creates no simplifier handoff. Report simplification as skipped.
|
|
156
|
+
Otherwise the simplifier records handoffs/simplifier/S.json before candidate-ready. This fixed result is unnumbered.
|
|
157
|
+
Simplifier execution or protocol errors stop the workflow under the same error policy as other runs.
|
|
132
158
|
|
|
133
159
|
### Explain findings and escalations
|
|
134
160
|
|
|
@@ -168,13 +194,13 @@ Submit the complete batch through maestro_resolve_findings before editing the sp
|
|
|
168
194
|
The tool saves every decision in the active verifier handoff before saving the next phase. Earlier numbered handoffs remain unchanged.
|
|
169
195
|
If any decision is revise-spec, the tool returns drafting-spec without approving the spec or running an agent.
|
|
170
196
|
Revision takes priority over fix-code and reject in mixed batches, while all decisions are saved.
|
|
171
|
-
Without revision, all rejections return
|
|
197
|
+
Without revision, all rejections return simplifier-running and any fix-code returns ready-for-builder.
|
|
172
198
|
For ready-for-builder, call maestro_run_builder separately. Do not fix the code yourself.
|
|
173
199
|
|
|
174
200
|
For either an escalation revision or a finding revision, first record the complete batch and enter drafting-spec as described above.
|
|
175
|
-
Then revise the same specId with the owner.
|
|
201
|
+
Then revise the same specId and its affected documents with the owner. Recheck the complete document set together.
|
|
176
202
|
Do not create a new spec. Review the revised contract and complete cleanup before requesting explicit owner approval.
|
|
177
|
-
Ask the owner to inspect
|
|
203
|
+
Ask the owner to inspect proposal.md, spec.md, and design.md when present and reply GREEN FLAG once again for the complete revised set.
|
|
178
204
|
Only after that explicit reply, call maestro_mark_spec_ready separately. After the transition succeeds, start the builder in the foreground.
|
|
179
205
|
The approved revised spec controls subsequent work. Previous escalations and findings become historical context. Do not resolve them as current decisions after the revision.
|
|
180
206
|
Keep their artifacts. Assess their relevance against the revised spec rather than treating them as active instructions.
|
|
@@ -193,6 +219,9 @@ The owner handles execution errors, protocol errors, and interrupted workflows m
|
|
|
193
219
|
|
|
194
220
|
At candidate-ready, the workflow is complete. No final tool call is required.
|
|
195
221
|
You own the final summary. Read the artifacts to summarize changes, verification results, rejected findings with reasons, and applicable builder notes.
|
|
222
|
+
For completed simplification, read handoffs/simplifier/S.json and report its saved summary and notes as the simplifier's report.
|
|
223
|
+
For example, summary "Simplified counting" and note "Updated the caller to read the Map." belong to the simplifier, not verifier evidence.
|
|
224
|
+
Do not claim the verifier independently checked later refactoring. Keep verification evidence and the simplifier report separate.
|
|
196
225
|
Do not describe rejected findings as passed verification.
|
|
197
226
|
Do not change files, run verification again, or modify the concluded workflow to prepare this summary.
|
|
198
227
|
The owner controls later review and changes. Later changes are outside this verification.`;
|
|
@@ -4,11 +4,8 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import type { Theme, ThemeColor } from '@earendil-works/pi-coding-agent';
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
type WorkflowPhase,
|
|
10
|
-
type WorkflowState,
|
|
11
|
-
} from '#workflow/state/schema.ts';
|
|
7
|
+
|
|
8
|
+
import { WORKFLOW_PHASES, type WorkflowPhase, type WorkflowState } from '#workflow/state/schema.ts';
|
|
12
9
|
|
|
13
10
|
export const WORKFLOW_PHASE_LABELS = {
|
|
14
11
|
[WORKFLOW_PHASES.DRAFTING_SPEC]: 'Preparing specification',
|
|
@@ -18,6 +15,7 @@ export const WORKFLOW_PHASE_LABELS = {
|
|
|
18
15
|
[WORKFLOW_PHASES.READY_FOR_VERIFIER]: 'Ready for verifier',
|
|
19
16
|
[WORKFLOW_PHASES.VERIFIER_RUNNING]: 'Verifier running',
|
|
20
17
|
[WORKFLOW_PHASES.FINDING_DECISIONS]: 'Owner decision needed: findings',
|
|
18
|
+
[WORKFLOW_PHASES.SIMPLIFIER_RUNNING]: 'Simplifier running',
|
|
21
19
|
[WORKFLOW_PHASES.CANDIDATE_READY]: 'Completed',
|
|
22
20
|
} as const;
|
|
23
21
|
|
|
@@ -29,6 +27,7 @@ export const WORKFLOW_PHASE_ICONS = {
|
|
|
29
27
|
[WORKFLOW_PHASES.READY_FOR_VERIFIER]: '📋',
|
|
30
28
|
[WORKFLOW_PHASES.VERIFIER_RUNNING]: '🔍',
|
|
31
29
|
[WORKFLOW_PHASES.FINDING_DECISIONS]: '💬',
|
|
30
|
+
[WORKFLOW_PHASES.SIMPLIFIER_RUNNING]: '🧹',
|
|
32
31
|
[WORKFLOW_PHASES.CANDIDATE_READY]: '✅',
|
|
33
32
|
} as const;
|
|
34
33
|
|
|
@@ -40,6 +39,7 @@ export const WORKFLOW_PHASE_COLORS = {
|
|
|
40
39
|
[WORKFLOW_PHASES.READY_FOR_VERIFIER]: 'accent',
|
|
41
40
|
[WORKFLOW_PHASES.VERIFIER_RUNNING]: 'accent',
|
|
42
41
|
[WORKFLOW_PHASES.FINDING_DECISIONS]: 'warning',
|
|
42
|
+
[WORKFLOW_PHASES.SIMPLIFIER_RUNNING]: 'accent',
|
|
43
43
|
[WORKFLOW_PHASES.CANDIDATE_READY]: 'success',
|
|
44
44
|
} as const satisfies Record<WorkflowPhase, ThemeColor>;
|
|
45
45
|
|
|
@@ -79,15 +79,9 @@ export const formatMaestroStatus = ({
|
|
|
79
79
|
|
|
80
80
|
const { phase, specId } = workflow.state;
|
|
81
81
|
|
|
82
|
-
const prefix = theme.fg(
|
|
83
|
-
'muted',
|
|
84
|
-
`Maestro active · ${shortenSpecId(specId)} · `,
|
|
85
|
-
);
|
|
82
|
+
const prefix = theme.fg('muted', `Maestro active · ${shortenSpecId(specId)} · `);
|
|
86
83
|
|
|
87
|
-
const label = theme.fg(
|
|
88
|
-
WORKFLOW_PHASE_COLORS[phase],
|
|
89
|
-
WORKFLOW_PHASE_LABELS[phase],
|
|
90
|
-
);
|
|
84
|
+
const label = theme.fg(WORKFLOW_PHASE_COLORS[phase], WORKFLOW_PHASE_LABELS[phase]);
|
|
91
85
|
|
|
92
86
|
return `${prefix}${WORKFLOW_PHASE_ICONS[phase]} ${label}`;
|
|
93
87
|
};
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import type { ExtensionContext } from '@earendil-works/pi-coding-agent';
|
|
7
|
+
|
|
7
8
|
import maestroSessionState from '#maestro/session/MaestroSessionState.ts';
|
|
8
9
|
import { formatMaestroStatus } from '#maestro/status/formatMaestroStatus.ts';
|
|
9
10
|
import { resolveToolRunContext } from '#tools/utils/resolveToolRunContext.ts';
|
|
@@ -11,9 +12,7 @@ import { readWorkflowState } from '#workflow/state/readWorkflowState.ts';
|
|
|
11
12
|
|
|
12
13
|
export const MAESTRO_STATUS_KEY = 'maestro';
|
|
13
14
|
|
|
14
|
-
export const refreshMaestroStatus = async (
|
|
15
|
-
context: ExtensionContext,
|
|
16
|
-
): Promise<void> => {
|
|
15
|
+
export const refreshMaestroStatus = async (context: ExtensionContext): Promise<void> => {
|
|
17
16
|
if (!maestroSessionState.isActive()) {
|
|
18
17
|
context.ui.setStatus(MAESTRO_STATUS_KEY, undefined);
|
|
19
18
|
|
|
@@ -50,9 +49,6 @@ export const refreshMaestroStatus = async (
|
|
|
50
49
|
);
|
|
51
50
|
} catch (error) {
|
|
52
51
|
context.ui.setStatus(MAESTRO_STATUS_KEY, undefined);
|
|
53
|
-
context.ui.notify(
|
|
54
|
-
error instanceof Error ? error.message : String(error),
|
|
55
|
-
'error',
|
|
56
|
-
);
|
|
52
|
+
context.ui.notify(error instanceof Error ? error.message : String(error), 'error');
|
|
57
53
|
}
|
|
58
54
|
};
|
package/src/specs/create.ts
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Objective: Create a
|
|
2
|
+
* Objective: Create a proposal, behavioral spec, and initial workflow state.
|
|
3
3
|
* Used: When an owner starts a new Maestro workflow.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { mkdir, writeFile } from 'node:fs/promises';
|
|
7
|
+
|
|
7
8
|
import { createSpecId } from '#ids/createSpecId.ts';
|
|
8
9
|
import type { MaestroPaths } from '#MaestroPaths.ts';
|
|
9
|
-
import {
|
|
10
|
+
import { renderSpecTemplate } from '#specs/template.ts';
|
|
10
11
|
import {
|
|
11
12
|
assertWorkflowState,
|
|
12
13
|
WORKFLOW_PHASES,
|
|
@@ -18,7 +19,9 @@ import { writeWorkflowState } from '#workflow/state/writeWorkflowState.ts';
|
|
|
18
19
|
export type CreatedSpec = {
|
|
19
20
|
specId: string;
|
|
20
21
|
specPath: string;
|
|
22
|
+
proposalFilePath: string;
|
|
21
23
|
specFilePath: string;
|
|
24
|
+
designFilePath: string;
|
|
22
25
|
workflowPath: string;
|
|
23
26
|
prototypesPath: string;
|
|
24
27
|
state: WorkflowState;
|
|
@@ -43,12 +46,16 @@ export const createSpec = async ({
|
|
|
43
46
|
|
|
44
47
|
const specId = createSpecId({ title, instant });
|
|
45
48
|
const specPath = paths.getSpecPath(specId);
|
|
49
|
+
const proposalFilePath = paths.getProposalFilePath(specId);
|
|
46
50
|
const specFilePath = paths.getSpecFilePath(specId);
|
|
51
|
+
const designFilePath = paths.getDesignFilePath(specId);
|
|
47
52
|
const workflowPath = paths.getWorkflowPath(specId);
|
|
48
53
|
const prototypesPath = paths.getPrototypesPath(specId);
|
|
49
54
|
|
|
50
|
-
const
|
|
51
|
-
|
|
55
|
+
const [proposal, spec] = await Promise.all([
|
|
56
|
+
renderSpecTemplate({ document: 'proposal.md', specId, title }),
|
|
57
|
+
renderSpecTemplate({ document: 'spec.md', specId, title }),
|
|
58
|
+
]);
|
|
52
59
|
|
|
53
60
|
const state = {
|
|
54
61
|
version: WORKFLOW_STATE_VERSION,
|
|
@@ -63,6 +70,7 @@ export const createSpec = async ({
|
|
|
63
70
|
|
|
64
71
|
try {
|
|
65
72
|
await mkdir(prototypesPath, { recursive: true });
|
|
73
|
+
await writeFile(proposalFilePath, proposal, { encoding: 'utf8', flag: 'wx' });
|
|
66
74
|
await writeFile(specFilePath, spec, { encoding: 'utf8', flag: 'wx' });
|
|
67
75
|
await writeWorkflowState({
|
|
68
76
|
path: workflowPath,
|
|
@@ -78,7 +86,9 @@ export const createSpec = async ({
|
|
|
78
86
|
return {
|
|
79
87
|
specId,
|
|
80
88
|
specPath,
|
|
89
|
+
proposalFilePath,
|
|
81
90
|
specFilePath,
|
|
91
|
+
designFilePath,
|
|
82
92
|
workflowPath,
|
|
83
93
|
prototypesPath,
|
|
84
94
|
state,
|
package/src/specs/template.ts
CHANGED
|
@@ -1,26 +1,22 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Objective: Load and render
|
|
3
|
-
* Used: When Maestro creates a new
|
|
2
|
+
* Objective: Load and render a required specification document from its bundled template.
|
|
3
|
+
* Used: When Maestro creates the proposal and behavioral spec for a new change.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { readFile } from 'node:fs/promises';
|
|
7
7
|
|
|
8
|
-
const TEMPLATE_URL = new URL('../../templates/spec.md', import.meta.url);
|
|
9
|
-
|
|
10
|
-
export const loadSpecTemplate = async (): Promise<string> =>
|
|
11
|
-
readFile(TEMPLATE_URL, 'utf8');
|
|
12
|
-
|
|
13
8
|
type RenderSpecTemplateInput = {
|
|
14
|
-
|
|
9
|
+
document: 'proposal.md' | 'spec.md';
|
|
15
10
|
specId: string;
|
|
16
11
|
title: string;
|
|
17
12
|
};
|
|
18
13
|
|
|
19
|
-
export const renderSpecTemplate = ({
|
|
20
|
-
|
|
14
|
+
export const renderSpecTemplate = async ({
|
|
15
|
+
document,
|
|
21
16
|
specId,
|
|
22
17
|
title,
|
|
23
|
-
}: RenderSpecTemplateInput): string =>
|
|
24
|
-
template
|
|
25
|
-
|
|
26
|
-
|
|
18
|
+
}: RenderSpecTemplateInput): Promise<string> => {
|
|
19
|
+
const template = await readFile(new URL(`../../templates/${document}`, import.meta.url), 'utf8');
|
|
20
|
+
|
|
21
|
+
return template.replace('<id>', specId).replace('<short, outcome-oriented title>', title.trim());
|
|
22
|
+
};
|
|
@@ -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
|
+
|
|
10
|
+
import {
|
|
11
|
+
SIMPLIFIER_HANDOFF_VERSION,
|
|
12
|
+
SimplifierSubmissionFields,
|
|
13
|
+
} from '#artifacts/simplifier-handoff/schema.ts';
|
|
14
|
+
import { resolveWorkflowContext } from '#tools/child/utils/resolveWorkflowContext.ts';
|
|
15
|
+
import { completeSimplifierPass } from '#workflow/simplifier/completeSimplifierPass.ts';
|
|
16
|
+
|
|
17
|
+
export const SIMPLIFIER_HANDOFF_TOOL = {
|
|
18
|
+
NAME: 'maestro_record_simplifier_handoff',
|
|
19
|
+
LABEL: 'Record Simplifier Handoff',
|
|
20
|
+
DESCRIPTION:
|
|
21
|
+
'Save the simplifier result at handoffs/simplifier/S.json and complete the workflow. Stop after success.',
|
|
22
|
+
} as const;
|
|
23
|
+
|
|
24
|
+
const SimplifierHandoffToolParameters = Type.Object(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
|
+
};
|