@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
@@ -6,6 +6,7 @@
6
6
  import { StringEnum } from '@earendil-works/pi-ai';
7
7
  import type { ExtensionAPI } from '@earendil-works/pi-coding-agent';
8
8
  import { Type } from 'typebox';
9
+
9
10
  import { FINDING_DECISIONS } from '#artifacts/verifier-handoff/schema.ts';
10
11
  import { SPEC_ID_PATTERN } from '#ids/isValidSpecId.ts';
11
12
  import { resolveToolRunContext } from '#tools/utils/resolveToolRunContext.ts';
@@ -86,9 +87,7 @@ const formatResolution = (details: FindingResolutionDetails): string => {
86
87
  const summary = `Recorded rejections for ${rejected}. Findings requiring code fixes: ${requiringFix}.`;
87
88
 
88
89
  if (details.phase === WORKFLOW_PHASES.DRAFTING_SPEC) {
89
- const requiringRevision = formatFindingIds(
90
- details.findingsRequiringSpecRevisionIds,
91
- );
90
+ const requiringRevision = formatFindingIds(details.findingsRequiringSpecRevisionIds);
92
91
 
93
92
  return `${summary} 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.`;
94
93
  }
@@ -106,13 +105,7 @@ export const registerResolveFindingsTool = (pi: ExtensionAPI): void => {
106
105
  label: RESOLVE_FINDINGS_TOOL.LABEL,
107
106
  description: RESOLVE_FINDINGS_TOOL.DESCRIPTION,
108
107
  parameters: ResolveFindingsToolParameters,
109
- async execute(
110
- _toolCallId,
111
- { specId, decisions },
112
- _signal,
113
- _onUpdate,
114
- context,
115
- ) {
108
+ async execute(_toolCallId, { specId, decisions }, _signal, _onUpdate, context) {
116
109
  const { paths } = await resolveToolRunContext(context.cwd);
117
110
 
118
111
  const resolved = await resolveFindings({
@@ -126,20 +119,13 @@ export const registerResolveFindingsTool = (pi: ExtensionAPI): void => {
126
119
  phase: resolved.state.phase,
127
120
  projectRoot: resolved.projectRoot,
128
121
  rejectedFindingIds: resolved.findings
129
- .filter(
130
- ({ decision }) => decision?.decision === FINDING_DECISIONS.REJECT,
131
- )
122
+ .filter(({ decision }) => decision?.decision === FINDING_DECISIONS.REJECT)
132
123
  .map(({ id }) => id),
133
124
  findingsRequiringSpecRevisionIds: resolved.findings
134
- .filter(
135
- ({ decision }) =>
136
- decision?.decision === FINDING_DECISIONS.REVISE_SPEC,
137
- )
125
+ .filter(({ decision }) => decision?.decision === FINDING_DECISIONS.REVISE_SPEC)
138
126
  .map(({ id }) => id),
139
127
  findingsRequiringFixIds: resolved.findings
140
- .filter(
141
- ({ decision }) => decision?.decision === FINDING_DECISIONS.FIX_CODE,
142
- )
128
+ .filter(({ decision }) => decision?.decision === FINDING_DECISIONS.FIX_CODE)
143
129
  .map(({ id }) => id),
144
130
  };
145
131
 
@@ -4,9 +4,11 @@
4
4
  */
5
5
 
6
6
  import { randomUUID } from 'node:crypto';
7
+
7
8
  import type { ExtensionAPI } from '@earendil-works/pi-coding-agent';
8
9
  import type { SubagentDelegationRequest } from 'pi-subagents/delegation';
9
10
  import { Type } from 'typebox';
11
+
10
12
  import { readBuilderHandoff } from '#artifacts/builder-handoff/readBuilderHandoff.ts';
11
13
  import {
12
14
  BUILDER_HANDOFF_STATUSES,
@@ -14,8 +16,8 @@ import {
14
16
  } from '#artifacts/builder-handoff/schema.ts';
15
17
  import { AGENTS } from '#config/schema.ts';
16
18
  import { SPEC_ID_PATTERN } from '#ids/isValidSpecId.ts';
17
- import type { MaestroPaths } from '#MaestroPaths.ts';
18
19
  import { refreshMaestroStatus } from '#maestro/status/refreshMaestroStatus.ts';
20
+ import type { MaestroPaths } from '#MaestroPaths.ts';
19
21
  import {
20
22
  assertDelegationResponse,
21
23
  waitForDelegationResponse,
@@ -24,7 +26,7 @@ import { resolveToolRunContext } from '#tools/utils/resolveToolRunContext.ts';
24
26
  import { prepareBuilderRun } from '#workflow/builder/prepareBuilderRun.ts';
25
27
  import { WORKFLOW_ROLES } from '#workflow/roles.ts';
26
28
  import { readWorkflowState } from '#workflow/state/readWorkflowState.ts';
27
- import { WORKFLOW_PHASES, type WorkflowState } from '#workflow/state/schema.ts';
29
+ import { WORKFLOW_PHASES } from '#workflow/state/schema.ts';
28
30
 
29
31
  export const RUN_BUILDER_TOOL = {
30
32
  NAME: 'maestro_run_builder',
@@ -69,76 +71,54 @@ const readBuilderResult = async ({
69
71
  const state = await readWorkflowState(paths.getWorkflowPath(specId));
70
72
 
71
73
  if (state.specId !== specId) {
72
- throw new Error(
73
- `Workflow spec ID mismatch: expected "${specId}", found "${state.specId}".`,
74
- );
74
+ throw new Error(`Workflow spec ID mismatch: expected "${specId}", found "${state.specId}".`);
75
75
  }
76
76
 
77
- return await readTerminalBuilderResult({ paths, specId, state });
78
- } catch (error) {
79
- const message = error instanceof Error ? error.message : String(error);
80
-
81
- throw new Error(`Builder protocol error: ${message}.`, { cause: error });
82
- }
83
- };
77
+ if (
78
+ state.phase !== WORKFLOW_PHASES.READY_FOR_VERIFIER &&
79
+ state.phase !== WORKFLOW_PHASES.ESCALATION_DECISIONS
80
+ ) {
81
+ throw new Error(`Builder returned with unexpected workflow phase "${state.phase}".`);
82
+ }
84
83
 
85
- type ReadTerminalBuilderResultInput = {
86
- paths: MaestroPaths;
87
- specId: string;
88
- state: WorkflowState;
89
- };
84
+ const handoff = await readBuilderHandoff({
85
+ path: await paths.getActiveBuilderHandoffPath(specId),
86
+ specId,
87
+ });
90
88
 
91
- const readTerminalBuilderResult = async ({
92
- paths,
93
- specId,
94
- state,
95
- }: ReadTerminalBuilderResultInput): Promise<BuilderRunResult> => {
96
- if (
97
- state.phase !== WORKFLOW_PHASES.READY_FOR_VERIFIER &&
98
- state.phase !== WORKFLOW_PHASES.ESCALATION_DECISIONS
99
- ) {
100
- throw new Error(
101
- `Builder returned with unexpected workflow phase "${state.phase}".`,
102
- );
103
- }
89
+ const expectedStatus =
90
+ state.phase === WORKFLOW_PHASES.READY_FOR_VERIFIER
91
+ ? BUILDER_HANDOFF_STATUSES.DONE
92
+ : BUILDER_HANDOFF_STATUSES.ESCALATION;
104
93
 
105
- const handoff = await readBuilderHandoff({
106
- path: await paths.getActiveBuilderHandoffPath(specId),
107
- specId,
108
- });
94
+ if (handoff.status !== expectedStatus) {
95
+ throw new Error(`Builder handoff status does not match workflow phase "${state.phase}".`);
96
+ }
109
97
 
110
- const expectedStatus =
111
- state.phase === WORKFLOW_PHASES.READY_FOR_VERIFIER
112
- ? BUILDER_HANDOFF_STATUSES.DONE
113
- : BUILDER_HANDOFF_STATUSES.ESCALATION;
98
+ if (state.phase === WORKFLOW_PHASES.READY_FOR_VERIFIER) {
99
+ return {
100
+ outcome: BUILDER_HANDOFF_STATUSES.DONE,
101
+ specId,
102
+ phase: state.phase,
103
+ handoff,
104
+ };
105
+ }
114
106
 
115
- if (handoff.status !== expectedStatus) {
116
- throw new Error(
117
- `Builder handoff status does not match workflow phase "${state.phase}".`,
118
- );
119
- }
107
+ if (handoff.escalations.some(({ resolution }) => resolution !== null)) {
108
+ throw new Error('Current builder escalations already contain an owner resolution.');
109
+ }
120
110
 
121
- if (state.phase === WORKFLOW_PHASES.READY_FOR_VERIFIER) {
122
111
  return {
123
- outcome: BUILDER_HANDOFF_STATUSES.DONE,
112
+ outcome: BUILDER_HANDOFF_STATUSES.ESCALATION,
124
113
  specId,
125
114
  phase: state.phase,
126
115
  handoff,
127
116
  };
128
- }
117
+ } catch (error) {
118
+ const message = error instanceof Error ? error.message : String(error);
129
119
 
130
- if (handoff.escalations.some(({ resolution }) => resolution !== null)) {
131
- throw new Error(
132
- 'Current builder escalations already contain an owner resolution.',
133
- );
120
+ throw new Error(`Builder protocol error: ${message}.`, { cause: error });
134
121
  }
135
-
136
- return {
137
- outcome: BUILDER_HANDOFF_STATUSES.ESCALATION,
138
- specId,
139
- phase: state.phase,
140
- handoff,
141
- };
142
122
  };
143
123
 
144
124
  const formatBuilderResult = (result: BuilderRunResult): string => {
@@ -4,14 +4,16 @@
4
4
  */
5
5
 
6
6
  import { randomUUID } from 'node:crypto';
7
+
7
8
  import type { ExtensionAPI } from '@earendil-works/pi-coding-agent';
8
9
  import type { SubagentDelegationRequest } from 'pi-subagents/delegation';
9
10
  import { Type } from 'typebox';
11
+
10
12
  import { readSimplifierHandoff } from '#artifacts/simplifier-handoff/readSimplifierHandoff.ts';
11
13
  import { AGENTS } from '#config/schema.ts';
12
14
  import { SPEC_ID_PATTERN } from '#ids/isValidSpecId.ts';
13
- import type { MaestroPaths } from '#MaestroPaths.ts';
14
15
  import { refreshMaestroStatus } from '#maestro/status/refreshMaestroStatus.ts';
16
+ import type { MaestroPaths } from '#MaestroPaths.ts';
15
17
  import {
16
18
  assertDelegationResponse,
17
19
  waitForDelegationResponse,
@@ -44,16 +46,11 @@ type GitReadinessInput = {
44
46
  projectRoot: string;
45
47
  };
46
48
 
47
- const isGitReady = async ({
48
- pi,
49
- projectRoot,
50
- }: GitReadinessInput): Promise<boolean> => {
49
+ const isGitReady = async ({ pi, projectRoot }: GitReadinessInput): Promise<boolean> => {
51
50
  try {
52
- const result = await pi.exec(
53
- 'git',
54
- ['rev-parse', '--is-inside-work-tree'],
55
- { cwd: projectRoot },
56
- );
51
+ const result = await pi.exec('git', ['rev-parse', '--is-inside-work-tree'], {
52
+ cwd: projectRoot,
53
+ });
57
54
 
58
55
  return result.code === 0 && result.stdout.trim() === 'true';
59
56
  } catch {
@@ -66,17 +63,12 @@ type ReadSimplifierResultInput = {
66
63
  specId: string;
67
64
  };
68
65
 
69
- const readSimplifierResult = async ({
70
- paths,
71
- specId,
72
- }: ReadSimplifierResultInput) => {
66
+ const readSimplifierResult = async ({ paths, specId }: ReadSimplifierResultInput) => {
73
67
  try {
74
68
  const state = await readWorkflowState(paths.getWorkflowPath(specId));
75
69
 
76
70
  if (state.specId !== specId) {
77
- throw new Error(
78
- `Workflow spec ID mismatch: expected "${specId}", found "${state.specId}".`,
79
- );
71
+ throw new Error(`Workflow spec ID mismatch: expected "${specId}", found "${state.specId}".`);
80
72
  }
81
73
 
82
74
  if (state.phase !== WORKFLOW_PHASES.CANDIDATE_READY) {
@@ -105,9 +97,7 @@ export const registerRunSimplifierTool = (pi: ExtensionAPI): void => {
105
97
  description: RUN_SIMPLIFIER_TOOL.DESCRIPTION,
106
98
  parameters: RunSimplifierToolParameters,
107
99
  async execute(toolCallId, { specId }, _signal, _onUpdate, context) {
108
- const { paths, config, projectRoot } = await resolveToolRunContext(
109
- context.cwd,
110
- );
100
+ const { paths, config, projectRoot } = await resolveToolRunContext(context.cwd);
111
101
 
112
102
  const workflowPath = paths.getWorkflowPath(specId);
113
103
  const state = await readWorkflowState(workflowPath);
@@ -4,15 +4,17 @@
4
4
  */
5
5
 
6
6
  import { randomUUID } from 'node:crypto';
7
+
7
8
  import type { ExtensionAPI } from '@earendil-works/pi-coding-agent';
8
9
  import type { SubagentDelegationRequest } from 'pi-subagents/delegation';
9
10
  import { Type } from 'typebox';
11
+
10
12
  import { readVerifierHandoff } from '#artifacts/verifier-handoff/readVerifierHandoff.ts';
11
13
  import type { VerifierHandoff } from '#artifacts/verifier-handoff/schema.ts';
12
14
  import { AGENTS } from '#config/schema.ts';
13
15
  import { SPEC_ID_PATTERN } from '#ids/isValidSpecId.ts';
14
- import type { MaestroPaths } from '#MaestroPaths.ts';
15
16
  import { refreshMaestroStatus } from '#maestro/status/refreshMaestroStatus.ts';
17
+ import type { MaestroPaths } from '#MaestroPaths.ts';
16
18
  import {
17
19
  assertDelegationResponse,
18
20
  waitForDelegationResponse,
@@ -20,7 +22,7 @@ import {
20
22
  import { resolveToolRunContext } from '#tools/utils/resolveToolRunContext.ts';
21
23
  import { WORKFLOW_ROLES } from '#workflow/roles.ts';
22
24
  import { readWorkflowState } from '#workflow/state/readWorkflowState.ts';
23
- import { WORKFLOW_PHASES, type WorkflowState } from '#workflow/state/schema.ts';
25
+ import { WORKFLOW_PHASES } from '#workflow/state/schema.ts';
24
26
  import { prepareVerifierRun } from '#workflow/verifier/prepareVerifierRun.ts';
25
27
 
26
28
  export const RUN_VERIFIER_TOOL = {
@@ -50,50 +52,6 @@ type VerifierRunResult = {
50
52
  handoff: VerifierHandoff;
51
53
  };
52
54
 
53
- type ReadTerminalVerifierResultInput = {
54
- paths: MaestroPaths;
55
- specId: string;
56
- state: WorkflowState;
57
- };
58
-
59
- const readTerminalVerifierResult = async ({
60
- paths,
61
- specId,
62
- state,
63
- }: ReadTerminalVerifierResultInput): Promise<VerifierRunResult> => {
64
- if (
65
- state.phase !== WORKFLOW_PHASES.SIMPLIFIER_RUNNING &&
66
- state.phase !== WORKFLOW_PHASES.FINDING_DECISIONS
67
- ) {
68
- throw new Error(
69
- `Verifier returned with unexpected workflow phase "${state.phase}".`,
70
- );
71
- }
72
-
73
- const handoff = await readVerifierHandoff({
74
- path: await paths.getActiveVerifierHandoffPath(specId),
75
- specId,
76
- });
77
-
78
- const expectedPhase =
79
- handoff.findings.length > 0
80
- ? WORKFLOW_PHASES.FINDING_DECISIONS
81
- : WORKFLOW_PHASES.SIMPLIFIER_RUNNING;
82
-
83
- if (state.phase !== expectedPhase) {
84
- throw new Error(
85
- `Verifier handoff findings do not match workflow phase "${state.phase}".`,
86
- );
87
- }
88
-
89
- return {
90
- specId,
91
- handoff,
92
- outcome: state.phase,
93
- phase: state.phase,
94
- };
95
- };
96
-
97
55
  type ReadVerifierResultInput = {
98
56
  paths: MaestroPaths;
99
57
  specId: string;
@@ -107,22 +65,40 @@ const readVerifierResult = async ({
107
65
  const state = await readWorkflowState(paths.getWorkflowPath(specId));
108
66
 
109
67
  if (state.specId !== specId) {
110
- throw new Error(
111
- `Workflow spec ID mismatch: expected "${specId}", found "${state.specId}".`,
112
- );
68
+ throw new Error(`Workflow spec ID mismatch: expected "${specId}", found "${state.specId}".`);
113
69
  }
114
70
 
115
71
  if (state.phase === WORKFLOW_PHASES.VERIFIER_RUNNING) {
116
- throw new Error(
117
- 'The verifier returned without recording a valid terminal handoff.',
118
- );
72
+ throw new Error('The verifier returned without recording a valid terminal handoff.');
119
73
  }
120
74
 
121
- return await readTerminalVerifierResult({
122
- paths,
75
+ if (
76
+ state.phase !== WORKFLOW_PHASES.SIMPLIFIER_RUNNING &&
77
+ state.phase !== WORKFLOW_PHASES.FINDING_DECISIONS
78
+ ) {
79
+ throw new Error(`Verifier returned with unexpected workflow phase "${state.phase}".`);
80
+ }
81
+
82
+ const handoff = await readVerifierHandoff({
83
+ path: await paths.getActiveVerifierHandoffPath(specId),
123
84
  specId,
124
- state,
125
85
  });
86
+
87
+ const expectedPhase =
88
+ handoff.findings.length > 0
89
+ ? WORKFLOW_PHASES.FINDING_DECISIONS
90
+ : WORKFLOW_PHASES.SIMPLIFIER_RUNNING;
91
+
92
+ if (state.phase !== expectedPhase) {
93
+ throw new Error(`Verifier handoff findings do not match workflow phase "${state.phase}".`);
94
+ }
95
+
96
+ return {
97
+ specId,
98
+ handoff,
99
+ outcome: state.phase,
100
+ phase: state.phase,
101
+ };
126
102
  } catch (error) {
127
103
  const message = error instanceof Error ? error.message : String(error);
128
104
 
@@ -50,8 +50,7 @@ export const matchesDelegationRequest = ({
50
50
 
51
51
  return (
52
52
  response.requestId === request.requestId &&
53
- (response.ownerRunId === undefined ||
54
- response.ownerRunId === request.ownerRunId) &&
53
+ (response.ownerRunId === undefined || response.ownerRunId === request.ownerRunId) &&
55
54
  (response.nodeId === undefined || response.nodeId === request.nodeId)
56
55
  );
57
56
  };
@@ -65,20 +64,16 @@ export const waitForDelegationResponse = async ({
65
64
  piEventsBus,
66
65
  request,
67
66
  }: WaitForDelegationResponseInput): Promise<SubagentDelegationResponse> => {
68
- const { promise, resolve } =
69
- Promise.withResolvers<SubagentDelegationResponse>();
70
-
71
- const unsubscribe = piEventsBus.on(
72
- SUBAGENT_DELEGATION_RESPONSE_EVENT,
73
- (payload) => {
74
- if (!matchesDelegationRequest({ request, payload })) {
75
- return;
76
- }
77
-
78
- // JUSTIFICATION: matchesDelegationRequest accepts only the response for this pi-subagents request.
79
- resolve(payload as SubagentDelegationResponse);
80
- },
81
- );
67
+ const { promise, resolve } = Promise.withResolvers<SubagentDelegationResponse>();
68
+
69
+ const unsubscribe = piEventsBus.on(SUBAGENT_DELEGATION_RESPONSE_EVENT, (payload) => {
70
+ if (!matchesDelegationRequest({ request, payload })) {
71
+ return;
72
+ }
73
+
74
+ // JUSTIFICATION: matchesDelegationRequest accepts only the response for this pi-subagents request.
75
+ resolve(payload as SubagentDelegationResponse);
76
+ });
82
77
 
83
78
  try {
84
79
  try {
@@ -97,9 +92,7 @@ export const waitForDelegationResponse = async ({
97
92
  }
98
93
  };
99
94
 
100
- export function assertDelegationResponse(
101
- response: SubagentDelegationResponse,
102
- ): void {
95
+ export function assertDelegationResponse(response: SubagentDelegationResponse): void {
103
96
  if (response.status === DELEGATION_STATUSES.COMPLETED) {
104
97
  return;
105
98
  }
@@ -121,7 +114,5 @@ export function assertDelegationResponse(
121
114
  throw new Error(`Delegation error: ${error}`);
122
115
  }
123
116
 
124
- throw new Error(
125
- `Protocol error: unsupported final response status "${response.status}".`,
126
- );
117
+ throw new Error(`Protocol error: unsupported final response status "${response.status}".`);
127
118
  }
@@ -4,6 +4,7 @@
4
4
  */
5
5
 
6
6
  import { realpath } from 'node:fs/promises';
7
+
7
8
  import { loadConfiguration } from '#config/loadConfiguration.ts';
8
9
  import type { MaestroConfig } from '#config/schema.ts';
9
10
  import { MaestroPaths } from '#MaestroPaths.ts';
@@ -14,9 +15,7 @@ export type ToolRunContext = {
14
15
  paths: MaestroPaths;
15
16
  };
16
17
 
17
- export const resolveToolRunContext = async (
18
- cwd: string,
19
- ): Promise<ToolRunContext> => {
18
+ export const resolveToolRunContext = async (cwd: string): Promise<ToolRunContext> => {
20
19
  const projectRoot = await realpath(cwd);
21
20
  const config = await loadConfiguration(projectRoot);
22
21
 
@@ -3,7 +3,5 @@
3
3
  * Used: When filesystem operations handle specific error codes.
4
4
  */
5
5
 
6
- export const isErrnoException = (
7
- error: unknown,
8
- ): error is NodeJS.ErrnoException =>
6
+ export const isErrnoException = (error: unknown): error is NodeJS.ErrnoException =>
9
7
  error instanceof Error && 'code' in error && typeof error.code === 'string';
@@ -4,6 +4,7 @@
4
4
  */
5
5
 
6
6
  import { access } from 'node:fs/promises';
7
+
7
8
  import { isErrnoException } from '#utils/is-errno-exception.ts';
8
9
 
9
10
  export const pathExists = async (path: string): Promise<boolean> => {
@@ -4,6 +4,7 @@
4
4
  */
5
5
 
6
6
  import { relative } from 'node:path';
7
+
7
8
  import { isPathWithinOrEqual } from '#utils/path-within-or-equal.ts';
8
9
 
9
10
  type IsPathStrictlyWithinInput = {
@@ -11,9 +12,5 @@ type IsPathStrictlyWithinInput = {
11
12
  path: string;
12
13
  };
13
14
 
14
- export const isPathStrictlyWithin = ({
15
- parent,
16
- path,
17
- }: IsPathStrictlyWithinInput): boolean =>
18
- relative(parent, path) !== '' &&
19
- isPathWithinOrEqual({ parent, candidate: path });
15
+ export const isPathStrictlyWithin = ({ parent, path }: IsPathStrictlyWithinInput): boolean =>
16
+ relative(parent, path) !== '' && isPathWithinOrEqual({ parent, candidate: path });
@@ -10,10 +10,7 @@ type IsPathWithinOrEqualInput = {
10
10
  candidate: string;
11
11
  };
12
12
 
13
- export const isPathWithinOrEqual = ({
14
- parent,
15
- candidate,
16
- }: IsPathWithinOrEqualInput): boolean => {
13
+ export const isPathWithinOrEqual = ({ parent, candidate }: IsPathWithinOrEqualInput): boolean => {
17
14
  const pathToCandidate = relative(parent, candidate);
18
15
 
19
16
  return (
@@ -10,10 +10,7 @@ type ReadJsonFileInput = {
10
10
  description: string;
11
11
  };
12
12
 
13
- export const readJsonFile = async ({
14
- path,
15
- description,
16
- }: ReadJsonFileInput): Promise<unknown> => {
13
+ export const readJsonFile = async ({ path, description }: ReadJsonFileInput): Promise<unknown> => {
17
14
  const content = await readFile(path, 'utf8');
18
15
 
19
16
  try {
@@ -10,10 +10,7 @@ type WriteJsonInput = {
10
10
  data: unknown;
11
11
  };
12
12
 
13
- export const writeJson = async ({
14
- path,
15
- data,
16
- }: WriteJsonInput): Promise<void> => {
13
+ export const writeJson = async ({ path, data }: WriteJsonInput): Promise<void> => {
17
14
  const content = JSON.stringify(data, null, 2);
18
15
 
19
16
  if (content === undefined) {
@@ -4,7 +4,9 @@
4
4
  */
5
5
 
6
6
  import { mkdir, readFile } from 'node:fs/promises';
7
+
7
8
  import { Value } from 'typebox/value';
9
+
8
10
  import { assertBuilderHandoff } from '#artifacts/builder-handoff/assertBuilderHandoff.ts';
9
11
  import {
10
12
  BUILDER_HANDOFF_VERSION,
@@ -33,8 +35,7 @@ function assertBuilderHandoffSubmission(
33
35
  ): asserts input is BuilderHandoffSubmissionInput {
34
36
  const [error] = Value.Errors(BuilderHandoffSubmissionSchema, input);
35
37
 
36
- if (error !== undefined)
37
- throw new Error(`Invalid builder submission: ${error.message}.`);
38
+ if (error !== undefined) throw new Error(`Invalid builder submission: ${error.message}.`);
38
39
  }
39
40
 
40
41
  type CompleteBuilderPassInput = {
@@ -3,8 +3,8 @@
3
3
  * Used: Before Maestro runs a builder pass.
4
4
  */
5
5
 
6
- import type { MaestroPaths } from '#MaestroPaths.ts';
7
6
  import maestroSessionState from '#maestro/session/MaestroSessionState.ts';
7
+ import type { MaestroPaths } from '#MaestroPaths.ts';
8
8
  import { pathExists } from '#utils/path-exists.ts';
9
9
  import { readWorkflowState } from '#workflow/state/readWorkflowState.ts';
10
10
  import { WORKFLOW_EVENTS, WORKFLOW_PHASES } from '#workflow/state/schema.ts';
@@ -21,16 +21,11 @@ type AssertBuilderRunBaseInput = {
21
21
  specId: string;
22
22
  };
23
23
 
24
- async function assertBuilderRunBase({
25
- paths,
26
- specId,
27
- }: AssertBuilderRunBaseInput): Promise<void> {
24
+ async function assertBuilderRunBase({ paths, specId }: AssertBuilderRunBaseInput): Promise<void> {
28
25
  const state = await readWorkflowState(paths.getWorkflowPath(specId));
29
26
 
30
27
  if (state.specId !== specId) {
31
- throw new Error(
32
- `Workflow spec ID mismatch: expected "${specId}", found "${state.specId}".`,
33
- );
28
+ throw new Error(`Workflow spec ID mismatch: expected "${specId}", found "${state.specId}".`);
34
29
  }
35
30
 
36
31
  if (state.phase !== WORKFLOW_PHASES.READY_FOR_BUILDER) {
@@ -26,16 +26,12 @@ type ApplyEscalationDecisionsInput = {
26
26
  decisions: EscalationDecision[];
27
27
  };
28
28
 
29
- const applyEscalationDecisions = ({
30
- escalations,
31
- decisions,
32
- }: ApplyEscalationDecisionsInput) => {
29
+ const applyEscalationDecisions = ({ escalations, decisions }: ApplyEscalationDecisionsInput) => {
33
30
  if (escalations.some(({ resolution }) => resolution !== null)) {
34
31
  throw new Error('Current questions already contain an owner resolution.');
35
32
  }
36
33
 
37
- if (decisions.length === 0)
38
- throw new Error('At least one escalation decision is required.');
34
+ if (decisions.length === 0) throw new Error('At least one escalation decision is required.');
39
35
 
40
36
  const expectedIds = new Set(escalations.map(({ id }) => id));
41
37
  const resolutions = new Map<string, EscalationResolution>();
@@ -9,9 +9,7 @@ import {
9
9
  } from '#artifacts/builder-handoff/schema.ts';
10
10
  import { WORKFLOW_EVENTS, type WorkflowEvent } from '#workflow/state/schema.ts';
11
11
 
12
- export const getWorkflowEventByBuilderHandoff = ({
13
- status,
14
- }: BuilderHandoff): WorkflowEvent => {
12
+ export const getWorkflowEventByBuilderHandoff = ({ status }: BuilderHandoff): WorkflowEvent => {
15
13
  if (status === BUILDER_HANDOFF_STATUSES.DONE) {
16
14
  return WORKFLOW_EVENTS.BUILDER_DONE;
17
15
  }
@@ -5,6 +5,7 @@
5
5
 
6
6
  import { mkdir } from 'node:fs/promises';
7
7
  import { dirname } from 'node:path';
8
+
8
9
  import { assertSimplifierHandoff } from '#artifacts/simplifier-handoff/assertSimplifierHandoff.ts';
9
10
  import type { MaestroPaths } from '#MaestroPaths.ts';
10
11
  import { writeJson } from '#utils/write-json.ts';
@@ -28,9 +29,7 @@ export const completeSimplifierPass = async ({
28
29
  const state = await readWorkflowState(workflowPath);
29
30
 
30
31
  if (state.specId !== specId) {
31
- throw new Error(
32
- `Workflow spec ID mismatch: expected "${specId}", found "${state.specId}".`,
33
- );
32
+ throw new Error(`Workflow spec ID mismatch: expected "${specId}", found "${state.specId}".`);
34
33
  }
35
34
 
36
35
  if (state.phase !== WORKFLOW_PHASES.SIMPLIFIER_RUNNING) {