@emiliosp/pi-maestro 0.5.2 → 0.6.1

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 (65) hide show
  1. package/README.md +38 -41
  2. package/agents/builder.md +16 -18
  3. package/agents/verifier.md +24 -21
  4. package/docs/configuration.md +16 -24
  5. package/docs/subagent-integration.md +18 -40
  6. package/docs/workflow.md +106 -169
  7. package/package.json +1 -2
  8. package/src/MaestroPaths.ts +111 -47
  9. package/src/artifacts/builder-handoff/assertBuilderHandoff.ts +2 -15
  10. package/src/artifacts/builder-handoff/readBuilderHandoff.ts +0 -3
  11. package/src/artifacts/builder-handoff/schema.ts +1 -1
  12. package/src/artifacts/builder-handoff/writeBuilderHandoff.ts +3 -5
  13. package/src/artifacts/escalation/createEscalation.ts +7 -7
  14. package/src/artifacts/escalation/getNextEscalationId.ts +0 -3
  15. package/src/artifacts/escalation/readEscalation.ts +1 -11
  16. package/src/artifacts/escalation/readEscalationHistory.ts +0 -3
  17. package/src/artifacts/escalation/resolveEscalation.ts +2 -12
  18. package/src/artifacts/escalation/schema.ts +0 -1
  19. package/src/artifacts/verifier-handoff/assertVerifierHandoff.ts +2 -9
  20. package/src/artifacts/verifier-handoff/readVerifierHandoff.ts +0 -3
  21. package/src/artifacts/verifier-handoff/schema.ts +20 -6
  22. package/src/artifacts/verifier-handoff/writeVerifierHandoff.ts +7 -7
  23. package/src/config/loadConfiguration.ts +18 -18
  24. package/src/maestro/checks/assertEnvironment.ts +13 -17
  25. package/src/maestro/instructions/getMaestroInstructions.ts +33 -24
  26. package/src/specs/create.ts +0 -2
  27. package/src/tools/child/open-escalation.ts +3 -4
  28. package/src/tools/child/record-builder-handoff.ts +4 -4
  29. package/src/tools/child/record-verifier-handoff.ts +13 -26
  30. package/src/tools/child/utils/resolveWorkflowContext.ts +3 -3
  31. package/src/tools/main/mark-spec-ready.ts +2 -2
  32. package/src/tools/main/resolve-escalation.ts +2 -4
  33. package/src/tools/main/resolve-findings.ts +17 -15
  34. package/src/tools/main/run-builder.ts +10 -38
  35. package/src/tools/main/run-verifier.ts +6 -33
  36. package/src/tools/utils/resolveToolRunContext.ts +8 -8
  37. package/src/utils/path-strictly-within.ts +4 -4
  38. package/src/utils/write-json.ts +24 -0
  39. package/src/workflow/builder/completeBuilderPass.ts +6 -15
  40. package/src/workflow/builder/prepareBuilderRun.ts +3 -34
  41. package/src/workflow/escalation/openBuilderEscalation.ts +2 -10
  42. package/src/workflow/escalation/resolveBuilderEscalation.ts +9 -14
  43. package/src/workflow/findings/resolveFindings.ts +25 -135
  44. package/src/workflow/spec/markSpecReady.ts +0 -1
  45. package/src/workflow/state/readWorkflowState.ts +2 -2
  46. package/src/workflow/state/schema.ts +0 -1
  47. package/src/workflow/state/writeWorkflowState.ts +7 -63
  48. package/src/workflow/transitions.ts +2 -2
  49. package/src/workflow/verifier/completeVerifierPass.ts +8 -14
  50. package/src/workflow/verifier/prepareVerifierRun.ts +4 -41
  51. package/src/artifacts/verifier-handoff/rejectVerifierFinding.ts +0 -54
  52. package/src/git/command.ts +0 -172
  53. package/src/git/commits/createCommit.ts +0 -76
  54. package/src/git/commits/createWorkflowCheckpointCommit.ts +0 -24
  55. package/src/git/commits/findCommitByMessage.ts +0 -39
  56. package/src/git/commits/getStagedPaths.ts +0 -23
  57. package/src/git/history/getParentCommit.ts +0 -25
  58. package/src/git/repository/assertRepositoryTrusted.ts +0 -16
  59. package/src/git/repository/findRepositoryRoot.ts +0 -19
  60. package/src/git/repository/getCurrentBranch.ts +0 -18
  61. package/src/git/repository/getHeadCommit.ts +0 -18
  62. package/src/git/repository/getRepositoryStatus.ts +0 -74
  63. package/src/git/utils/hasGitExitCode.ts +0 -19
  64. package/src/utils/write-atomically.ts +0 -41
  65. package/src/utils/write-json-atomically.ts +0 -28
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Objective: Resolve the current builder escalation and checkpoint the decision.
2
+ * Objective: Resolve the current builder escalation and save the decision.
3
3
  * Used: When the owner keeps the approved contract and makes a decision.
