@emiliosp/pi-maestro 0.9.4 → 0.10.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/CHANGELOG.md CHANGED
@@ -2,6 +2,30 @@
2
2
 
3
3
  This file summarizes meaningful changes to the codebase, including behavior, data formats, architecture, and development tools. Git tags identify version boundaries; GitHub Releases are not required. Version-only changes and routine maintenance are omitted.
4
4
 
5
+ ## Unreleased
6
+
7
+ 1. Add `revise-spec` decisions for `verifier` `findings`, with the owner's reason and agreed requirement change saved in the active `handoff`.
8
+ 2. Route any revision batch to `drafting-spec` before revision, with priority over code fixes and rejections.
9
+ 3. Require separate renewed approval from `drafting-spec` and reject direct approval from `finding-decisions`.
10
+
11
+ 4. Require the boolean `specChangeRequired` in `maestro_resolve_escalations`. Save every current answer in the active `builder` `handoff` before changing phase.
12
+ 5. Route revision decisions to `drafting-spec`. Revise the same `spec` and obtain renewed `GREEN FLAG` before a separate approval and `builder` `run`.
13
+ 6. Reject direct `spec` approval from `escalation-decisions`. Decisions without revision still return to `ready-for-builder` without renewed approval.
14
+ 7. Breaking: rename the saved decision phases to `escalation-decisions` and `finding-decisions`. Existing workflows using the previous names require manual `owner` handling.
15
+
16
+ ## 0.9.5
17
+
18
+ 1. Added a shared check that requires every approved `spec` criterion ID in `builder` and `verifier` `handoffs`. See [PR #37](https://github.com/emilioSp/pi-maestro/pull/37).
19
+ 2. Missing IDs reject submission before saving a report or changing phase. Extra report IDs remain allowed, and existing report validation is unchanged.
20
+ 3. Reused the raw heading rule `^### AC(\d+):`, including leading zeros and matching lines inside code blocks. No matching headings impose no new restriction.
21
+ 4. Added unit tests for extraction and one missing-ID integration test per role.
22
+
23
+ ## 0.9.4
24
+
25
+ 1. Added numbering validation to `spec` approval. Matched `acceptance criterion` headings must start at `AC1` and continue without gaps. See [PR #36](https://github.com/emilioSp/pi-maestro/pull/36).
26
+ 2. Invalid numbering rejects approval before saving the workflow and leaves all saved files unchanged. No matching headings still permits approval.
27
+ 3. Added `Maestro` guidance to correct only numbering and affected references, then retry with the existing `GREEN FLAG`. No additional `owner` approval is required before starting the `builder`.
28
+
5
29
  ## 0.9.3
6
30
 
7
31
  1. Added a parent `pi-subagents` availability check during activation. A matching successful version 1 `ping` reply is required within 2000 ms. See [PR #35](https://github.com/emilioSp/pi-maestro/pull/35).
package/README.md CHANGED
@@ -79,7 +79,11 @@ The `owner` must not edit product files while the workflow runs.
79
79
  `Maestro` can perform temporary experiments with `owner` agreement during `spec` preparation and permitted revisions. See [Workflow](docs/workflow.md#spec-approval).
80
80
 
81
81
  An `escalation` asks the `owner` to decide an implementation question, and a `finding` records a technical issue reported by the `verifier`. The `owner` discusses both with `Maestro`, never directly with the `builder` or `verifier`.
82
- If a `spec` change is needed during an `escalation` or `finding` decision, the `owner` reviews the revised `spec` and replies `GREEN FLAG` again. `Maestro` then starts another `builder` `run`.
82
+ `Maestro` records all current `escalation` answers or `finding` decisions in the active `handoff` before changing phase.
83
+
84
+ If any decision requires a `spec` change, the workflow returns to `drafting-spec` before the `owner` and `Maestro` revise the same `spec`.
85
+ The `owner` reviews the revised `spec` and replies `GREEN FLAG` again.
86
+ `Maestro` records renewed approval separately before another `builder` `run`.
83
87
 
84
88
  The workflow ends at `candidate-ready`. Rejected `findings` retain their reasons. Later changes are outside the completed verification. The `owner` controls any later Git use, pull request, and merge.
85
89
 
package/ROADMAP.md CHANGED
@@ -60,7 +60,7 @@ Renamed `test/` to `tests/` at the owner's request. Updated imports, fixture pat
60
60
 
61
61
  #### Evidence
62
62
 
63
- [Test support](tests/support/), [fixtures](tests/fixtures/), [lint configuration](biome.json), and [project instructions](AGENTS.md#tests).
63
+ [Test support](tests/support/), [fixtures](tests/fixtures/), and [lint configuration](biome.json).
64
64
 
65
65
  ### R-16: Verify code quality against AGENTS.md instructions
66
66
 
@@ -86,23 +86,74 @@ A later integration test requested by the `owner` directly checks the environmen
86
86
 
87
87
  [Specification](.specs/20261013-194407-check-pi-subagents-availability-with-ping-at-activation/spec.md), [verifier report](.specs/20261013-194407-check-pi-subagents-availability-with-ping-at-activation/handoffs/verifier/V1.json), and [merged PR #35](https://github.com/emilioSp/pi-maestro/pull/35).
88
88
 
89
- ## Now
90
-
91
89
  ### R-05: Keep acceptance criterion numbering continuous
92
90
 
91
+ Implemented in `0.9.4`. `Spec` approval rejects numbered `acceptance criterion` headings that do not start at `AC1` and continue without gaps. Rejection leaves saved files unchanged.
92
+
93
+ `Maestro` corrects numbering and affected references without changing criterion order or requirements, then retries with the existing `GREEN FLAG`. No additional `owner` approval is required.
94
+
95
+ All 3 `acceptance criteria` passed with no `verifier` `findings`. The saved workflow reached `candidate-ready`.
96
+
97
+ #### Evidence
98
+
99
+ [Specification](.specs/20261013-202811-check-acceptance-criterion-numbering-before-builder-execution/spec.md), [verifier report](.specs/20261013-202811-check-acceptance-criterion-numbering-before-builder-execution/handoffs/verifier/V1.json), and [merged PR #36](https://github.com/emilioSp/pi-maestro/pull/36).
100
+
101
+ ### R-10: Match handoff criteria to the approved spec
102
+
103
+ Implemented in `0.9.5` with the narrower scope approved by the `owner`. Both `handoff` submissions require every `acceptance criterion` ID from the approved `spec` before saving a report or changing phase. Extra report IDs remain allowed. Existing report validation is unchanged, and no new check rejects duplicate `spec` IDs.
104
+
105
+ Both `acceptance criteria` passed independent verification with no `findings`. The saved workflow reached `candidate-ready`.
106
+
107
+ Later changes requested by the `owner` extracted the shared check and replaced repeated integration scenarios with unit tests. One missing-ID integration test remains per role. These changes are outside the completed independent verification. The final `npm run check` passed all 227 tests and coverage thresholds.
108
+
109
+ #### Evidence
110
+
111
+ [Specification](.specs/20261013-211629-match-handoff-criteria-to-the-approved-spec/spec.md), [verifier report](.specs/20261013-211629-match-handoff-criteria-to-the-approved-spec/handoffs/verifier/V1.json), and [merged PR #37](https://github.com/emilioSp/pi-maestro/pull/37).
112
+
113
+ ### R-17: Record escalation resolutions when the spec changes
114
+
93
115
  #### Objective
94
116
 
95
- When criteria are removed during `spec` preparation or revision, renumber the remaining criteria without gaps. Numbering starts at `AC1` and continues through the final criterion.
117
+ Save every current `escalation` answer in the original `builder` `handoff`, whether the `spec` stays unchanged or requires revision.
118
+ Each `resolution` records the owner's chosen solution and reason. A decision requiring revision also identifies the `escalation` and describes the agreed `spec` change.
119
+
120
+ #### Agreed flow
121
+
122
+ `Maestro` records the complete decision batch through `maestro_resolve_escalations` before changing phase.
96
123
 
97
- #### Current state
124
+ | Decision | Next phase and action |
125
+ |---|---|
126
+ | No `spec` change | `escalation-decisions` → `ready-for-builder`. The `builder` continues under the approved `spec`, without renewed approval. |
127
+ | `Spec` change required | `escalation-decisions` → `drafting-spec`. The `owner` and `Maestro` revise and review the same `spec`. |
128
+ | Revised `spec` receives renewed `GREEN FLAG` | `drafting-spec` → `ready-for-builder` through separate `maestro_mark_spec_ready` approval. |
98
129
 
99
- Implemented approval-time numbering validation and `Maestro` guidance for numbering-only recovery using the existing GREEN FLAG. Rejected numbering leaves saved files unchanged. Independent verification is pending.
130
+ Direct `spec` approval from `escalation-decisions` is rejected. Recording answers does not move to `ready-for-verifier`; that phase requires a completed `builder` `pass`.
100
131
 
101
- [Specification](.specs/20261013-202811-check-acceptance-criterion-numbering-before-builder-execution/spec.md).
132
+ #### Implementation boundaries
133
+
134
+ Reuse the existing resolution operation and `handoff` fields. Do not add a parallel decision store, tool, or workflow phase.
135
+ Define both resolution transitions explicitly in `src/workflow/transitions.ts` and apply them through `transitionWorkflow`. Direct phase assignment elsewhere is banned.
136
+ Preserve initial `spec` approval, `verifier` `finding` decisions, existing batch validation, and historical `handoffs`.
102
137
 
103
138
  #### Completed when
104
139
 
105
- The `spec` and its current references use consistent numbering.
140
+ Saved `resolutions`, workflow phases, tool results, agent instructions, and human documentation follow the agreed flow.
141
+ Verification checks both observable behavior and use of the central transition definitions for each resolution branch. Passing behavior tests alone is not enough.
142
+ The new `spec` includes coverage labels and omits `acceptance criteria` already covered by tests of the same scenario.
143
+
144
+ ### R-20: Route finding-driven spec revisions through drafting-spec
145
+
146
+ Implemented the approved revision route. `Maestro` records every current `finding` decision before returning to `drafting-spec` when revision is required.
147
+ The `owner` reviews the same revised `spec` and gives renewed `GREEN FLAG` before separate approval and another `builder` `run`.
148
+ Revision takes priority in mixed batches. Direct approval from `finding-decisions` is rejected.
149
+
150
+ Later event naming cleanup identifies `builder` and `owner` actions explicitly. Both revision routes use `OWNER_REQUEST_SPEC_REVISION` without changing the phase flow. Batch resolution uses `RESOLVE_ESCALATIONS`, and the status label refers to `escalations` in the plural. The saved decision phases are named `escalation-decisions` and `finding-decisions`.
151
+
152
+ #### Evidence
153
+
154
+ [Specification](.specs/20261015-154812-route-finding-driven-spec-revisions-through-drafting-spec/spec.md) and [resolution integration tests](src/tools/main/resolve-findings.integration.test.ts).
155
+
156
+ ## Now
106
157
 
107
158
  ## Next
108
159
 
@@ -152,38 +203,28 @@ Explore how `Maestro` can reconcile saved `artifacts` and workflow state. The re
152
203
 
153
204
  Not yet defined. The `owner` must define the intended outcome, recovery boundaries, and completion condition before this item can move forward.
154
205
 
155
- ### R-10: Match handoff criteria to the approved spec
206
+ ### R-18: Label acceptance criterion coverage
156
207
 
157
208
  #### Objective
158
209
 
159
- Prevent `builder` and `verifier` reports from omitting or adding `acceptance criteria`. Each report must contain exactly the criterion IDs in the approved `spec`, with no duplicates in either place. Existing duplicate checks in reports do not establish this match.
160
-
161
- The `specification` must define the criterion heading format used to extract IDs. For example, a heading can use `### AC1: ...`.
210
+ Label each `acceptance criterion` in a `spec` as success, failure, or keep current behavior unchanged.
211
+ The label makes the purpose of its `probe` clear. Failure means an invalid action is correctly rejected.
162
212
 
163
213
  #### Completed when
164
214
 
165
- Both `handoff` submissions reject missing, extra, or duplicate criterion IDs before saving a report or changing phase.
215
+ The `spec` template and `Maestro` guidance require a coverage label for every `acceptance criterion`.
216
+ Each label matches the scenario and `expected result`. A `spec` does not need all three categories when they do not apply.
166
217
 
167
- ### R-11: Reduce temporary verifier changes
218
+ ### R-19: Avoid acceptance criteria already covered by tests
168
219
 
169
220
  #### Objective
170
221
 
171
- Reduce the risk that verification changes the `candidate`. Existing instructions already require exact restoration, cleanup, and reporting of unresolved restoration problems. Refine the remaining guidance rather than duplicate those rules.
172
-
173
- Prefer checks that leave existing files unchanged and use separate temporary files when possible. Change an existing file only when a criterion requires it. Retain its exact contents and restore it immediately after each `probe`, including failed `probes`. Prefer check-only commands over commands that apply automatic fixes.
174
-
175
- `Maestro` currently has no file-hash restoration check. Adding one requires a separate scope decision.
176
-
177
- #### Completed when
178
-
179
- `Verifier` instructions cover these limits and require restoration and temporary-file cleanup before `handoff`. If restoration fails, the `verifier` must stop and report affected paths and remaining changes.
180
-
181
- ### R-13: Show when Maestro is working
182
-
183
- #### Objective
222
+ Do not add an `acceptance criterion` when an existing test already covers the same starting conditions, action, and `expected result`.
184
223
 
185
- Show an activity indicator in Pi's status bar while progress does not require an `owner` decision. A spinner is one option, not an agreed implementation.
224
+ Similar tests do not count if they miss the proposed scenario.
186
225
 
187
226
  #### Completed when
188
227
 
189
- The status clearly distinguishes work in progress from a request for `owner` action.
228
+ `Maestro` guidance requires inspection of existing tests before adding `acceptance criteria`.
229
+ During `spec` review, `Maestro` removes criteria already covered by tests and keeps numbering continuous.
230
+ Required behavior remains documented in the `spec`.
@@ -115,5 +115,5 @@ Do not delete artifacts, change workflow state, or bypass the tool to force comp
115
115
  If restoration or submission cannot be completed safely, report the exact blocker and remaining changes to Maestro and stop.
116
116
 
117
117
  After a successful handoff, return a concise summary and the saved artifact path to Maestro and stop. Do not run more checks.
118
- The handoff produces `findings-decision` when findings exist and `candidate-ready` when none exist.
118
+ The handoff produces `finding-decisions` when findings exist and `candidate-ready` when none exist.
119
119
  Report that tool result without deciding whether the owner must accept, reject, or fix any finding.
package/docs/glossary.md CHANGED
@@ -21,6 +21,7 @@
21
21
  | `Escalation` | An implementation question or reportable technical problem that the `builder` records in its `handoff` for an `owner` decision. |
22
22
  | `GREEN FLAG` | The owner's explicit reply that approves the current `spec` and starts the `builder`. |
23
23
  | `fix-code` | An `owner` decision that requests code fixes without changing the approved `spec`. |
24
+ | `revise-spec` | An `owner` decision that records a reason and agreed requirement change, then returns to `drafting-spec` for revision and renewed approval. |
24
25
  | `reject` | An `owner` decision that declines a `finding` and records a reason. It does not mean that verification passed. |
25
26
  | `candidate-ready` | The completed workflow phase. The `verifier` reported no `findings`, or the `owner` rejected every `finding` with a reason. Final review remains with the `owner`. |
26
27
 
package/docs/workflow.md CHANGED
@@ -61,16 +61,16 @@ flowchart TD
61
61
  findings -->|None| candidate
62
62
 
63
63
  buildOutcome -->|Escalation| ownerEscalation{Owner decides every question}
64
- ownerEscalation -->|Spec unchanged| recordEscalation[Maestro records all answers]
65
- recordEscalation --> build
66
- ownerEscalation -->|Spec changes| reviseSpec[Owner and Maestro revise the spec]
64
+ ownerEscalation --> recordEscalation[Maestro records all answers]
65
+ recordEscalation -->|Spec unchanged| build
66
+ recordEscalation -->|Spec changes| reviseSpec[Owner and Maestro revise the spec]
67
67
  reviseSpec --> approval
68
68
 
69
69
  findings -->|One or more| ownerFindings[Owner decides every finding]
70
- ownerFindings -->|Reject all with reasons| candidate
71
- ownerFindings -->|Fix code| recordFixes[Maestro records the decisions]
72
- recordFixes --> build
73
- ownerFindings -->|Revise spec| reviseSpec
70
+ ownerFindings --> recordFindings[Maestro records all decisions]
71
+ recordFindings -->|Reject all with reasons| candidate
72
+ recordFindings -->|Fix code without spec changes| build
73
+ recordFindings -->|Spec changes| reviseSpec
74
74
 
75
75
  candidate --> summary
76
76
  summary --> review
@@ -86,13 +86,13 @@ The default path is `.specs/<spec-id>/workflow.json`. The `spec` directory is [c
86
86
 
87
87
  | Phase | Meaning |
88
88
  |---|---|
89
- | `drafting-spec` | The `owner` and `Maestro` are preparing the initial `spec`. |
89
+ | `drafting-spec` | The `owner` and `Maestro` prepare the initial `spec` or revise it after recorded `escalation` answers or `finding` decisions. |
90
90
  | `ready-for-builder` | The approved `spec` or recorded `owner` decisions permit a `builder` `run`. |
91
91
  | `builder-running` | A `builder` `run` is active. |
92
- | `escalation-decision` | The `owner` must decide how to handle every `escalation` question. |
92
+ | `escalation-decisions` | The `owner` must decide how to handle every `escalation` question. |
93
93
  | `ready-for-verifier` | The `builder` completed the work and the `verifier` can start. |
94
94
  | `verifier-running` | A `verifier` `run` is active. |
95
- | `findings-decision` | The `owner` must decide how to handle every current `finding`. |
95
+ | `finding-decisions` | The `owner` must decide how to handle every current `finding`. |
96
96
  | `candidate-ready` | The `verifier` reported no `findings`, or the `owner` rejected every `finding` with a reason. The workflow is complete. |
97
97
 
98
98
  ## Spec approval
@@ -104,7 +104,7 @@ For the initial `spec`:
104
104
  3. `Maestro` asks the `owner` to inspect the current `spec.md` and reply `GREEN FLAG` to approve it and start the `builder`.
105
105
  4. After that reply, `Maestro` saves `ready-for-builder` and starts the `builder` in the foreground.
106
106
 
107
- Approval freezes the `spec`. The `spec` and its visual prototypes remain unchanged during `builder` and `verifier` execution. `Spec` revisions are limited to the decision phases described in [Spec revision](#spec-revision).
107
+ Approval freezes the `spec`. The `spec` and its visual prototypes remain unchanged during `builder` and `verifier` execution. See [Spec revision](#spec-revision) for permitted revisions.
108
108
 
109
109
  ## Acceptance criteria
110
110
 
@@ -125,14 +125,17 @@ For instance a technical failure, an undefined behavior, a conflict with the `sp
125
125
 
126
126
  The `builder` saves all questions from one `pass` in the `escalations` list of its numbered `handoff`. `Maestro` identifies each question with both report and `escalation` IDs, such as `B1/E1`. See [Escalation references](glossary.md#escalation-references) for their scope.
127
127
 
128
- `Maestro` presents each question, evidence, options, consequences, and next steps. The workflow pauses in `escalation-decision` while the `owner` decides how to proceed.
128
+ `Maestro` presents each question, evidence, options, consequences, and next steps. The workflow pauses in `escalation-decisions` while the `owner` decides how to proceed.
129
129
 
130
- If the `spec` stays unchanged, the `owner` gives `Maestro` an answer and reason for every current question. The `owner` can select a listed option or give a different decision.
130
+ The `owner` gives `Maestro` an answer and reason for every current question, whether or not the `spec` must change.
131
+ The `owner` can select a listed option or give a different decision.
132
+
133
+ `Maestro` saves all answers in the current `builder` `handoff` before the workflow continues.
131
134
 
132
135
  | `Owner` choice | Result |
133
136
  |---|---|
134
- | Keep the current `spec` | `Maestro` saves all answers and reasons together in the active `builder` `handoff`, returns to `ready-for-builder`, and starts another `builder` `run`. |
135
- | Change the `spec` | The `owner` and `Maestro` revise the same `spec` and obtain renewed approval before another `builder` `run`. |
137
+ | Keep the current `spec` | `Maestro` starts another `builder` `run` without renewed approval. |
138
+ | Change the `spec` | The `owner` and `Maestro` revise the same `spec`. The `owner` reviews it and replies `GREEN FLAG` before another `builder` `run`. |
136
139
 
137
140
  `Owner` decisions update only the questions' `resolution` fields. The handoff's other content and earlier `handoffs` remain unchanged.
138
141
 
@@ -147,19 +150,27 @@ Every current `finding` requires an `owner` decision, regardless of severity. `M
147
150
  | Decision | Result |
148
151
  |---|---|
149
152
  | `reject` | Records the owner's reason. If every `finding` is rejected, the workflow reaches `candidate-ready`. |
150
- | `fix-code` | Keeps the current `spec` and returns to `ready-for-builder` for code fixes. |
153
+ | `fix-code` | Keeps the current `spec` and returns to `ready-for-builder` for code fixes when no decision requires revision. |
154
+ | `revise-spec` | Records the owner's reason and agreed requirement change, then returns to `drafting-spec`. |
151
155
 
152
- Any `fix-code` decision requires another `builder` `run`, including when other `findings` are rejected. After the `builder` completes the fixes, the `verifier` checks the work again.
156
+ `Maestro` saves every current decision together before the phase changes.
157
+ If any `finding` requires revision, revision takes priority over code fixes and rejections. See [Spec revision](#spec-revision).
153
158
 
154
- If the `spec` must change, the `owner` and `Maestro` use [Spec revision](#spec-revision) instead. Earlier `findings` then become historical context.
159
+ Without revision, any `fix-code` decision requires another `builder` `run`, including when other `findings` are rejected.
160
+ After the `builder` completes the fixes, the `verifier` checks the work again.
155
161
 
156
162
  ## Spec revision
157
163
 
158
- `Spec` revisions are allowed only in `escalation-decision` or `findings-decision`. `Maestro` revises the same `spec` with the `owner`, including its prototypes when needed.
164
+ For either `escalation` answers or `finding` decisions, `Maestro` records the complete batch before the workflow returns to `drafting-spec`.
165
+ This decision does not approve the `spec` or start an agent.
166
+ The `owner` and `Maestro` then revise the same `spec` together in `drafting-spec`.
159
167
 
160
- After review and cleanup, `Maestro` asks the `owner` to inspect the revised `spec` and reply `GREEN FLAG` again. `Maestro` then records `ready-for-builder` and starts another `builder` `run`.
168
+ The `owner` reviews the changes and replies `GREEN FLAG` again.
169
+ `Maestro` records that renewed approval separately, moves to `ready-for-builder`, and starts another `builder` `run`.
170
+ Approval is permitted only in `drafting-spec`, not directly from either decision phase.
161
171
 
162
- Previous `escalations`, `findings`, and `handoffs` remain as historical context. The revised `spec` defines the requirements for subsequent work.
172
+ The revised `spec` controls subsequent work.
173
+ Earlier `escalations`, `findings`, and their decisions remain historical context.
163
174
 
164
175
  ## Verification boundary
165
176
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@emiliosp/pi-maestro",
3
- "version": "0.9.4",
3
+ "version": "0.10.0",
4
4
  "description": "A spec-driven multiagent development workflow for Pi.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -35,10 +35,19 @@ const FindingEvidenceSchema = Type.Object(
35
35
  export const FINDING_DECISIONS = {
36
36
  REJECT: 'reject',
37
37
  FIX_CODE: 'fix-code',
38
+ REVISE_SPEC: 'revise-spec',
38
39
  } as const;
39
40
 
40
41
  const FindingDecisionSchema = Type.Union([
41
42
  Type.Null(),
43
+ Type.Object(
44
+ {
45
+ decision: Type.Literal(FINDING_DECISIONS.REVISE_SPEC),
46
+ reason: Type.String({ minLength: 1, pattern: '\\S' }),
47
+ specChange: Type.String({ minLength: 1, pattern: '\\S' }),
48
+ },
49
+ { additionalProperties: false },
50
+ ),
42
51
  Type.Object(
43
52
  {
44
53
  decision: Type.Literal(FINDING_DECISIONS.REJECT),
@@ -55,7 +55,7 @@ Wait for each tool result before taking the next workflow action. Do not launch
55
55
  11. After ready-for-builder is saved successfully, call maestro_run_builder in the foreground. Do not request a separate start approval.
56
56
 
57
57
  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, escalation-decision, or findings-decision, correct only numbering and affected references in the current spec.md.
58
+ In drafting-spec, correct only numbering and affected references in the current spec.md.
59
59
  Do not change criterion order or requirements.
60
60
  For example, change headings AC1 and AC3 to AC1 and AC2, and change the affected AC3 references to AC2.
61
61
  Retry maestro_mark_spec_ready with the active specId using the existing GREEN FLAG. Do not request another approval.
@@ -81,9 +81,10 @@ In spec.md, keep only a brief reason, essential references, and unresolved limit
81
81
 
82
82
  ### Spec edits, checks, and experiments
83
83
 
84
- Edit the active spec.md and its prototypes/ directory only in drafting-spec or during owner-directed contract revisions in decision phases.
85
- The numbering-only recovery procedure above also permits spec.md corrections after failed numbering validation in those phases.
86
- The decision phases are escalation-decision and findings-decision. Do not edit the spec or prototypes in other phases.
84
+ Edit the active spec.md and its prototypes/ directory only in drafting-spec.
85
+ For either revision origin, record the complete owner decision batch through the corresponding resolution tool before editing in drafting-spec.
86
+ The numbering-only recovery procedure above also permits spec.md corrections after failed numbering validation in drafting-spec.
87
+ Do not edit the spec or prototypes in other phases.
87
88
  The approved spec is frozen. Never change it during builder or verifier execution.
88
89
  Use any available tool for these permitted edits. Do not change other workflow artifacts.
89
90
 
@@ -107,7 +108,7 @@ The tool saves builder-running before launch. The builder records its result bef
107
108
  Read the returned artifact and follow its outcome:
108
109
 
109
110
  1. done: The phase is ready-for-verifier. Call maestro_run_verifier with the active specId.
110
- 2. escalation: The phase is escalation-decision. Present all questions from the active builder handoff, with evidence, options, consequences, and next steps.
111
+ 2. escalation: The phase is escalation-decisions. Present all questions from the active builder handoff, with evidence, options, consequences, and next steps.
111
112
 
112
113
  Reportable technical problems that prevent builder completion use the existing escalation process. The owner decides how work continues.
113
114
  A significant discovery needs an escalation when the owner must choose between meaningful alternatives, even without a technical blocker.
@@ -119,7 +120,7 @@ The verifier restores only its temporary changes and removes only its own tempor
119
120
  Each completed pass creates the next numbered file: handoffs/builder/B1.json, B2.json or handoffs/verifier/V1.json, V2.json.
120
121
  Earlier handoffs remain on the file system. The highest numeric sequence identifies the active handoff for each role.
121
122
  Use earlier handoffs only as references. Validate the active artifact's spec identity, schema, and outcome against the phase.
122
- Read the returned verifier handoff. If there are findings, follow findings-decision. If there are none, follow candidate-ready.
123
+ Read the returned verifier handoff. If there are findings, follow finding-decisions. If there are none, follow candidate-ready.
123
124
 
124
125
  ### Explain findings and escalations
125
126
 
@@ -142,23 +143,32 @@ Keep excerpts and explanations focused, but do not replace the details with seve
142
143
 
143
144
  ### Record owner decisions
144
145
 
145
- In escalation-decision, collect an explicit owner answer for every current question. Do not choose options for the owner.
146
- If the contract stays unchanged, call maestro_resolve_escalations once with specId and the complete decisions array. Each item contains escalationId, selectedOptionId, decision, and reason. Use selectedOptionId: null for an owner decision outside the listed options. The tool saves all resolutions inside the active builder handoff. Do not submit partial batches.
147
- The tool saves all resolutions and returns ready-for-builder. Call maestro_run_builder separately.
148
- If the contract must change, use the spec revision procedure below instead of resolving the escalation against the old contract.
149
-
150
- In findings-decision, present every current finding. Every finding requires an owner decision, regardless of severity.
151
- If the contract stays unchanged, collect reject or fix-code for every finding. Each reject requires the owner's reason.
152
- Submit all decisions together through maestro_resolve_findings. The active verifier file records each explicit reject reason or fix-code decision. Do not invent reasons or omit findings.
153
- If all findings are rejected, the tool returns candidate-ready. Any fix-code returns ready-for-builder, including mixed decisions.
146
+ In escalation-decisions, collect an explicit owner answer for every current question. Do not choose options for the owner.
147
+ Call maestro_resolve_escalations once with specId, the required boolean specChangeRequired, and the complete decisions array, whether or not the contract must change.
148
+ Each item contains escalationId, selectedOptionId, decision, and reason. Record the chosen solution and the owner's reason.
149
+ Use selectedOptionId: null for an owner decision outside the listed options. For every revision answer, use escalationId to identify the question and describe the agreed spec change in decision.
150
+ The tool saves all resolutions inside the active builder handoff before saving the next phase. Do not submit partial batches.
151
+ If no answer requires revision, set specChangeRequired: false. The tool returns ready-for-builder. Call maestro_run_builder separately without renewed approval.
152
+ If any answer requires revision, set specChangeRequired: true. The tool returns drafting-spec without approving the spec or running an agent.
153
+ Then follow the spec revision procedure below. Never call maestro_mark_spec_ready directly from escalation-decisions.
154
+
155
+ In finding-decisions, present every current finding. Every finding requires an owner decision, regardless of severity.
156
+ Collect reject, fix-code, or revise-spec for every finding. Each reject requires the owner's reason.
157
+ Each revise-spec requires findingId, decision: revise-spec, the owner's reason, and specChange describing the agreed requirement change.
158
+ The revision reason and specChange must contain nonwhitespace text.
159
+ Submit the complete batch through maestro_resolve_findings before editing the spec. Do not invent reasons or omit findings.
160
+ The tool saves every decision in the active verifier handoff before saving the next phase. Earlier numbered handoffs remain unchanged.
161
+ If any decision is revise-spec, the tool returns drafting-spec without approving the spec or running an agent.
162
+ Revision takes priority over fix-code and reject in mixed batches, while all decisions are saved.
163
+ Without revision, all rejections return candidate-ready and any fix-code returns ready-for-builder.
154
164
  For ready-for-builder, call maestro_run_builder separately. Do not fix the code yourself.
155
- If any finding requires a contract change, use the spec revision procedure instead.
156
165
 
157
- For a spec revision, remain in escalation-decision or findings-decision while revising the same specId with the owner.
166
+ For either an escalation revision or a finding revision, first record the complete batch and enter drafting-spec as described above.
167
+ Then revise the same specId with the owner.
158
168
  Do not create a new spec. Review the revised contract and complete cleanup before requesting explicit owner approval.
159
169
  Ask the owner to inspect the revised spec and reply GREEN FLAG again.
160
- Only after that explicit reply, call maestro_mark_spec_ready. After the transition succeeds, start the builder in the foreground.
161
- Previous escalations and findings become historical context. Do not resolve them as current decisions after the revision.
170
+ Only after that explicit reply, call maestro_mark_spec_ready separately. After the transition succeeds, start the builder in the foreground.
171
+ The approved revised spec controls subsequent work. Previous escalations and findings become historical context. Do not resolve them as current decisions after the revision.
162
172
  Keep their artifacts. Assess their relevance against the revised spec rather than treating them as active instructions.
163
173
 
164
174
  ### Errors and completion
@@ -14,10 +14,10 @@ export const WORKFLOW_PHASE_LABELS = {
14
14
  [WORKFLOW_PHASES.DRAFTING_SPEC]: 'Preparing specification',
15
15
  [WORKFLOW_PHASES.READY_FOR_BUILDER]: 'Ready for builder',
16
16
  [WORKFLOW_PHASES.BUILDER_RUNNING]: 'Builder running',
17
- [WORKFLOW_PHASES.ESCALATION_DECISION]: 'Owner decision needed: escalation',
17
+ [WORKFLOW_PHASES.ESCALATION_DECISIONS]: 'Owner decision needed: escalations',
18
18
  [WORKFLOW_PHASES.READY_FOR_VERIFIER]: 'Ready for verifier',
19
19
  [WORKFLOW_PHASES.VERIFIER_RUNNING]: 'Verifier running',
20
- [WORKFLOW_PHASES.FINDINGS_DECISION]: 'Owner decision needed: findings',
20
+ [WORKFLOW_PHASES.FINDING_DECISIONS]: 'Owner decision needed: findings',
21
21
  [WORKFLOW_PHASES.CANDIDATE_READY]: 'Completed',
22
22
  } as const;
23
23
 
@@ -25,10 +25,10 @@ export const WORKFLOW_PHASE_ICONS = {
25
25
  [WORKFLOW_PHASES.DRAFTING_SPEC]: '📝',
26
26
  [WORKFLOW_PHASES.READY_FOR_BUILDER]: '🚧',
27
27
  [WORKFLOW_PHASES.BUILDER_RUNNING]: '🛠️',
28
- [WORKFLOW_PHASES.ESCALATION_DECISION]: '✋',
28
+ [WORKFLOW_PHASES.ESCALATION_DECISIONS]: '✋',
29
29
  [WORKFLOW_PHASES.READY_FOR_VERIFIER]: '📋',
30
30
  [WORKFLOW_PHASES.VERIFIER_RUNNING]: '🔍',
31
- [WORKFLOW_PHASES.FINDINGS_DECISION]: '💬',
31
+ [WORKFLOW_PHASES.FINDING_DECISIONS]: '💬',
32
32
  [WORKFLOW_PHASES.CANDIDATE_READY]: '✅',
33
33
  } as const;
34
34
 
@@ -36,10 +36,10 @@ export const WORKFLOW_PHASE_COLORS = {
36
36
  [WORKFLOW_PHASES.DRAFTING_SPEC]: 'accent',
37
37
  [WORKFLOW_PHASES.READY_FOR_BUILDER]: 'accent',
38
38
  [WORKFLOW_PHASES.BUILDER_RUNNING]: 'accent',
39
- [WORKFLOW_PHASES.ESCALATION_DECISION]: 'warning',
39
+ [WORKFLOW_PHASES.ESCALATION_DECISIONS]: 'warning',
40
40
  [WORKFLOW_PHASES.READY_FOR_VERIFIER]: 'accent',
41
41
  [WORKFLOW_PHASES.VERIFIER_RUNNING]: 'accent',
42
- [WORKFLOW_PHASES.FINDINGS_DECISION]: 'warning',
42
+ [WORKFLOW_PHASES.FINDING_DECISIONS]: 'warning',
43
43
  [WORKFLOW_PHASES.CANDIDATE_READY]: 'success',
44
44
  } as const satisfies Record<WorkflowPhase, ThemeColor>;
45
45
 
@@ -14,7 +14,7 @@ export const MARK_SPEC_READY_TOOL = {
14
14
  NAME: 'maestro_mark_spec_ready',
15
15
  LABEL: 'Mark Spec Ready',
16
16
  DESCRIPTION:
17
- 'Approve the current spec.md after the owner replies GREEN FLAG and move the workflow to ready-for-builder. Only the owner reply GREEN FLAG authorizes approval. After this transition succeeds, call maestro_run_builder in the foreground.',
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
18
  } as const;
19
19
 
20
20
  const MarkSpecReadyToolParameters = Type.Object(
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Objective: Register owner resolution of every current builder escalation.
3
- * Used: When the owner keeps the approved contract and resolves all current questions.
3
+ * Used: When the owner resolves all current questions, including spec revision decisions.
4
4
  */
5
5
 
6
6
  import type { ExtensionAPI } from '@earendil-works/pi-coding-agent';
@@ -17,12 +17,13 @@ export const RESOLVE_ESCALATIONS_TOOL = {
17
17
  NAME: 'maestro_resolve_escalations',
18
18
  LABEL: 'Resolve Escalations',
19
19
  DESCRIPTION:
20
- 'Record the explicit owner decision for every current builder escalation when the approved contract remains valid. Save the resolution and workflow transition, then return ready-for-builder without running the builder. If the contract must change, edit spec.md and use maestro_mark_spec_ready instead.',
20
+ 'Record the complete owner decision batch in the active builder handoff before changing phase. Set specChangeRequired to false for ready-for-builder, or true for drafting-spec when any answer requires revision. For each revision answer, identify escalationId and describe the agreed spec change in decision. In drafting-spec, revise the same spec and obtain renewed GREEN FLAG before a separate maestro_mark_spec_ready call. This tool neither approves a spec nor runs an agent.',
21
21
  } as const;
22
22
 
23
23
  const ResolveEscalationsToolParameters = Type.Object(
24
24
  {
25
25
  specId: Type.String({ pattern: SPEC_ID_PATTERN.source }),
26
+ specChangeRequired: Type.Boolean(),
26
27
  decisions: Type.Array(
27
28
  Type.Object(
28
29
  {
@@ -44,26 +45,32 @@ export const registerResolveEscalationsTool = (pi: ExtensionAPI): void => {
44
45
  description: RESOLVE_ESCALATIONS_TOOL.DESCRIPTION,
45
46
  parameters: ResolveEscalationsToolParameters,
46
47
  async execute(_toolCallId, params, _signal, _onUpdate, context) {
47
- const { specId, decisions } = params;
48
+ const { specId, specChangeRequired, decisions } = params;
48
49
  const { paths } = await resolveToolRunContext(context.cwd);
49
50
 
50
51
  const resolved = await resolveEscalations({
51
52
  paths,
52
53
  specId,
54
+ specChangeRequired,
53
55
  decisions,
54
56
  });
55
57
 
58
+ const nextAction = specChangeRequired
59
+ ? 'Revise the same spec with the owner and obtain renewed GREEN FLAG. Then call maestro_mark_spec_ready separately before running the builder.'
60
+ : 'Run the builder separately. No renewed approval is required.';
61
+
56
62
  return {
57
63
  content: [
58
64
  {
59
65
  type: 'text',
60
- text: `All current escalations resolved. Spec ${resolved.state.specId} is ready-for-builder. Run the builder separately.`,
66
+ text: `All current escalations resolved. Spec ${resolved.state.specId} is ${resolved.state.phase}. ${nextAction}`,
61
67
  },
62
68
  ],
63
69
  details: {
64
70
  specId: resolved.state.specId,
65
71
  handoff: resolved.handoff,
66
72
  phase: resolved.state.phase,
73
+ nextAction,
67
74
  projectRoot: resolved.projectRoot,
68
75
  },
69
76
  };
@@ -16,7 +16,7 @@ export const RESOLVE_FINDINGS_TOOL = {
16
16
  NAME: 'maestro_resolve_findings',
17
17
  LABEL: 'Resolve Findings',
18
18
  DESCRIPTION:
19
- 'Record the explicit owner decision for every current verifier finding when the approved contract remains valid. Save the resolution and workflow transition. If the contract must change, edit spec.md and use maestro_mark_spec_ready instead.',
19
+ 'Record the complete explicit owner decision batch for current verifier findings before changing phase. Any revise-spec decision returns to drafting-spec without approval or an agent run. Then revise the same spec with the owner, obtain renewed GREEN FLAG, and call maestro_mark_spec_ready separately before running the builder.',
20
20
  } as const;
21
21
 
22
22
  const FindingDecisionFields = {
@@ -44,9 +44,20 @@ const FixCodeFindingDecisionSchema = Type.Object(
44
44
  },
45
45
  );
46
46
 
47
+ const ReviseSpecFindingDecisionSchema = Type.Object(
48
+ {
49
+ ...FindingDecisionFields,
50
+ decision: StringEnum([FINDING_DECISIONS.REVISE_SPEC] as const),
51
+ reason: Type.String({ minLength: 1, pattern: '\\S' }),
52
+ specChange: Type.String({ minLength: 1, pattern: '\\S' }),
53
+ },
54
+ { additionalProperties: false },
55
+ );
56
+
47
57
  const FindingDecisionSchema = Type.Union([
48
58
  RejectFindingDecisionSchema,
49
59
  FixCodeFindingDecisionSchema,
60
+ ReviseSpecFindingDecisionSchema,
50
61
  ]);
51
62
 
52
63
  const ResolveFindingsToolParameters = Type.Object(
@@ -63,6 +74,7 @@ type FindingResolutionDetails = {
63
74
  projectRoot: string;
64
75
  rejectedFindingIds: string[];
65
76
  findingsRequiringFixIds: string[];
77
+ findingsRequiringSpecRevisionIds: string[];
66
78
  };
67
79
 
68
80
  const formatFindingIds = (findingIds: string[]): string =>
@@ -72,6 +84,14 @@ const formatResolution = (details: FindingResolutionDetails): string => {
72
84
  const rejected = formatFindingIds(details.rejectedFindingIds);
73
85
  const requiringFix = formatFindingIds(details.findingsRequiringFixIds);
74
86
 
87
+ if (details.phase === WORKFLOW_PHASES.DRAFTING_SPEC) {
88
+ const requiringRevision = formatFindingIds(
89
+ details.findingsRequiringSpecRevisionIds,
90
+ );
91
+
92
+ return `Recorded rejections for ${rejected}. Findings requiring code fixes: ${requiringFix}. Findings requiring spec revision: ${requiringRevision}. The workflow is drafting-spec. Revise the same spec with the owner and obtain renewed GREEN FLAG. Then call maestro_mark_spec_ready separately before running the builder.`;
93
+ }
94
+
75
95
  if (details.phase === WORKFLOW_PHASES.READY_FOR_BUILDER) {
76
96
  return `Recorded rejections for ${rejected}. Findings requiring code fixes: ${requiringFix}. The workflow is ready-for-builder. Run the builder separately.`;
77
97
  }
@@ -109,6 +129,12 @@ export const registerResolveFindingsTool = (pi: ExtensionAPI): void => {
109
129
  ({ decision }) => decision?.decision === FINDING_DECISIONS.REJECT,
110
130
  )
111
131
  .map(({ id }) => id),
132
+ findingsRequiringSpecRevisionIds: resolved.findings
133
+ .filter(
134
+ ({ decision }) =>
135
+ decision?.decision === FINDING_DECISIONS.REVISE_SPEC,
136
+ )
137
+ .map(({ id }) => id),
112
138
  findingsRequiringFixIds: resolved.findings
113
139
  .filter(
114
140
  ({ decision }) => decision?.decision === FINDING_DECISIONS.FIX_CODE,
@@ -52,7 +52,7 @@ type BuilderRunResult =
52
52
  | {
53
53
  outcome: typeof BUILDER_HANDOFF_STATUSES.ESCALATION;
54
54
  specId: string;
55
- phase: typeof WORKFLOW_PHASES.ESCALATION_DECISION;
55
+ phase: typeof WORKFLOW_PHASES.ESCALATION_DECISIONS;
56
56
  handoff: BuilderHandoff;
57
57
  };
58
58
 
@@ -120,7 +120,7 @@ const buildRunResult = async ({
120
120
  return await readDoneBuilderResult({ paths, specId });
121
121
  }
122
122
 
123
- if (state.phase === WORKFLOW_PHASES.ESCALATION_DECISION) {
123
+ if (state.phase === WORKFLOW_PHASES.ESCALATION_DECISIONS) {
124
124
  const handoff = await readBuilderHandoff({
125
125
  path: await paths.getActiveBuilderHandoffPath(specId),
126
126
  specId,
@@ -47,9 +47,9 @@ type CandidateReadyResult = {
47
47
  };
48
48
 
49
49
  type FindingsResult = {
50
- outcome: typeof WORKFLOW_PHASES.FINDINGS_DECISION;
50
+ outcome: typeof WORKFLOW_PHASES.FINDING_DECISIONS;
51
51
  specId: string;
52
- phase: typeof WORKFLOW_PHASES.FINDINGS_DECISION;
52
+ phase: typeof WORKFLOW_PHASES.FINDING_DECISIONS;
53
53
  handoff: VerifierHandoff;
54
54
  };
55
55
 
@@ -70,7 +70,7 @@ const readTerminalVerifierResult = async ({
70
70
  > => {
71
71
  if (
72
72
  state.phase !== WORKFLOW_PHASES.CANDIDATE_READY &&
73
- state.phase !== WORKFLOW_PHASES.FINDINGS_DECISION
73
+ state.phase !== WORKFLOW_PHASES.FINDING_DECISIONS
74
74
  ) {
75
75
  throw new Error(
76
76
  `Verifier returned with unexpected workflow phase "${state.phase}".`,
@@ -84,7 +84,7 @@ const readTerminalVerifierResult = async ({
84
84
 
85
85
  const expectedPhase =
86
86
  handoff.findings.length > 0
87
- ? WORKFLOW_PHASES.FINDINGS_DECISION
87
+ ? WORKFLOW_PHASES.FINDING_DECISIONS
88
88
  : WORKFLOW_PHASES.CANDIDATE_READY;
89
89
 
90
90
  if (state.phase !== expectedPhase) {
@@ -3,7 +3,7 @@
3
3
  * Used: When the builder reports done or escalation through the child tool.
4
4
  */
5
5
 
6
- import { mkdir } from 'node:fs/promises';
6
+ import { mkdir, readFile } from 'node:fs/promises';
7
7
  import { Value } from 'typebox/value';
8
8
  import { assertBuilderHandoff } from '#artifacts/builder-handoff/assertBuilderHandoff.ts';
9
9
  import {
@@ -19,6 +19,7 @@ import { readWorkflowState } from '#workflow/state/readWorkflowState.ts';
19
19
  import { WORKFLOW_PHASES, type WorkflowState } from '#workflow/state/schema.ts';
20
20
  import { writeWorkflowState } from '#workflow/state/writeWorkflowState.ts';
21
21
  import { transitionWorkflow } from '#workflow/transitions.ts';
22
+ import { assertSpecAcceptanceCriteria } from '#workflow/utils/assertSpecAcceptanceCriteria.ts';
22
23
 
23
24
  export type CompletedBuilderPass = {
24
25
  handoff: BuilderHandoff;
@@ -76,6 +77,11 @@ export const completeBuilderPass = async ({
76
77
  assertBuilderHandoff(handoffInput);
77
78
  const { handoff } = handoffInput;
78
79
 
80
+ assertSpecAcceptanceCriteria({
81
+ markdown: await readFile(paths.getSpecFilePath(specId), 'utf8'),
82
+ criterionIds: new Set(handoff.acceptanceCriteria.map(({ id }) => id)),
83
+ });
84
+
79
85
  const nextState = transitionWorkflow({
80
86
  state: currentState,
81
87
  event: getWorkflowEventByBuilderHandoff(handoff),
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Objective: Save all owner resolutions in the active builder handoff.
3
- * Used: When the owner keeps the contract and resolves current questions together.
3
+ * Used: When the owner resolves current questions, with or without spec revision.
4
4
  */
5
5
 
6
6
  import { readBuilderHandoff } from '#artifacts/builder-handoff/readBuilderHandoff.ts';
@@ -10,8 +10,9 @@ import {
10
10
  } from '#artifacts/builder-handoff/schema.ts';
11
11
  import { writeBuilderHandoff } from '#artifacts/builder-handoff/writeBuilderHandoff.ts';
12
12
  import type { MaestroPaths } from '#MaestroPaths.ts';
13
+ import { getWorkflowEventBySpecChangeRequired } from '#workflow/builder/utils/getWorkflowEventBySpecChangeRequired.ts';
13
14
  import { readWorkflowState } from '#workflow/state/readWorkflowState.ts';
14
- import { WORKFLOW_EVENTS, WORKFLOW_PHASES } from '#workflow/state/schema.ts';
15
+ import { WORKFLOW_PHASES } from '#workflow/state/schema.ts';
15
16
  import { writeWorkflowState } from '#workflow/state/writeWorkflowState.ts';
16
17
  import { transitionWorkflow } from '#workflow/transitions.ts';
17
18
 
@@ -22,12 +23,14 @@ export type EscalationDecision = EscalationResolution & {
22
23
  type ResolveEscalationsInput = {
23
24
  paths: MaestroPaths;
24
25
  specId: string;
26
+ specChangeRequired: boolean;
25
27
  decisions: EscalationDecision[];
26
28
  };
27
29
 
28
30
  export const resolveEscalations = async ({
29
31
  paths,
30
32
  specId,
33
+ specChangeRequired,
31
34
  decisions,
32
35
  }: ResolveEscalationsInput) => {
33
36
  const workflowPath = paths.getWorkflowPath(specId);
@@ -35,9 +38,9 @@ export const resolveEscalations = async ({
35
38
 
36
39
  if (state.specId !== specId) throw new Error('Workflow spec ID mismatch.');
37
40
 
38
- if (state.phase !== WORKFLOW_PHASES.ESCALATION_DECISION) {
41
+ if (state.phase !== WORKFLOW_PHASES.ESCALATION_DECISIONS) {
39
42
  throw new Error(
40
- `Escalation resolution requires escalation-decision state, found "${state.phase}".`,
43
+ `Escalation resolution requires escalation-decisions state, found "${state.phase}".`,
41
44
  );
42
45
  }
43
46
 
@@ -81,7 +84,7 @@ export const resolveEscalations = async ({
81
84
 
82
85
  const nextState = transitionWorkflow({
83
86
  state,
84
- event: WORKFLOW_EVENTS.RESOLVE_ESCALATION,
87
+ event: getWorkflowEventBySpecChangeRequired(specChangeRequired),
85
88
  });
86
89
 
87
90
  // The artifact writer validates every resolution before the first protocol write.
@@ -17,7 +17,7 @@ export const getWorkflowEventByBuilderHandoff = ({
17
17
  }
18
18
 
19
19
  if (status === BUILDER_HANDOFF_STATUSES.ESCALATION) {
20
- return WORKFLOW_EVENTS.OPEN_ESCALATION;
20
+ return WORKFLOW_EVENTS.BUILDER_OPEN_ESCALATIONS;
21
21
  }
22
22
 
23
23
  throw new Error(`Unexpected builder handoff status: "${status}".`);
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Objective: Select the workflow event for resolving escalations.
3
+ * Used: When owner answers determine whether the spec must change.
4
+ */
5
+
6
+ import { WORKFLOW_EVENTS, type WorkflowEvent } from '#workflow/state/schema.ts';
7
+
8
+ export const getWorkflowEventBySpecChangeRequired = (
9
+ specChangeRequired: boolean,
10
+ ): WorkflowEvent => {
11
+ if (specChangeRequired) {
12
+ return WORKFLOW_EVENTS.OWNER_REQUEST_SPEC_REVISION;
13
+ }
14
+
15
+ return WORKFLOW_EVENTS.RESOLVE_ESCALATIONS;
16
+ };
@@ -13,34 +13,35 @@ export const WORKFLOW_PHASES = {
13
13
  DRAFTING_SPEC: 'drafting-spec',
14
14
  READY_FOR_BUILDER: 'ready-for-builder',
15
15
  BUILDER_RUNNING: 'builder-running',
16
- ESCALATION_DECISION: 'escalation-decision',
16
+ ESCALATION_DECISIONS: 'escalation-decisions',
17
17
  READY_FOR_VERIFIER: 'ready-for-verifier',
18
18
  VERIFIER_RUNNING: 'verifier-running',
19
- FINDINGS_DECISION: 'findings-decision',
19
+ FINDING_DECISIONS: 'finding-decisions',
20
20
  CANDIDATE_READY: 'candidate-ready',
21
21
  } as const;
22
22
 
23
23
  export const WORKFLOW_EVENTS = {
24
24
  MARK_SPEC_READY: 'mark-spec-ready',
25
25
  RUN_BUILDER: 'run-builder',
26
- OPEN_ESCALATION: 'open-escalation',
27
- RESOLVE_ESCALATION: 'resolve-escalation',
26
+ BUILDER_OPEN_ESCALATIONS: 'builder-open-escalations',
27
+ RESOLVE_ESCALATIONS: 'resolve-escalations',
28
+ OWNER_REQUEST_SPEC_REVISION: 'owner-request-spec-revision',
28
29
  BUILDER_DONE: 'builder-done',
29
30
  RUN_VERIFIER: 'run-verifier',
30
31
  VERIFIER_FOUND_FINDINGS: 'verifier-found-findings',
31
- VERIFIER_APPROVED: 'verifier-approved',
32
- REJECT_FINDINGS: 'reject-findings',
33
- REQUEST_FIXES: 'request-fixes',
32
+ VERIFIER_DONE: 'verifier-done',
33
+ OWNER_REJECT_FINDINGS: 'owner-reject-findings',
34
+ OWNER_REQUEST_FIXES: 'owner-request-fixes',
34
35
  } as const;
35
36
 
36
37
  export const WorkflowPhaseSchema = Type.Union([
37
38
  Type.Literal(WORKFLOW_PHASES.DRAFTING_SPEC),
38
39
  Type.Literal(WORKFLOW_PHASES.READY_FOR_BUILDER),
39
40
  Type.Literal(WORKFLOW_PHASES.BUILDER_RUNNING),
40
- Type.Literal(WORKFLOW_PHASES.ESCALATION_DECISION),
41
+ Type.Literal(WORKFLOW_PHASES.ESCALATION_DECISIONS),
41
42
  Type.Literal(WORKFLOW_PHASES.READY_FOR_VERIFIER),
42
43
  Type.Literal(WORKFLOW_PHASES.VERIFIER_RUNNING),
43
- Type.Literal(WORKFLOW_PHASES.FINDINGS_DECISION),
44
+ Type.Literal(WORKFLOW_PHASES.FINDING_DECISIONS),
44
45
  Type.Literal(WORKFLOW_PHASES.CANDIDATE_READY),
45
46
  ]);
46
47
 
@@ -19,25 +19,28 @@ type WorkflowTransition = {
19
19
 
20
20
  export const WORKFLOW_TRANSITIONS: Record<WorkflowEvent, WorkflowTransition> = {
21
21
  [WORKFLOW_EVENTS.MARK_SPEC_READY]: {
22
- from: [
23
- WORKFLOW_PHASES.DRAFTING_SPEC,
24
- WORKFLOW_PHASES.ESCALATION_DECISION,
25
- WORKFLOW_PHASES.FINDINGS_DECISION,
26
- ],
22
+ from: [WORKFLOW_PHASES.DRAFTING_SPEC],
27
23
  to: WORKFLOW_PHASES.READY_FOR_BUILDER,
28
24
  },
29
25
  [WORKFLOW_EVENTS.RUN_BUILDER]: {
30
26
  from: [WORKFLOW_PHASES.READY_FOR_BUILDER],
31
27
  to: WORKFLOW_PHASES.BUILDER_RUNNING,
32
28
  },
33
- [WORKFLOW_EVENTS.OPEN_ESCALATION]: {
29
+ [WORKFLOW_EVENTS.BUILDER_OPEN_ESCALATIONS]: {
34
30
  from: [WORKFLOW_PHASES.BUILDER_RUNNING],
35
- to: WORKFLOW_PHASES.ESCALATION_DECISION,
31
+ to: WORKFLOW_PHASES.ESCALATION_DECISIONS,
36
32
  },
37
- [WORKFLOW_EVENTS.RESOLVE_ESCALATION]: {
38
- from: [WORKFLOW_PHASES.ESCALATION_DECISION],
33
+ [WORKFLOW_EVENTS.RESOLVE_ESCALATIONS]: {
34
+ from: [WORKFLOW_PHASES.ESCALATION_DECISIONS],
39
35
  to: WORKFLOW_PHASES.READY_FOR_BUILDER,
40
36
  },
37
+ [WORKFLOW_EVENTS.OWNER_REQUEST_SPEC_REVISION]: {
38
+ from: [
39
+ WORKFLOW_PHASES.ESCALATION_DECISIONS,
40
+ WORKFLOW_PHASES.FINDING_DECISIONS,
41
+ ],
42
+ to: WORKFLOW_PHASES.DRAFTING_SPEC,
43
+ },
41
44
  [WORKFLOW_EVENTS.BUILDER_DONE]: {
42
45
  from: [WORKFLOW_PHASES.BUILDER_RUNNING],
43
46
  to: WORKFLOW_PHASES.READY_FOR_VERIFIER,
@@ -48,18 +51,18 @@ export const WORKFLOW_TRANSITIONS: Record<WorkflowEvent, WorkflowTransition> = {
48
51
  },
49
52
  [WORKFLOW_EVENTS.VERIFIER_FOUND_FINDINGS]: {
50
53
  from: [WORKFLOW_PHASES.VERIFIER_RUNNING],
51
- to: WORKFLOW_PHASES.FINDINGS_DECISION,
54
+ to: WORKFLOW_PHASES.FINDING_DECISIONS,
52
55
  },
53
- [WORKFLOW_EVENTS.VERIFIER_APPROVED]: {
56
+ [WORKFLOW_EVENTS.VERIFIER_DONE]: {
54
57
  from: [WORKFLOW_PHASES.VERIFIER_RUNNING],
55
58
  to: WORKFLOW_PHASES.CANDIDATE_READY,
56
59
  },
57
- [WORKFLOW_EVENTS.REJECT_FINDINGS]: {
58
- from: [WORKFLOW_PHASES.FINDINGS_DECISION],
60
+ [WORKFLOW_EVENTS.OWNER_REJECT_FINDINGS]: {
61
+ from: [WORKFLOW_PHASES.FINDING_DECISIONS],
59
62
  to: WORKFLOW_PHASES.CANDIDATE_READY,
60
63
  },
61
- [WORKFLOW_EVENTS.REQUEST_FIXES]: {
62
- from: [WORKFLOW_PHASES.FINDINGS_DECISION],
64
+ [WORKFLOW_EVENTS.OWNER_REQUEST_FIXES]: {
65
+ from: [WORKFLOW_PHASES.FINDING_DECISIONS],
63
66
  to: WORKFLOW_PHASES.READY_FOR_BUILDER,
64
67
  },
65
68
  };
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Objective: Require every spec criterion ID in a handoff.
3
+ * Used: Before builder and verifier submissions save their reports.
4
+ */
5
+
6
+ type AssertSpecAcceptanceCriteriaInput = {
7
+ markdown: string;
8
+ criterionIds: Set<string>;
9
+ };
10
+
11
+ export function assertSpecAcceptanceCriteria({
12
+ markdown,
13
+ criterionIds,
14
+ }: AssertSpecAcceptanceCriteriaInput): void {
15
+ const missingIds = [...markdown.matchAll(/^### AC(\d+):/gm)]
16
+ .map((match) => `AC${match[1]}`)
17
+ .filter((id) => !criterionIds.has(id));
18
+
19
+ if (missingIds.length > 0) {
20
+ throw new Error(
21
+ `Handoff is missing acceptance criterion IDs from spec.md: ${missingIds.join(', ')}.`,
22
+ );
23
+ }
24
+ }
@@ -9,13 +9,13 @@ import { WORKFLOW_PHASES, type WorkflowState } from '#workflow/state/schema.ts';
9
9
 
10
10
  const BUILDER_PHASES: readonly WorkflowState['phase'][] = [
11
11
  WORKFLOW_PHASES.BUILDER_RUNNING,
12
- WORKFLOW_PHASES.ESCALATION_DECISION,
12
+ WORKFLOW_PHASES.ESCALATION_DECISIONS,
13
13
  WORKFLOW_PHASES.READY_FOR_VERIFIER,
14
14
  ];
15
15
 
16
16
  const VERIFIER_PHASES: readonly WorkflowState['phase'][] = [
17
17
  WORKFLOW_PHASES.VERIFIER_RUNNING,
18
- WORKFLOW_PHASES.FINDINGS_DECISION,
18
+ WORKFLOW_PHASES.FINDING_DECISIONS,
19
19
  ];
20
20
 
21
21
  export const getAgentRole = (
@@ -3,7 +3,7 @@
3
3
  * Used: When the verifier submits its terminal handoff.
4
4
  */
5
5
 
6
- import { mkdir } from 'node:fs/promises';
6
+ import { mkdir, readFile } from 'node:fs/promises';
7
7
  import { assertVerifierHandoff } from '#artifacts/verifier-handoff/assertVerifierHandoff.ts';
8
8
  import type { VerifierHandoff } from '#artifacts/verifier-handoff/schema.ts';
9
9
  import { writeVerifierHandoff } from '#artifacts/verifier-handoff/writeVerifierHandoff.ts';
@@ -12,6 +12,7 @@ import { readWorkflowState } from '#workflow/state/readWorkflowState.ts';
12
12
  import { WORKFLOW_PHASES, type WorkflowState } from '#workflow/state/schema.ts';
13
13
  import { writeWorkflowState } from '#workflow/state/writeWorkflowState.ts';
14
14
  import { transitionWorkflow } from '#workflow/transitions.ts';
15
+ import { assertSpecAcceptanceCriteria } from '#workflow/utils/assertSpecAcceptanceCriteria.ts';
15
16
  import { getWorkflowEventByVerifierHandoff } from '#workflow/verifier/utils/getWorkflowEventByVerifierHandoff.ts';
16
17
 
17
18
  export type CompletedVerifierPass = {
@@ -56,6 +57,11 @@ export const completeVerifierPass = async ({
56
57
  assertVerifierHandoff(handoffInput);
57
58
  const { handoff } = handoffInput;
58
59
 
60
+ assertSpecAcceptanceCriteria({
61
+ markdown: await readFile(paths.getSpecFilePath(specId), 'utf8'),
62
+ criterionIds: new Set(handoff.acceptanceCriteria.map(({ id }) => id)),
63
+ });
64
+
59
65
  const nextState = transitionWorkflow({
60
66
  state: currentState,
61
67
  event: getWorkflowEventByVerifierHandoff(handoff),
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Objective: Record owner decisions for current verifier findings on the active handoff.
3
- * Used: When the owner resolves a findings-decision handoff.
3
+ * Used: When the owner resolves a finding-decisions handoff.
4
4
  */
5
5
 
6
6
  import { assertVerifierHandoff } from '#artifacts/verifier-handoff/assertVerifierHandoff.ts';
@@ -29,7 +29,17 @@ type FindingDecisionApproved = {
29
29
  decision: typeof FINDING_DECISIONS.FIX_CODE;
30
30
  };
31
31
 
32
- export type FindingDecision = FindingDecisionApproved | FindingDecisionRejected;
32
+ type FindingDecisionRevision = {
33
+ findingId: string;
34
+ decision: typeof FINDING_DECISIONS.REVISE_SPEC;
35
+ reason: string;
36
+ specChange: string;
37
+ };
38
+
39
+ export type FindingDecision =
40
+ | FindingDecisionApproved
41
+ | FindingDecisionRejected
42
+ | FindingDecisionRevision;
33
43
 
34
44
  export type ResolvedFindings = {
35
45
  findings: VerifierFinding[];
@@ -98,9 +108,9 @@ const readFindingWorkflow = async ({
98
108
  );
99
109
  }
100
110
 
101
- if (state.phase !== WORKFLOW_PHASES.FINDINGS_DECISION) {
111
+ if (state.phase !== WORKFLOW_PHASES.FINDING_DECISIONS) {
102
112
  throw new Error(
103
- `Finding resolution requires findings-decision state, found "${state.phase}".`,
113
+ `Finding resolution requires finding-decisions state, found "${state.phase}".`,
104
114
  );
105
115
  }
106
116
 
@@ -143,6 +153,17 @@ const buildFindingWorkflow = ({
143
153
  const findings = handoff.findings.map((finding) => {
144
154
  const decision = decisionsById.get(finding.id);
145
155
 
156
+ if (decision?.decision === FINDING_DECISIONS.REVISE_SPEC) {
157
+ return {
158
+ ...finding,
159
+ decision: {
160
+ decision: FINDING_DECISIONS.REVISE_SPEC,
161
+ reason: decision.reason,
162
+ specChange: decision.specChange,
163
+ },
164
+ };
165
+ }
166
+
146
167
  if (decision?.decision === FINDING_DECISIONS.REJECT) {
147
168
  return {
148
169
  ...finding,
@@ -13,19 +13,27 @@ export const getWorkflowEventByVerifierHandoff = (
13
13
  handoff: VerifierHandoff,
14
14
  ): WorkflowEvent => {
15
15
  if (handoff.findings.length === 0) {
16
- return WORKFLOW_EVENTS.VERIFIER_APPROVED;
16
+ return WORKFLOW_EVENTS.VERIFIER_DONE;
17
17
  }
18
18
 
19
19
  if (handoff.findings.every(({ decision }) => decision === null)) {
20
20
  return WORKFLOW_EVENTS.VERIFIER_FOUND_FINDINGS;
21
21
  }
22
22
 
23
+ if (
24
+ handoff.findings.some(
25
+ ({ decision }) => decision?.decision === FINDING_DECISIONS.REVISE_SPEC,
26
+ )
27
+ ) {
28
+ return WORKFLOW_EVENTS.OWNER_REQUEST_SPEC_REVISION;
29
+ }
30
+
23
31
  if (
24
32
  handoff.findings.some(
25
33
  ({ decision }) => decision?.decision === FINDING_DECISIONS.FIX_CODE,
26
34
  )
27
35
  ) {
28
- return WORKFLOW_EVENTS.REQUEST_FIXES;
36
+ return WORKFLOW_EVENTS.OWNER_REQUEST_FIXES;
29
37
  }
30
38
 
31
39
  if (
@@ -33,7 +41,7 @@ export const getWorkflowEventByVerifierHandoff = (
33
41
  ({ decision }) => decision?.decision === FINDING_DECISIONS.REJECT,
34
42
  )
35
43
  ) {
36
- return WORKFLOW_EVENTS.REJECT_FINDINGS;
44
+ return WORKFLOW_EVENTS.OWNER_REJECT_FINDINGS;
37
45
  }
38
46
 
39
47
  throw new Error('Unexpected verifier finding decisions.');