@emiliosp/pi-maestro 0.6.2 → 0.7.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/README.md +15 -7
- package/agents/builder.md +6 -7
- package/agents/verifier.md +3 -3
- package/docs/glossary.md +69 -0
- package/docs/subagent-integration.md +1 -1
- package/docs/workflow.md +55 -23
- package/extensions/maestro-subagent.ts +0 -2
- package/extensions/maestro.ts +5 -5
- package/package.json +3 -4
- package/src/MaestroPaths.ts +0 -32
- package/src/artifacts/builder-handoff/assertBuilderHandoff.ts +29 -0
- package/src/artifacts/builder-handoff/schema.ts +87 -23
- package/src/maestro/instructions/getMaestroInstructions.ts +7 -7
- package/src/specs/create.ts +0 -4
- package/src/tools/child/record-builder-handoff.ts +14 -16
- package/src/tools/main/resolve-escalations.ts +72 -0
- package/src/tools/main/run-builder.ts +14 -10
- package/src/workflow/builder/completeBuilderPass.ts +20 -32
- package/src/workflow/escalation/resolveEscalations.ts +100 -0
- package/src/artifacts/escalation/assertEscalation.ts +0 -66
- package/src/artifacts/escalation/createEscalation.ts +0 -52
- package/src/artifacts/escalation/getNextEscalationId.ts +0 -23
- package/src/artifacts/escalation/readEscalation.ts +0 -40
- package/src/artifacts/escalation/readEscalationHistory.ts +0 -44
- package/src/artifacts/escalation/resolveEscalation.ts +0 -43
- package/src/artifacts/escalation/schema.ts +0 -73
- package/src/tools/child/open-escalation.ts +0 -71
- package/src/tools/main/resolve-escalation.ts +0 -65
- package/src/workflow/escalation/openBuilderEscalation.ts +0 -82
- package/src/workflow/escalation/resolveBuilderEscalation.ts +0 -118
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Objective: Create an unresolved escalation.
|
|
3
|
-
* Used: When Maestro handles escalation artifacts.
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { join } from 'node:path';
|
|
7
|
-
import { assertEscalation } from '#artifacts/escalation/assertEscalation.ts';
|
|
8
|
-
import { getNextEscalationId } from '#artifacts/escalation/getNextEscalationId.ts';
|
|
9
|
-
import {
|
|
10
|
-
ESCALATION_VERSION,
|
|
11
|
-
type Escalation,
|
|
12
|
-
type NewEscalation,
|
|
13
|
-
} from '#artifacts/escalation/schema.ts';
|
|
14
|
-
import { writeJson } from '#utils/write-json.ts';
|
|
15
|
-
|
|
16
|
-
type CreateEscalationInput = {
|
|
17
|
-
directory: string;
|
|
18
|
-
specId: string;
|
|
19
|
-
escalation: NewEscalation;
|
|
20
|
-
};
|
|
21
|
-
|
|
22
|
-
export const createEscalation = async ({
|
|
23
|
-
directory,
|
|
24
|
-
specId,
|
|
25
|
-
escalation,
|
|
26
|
-
}: CreateEscalationInput): Promise<{
|
|
27
|
-
path: string;
|
|
28
|
-
escalation: Escalation;
|
|
29
|
-
}> => {
|
|
30
|
-
const id = await getNextEscalationId({
|
|
31
|
-
directory,
|
|
32
|
-
specId,
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
const newEscalation = {
|
|
36
|
-
question: escalation.question,
|
|
37
|
-
context: escalation.context,
|
|
38
|
-
options: escalation.options,
|
|
39
|
-
recommendation: escalation.recommendation,
|
|
40
|
-
notes: escalation.notes,
|
|
41
|
-
version: ESCALATION_VERSION,
|
|
42
|
-
specId,
|
|
43
|
-
id,
|
|
44
|
-
resolution: null,
|
|
45
|
-
};
|
|
46
|
-
|
|
47
|
-
assertEscalation(newEscalation);
|
|
48
|
-
const path = join(directory, `${id}.json`);
|
|
49
|
-
await writeJson({ path, data: newEscalation });
|
|
50
|
-
|
|
51
|
-
return { path, escalation: newEscalation };
|
|
52
|
-
};
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Objective: Allocate the next escalation ID from history.
|
|
3
|
-
* Used: When Maestro handles escalation artifacts.
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { readEscalationHistory } from '#artifacts/escalation/readEscalationHistory.ts';
|
|
7
|
-
|
|
8
|
-
type GetNextEscalationIdInput = {
|
|
9
|
-
directory: string;
|
|
10
|
-
specId: string;
|
|
11
|
-
};
|
|
12
|
-
|
|
13
|
-
export const getNextEscalationId = async ({
|
|
14
|
-
directory,
|
|
15
|
-
specId,
|
|
16
|
-
}: GetNextEscalationIdInput): Promise<string> => {
|
|
17
|
-
const history = await readEscalationHistory({
|
|
18
|
-
directory,
|
|
19
|
-
specId,
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
return `E${history.length + 1}`;
|
|
23
|
-
};
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Objective: Read an escalation for its workflow.
|
|
3
|
-
* Used: When Maestro handles escalation artifacts.
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { assertEscalation } from '#artifacts/escalation/assertEscalation.ts';
|
|
7
|
-
import type { Escalation } from '#artifacts/escalation/schema.ts';
|
|
8
|
-
import { readJsonFile } from '#utils/read-json.ts';
|
|
9
|
-
|
|
10
|
-
type AssertWorkflowEscalationInput = {
|
|
11
|
-
escalation: Escalation;
|
|
12
|
-
specId: string;
|
|
13
|
-
};
|
|
14
|
-
|
|
15
|
-
function assertWorkflowEscalation({
|
|
16
|
-
escalation,
|
|
17
|
-
specId,
|
|
18
|
-
}: AssertWorkflowEscalationInput): void {
|
|
19
|
-
if (escalation.specId !== specId) {
|
|
20
|
-
throw new Error(
|
|
21
|
-
`Escalation spec ID mismatch: expected "${specId}", found "${escalation.specId}".`,
|
|
22
|
-
);
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
type ReadEscalationInput = {
|
|
27
|
-
path: string;
|
|
28
|
-
specId: string;
|
|
29
|
-
};
|
|
30
|
-
|
|
31
|
-
export const readEscalation = async ({
|
|
32
|
-
path,
|
|
33
|
-
specId,
|
|
34
|
-
}: ReadEscalationInput): Promise<Escalation> => {
|
|
35
|
-
const escalation = await readJsonFile({ path, description: 'Escalation' });
|
|
36
|
-
assertEscalation(escalation);
|
|
37
|
-
assertWorkflowEscalation({ escalation, specId });
|
|
38
|
-
|
|
39
|
-
return escalation;
|
|
40
|
-
};
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Objective: Read and order validated escalation history.
|
|
3
|
-
* Used: When Maestro handles escalation artifacts.
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { readdir } from 'node:fs/promises';
|
|
7
|
-
import { join } from 'node:path';
|
|
8
|
-
import { readEscalation } from '#artifacts/escalation/readEscalation.ts';
|
|
9
|
-
import type { Escalation } from '#artifacts/escalation/schema.ts';
|
|
10
|
-
|
|
11
|
-
const ESCALATION_FILE_PATTERN = /^E([1-9]\d*)\.json$/;
|
|
12
|
-
|
|
13
|
-
type ReadEscalationHistoryInput = {
|
|
14
|
-
directory: string;
|
|
15
|
-
specId: string;
|
|
16
|
-
};
|
|
17
|
-
|
|
18
|
-
export const readEscalationHistory = async ({
|
|
19
|
-
directory,
|
|
20
|
-
specId,
|
|
21
|
-
}: ReadEscalationHistoryInput): Promise<Escalation[]> => {
|
|
22
|
-
const files = await readdir(directory);
|
|
23
|
-
|
|
24
|
-
if (!files.every((file) => ESCALATION_FILE_PATTERN.test(file))) {
|
|
25
|
-
throw new Error('Escalation history contains an invalid entry.');
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
const orderedFiles = files.sort(
|
|
29
|
-
(left, right) => Number(left.slice(1, -5)) - Number(right.slice(1, -5)),
|
|
30
|
-
);
|
|
31
|
-
|
|
32
|
-
const escalations: Escalation[] = [];
|
|
33
|
-
|
|
34
|
-
for (const file of orderedFiles.values()) {
|
|
35
|
-
const escalation = await readEscalation({
|
|
36
|
-
path: join(directory, file),
|
|
37
|
-
specId,
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
escalations.push(escalation);
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
return escalations;
|
|
44
|
-
};
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Objective: Record an owner escalation resolution.
|
|
3
|
-
* Used: When Maestro handles escalation artifacts.
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { assertEscalation } from '#artifacts/escalation/assertEscalation.ts';
|
|
7
|
-
import { readEscalation } from '#artifacts/escalation/readEscalation.ts';
|
|
8
|
-
import type {
|
|
9
|
-
Escalation,
|
|
10
|
-
EscalationResolution,
|
|
11
|
-
} from '#artifacts/escalation/schema.ts';
|
|
12
|
-
import { writeJson } from '#utils/write-json.ts';
|
|
13
|
-
|
|
14
|
-
type ResolveEscalationInput = {
|
|
15
|
-
path: string;
|
|
16
|
-
specId: string;
|
|
17
|
-
resolution: EscalationResolution;
|
|
18
|
-
};
|
|
19
|
-
|
|
20
|
-
export const resolveEscalation = async ({
|
|
21
|
-
path,
|
|
22
|
-
specId,
|
|
23
|
-
resolution,
|
|
24
|
-
}: ResolveEscalationInput): Promise<Escalation> => {
|
|
25
|
-
const current = await readEscalation({
|
|
26
|
-
path,
|
|
27
|
-
specId,
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
if (current.resolution !== null) {
|
|
31
|
-
throw new Error(`Escalation "${current.id}" is already resolved.`);
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
const resolvedEscalation = {
|
|
35
|
-
...current,
|
|
36
|
-
resolution,
|
|
37
|
-
};
|
|
38
|
-
|
|
39
|
-
assertEscalation(resolvedEscalation);
|
|
40
|
-
await writeJson({ path, data: resolvedEscalation });
|
|
41
|
-
|
|
42
|
-
return resolvedEscalation;
|
|
43
|
-
};
|
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Objective: Define escalation options and resolution contracts.
|
|
3
|
-
* Used: When Maestro handles escalation artifacts.
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { type Static, Type } from 'typebox';
|
|
7
|
-
import { SPEC_ID_PATTERN } from '#ids/isValidSpecId.ts';
|
|
8
|
-
|
|
9
|
-
export const ESCALATION_VERSION = '1.0.0';
|
|
10
|
-
|
|
11
|
-
export const ESCALATION_ID_PATTERN = /^E([1-9]\d*)$/;
|
|
12
|
-
|
|
13
|
-
export const EscalationOptionSchema = Type.Object(
|
|
14
|
-
{
|
|
15
|
-
id: Type.String({ minLength: 1 }),
|
|
16
|
-
description: Type.String({ minLength: 1 }),
|
|
17
|
-
consequences: Type.String({ minLength: 1 }),
|
|
18
|
-
nextStep: Type.String({ minLength: 1 }),
|
|
19
|
-
},
|
|
20
|
-
{ additionalProperties: false },
|
|
21
|
-
);
|
|
22
|
-
|
|
23
|
-
export const EscalationRecommendationSchema = Type.Object(
|
|
24
|
-
{
|
|
25
|
-
optionId: Type.String({ minLength: 1 }),
|
|
26
|
-
reason: Type.String({ minLength: 1 }),
|
|
27
|
-
},
|
|
28
|
-
{ additionalProperties: false },
|
|
29
|
-
);
|
|
30
|
-
|
|
31
|
-
export const EscalationResolutionSchema = Type.Object(
|
|
32
|
-
{
|
|
33
|
-
selectedOptionId: Type.Union([Type.String({ minLength: 1 }), Type.Null()]),
|
|
34
|
-
decision: Type.String({ minLength: 1 }),
|
|
35
|
-
reason: Type.String({ minLength: 1 }),
|
|
36
|
-
},
|
|
37
|
-
{ additionalProperties: false },
|
|
38
|
-
);
|
|
39
|
-
|
|
40
|
-
const NewEscalationFields = {
|
|
41
|
-
question: Type.String({ minLength: 1 }),
|
|
42
|
-
context: Type.String({ minLength: 1 }),
|
|
43
|
-
options: Type.Array(EscalationOptionSchema, { minItems: 1 }),
|
|
44
|
-
notes: Type.Array(Type.String()),
|
|
45
|
-
};
|
|
46
|
-
|
|
47
|
-
export const NewEscalationSchema = Type.Object(
|
|
48
|
-
{
|
|
49
|
-
...NewEscalationFields,
|
|
50
|
-
recommendation: Type.Union([EscalationRecommendationSchema, Type.Null()]),
|
|
51
|
-
},
|
|
52
|
-
{ additionalProperties: false },
|
|
53
|
-
);
|
|
54
|
-
|
|
55
|
-
export const EscalationSchema = Type.Object(
|
|
56
|
-
{
|
|
57
|
-
version: Type.Literal(ESCALATION_VERSION),
|
|
58
|
-
specId: Type.String({ pattern: SPEC_ID_PATTERN.source }),
|
|
59
|
-
id: Type.String({ pattern: ESCALATION_ID_PATTERN.source }),
|
|
60
|
-
...NewEscalationFields,
|
|
61
|
-
recommendation: Type.Union([EscalationRecommendationSchema, Type.Null()]),
|
|
62
|
-
resolution: Type.Union([EscalationResolutionSchema, Type.Null()]),
|
|
63
|
-
},
|
|
64
|
-
{ additionalProperties: false },
|
|
65
|
-
);
|
|
66
|
-
|
|
67
|
-
export type EscalationOption = Static<typeof EscalationOptionSchema>;
|
|
68
|
-
|
|
69
|
-
export type EscalationResolution = Static<typeof EscalationResolutionSchema>;
|
|
70
|
-
|
|
71
|
-
export type Escalation = Static<typeof EscalationSchema>;
|
|
72
|
-
|
|
73
|
-
export type NewEscalation = Static<typeof NewEscalationSchema>;
|
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Objective: Register the builder escalation tool for the current project.
|
|
3
|
-
* Used: When the builder finds a significant discovery that requires an owner decision.
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import type { ExtensionAPI } from '@earendil-works/pi-coding-agent';
|
|
7
|
-
import { Type } from 'typebox';
|
|
8
|
-
import {
|
|
9
|
-
EscalationOptionSchema,
|
|
10
|
-
EscalationRecommendationSchema,
|
|
11
|
-
} from '#artifacts/escalation/schema.ts';
|
|
12
|
-
import { SPEC_ID_PATTERN } from '#ids/isValidSpecId.ts';
|
|
13
|
-
import { resolveWorkflowContext } from '#tools/child/utils/resolveWorkflowContext.ts';
|
|
14
|
-
import { openBuilderEscalation } from '#workflow/escalation/openBuilderEscalation.ts';
|
|
15
|
-
|
|
16
|
-
export const BUILDER_ESCALATION_TOOL = {
|
|
17
|
-
NAME: 'maestro_open_escalation',
|
|
18
|
-
LABEL: 'Open Builder Escalation',
|
|
19
|
-
DESCRIPTION:
|
|
20
|
-
'Ask the owner to choose between options for a significant discovery or unresolved decision. An escalation is not limited to a technical failure or blocker. After success, stop. The tool saves the escalation and workflow phase. Do not wait for the owner.',
|
|
21
|
-
} as const;
|
|
22
|
-
|
|
23
|
-
const BuilderEscalationToolParameters = Type.Object(
|
|
24
|
-
{
|
|
25
|
-
specId: Type.String({ pattern: SPEC_ID_PATTERN.source }),
|
|
26
|
-
question: Type.String({ minLength: 1 }),
|
|
27
|
-
context: Type.String({ minLength: 1 }),
|
|
28
|
-
options: Type.Array(EscalationOptionSchema, { minItems: 1 }),
|
|
29
|
-
recommendation: Type.Union([EscalationRecommendationSchema, Type.Null()]),
|
|
30
|
-
notes: Type.Array(Type.String()),
|
|
31
|
-
},
|
|
32
|
-
{ additionalProperties: false },
|
|
33
|
-
);
|
|
34
|
-
|
|
35
|
-
export const registerOpenEscalationTool = (pi: ExtensionAPI): void => {
|
|
36
|
-
pi.registerTool({
|
|
37
|
-
name: BUILDER_ESCALATION_TOOL.NAME,
|
|
38
|
-
label: BUILDER_ESCALATION_TOOL.LABEL,
|
|
39
|
-
description: BUILDER_ESCALATION_TOOL.DESCRIPTION,
|
|
40
|
-
parameters: BuilderEscalationToolParameters,
|
|
41
|
-
async execute(_toolCallId, params, _signal, _onUpdate, context) {
|
|
42
|
-
const { specId, ...escalation } = params;
|
|
43
|
-
|
|
44
|
-
const { paths } = await resolveWorkflowContext({
|
|
45
|
-
cwd: context.cwd,
|
|
46
|
-
specId,
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
const opened = await openBuilderEscalation({
|
|
50
|
-
paths,
|
|
51
|
-
specId,
|
|
52
|
-
escalation,
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
return {
|
|
56
|
-
content: [
|
|
57
|
-
{
|
|
58
|
-
type: 'text',
|
|
59
|
-
text: `Escalation ${opened.escalation.id} recorded. The escalation and workflow phase are saved. Stop now. Do not wait for the owner.`,
|
|
60
|
-
},
|
|
61
|
-
],
|
|
62
|
-
details: {
|
|
63
|
-
specId: opened.escalation.specId,
|
|
64
|
-
escalationId: opened.escalation.id,
|
|
65
|
-
escalationPath: opened.escalationPath,
|
|
66
|
-
phase: opened.state.phase,
|
|
67
|
-
},
|
|
68
|
-
};
|
|
69
|
-
},
|
|
70
|
-
});
|
|
71
|
-
};
|
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Objective: Register owner resolution of the current builder escalation.
|
|
3
|
-
* Used: When the owner keeps the approved contract and chooses an escalation resolution.
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import type { ExtensionAPI } from '@earendil-works/pi-coding-agent';
|
|
7
|
-
import { Type } from 'typebox';
|
|
8
|
-
import {
|
|
9
|
-
ESCALATION_ID_PATTERN,
|
|
10
|
-
EscalationResolutionSchema,
|
|
11
|
-
} from '#artifacts/escalation/schema.ts';
|
|
12
|
-
import { SPEC_ID_PATTERN } from '#ids/isValidSpecId.ts';
|
|
13
|
-
import { resolveToolRunContext } from '#tools/utils/resolveToolRunContext.ts';
|
|
14
|
-
import { resolveBuilderEscalation } from '#workflow/escalation/resolveBuilderEscalation.ts';
|
|
15
|
-
|
|
16
|
-
export const RESOLVE_ESCALATION_TOOL = {
|
|
17
|
-
NAME: 'maestro_resolve_escalation',
|
|
18
|
-
LABEL: 'Resolve Escalation',
|
|
19
|
-
DESCRIPTION:
|
|
20
|
-
'Record the explicit owner decision for the 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.',
|
|
21
|
-
} as const;
|
|
22
|
-
|
|
23
|
-
const ResolveEscalationToolParameters = Type.Object(
|
|
24
|
-
{
|
|
25
|
-
specId: Type.String({ pattern: SPEC_ID_PATTERN.source }),
|
|
26
|
-
escalationId: Type.String({ pattern: ESCALATION_ID_PATTERN.source }),
|
|
27
|
-
...EscalationResolutionSchema.properties,
|
|
28
|
-
},
|
|
29
|
-
{ additionalProperties: false },
|
|
30
|
-
);
|
|
31
|
-
|
|
32
|
-
export const registerResolveEscalationTool = (pi: ExtensionAPI): void => {
|
|
33
|
-
pi.registerTool({
|
|
34
|
-
name: RESOLVE_ESCALATION_TOOL.NAME,
|
|
35
|
-
label: RESOLVE_ESCALATION_TOOL.LABEL,
|
|
36
|
-
description: RESOLVE_ESCALATION_TOOL.DESCRIPTION,
|
|
37
|
-
parameters: ResolveEscalationToolParameters,
|
|
38
|
-
async execute(_toolCallId, params, _signal, _onUpdate, context) {
|
|
39
|
-
const { specId, escalationId, ...resolution } = params;
|
|
40
|
-
const { paths } = await resolveToolRunContext(context.cwd);
|
|
41
|
-
|
|
42
|
-
const resolved = await resolveBuilderEscalation({
|
|
43
|
-
paths,
|
|
44
|
-
specId,
|
|
45
|
-
escalationId,
|
|
46
|
-
resolution,
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
return {
|
|
50
|
-
content: [
|
|
51
|
-
{
|
|
52
|
-
type: 'text',
|
|
53
|
-
text: `Escalation ${resolved.escalation.id} resolved. Spec ${resolved.state.specId} is ready-for-builder. Run the builder separately.`,
|
|
54
|
-
},
|
|
55
|
-
],
|
|
56
|
-
details: {
|
|
57
|
-
specId: resolved.state.specId,
|
|
58
|
-
escalationId: resolved.escalation.id,
|
|
59
|
-
phase: resolved.state.phase,
|
|
60
|
-
projectRoot: resolved.projectRoot,
|
|
61
|
-
},
|
|
62
|
-
};
|
|
63
|
-
},
|
|
64
|
-
});
|
|
65
|
-
};
|
|
@@ -1,82 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Objective: Open a builder escalation and move the workflow to an owner decision.
|
|
3
|
-
* Used: When a builder identifies a significant discovery that requires owner attention.
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { mkdir } from 'node:fs/promises';
|
|
7
|
-
import { createEscalation } from '#artifacts/escalation/createEscalation.ts';
|
|
8
|
-
import type {
|
|
9
|
-
Escalation,
|
|
10
|
-
NewEscalation,
|
|
11
|
-
} from '#artifacts/escalation/schema.ts';
|
|
12
|
-
import type { MaestroPaths } from '#MaestroPaths.ts';
|
|
13
|
-
import { readWorkflowState } from '#workflow/state/readWorkflowState.ts';
|
|
14
|
-
import {
|
|
15
|
-
WORKFLOW_EVENTS,
|
|
16
|
-
WORKFLOW_PHASES,
|
|
17
|
-
type WorkflowState,
|
|
18
|
-
} from '#workflow/state/schema.ts';
|
|
19
|
-
import { writeWorkflowState } from '#workflow/state/writeWorkflowState.ts';
|
|
20
|
-
import { transitionWorkflow } from '#workflow/transitions.ts';
|
|
21
|
-
|
|
22
|
-
export type OpenedBuilderEscalation = {
|
|
23
|
-
escalation: Escalation;
|
|
24
|
-
state: WorkflowState;
|
|
25
|
-
projectRoot: string;
|
|
26
|
-
workflowPath: string;
|
|
27
|
-
escalationPath: string;
|
|
28
|
-
};
|
|
29
|
-
|
|
30
|
-
type OpenBuilderEscalationInput = {
|
|
31
|
-
paths: MaestroPaths;
|
|
32
|
-
specId: string;
|
|
33
|
-
escalation: NewEscalation;
|
|
34
|
-
};
|
|
35
|
-
|
|
36
|
-
export const openBuilderEscalation = async ({
|
|
37
|
-
paths,
|
|
38
|
-
specId,
|
|
39
|
-
escalation,
|
|
40
|
-
}: OpenBuilderEscalationInput): Promise<OpenedBuilderEscalation> => {
|
|
41
|
-
const workflowPath = paths.getWorkflowPath(specId);
|
|
42
|
-
const escalationsPath = paths.getEscalationsPath(specId);
|
|
43
|
-
const currentState = await readWorkflowState(workflowPath);
|
|
44
|
-
|
|
45
|
-
if (currentState.specId !== specId) {
|
|
46
|
-
throw new Error(
|
|
47
|
-
`Workflow spec ID mismatch: expected "${specId}", found "${currentState.specId}".`,
|
|
48
|
-
);
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
if (currentState.phase !== WORKFLOW_PHASES.BUILDER_RUNNING) {
|
|
52
|
-
throw new Error(
|
|
53
|
-
`Builder escalation requires builder-running state, found "${currentState.phase}".`,
|
|
54
|
-
);
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
const nextState = transitionWorkflow({
|
|
58
|
-
state: currentState,
|
|
59
|
-
event: WORKFLOW_EVENTS.OPEN_ESCALATION,
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
await mkdir(escalationsPath, { recursive: true });
|
|
63
|
-
|
|
64
|
-
const created = await createEscalation({
|
|
65
|
-
directory: escalationsPath,
|
|
66
|
-
specId: currentState.specId,
|
|
67
|
-
escalation,
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
await writeWorkflowState({
|
|
71
|
-
path: workflowPath,
|
|
72
|
-
state: nextState,
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
return {
|
|
76
|
-
escalation: created.escalation,
|
|
77
|
-
state: nextState,
|
|
78
|
-
projectRoot: paths.getProjectRoot(),
|
|
79
|
-
workflowPath,
|
|
80
|
-
escalationPath: created.path,
|
|
81
|
-
};
|
|
82
|
-
};
|
|
@@ -1,118 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Objective: Resolve the current builder escalation and save the decision.
|
|
3
|
-
* Used: When the owner keeps the approved contract and makes a decision.
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { readEscalationHistory } from '#artifacts/escalation/readEscalationHistory.ts';
|
|
7
|
-
import { resolveEscalation } from '#artifacts/escalation/resolveEscalation.ts';
|
|
8
|
-
import type {
|
|
9
|
-
Escalation,
|
|
10
|
-
EscalationResolution,
|
|
11
|
-
} from '#artifacts/escalation/schema.ts';
|
|
12
|
-
import type { MaestroPaths } from '#MaestroPaths.ts';
|
|
13
|
-
import { readWorkflowState } from '#workflow/state/readWorkflowState.ts';
|
|
14
|
-
import {
|
|
15
|
-
WORKFLOW_EVENTS,
|
|
16
|
-
WORKFLOW_PHASES,
|
|
17
|
-
type WorkflowState,
|
|
18
|
-
} from '#workflow/state/schema.ts';
|
|
19
|
-
import { writeWorkflowState } from '#workflow/state/writeWorkflowState.ts';
|
|
20
|
-
import { transitionWorkflow } from '#workflow/transitions.ts';
|
|
21
|
-
|
|
22
|
-
export type ResolvedBuilderEscalation = {
|
|
23
|
-
escalation: Escalation;
|
|
24
|
-
state: WorkflowState;
|
|
25
|
-
projectRoot: string;
|
|
26
|
-
};
|
|
27
|
-
|
|
28
|
-
type AssertCurrentEscalationInput<CurrentEscalation = Escalation | undefined> =
|
|
29
|
-
{
|
|
30
|
-
currentEscalation: CurrentEscalation;
|
|
31
|
-
escalationId: string;
|
|
32
|
-
};
|
|
33
|
-
|
|
34
|
-
function assertCurrentEscalation(
|
|
35
|
-
input: AssertCurrentEscalationInput,
|
|
36
|
-
): asserts input is AssertCurrentEscalationInput<Escalation> {
|
|
37
|
-
const { currentEscalation, escalationId } = input;
|
|
38
|
-
|
|
39
|
-
if (currentEscalation === undefined) {
|
|
40
|
-
throw new Error('There is no escalation to solve.');
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
if (currentEscalation.id !== escalationId) {
|
|
44
|
-
throw new Error(
|
|
45
|
-
`Only the current escalation can be resolved: expected "${currentEscalation.id}", found "${escalationId}".`,
|
|
46
|
-
);
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
if (currentEscalation.resolution !== null) {
|
|
50
|
-
throw new Error(`Escalation "${escalationId}" is already resolved.`);
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
type ResolveBuilderEscalationInput = {
|
|
55
|
-
paths: MaestroPaths;
|
|
56
|
-
specId: string;
|
|
57
|
-
escalationId: string;
|
|
58
|
-
resolution: EscalationResolution;
|
|
59
|
-
};
|
|
60
|
-
|
|
61
|
-
export const resolveBuilderEscalation = async ({
|
|
62
|
-
paths,
|
|
63
|
-
specId,
|
|
64
|
-
escalationId,
|
|
65
|
-
resolution,
|
|
66
|
-
}: ResolveBuilderEscalationInput): Promise<ResolvedBuilderEscalation> => {
|
|
67
|
-
const workflowPath = paths.getWorkflowPath(specId);
|
|
68
|
-
const escalationsPath = paths.getEscalationsPath(specId);
|
|
69
|
-
const currentState = await readWorkflowState(workflowPath);
|
|
70
|
-
|
|
71
|
-
if (currentState.specId !== specId) {
|
|
72
|
-
throw new Error(
|
|
73
|
-
`Workflow spec ID mismatch: expected "${specId}", found "${currentState.specId}".`,
|
|
74
|
-
);
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
if (currentState.phase !== WORKFLOW_PHASES.ESCALATION_DECISION) {
|
|
78
|
-
throw new Error(
|
|
79
|
-
`Escalation resolution requires escalation-decision state, found "${currentState.phase}".`,
|
|
80
|
-
);
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
const history = await readEscalationHistory({
|
|
84
|
-
directory: escalationsPath,
|
|
85
|
-
specId,
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
const escalationInput = { currentEscalation: history.at(-1), escalationId };
|
|
89
|
-
assertCurrentEscalation(escalationInput);
|
|
90
|
-
const { currentEscalation } = escalationInput;
|
|
91
|
-
|
|
92
|
-
const nextState = transitionWorkflow({
|
|
93
|
-
state: currentState,
|
|
94
|
-
event: WORKFLOW_EVENTS.RESOLVE_ESCALATION,
|
|
95
|
-
});
|
|
96
|
-
|
|
97
|
-
const escalationPath = paths.getEscalationPath({
|
|
98
|
-
specId,
|
|
99
|
-
escalationNumber: Number(currentEscalation.id.slice(1)),
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
const resolvedEscalation = await resolveEscalation({
|
|
103
|
-
path: escalationPath,
|
|
104
|
-
specId,
|
|
105
|
-
resolution,
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
await writeWorkflowState({
|
|
109
|
-
path: workflowPath,
|
|
110
|
-
state: nextState,
|
|
111
|
-
});
|
|
112
|
-
|
|
113
|
-
return {
|
|
114
|
-
escalation: resolvedEscalation,
|
|
115
|
-
state: nextState,
|
|
116
|
-
projectRoot: paths.getProjectRoot(),
|
|
117
|
-
};
|
|
118
|
-
};
|