@emiliosp/pi-maestro 0.11.0 → 0.13.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.
Files changed (74) hide show
  1. package/ADR.md +82 -23
  2. package/CHANGELOG.md +48 -28
  3. package/MISSION.md +2 -4
  4. package/README.md +55 -25
  5. package/TECH_STACK.md +3 -3
  6. package/agents/builder.md +53 -57
  7. package/agents/simplifier.md +79 -61
  8. package/agents/verifier.md +52 -64
  9. package/docs/configuration.md +1 -1
  10. package/docs/glossary.md +43 -40
  11. package/docs/subagent-integration.md +7 -8
  12. package/docs/workflow.md +137 -63
  13. package/extensions/maestro-subagent.ts +1 -0
  14. package/extensions/maestro.ts +60 -30
  15. package/package.json +38 -40
  16. package/src/MaestroPaths.ts +22 -35
  17. package/src/artifacts/builder-handoff/assertBuilderHandoff.ts +6 -15
  18. package/src/artifacts/builder-handoff/schema.ts +3 -6
  19. package/src/artifacts/simplifier-handoff/assertSimplifierHandoff.ts +3 -3
  20. package/src/artifacts/simplifier-handoff/schema.ts +1 -0
  21. package/src/artifacts/verifier-handoff/assertVerifierHandoff.ts +4 -10
  22. package/src/artifacts/verifier-handoff/schema.ts +3 -6
  23. package/src/artifacts/verifier-handoff/writeVerifierHandoff.ts +1 -3
  24. package/src/config/assertConfiguration.ts +3 -6
  25. package/src/config/defaults.ts +1 -5
  26. package/src/config/loadConfiguration.ts +4 -11
  27. package/src/config/schema.ts +1 -2
  28. package/src/ids/createSpecId.ts +1 -2
  29. package/src/ids/isValidSpecId.ts +1 -2
  30. package/src/maestro/checks/assertEnvironment.ts +19 -50
  31. package/src/maestro/instructions/getMaestroInstructions.ts +47 -26
  32. package/src/maestro/session/MaestroSessionState.ts +15 -0
  33. package/src/maestro/status/formatMaestroStatus.ts +4 -13
  34. package/src/maestro/status/refreshMaestroStatus.ts +3 -7
  35. package/src/specs/create.ts +14 -4
  36. package/src/specs/template.ts +10 -14
  37. package/src/tools/child/record-builder-handoff.ts +1 -0
  38. package/src/tools/child/record-simplifier-handoff.ts +4 -4
  39. package/src/tools/child/record-verifier-handoff.ts +2 -3
  40. package/src/tools/child/utils/resolveWorkflowContext.ts +1 -4
  41. package/src/tools/main/create-spec.ts +3 -2
  42. package/src/tools/main/mark-spec-ready.ts +2 -1
  43. package/src/tools/main/resolve-escalations.ts +1 -0
  44. package/src/tools/main/resolve-findings.ts +6 -20
  45. package/src/tools/main/run-builder.ts +37 -57
  46. package/src/tools/main/run-simplifier.ts +10 -20
  47. package/src/tools/main/run-verifier.ts +31 -55
  48. package/src/tools/utils/pi-subagent-delegation.ts +13 -22
  49. package/src/tools/utils/resolveToolRunContext.ts +2 -3
  50. package/src/utils/is-errno-exception.ts +1 -3
  51. package/src/utils/path-exists.ts +1 -0
  52. package/src/utils/path-strictly-within.ts +3 -6
  53. package/src/utils/path-within-or-equal.ts +1 -4
  54. package/src/utils/read-json.ts +1 -4
  55. package/src/utils/write-json.ts +1 -4
  56. package/src/workflow/builder/completeBuilderPass.ts +3 -2
  57. package/src/workflow/builder/prepareBuilderRun.ts +3 -8
  58. package/src/workflow/builder/resolveEscalations.ts +2 -6
  59. package/src/workflow/builder/utils/getWorkflowEventByBuilderHandoff.ts +1 -3
  60. package/src/workflow/simplifier/completeSimplifierPass.ts +2 -3
  61. package/src/workflow/spec/markSpecReady.ts +10 -4
  62. package/src/workflow/state/readWorkflowState.ts +2 -7
  63. package/src/workflow/state/schema.ts +5 -10
  64. package/src/workflow/state/writeWorkflowState.ts +1 -4
  65. package/src/workflow/transitions.ts +3 -11
  66. package/src/workflow/utils/assertSpecAcceptanceCriteria.ts +2 -1
  67. package/src/workflow/utils/getAgentRole.ts +1 -3
  68. package/src/workflow/verifier/completeVerifierPass.ts +1 -0
  69. package/src/workflow/verifier/resolveFindings.ts +9 -29
  70. package/src/workflow/verifier/utils/getWorkflowEventByVerifierHandoff.ts +5 -20
  71. package/templates/design.md +20 -0
  72. package/templates/proposal.md +23 -0
  73. package/templates/spec.md +21 -50
  74. package/ROADMAP.md +0 -319