4
4
  */
5
5
 
@@ -9,7 +9,6 @@ import type {
9
9
  Escalation,
10
10
  EscalationResolution,
11
11
  } from '#artifacts/escalation/schema.ts';
12
- import { createWorkflowCheckpointCommit } from '#git/commits/createWorkflowCheckpointCommit.ts';
13
12
  import type { MaestroPaths } from '#MaestroPaths.ts';
14
13
  import { readWorkflowState } from '#workflow/state/readWorkflowState.ts';
15
14
  import {
@@ -23,8 +22,7 @@ import { transitionWorkflow } from '#workflow/transitions.ts';
23
22
  export type ResolvedBuilderEscalation = {
24
23
  escalation: Escalation;
25
24
  state: WorkflowState;
26
- repositoryRoot: string;
27
- checkpointCommit: string;
25
+ projectRoot: string;
28
26
  };
29
27
 
30
28
  type AssertCurrentEscalationInput<CurrentEscalation = Escalation | undefined> =
@@ -70,6 +68,12 @@ export const resolveBuilderEscalation = async ({
70
68
  const escalationsPath = paths.getEscalationsPath(specId);
71
69
  const currentState = await readWorkflowState(workflowPath);
72
70
 
71
+ if (currentState.specId !== specId) {
72
+ throw new Error(
73
+ `Workflow spec ID mismatch: expected "${specId}", found "${currentState.specId}".`,
74
+ );
75
+ }
76
+
73
77
  if (currentState.phase !== WORKFLOW_PHASES.ESCALATION_DECISION) {
74
78
  throw new Error(
75
79
  `Escalation resolution requires escalation-decision state, found "${currentState.phase}".`,
@@ -79,7 +83,6 @@ export const resolveBuilderEscalation = async ({
79
83
  const history = await readEscalationHistory({
80
84
  directory: escalationsPath,
81
85
  specId,
82
- currentRevision: currentState.revision,
83
86
  });
84
87
 
85
88
  const escalationInput = { currentEscalation: history.at(-1), escalationId };
@@ -99,25 +102,17 @@ export const resolveBuilderEscalation = async ({
99
102
  const resolvedEscalation = await resolveEscalation({
100
103
  path: escalationPath,
101
104
  specId,
102
- revision: nextState.revision,
103
105
  resolution,
104
106
  });
105
107
 
106
108
  await writeWorkflowState({
107
109
  path: workflowPath,
108
110
  state: nextState,
109
- currentRevision: currentState.revision,
110
- });
111
-
112
- const checkpointCommit = await createWorkflowCheckpointCommit({
113
- repositoryRoot: paths.getRepositoryRoot(),
114
- expectedPaths: [workflowPath, escalationPath],
115
111
  });
116
112
 
117
113
  return {
118
114
  escalation: resolvedEscalation,
119
115
  state: nextState,
120
- repositoryRoot: paths.getRepositoryRoot(),
121
- checkpointCommit,
116
+ projectRoot: paths.getProjectRoot(),
122
117
  };
123
118
  };
@@ -1,21 +1,17 @@
1
1
  /**
2
- * Objective: Record owner decisions for current verifier findings on the current branch.
2
+ * Objective: Record owner decisions for current verifier findings on the active handoff.
3
3
  * Used: When the owner resolves a findings-decision handoff.
4
4
  */
5
5
 
6
- import { relative } from 'node:path';
7
6
  import { assertVerifierHandoff } from '#artifacts/verifier-handoff/assertVerifierHandoff.ts';
8
7
  import { readVerifierHandoff } from '#artifacts/verifier-handoff/readVerifierHandoff.ts';
9
8
  import type {
10
9
  VerifierFinding,
11
10
  VerifierHandoff,
12
11
  } from '#artifacts/verifier-handoff/schema.ts';
13
- import { runGitCommand } from '#git/command.ts';
14
- import { createWorkflowCheckpointCommit } from '#git/commits/createWorkflowCheckpointCommit.ts';
15
- import { getHeadCommit } from '#git/repository/getHeadCommit.ts';
16
- import { getRepositoryStatus } from '#git/repository/getRepositoryStatus.ts';
12
+ import { FINDING_DECISIONS } from '#artifacts/verifier-handoff/schema.ts';
17
13
  import type { MaestroPaths } from '#MaestroPaths.ts';
18
- import { writeJsonAtomically } from '#utils/write-json-atomically.ts';
14
+ import { writeJson } from '#utils/write-json.ts';
19
15
  import { readWorkflowState } from '#workflow/state/readWorkflowState.ts';
20
16
  import {
21
17
  WORKFLOW_EVENTS,
@@ -25,11 +21,6 @@ import {
25
21
  import { writeWorkflowState } from '#workflow/state/writeWorkflowState.ts';
26
22
  import { transitionWorkflow } from '#workflow/transitions.ts';
27
23
 
28
- export const FINDING_DECISIONS = {
29
- REJECT: 'reject',
30
- FIX_CODE: 'fix-code',
31
- } as const;
32
-
33
24
  type FindingDecisionRejected = {
34
25
  findingId: string;
35
26
  decision: typeof FINDING_DECISIONS.REJECT;
@@ -46,8 +37,7 @@ export type FindingDecision = FindingDecisionApproved | FindingDecisionRejected;
46
37
  export type ResolvedFindings = {
47
38
  findings: VerifierFinding[];
48
39
  state: WorkflowState;
49
- repositoryRoot: string;
50
- checkpointCommit: string;
40
+ projectRoot: string;
51
41
  };
52
42
 
53
43
  type AssertDecisionsInput = {
@@ -118,9 +108,8 @@ const readFindingWorkflow = async ({
118
108
  }
119
109
 
120
110
  const handoff = await readVerifierHandoff({
121
- path: paths.getVerifierHandoffPath(specId),
111
+ path: await paths.getActiveVerifierHandoffPath(specId),
122
112
  specId,
123
- revision: state.revision,
124
113
  });
125
114
 
126
115
  if (handoff.findings.length === 0) {
@@ -129,8 +118,10 @@ const readFindingWorkflow = async ({
129
118
  );
130
119
  }
131
120
 
132
- if (handoff.findings.some(({ rejection }) => rejection !== null)) {
133
- throw new Error('Current verifier findings already contain a rejection.');
121
+ if (handoff.findings.some(({ decision }) => decision !== null)) {
122
+ throw new Error(
123
+ 'Current verifier findings already contain an owner decision.',
124
+ );
134
125
  }
135
126
 
136
127
  return { state, handoff };
@@ -155,11 +146,17 @@ const buildFindingWorkflow = ({
155
146
  const findings = handoff.findings.map((finding) => {
156
147
  const decision = decisionsById.get(finding.id);
157
148
 
158
- if (decision?.decision !== FINDING_DECISIONS.REJECT) {
159
- return finding;
149
+ if (decision?.decision === FINDING_DECISIONS.REJECT) {
150
+ return {
151
+ ...finding,
152
+ decision: {
153
+ decision: FINDING_DECISIONS.REJECT,
154
+ reason: decision.reason.trim(),
155
+ },
156
+ };
160
157
  }
161
158
 
162
- return { ...finding, rejection: { reason: decision.reason.trim() } };
159
+ return { ...finding, decision: { decision: FINDING_DECISIONS.FIX_CODE } };
163
160
  });
164
161
 
165
162
  const isFixCodeRequested = decisions.some(
@@ -173,121 +170,15 @@ const buildFindingWorkflow = ({
173
170
  : WORKFLOW_EVENTS.REJECT_FINDINGS,
174
171
  });
175
172
 
176
- const nextHandoff = { ...handoff, revision: nextState.revision, findings };
173
+ const nextHandoff = { ...handoff, findings };
177
174
  assertVerifierHandoff({
178
175
  handoff: nextHandoff,
179
176
  specId: state.specId,
180
- revision: nextState.revision,
181
177
  });
182
178
 
183
179
  return { state: nextState, handoff: nextHandoff };
184
180
  };
185
181
 
186
- type AssertNoChangesOutsideFindingArtifactsInput = {
187
- repositoryRoot: string;
188
- workflowPath: string;
189
- handoffPath: string;
190
- };
191
-
192
- async function assertNoChangesOutsideFindingArtifacts({
193
- repositoryRoot,
194
- workflowPath,
195
- handoffPath,
196
- }: AssertNoChangesOutsideFindingArtifactsInput): Promise<void> {
197
- const headCommit = await getHeadCommit(repositoryRoot);
198
-
199
- // Compare both the checkout and index with HEAD. The index comparison also
200
- // catches staged changes when the working file was restored on disk.
201
- const [diff, stagedDiff, status] = await Promise.all([
202
- runGitCommand({
203
- arguments: [
204
- 'diff',
205
- '--no-renames',
206
- '--name-only',
207
- '-z',
208
- headCommit,
209
- '--',
210
- ],
211
- cwd: repositoryRoot,
212
- }),
213
- runGitCommand({
214
- arguments: [
215
- 'diff',
216
- '--cached',
217
- '--no-renames',
218
- '--name-only',
219
- '-z',
220
- headCommit,
221
- '--',
222
- ],
223
- cwd: repositoryRoot,
224
- }),
225
- getRepositoryStatus(repositoryRoot),
226
- ]);
227
-
228
- const allowedPaths = new Set([
229
- relative(repositoryRoot, workflowPath),
230
- relative(repositoryRoot, handoffPath),
231
- ]);
232
-
233
- const changedTrackedPaths = [diff.stdout, stagedDiff.stdout]
234
- .flatMap((output) => output.split('\0'))
235
- .filter((path) => path.length > 0);
236
-
237
- if (
238
- changedTrackedPaths.some((path) => !allowedPaths.has(path)) ||
239
- status.untracked.some((path) => !allowedPaths.has(path))
240
- ) {
241
- throw new Error(
242
- 'Finding resolution requires no changes outside its protocol files.',
243
- );
244
- }
245
- }
246
-
247
- type CommitFindingWorkflowInput = {
248
- paths: MaestroPaths;
249
- specId: string;
250
- currentRevision: number;
251
- workflow: FindingWorkflow;
252
- };
253
-
254
- const commitFindingWorkflow = async ({
255
- paths,
256
- specId,
257
- currentRevision,
258
- workflow,
259
- }: CommitFindingWorkflowInput): Promise<string> => {
260
- const repositoryRoot = paths.getRepositoryRoot();
261
- const workflowPath = paths.getWorkflowPath(specId);
262
- const handoffPath = paths.getVerifierHandoffPath(specId);
263
-
264
- await assertNoChangesOutsideFindingArtifacts({
265
- repositoryRoot,
266
- workflowPath,
267
- handoffPath,
268
- });
269
-
270
- await writeJsonAtomically({ path: handoffPath, data: workflow.handoff });
271
- await writeWorkflowState({
272
- path: workflowPath,
273
- state: workflow.state,
274
- currentRevision,
275
- });
276
-
277
- const checkpointCommit = await createWorkflowCheckpointCommit({
278
- repositoryRoot,
279
- expectedPaths: [handoffPath, workflowPath],
280
- });
281
-
282
- if (!(await getRepositoryStatus(repositoryRoot)).clean) {
283
- throw new Error(
284
- 'Finding resolution requires a clean checkout after its commit.',
285
- );
286
- }
287
-
288
- return checkpointCommit;
289
- };
290
-
291
182
  type ResolveFindingsInput = {
292
183
  paths: MaestroPaths;
293
184
  specId: string;
@@ -306,17 +197,16 @@ export const resolveFindings = async ({
306
197
  decisions,
307
198
  });
308
199
 
309
- const checkpointCommit = await commitFindingWorkflow({
310
- paths,
311
- specId,
312
- currentRevision: currentWorkflow.state.revision,
313
- workflow,
200
+ const handoffPath = await paths.getActiveVerifierHandoffPath(specId);
201
+ await writeJson({ path: handoffPath, data: workflow.handoff });
202
+ await writeWorkflowState({
203
+ path: paths.getWorkflowPath(specId),
204
+ state: workflow.state,
314
205
  });
315
206
 
316
207
  return {
317
208
  findings: workflow.handoff.findings,
318
209
  state: workflow.state,
319
- repositoryRoot: paths.getRepositoryRoot(),
320
- checkpointCommit,
210
+ projectRoot: paths.getProjectRoot(),
321
211
  };
322
212
  };
@@ -54,7 +54,6 @@ export const markSpecReady = async ({
54
54
  await writeWorkflowState({
55
55
  path: workflowPath,
56
56
  state,
57
- currentRevision: current.revision,
58
57
  });
59
58
 
60
59
  return state;
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Objective: Read and validate a workflow state file.
3
- * Used: When Maestro loads a workflow phase or revision.
3
+ * Used: When Maestro loads a workflow phase.
4
4
  */
5
5
 
6
6
  import { readJsonFile } from '#utils/read-json.ts';
@@ -15,5 +15,5 @@ export const readWorkflowState = async (
15
15
  const state = await readJsonFile({ path, description: 'Workflow state' });
16
16
  assertWorkflowState(state);
17
17
 
18
- return state;
18
+ return { version: state.version, specId: state.specId, phase: state.phase };
19
19
  };
@@ -51,7 +51,6 @@ export const WorkflowStateSchema = Type.Object(
51
51
  {
52
52
  version: Type.Literal(WORKFLOW_STATE_VERSION),
53
53
  specId: Type.String({ pattern: SPEC_ID_PATTERN.source }),
54
- revision: Type.Integer({ minimum: 1 }),
55
54
  phase: WorkflowPhaseSchema,
56
55
  },
57
56
  { additionalProperties: false },
@@ -1,82 +1,26 @@
1
1
  /**
2
- * Objective: Atomically write a validated workflow state at a new revision.
3
- * Used: When Maestro persists a workflow transition.
2
+ * Objective: Write a validated workflow state.
3
+ * Used: When Maestro persists a sequential workflow transition.
4
4
  */
5
5
 
6
- import { type FileHandle, open, rm } from 'node:fs/promises';
7
- import { isErrnoException } from '#utils/is-errno-exception.ts';
8
- import { pathExists } from '#utils/path-exists.ts';
9
- import { writeJsonAtomically } from '#utils/write-json-atomically.ts';
10
- import { readWorkflowState } from '#workflow/state/readWorkflowState.ts';
6
+ import { writeJson } from '#utils/write-json.ts';
11
7
  import {
12
8
  assertWorkflowState,
13
9
  type WorkflowState,
14
10
  } from '#workflow/state/schema.ts';
15
11
 
16
- const acquireWorkflowLock = async (path: string): Promise<FileHandle> => {
17
- try {
18
- return await open(path, 'wx', 0o600);
19
- } catch (error) {
20
- if (isErrnoException(error) && error.code === 'EEXIST') {
21
- throw new Error('Another workflow state update is in progress.', {
22
- cause: error,
23
- });
24
- }
25
-
26
- throw error;
27
- }
28
- };
29
-
30
12
  type WriteWorkflowStateInput = {
31
13
  path: string;
32
14
  state: WorkflowState;
33
- currentRevision: number;
34
15
  };
35
16
 
36
17
  export const writeWorkflowState = async ({
37
18
  path,
38
19
  state,
39
- currentRevision,
40
20
  }: WriteWorkflowStateInput): Promise<void> => {
41
21
  assertWorkflowState(state);
42
-
43
- if (!Number.isSafeInteger(currentRevision) || currentRevision < 0) {
44
- throw new Error(
45
- 'Expected workflow revision must be a non-negative integer.',
46
- );
47
- }
48
-
49
- if (state.revision <= currentRevision) {
50
- throw new Error(
51
- `Workflow revision must be higher than expected revision ${currentRevision}.`,
52
- );
53
- }
54
-
55
- const lockPath = `${path}.lock`;
56
- const lock = await acquireWorkflowLock(lockPath);
57
-
58
- try {
59
- const exists = await pathExists(path);
60
-
61
- if (!exists && currentRevision !== 0) {
62
- throw new Error(
63
- `Stale workflow revision: expected ${currentRevision}, but no state exists.`,
64
- );
65
- }
66
-
67
- if (exists) {
68
- const current = await readWorkflowState(path);
69
-
70
- if (current.revision !== currentRevision) {
71
- throw new Error(
72
- `Stale workflow revision: expected ${currentRevision}, found ${current.revision}.`,
73
- );
74
- }
75
- }
76
-
77
- await writeJsonAtomically({ path, data: state });
78
- } finally {
79
- await lock.close();
80
- await rm(lockPath, { force: true });
81
- }
22
+ await writeJson({
23
+ path,
24
+ data: { version: state.version, specId: state.specId, phase: state.phase },
25
+ });
82
26
  };
@@ -92,8 +92,8 @@ export const transitionWorkflow = ({
92
92
  }
93
93
 
94
94
  return {
95
- ...current,
96
- revision: current.revision + 1,
95
+ version: current.version,
96
+ specId: current.specId,
97
97
  phase: transition.to,
98
98
  };
99
99
  };
@@ -4,12 +4,10 @@
4
4
  */
5
5
 
6
6
  import { mkdir } from 'node:fs/promises';
7
- import { dirname } from 'node:path';
8
7
  import { assertVerifierHandoff } from '#artifacts/verifier-handoff/assertVerifierHandoff.ts';
9
8
  import type { VerifierHandoff } from '#artifacts/verifier-handoff/schema.ts';
10
9
  import { writeVerifierHandoff } from '#artifacts/verifier-handoff/writeVerifierHandoff.ts';
11
10
  import type { MaestroPaths } from '#MaestroPaths.ts';
12
- import { pathExists } from '#utils/path-exists.ts';
13
11
  import { readWorkflowState } from '#workflow/state/readWorkflowState.ts';
14
12
  import {
15
13
  WORKFLOW_EVENTS,
@@ -22,7 +20,8 @@ import { transitionWorkflow } from '#workflow/transitions.ts';
22
20
  export type CompletedVerifierPass = {
23
21
  handoff: VerifierHandoff;
24
22
  state: WorkflowState;
25
- repositoryRoot: string;
23
+ projectRoot: string;
24
+ handoffPath: string;
26
25
  };
27
26
 
28
27
  type CompleteVerifierPassInput = {
@@ -36,9 +35,8 @@ export const completeVerifierPass = async ({
36
35
  specId,
37
36
  handoff: draftHandoff,
38
37
  }: CompleteVerifierPassInput): Promise<CompletedVerifierPass> => {
39
- const repositoryRoot = paths.getRepositoryRoot();
38
+ const projectRoot = paths.getProjectRoot();
40
39
  const workflowPath = paths.getWorkflowPath(specId);
41
- const handoffPath = paths.getVerifierHandoffPath(specId);
42
40
  const currentState = await readWorkflowState(workflowPath);
43
41
 
44
42
  if (currentState.specId !== specId) {
@@ -53,14 +51,9 @@ export const completeVerifierPass = async ({
53
51
  );
54
52
  }
55
53
 
56
- if (await pathExists(handoffPath)) {
57
- throw new Error('Verifier terminal handoff already exists.');
58
- }
59
-
60
54
  const handoffInput = {
61
55
  handoff: draftHandoff,
62
56
  specId,
63
- revision: currentState.revision + 1,
64
57
  };
65
58
 
66
59
  assertVerifierHandoff(handoffInput);
@@ -74,22 +67,23 @@ export const completeVerifierPass = async ({
74
67
  : WORKFLOW_EVENTS.VERIFIER_APPROVED,
75
68
  });
76
69
 
77
- await mkdir(dirname(handoffPath), { recursive: true });
70
+ await mkdir(paths.getVerifierHandoffsPath(specId), { recursive: true });
71
+ const handoffPath = await paths.getNextVerifierHandoffPath(specId);
72
+
78
73
  await writeVerifierHandoff({
79
74
  path: handoffPath,
80
75
  handoff,
81
76
  specId,
82
- revision: nextState.revision,
83
77
  });
84
78
  await writeWorkflowState({
85
79
  path: workflowPath,
86
80
  state: nextState,
87
- currentRevision: currentState.revision,
88
81
  });
89
82
 
90
83
  return {
91
84
  handoff,
85
+ handoffPath,
92
86
  state: nextState,
93
- repositoryRoot,
87
+ projectRoot,
94
88
  };
95
89
  };
@@ -1,13 +1,9 @@
1
1
  /**
2
- * Objective: Prepare a committed verifier run checkpoint in the current checkout.
2
+ * Objective: Prepare a verifier run transition in the project directory.
3
3
  * Used: When Maestro starts a verifier pass.
4
4
  */
5
5
 
6
- import { rm } from 'node:fs/promises';
7
- import { createWorkflowCheckpointCommit } from '#git/commits/createWorkflowCheckpointCommit.ts';
8
- import { getRepositoryStatus } from '#git/repository/getRepositoryStatus.ts';
9
6
  import type { MaestroPaths } from '#MaestroPaths.ts';
10
- import { pathExists } from '#utils/path-exists.ts';
11
7
  import { readWorkflowState } from '#workflow/state/readWorkflowState.ts';
12
8
  import { WORKFLOW_EVENTS, WORKFLOW_PHASES } from '#workflow/state/schema.ts';
13
9
  import { writeWorkflowState } from '#workflow/state/writeWorkflowState.ts';
@@ -15,10 +11,7 @@ import { transitionWorkflow } from '#workflow/transitions.ts';
15
11
 
16
12
  export type VerifierRun = {
17
13
  specId: string;
18
- repositoryRoot: string;
19
- candidateCommit: string;
20
- checkpointCommit: string;
21
- revision: number;
14
+ projectRoot: string;
22
15
  };
23
16
 
24
17
  type PrepareVerifierRunInput = {
@@ -30,9 +23,8 @@ export const prepareVerifierRun = async ({
30
23
  paths,
31
24
  specId,
32
25
  }: PrepareVerifierRunInput): Promise<VerifierRun> => {
33
- const repositoryRoot = paths.getRepositoryRoot();
26
+ const projectRoot = paths.getProjectRoot();
34
27
  const workflowPath = paths.getWorkflowPath(specId);
35
- const handoffPath = paths.getVerifierHandoffPath(specId);
36
28
  const currentState = await readWorkflowState(workflowPath);
37
29
 
38
30
  if (currentState.specId !== specId) {
@@ -47,47 +39,18 @@ export const prepareVerifierRun = async ({
47
39
  );
48
40
  }
49
41
 
50
- const status = await getRepositoryStatus(repositoryRoot);
51
-
52
- if (!status.clean) {
53
- throw new Error(
54
- 'Verifier run requires a committed ready-for-verifier candidate.',
55
- );
56
- }
57
-
58
42
  const nextState = transitionWorkflow({
59
43
  state: currentState,
60
44
  event: WORKFLOW_EVENTS.RUN_VERIFIER,
61
45
  });
62
46
 
63
- const handoffExists = await pathExists(handoffPath);
64
-
65
- if (handoffExists) {
66
- await rm(handoffPath);
67
- }
68
-
69
47
  await writeWorkflowState({
70
48
  path: workflowPath,
71
49
  state: nextState,
72
- currentRevision: currentState.revision,
73
- });
74
-
75
- const expectedPaths = [workflowPath];
76
-
77
- if (handoffExists) {
78
- expectedPaths.push(handoffPath);
79
- }
80
-
81
- const checkpointCommit = await createWorkflowCheckpointCommit({
82
- repositoryRoot,
83
- expectedPaths,
84
50
  });
85
51
 
86
52
  return {
87
53
  specId,
88
- repositoryRoot,
89
- candidateCommit: checkpointCommit,
90
- checkpointCommit,
91
- revision: nextState.revision,
54
+ projectRoot,
92
55
  };
93
56
  };
@@ -1,54 +0,0 @@
1
- /**
2
- * Objective: Record an owner rejection on one verifier finding.
3
- * Used: When Maestro handles verifier handoff artifacts.
4
- */
5
-
6
- import { assertVerifierHandoff } from '#artifacts/verifier-handoff/assertVerifierHandoff.ts';
7
- import { readVerifierHandoff } from '#artifacts/verifier-handoff/readVerifierHandoff.ts';
8
- import type { VerifierHandoff } from '#artifacts/verifier-handoff/schema.ts';
9
- import { writeJsonAtomically } from '#utils/write-json-atomically.ts';
10
-
11
- type RejectVerifierFindingInput = {
12
- path: string;
13
- specId: string;
14
- revision: number;
15
- findingId: string;
16
- reason: string;
17
- };
18
-
19
- export const rejectVerifierFinding = async ({
20
- path,
21
- specId,
22
- revision,
23
- findingId,
24
- reason,
25
- }: RejectVerifierFindingInput): Promise<VerifierHandoff> => {
26
- const handoff = await readVerifierHandoff({ path, specId, revision });
27
- const finding = handoff.findings.find(({ id }) => id === findingId);
28
-
29
- if (finding === undefined) {
30
- throw new Error(`Verifier finding "${findingId}" does not exist.`);
31
- }
32
-
33
- if (finding.rejection !== null) {
34
- throw new Error(`Verifier finding "${findingId}" is already rejected.`);
35
- }
36
-
37
- if (reason.length === 0) {
38
- throw new Error('Verifier finding rejection reason must be non-empty.');
39
- }
40
-
41
- const rejectedHandoff = {
42
- ...handoff,
43
- findings: handoff.findings.map((currentFinding) =>
44
- currentFinding.id === findingId
45
- ? { ...currentFinding, rejection: { reason } }
46
- : currentFinding,
47
- ),
48
- };
49
-
50
- assertVerifierHandoff({ handoff: rejectedHandoff, specId, revision });
51
- await writeJsonAtomically({ path, data: rejectedHandoff });
52
-
53
- return rejectedHandoff;
54
- };