@@ -21,8 +21,7 @@ export const THINKING_LEVELS = {
21
21
  MAX: 'max',
22
22
  } as const;
23
23
 
24
- export type ThinkingLevel =
25
- (typeof THINKING_LEVELS)[keyof typeof THINKING_LEVELS];
24
+ export type ThinkingLevel = (typeof THINKING_LEVELS)[keyof typeof THINKING_LEVELS];
26
25
 
27
26
  export const MODEL_PATTERN = '^[^/\\s]+/[^/\\s]+$';
28
27
 
@@ -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)}`;
@@ -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
- import type {
9
- ExtensionAPI,
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,24 +53,13 @@ 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 [
67
- config.builder.model,
68
- config.verifier.model,
69
- config.simplifier.model,
70
- ]) {
59
+ for (const model of [config.builder.model, config.verifier.model, config.simplifier.model]) {
71
60
  const [provider, id] = model.split('/');
72
61
 
73
- if (
74
- !availableModels.some(
75
- (entry) => entry.provider === provider && entry.id === id,
76
- )
77
- ) {
62
+ if (!availableModels.some((entry) => entry.provider === provider && entry.id === id)) {
78
63
  throw new Error(`Configured model is not available: "${model}".`);
79
64
  }
80
65
  }
@@ -102,17 +87,7 @@ const isReplyWithRequestId = (
102
87
  ): reply is Record<string, unknown> & { requestId: string } =>
103
88
  isRecord(reply) && typeof reply.requestId === 'string';
104
89
 
105
- type SuccessfulPingReply = {
106
- version: typeof SUBAGENT_PING.VERSION;
107
- requestId: string;
108
- method: typeof SUBAGENT_PING.METHOD;
109
- success: true;
110
- data: { version: typeof SUBAGENT_PING.VERSION };
111
- };
112
-
113
- function assertSuccessfulPingReply(
114
- reply: unknown,
115
- ): asserts reply is SuccessfulPingReply {
90
+ function assertSuccessfulPingReply(reply: unknown): void {
116
91
  if (
117
92
  !isReplyWithRequestId(reply) ||
118
93
  reply.version !== SUBAGENT_PING.VERSION ||
@@ -125,22 +100,17 @@ function assertSuccessfulPingReply(
125
100
  }
126
101
  }
127
102
 
128
- async function assertSubagentsRespond(
129
- piEventsBus: ExtensionAPI['events'],
130
- ): Promise<void> {
103
+ async function assertSubagentsRespond(piEventsBus: ExtensionAPI['events']): Promise<void> {
131
104
  const requestId = randomUUID();
132
105
  const { promise, resolve, reject } = Promise.withResolvers<unknown>();
133
106
 
134
- const unsubscribe = piEventsBus.on(
135
- `${SUBAGENT_PING.REPLY_EVENT_PREFIX}${requestId}`,
136
- (reply) => {
137
- if (isReplyWithRequestId(reply) && reply.requestId !== requestId) {
138
- return;
139
- }
107
+ const unsubscribe = piEventsBus.on(`${SUBAGENT_PING.REPLY_EVENT_PREFIX}${requestId}`, (reply) => {
108
+ if (isReplyWithRequestId(reply) && reply.requestId !== requestId) {
109
+ return;
110
+ }
140
111
 
141
- resolve(reply);
142
- },
143
- );
112
+ resolve(reply);
113
+ });
144
114
 
145
115
  const timeout = setTimeout(
146
116
  () => reject(new Error('Pi-subagents ping timed out after 2000 ms.')),
@@ -186,9 +156,8 @@ export async function assertEnvironment({
186
156
  try {
187
157
  await assertSubagentsRespond(piEventsBus);
188
158
  } catch (error) {
189
- throw new Error(
190
- `${SUBAGENT_PING.INSTALLATION_ERROR} ${getErrorMessage(error)}`,
191
- { cause: error },
192
- );
159
+ throw new Error(`${SUBAGENT_PING.INSTALLATION_ERROR} ${getErrorMessage(error)}`, {
160
+ cause: error,
161
+ });
193
162
  }
194
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
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.
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
 
@@ -42,38 +43,56 @@ Wait for each tool result before taking the next workflow action. Do not launch
42
43
 
43
44
  ### Prepare and approve the spec
44
45
 
45
- 1. Call maestro_create_spec with the change title. Use the generated spec.md structure from the package's templates/spec.md.
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 spec before asking the next question.
48
- 4. Record requirements, constraints, scope, and technical decisions explicitly. Do not invent requirements or silently resolve owner decisions.
49
- 5. Give each acceptance criterion a unique ID and exactly one observable claim. Include one Coverage label: success, failure, or unchanged. Describe its probe scenario and expected result in plain language. Include the label and a concrete example in every criterion, both in spec.md and when presenting it to the owner.
50
- 6. Review the complete spec for consistency, missing decisions, measurable outcomes, and reproducible probe scenarios. Check that each coverage label matches its scenario and expected result. Remove criteria whose exact scenarios already have test coverage. Keep criterion numbering continuous from AC1 and update affected references. Remove repetition and unnecessary implementation details. Resolve gaps with the owner.
51
- 7. Ask the owner to inspect the current spec.md and reply GREEN FLAG to approve it and start the builder.
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 the spec as the contract. Call maestro_mark_spec_ready with the active specId.
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 the current spec.md.
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 spec.md for the owner. Keep detail proportional to the change and state each requirement once.
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, scope, constraints, and approved architectural decisions in the spec. Leave routine implementation choices to the builder.
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
- Use success when a valid action produces the required result. Use failure when an invalid action is correctly rejected.
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
- Keep required behavior in the requirements and constraints even when its criterion is omitted. These rules also apply during spec revisions.
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 spec.md, keep only a brief reason, essential references, and unresolved limits that affect the decision. Follow the check and experiment permissions below.
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
- Edit the active spec.md and its prototypes/ directory only in drafting-spec.
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 spec or prototypes in other phases.
96
- The approved spec is frozen. Never change it during builder, verifier, or simplifier execution.
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
 
@@ -179,9 +198,9 @@ Without revision, all rejections return simplifier-running and any fix-code retu
179
198
  For ready-for-builder, call maestro_run_builder separately. Do not fix the code yourself.
180
199
 
181
200
  For either an escalation revision or a finding revision, first record the complete batch and enter drafting-spec as described above.
182
- 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.
183
202
  Do not create a new spec. Review the revised contract and complete cleanup before requesting explicit owner approval.
184
- Ask the owner to inspect the revised spec and reply GREEN FLAG again.
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.
185
204
  Only after that explicit reply, call maestro_mark_spec_ready separately. After the transition succeeds, start the builder in the foreground.
186
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.
187
206
  Keep their artifacts. Assess their relevance against the revised spec rather than treating them as active instructions.
@@ -199,6 +218,8 @@ Disabling Maestro, restarting Pi, or using /resume clears live state. Do not rec
199
218
  The owner handles execution errors, protocol errors, and interrupted workflows manually.
200
219
 
201
220
  At candidate-ready, the workflow is complete. No final tool call is required.
221
+ Maestro stays active while you deliver the final summary and automatically turns off after the response finishes successfully, including when simplification was skipped.
222
+ Do not ask the owner to toggle /maestro or disable it before delivering the summary. Saved workflow files remain unchanged.
202
223
  You own the final summary. Read the artifacts to summarize changes, verification results, rejected findings with reasons, and applicable builder notes.
203
224
  For completed simplification, read handoffs/simplifier/S.json and report its saved summary and notes as the simplifier's report.
204
225
  For example, summary "Simplified counting" and note "Updated the caller to read the Map." belong to the simplifier, not verifier evidence.
@@ -6,6 +6,7 @@
6
6
  class MaestroSessionState {
7
7
  private active = false;
8
8
  private activeSpecId: string | null = null;
9
+ private waitingForFinalResponse = false;
9
10
 
10
11
  public isActive = (): boolean => this.active;
11
12
 
@@ -23,10 +24,24 @@ class MaestroSessionState {
23
24
 
24
25
  public activate = (): void => {
25
26
  this.active = true;
27
+ this.stopWaitingForFinalResponse();
28
+ };
29
+
30
+ public setWaitingForFinalResponseMode = (specId: string): void => {
31
+ if (this.active && this.activeSpecId === specId) {
32
+ this.waitingForFinalResponse = true;
33
+ }
34
+ };
35
+
36
+ public isWaitingForFinalResponse = (): boolean => this.waitingForFinalResponse;
37
+
38
+ public stopWaitingForFinalResponse = (): void => {
39
+ this.waitingForFinalResponse = false;
26
40
  };
27
41
 
28
42
  public clearActiveSpecId = (): void => {
29
43
  this.activeSpecId = null;
44
+ this.stopWaitingForFinalResponse();
30
45
  };
31
46
 
32
47
  public deactivate = (): void => {
@@ -4,11 +4,8 @@
4
4
  */
5
5
 
6
6
  import type { Theme, ThemeColor } from '@earendil-works/pi-coding-agent';
7
- import {
8
- WORKFLOW_PHASES,
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',
@@ -82,15 +79,9 @@ export const formatMaestroStatus = ({
82
79
 
83
80
  const { phase, specId } = workflow.state;
84
81
 
85
- const prefix = theme.fg(
86
- 'muted',
87
- `Maestro active · ${shortenSpecId(specId)} · `,
88
- );
82
+ const prefix = theme.fg('muted', `Maestro active · ${shortenSpecId(specId)} · `);
89
83
 
90
- const label = theme.fg(
91
- WORKFLOW_PHASE_COLORS[phase],
92
- WORKFLOW_PHASE_LABELS[phase],
93
- );
84
+ const label = theme.fg(WORKFLOW_PHASE_COLORS[phase], WORKFLOW_PHASE_LABELS[phase]);
94
85
 
95
86
  return `${prefix}${WORKFLOW_PHASE_ICONS[phase]} ${label}`;
96
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
  };
@@ -1,12 +1,13 @@
1
1
  /**
2
- * Objective: Create a spec directory, spec file, and initial workflow state.
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 { loadSpecTemplate, renderSpecTemplate } from '#specs/template.ts';
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 template = await loadSpecTemplate();
51
- const spec = renderSpecTemplate({ template, specId, title });
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,
@@ -1,26 +1,22 @@
1
1
  /**
2
- * Objective: Load and render the spec document template.
3
- * Used: When Maestro creates a new spec file.
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
- template: string;
9
+ document: 'proposal.md' | 'spec.md';
15
10
  specId: string;
16
11
  title: string;
17
12
  };
18
13
 
19
- export const renderSpecTemplate = ({
20
- template,
14
+ export const renderSpecTemplate = async ({
15
+ document,
21
16
  specId,
22
17
  title,
23
- }: RenderSpecTemplateInput): string =>
24
- template
25
- .replace('<id>', specId)
26
- .replace('<short, outcome-oriented title>', title.trim());
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
+ };
@@ -5,6 +5,7 @@
5
5
 
6
6
  import type { ExtensionAPI } from '@earendil-works/pi-coding-agent';
7
7
  import { Type } from 'typebox';
8
+
8
9
  import {
9
10
  BuilderDoneHandoffSubmissionSchema,
10
11
  BuilderEscalationHandoffSubmissionSchema,
@@ -6,6 +6,7 @@
6
6
  import type { ExtensionAPI } from '@earendil-works/pi-coding-agent';
7
7
  import { type Static, Type } from 'typebox';
8
8
  import { Value } from 'typebox/value';
9
+
9
10
  import {
10
11
  SIMPLIFIER_HANDOFF_VERSION,
11
12
  SimplifierSubmissionFields,
@@ -20,10 +21,9 @@ export const SIMPLIFIER_HANDOFF_TOOL = {
20
21
  'Save the simplifier result at handoffs/simplifier/S.json and complete the workflow. Stop after success.',
21
22
  } as const;
22
23
 
23
- const SimplifierHandoffToolParameters = Type.Object(
24
- SimplifierSubmissionFields,
25
- { additionalProperties: false },
26
- );
24
+ const SimplifierHandoffToolParameters = Type.Object(SimplifierSubmissionFields, {
25
+ additionalProperties: false,
26
+ });
27
27
 
28
28
  function assertSimplifierSubmission(
29
29
  input: unknown,
@@ -5,6 +5,7 @@
5
5
 
6
6
  import type { ExtensionAPI } from '@earendil-works/pi-coding-agent';
7
7
  import { Type } from 'typebox';
8
+
8
9
  import { BuilderAcceptanceCriterionSchema } from '#artifacts/builder-handoff/schema.ts';
9
10
  import {
10
11
  VERIFIER_HANDOFF_VERSION,
@@ -57,9 +58,7 @@ export const registerRecordVerifierHandoffTool = (pi: ExtensionAPI): void => {
57
58
  specId,
58
59
  });
59
60
 
60
- const currentState = await readWorkflowState(
61
- paths.getWorkflowPath(specId),
62
- );
61
+ const currentState = await readWorkflowState(paths.getWorkflowPath(specId));
63
62
 
64
63
  const handoff = {
65
64
  version: VERIFIER_HANDOFF_VERSION,
@@ -11,10 +11,7 @@ export type ResolveWorkflowContextInput = {
11
11
  specId: string;
12
12
  };
13
13
 
14
- export const resolveWorkflowContext = async ({
15
- cwd,
16
- specId,
17
- }: ResolveWorkflowContextInput) => {
14
+ export const resolveWorkflowContext = async ({ cwd, specId }: ResolveWorkflowContextInput) => {
18
15
  if (!isValidSpecId(specId)) {
19
16
  throw new Error(`Invalid spec ID: "${specId}".`);
20
17
  }
@@ -5,6 +5,7 @@
5
5
 
6
6
  import type { ExtensionAPI } from '@earendil-works/pi-coding-agent';
7
7
  import { Type } from 'typebox';
8
+
8
9
  import maestroSessionState from '#maestro/session/MaestroSessionState.ts';
9
10
  import { createSpec } from '#specs/create.ts';
10
11
  import { resolveToolRunContext } from '#tools/utils/resolveToolRunContext.ts';
@@ -13,7 +14,7 @@ export const CREATE_SPEC_TOOL = {
13
14
  NAME: 'maestro_create_spec',
14
15
  LABEL: 'Create Spec',
15
16
  DESCRIPTION:
16
- 'Create a new Maestro specification in drafting-spec phase from the approved template. Review the generated spec with the owner before marking it ready.',
17
+ 'Create proposal.md and spec.md in drafting-spec from the bundled templates. Add design.md only when significant technical decisions are needed. Review all present documents with the owner before marking the specification ready.',
17
18
  } as const;
18
19
 
19
20
  const CreateSpecToolParameters = Type.Object(
@@ -44,7 +45,7 @@ export const registerCreateSpecTool = (pi: ExtensionAPI): void => {
44
45
  content: [
45
46
  {
46
47
  type: 'text',
47
- text: `Spec ${created.specId} created in drafting-spec phase. Review ${created.specFilePath} with the owner before marking it ready.`,
48
+ text: `Spec ${created.specId} created in drafting-spec phase. Review ${created.proposalFilePath} and ${created.specFilePath} with the owner. Add ${created.designFilePath} only when significant technical decisions are needed; include it in the same approval.`,
48
49
  },
49
50
  ],
50
51
  details: created,
@@ -5,6 +5,7 @@
5
5
 
6
6
  import type { ExtensionAPI } from '@earendil-works/pi-coding-agent';
7
7
  import { Type } from 'typebox';
8
+
8
9
  import { SPEC_ID_PATTERN } from '#ids/isValidSpecId.ts';
9
10
  import maestroSessionState from '#maestro/session/MaestroSessionState.ts';
10
11
  import { resolveToolRunContext } from '#tools/utils/resolveToolRunContext.ts';
@@ -14,7 +15,7 @@ export const MARK_SPEC_READY_TOOL = {
14
15
  NAME: 'maestro_mark_spec_ready',
15
16
  LABEL: 'Mark Spec Ready',
16
17
  DESCRIPTION:
17
- 'Approve the current spec.md only from drafting-spec after the owner replies GREEN FLAG, then move to ready-for-builder. Only the owner reply GREEN FLAG authorizes approval. After this transition succeeds, call maestro_run_builder in the foreground.',
18
+ 'Approve proposal.md and spec.md together with design.md if present, only from drafting-spec after the owner replies GREEN FLAG. Freeze all present documents and move to ready-for-builder. Only the owner reply GREEN FLAG authorizes approval. After this transition succeeds, call maestro_run_builder in the foreground.',
18
19
  } as const;
19
20
 
20
21
  const MarkSpecReadyToolParameters = Type.Object(
@@ -5,6 +5,7 @@
5
5
 
6
6
  import type { ExtensionAPI } from '@earendil-works/pi-coding-agent';
7
7
  import { Type } from 'typebox';
8
+
8
9
  import {
9
10
  ESCALATION_ID_PATTERN,
10
11
  EscalationResolutionSchema,