@akanjs/devkit 2.3.9-rc.3 → 2.3.9-rc.4
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/akanContext.ts +8 -2
- package/package.json +2 -2
- package/workflow/artifacts.ts +396 -0
- package/workflow/executor.ts +258 -0
- package/workflow/index.ts +8 -1399
- package/workflow/plan.ts +210 -0
- package/workflow/primitive.ts +59 -0
- package/workflow/render.ts +215 -0
- package/workflow/source.ts +224 -0
- package/workflow/types.ts +283 -0
- package/workflow/utils.ts +23 -0
package/workflow/plan.ts
ADDED
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
import { capitalize } from "akanjs/common";
|
|
2
|
+
import { normalizeFieldType } from "./source";
|
|
3
|
+
import type {
|
|
4
|
+
WorkflowDiagnostic,
|
|
5
|
+
WorkflowInputSpec,
|
|
6
|
+
WorkflowInputValue,
|
|
7
|
+
WorkflowPlan,
|
|
8
|
+
WorkflowPlanInputs,
|
|
9
|
+
WorkflowRecommendation,
|
|
10
|
+
WorkflowSpec,
|
|
11
|
+
WorkflowSurfaceMode,
|
|
12
|
+
} from "./types";
|
|
13
|
+
|
|
14
|
+
const surfaceModes = new Set<WorkflowSurfaceMode>(["infer", "include", "skip"]);
|
|
15
|
+
|
|
16
|
+
const parseStringList = (value: unknown) => {
|
|
17
|
+
if (Array.isArray(value)) {
|
|
18
|
+
const values = value.filter((item): item is string => typeof item === "string" && item.trim().length > 0);
|
|
19
|
+
return values.length === value.length ? values : null;
|
|
20
|
+
}
|
|
21
|
+
if (typeof value !== "string") return null;
|
|
22
|
+
return value
|
|
23
|
+
.split(",")
|
|
24
|
+
.map((item) => item.trim())
|
|
25
|
+
.filter(Boolean);
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const normalizeInputValue = (name: string, spec: WorkflowInputSpec, value: unknown): WorkflowInputValue | null => {
|
|
29
|
+
if (spec.type === "string") return typeof value === "string" && value.length > 0 ? value : null;
|
|
30
|
+
if (spec.type === "string-list") {
|
|
31
|
+
const values = parseStringList(value);
|
|
32
|
+
return values && values.length > 0 ? values : null;
|
|
33
|
+
}
|
|
34
|
+
if (typeof value === "string" && surfaceModes.has(value as WorkflowSurfaceMode)) return value as WorkflowSurfaceMode;
|
|
35
|
+
throw new Error(`Unsupported workflow input value for ${name}`);
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export const listWorkflowSpecs = (specs: readonly WorkflowSpec[]) =>
|
|
39
|
+
[...specs].sort((a, b) => a.name.localeCompare(b.name));
|
|
40
|
+
|
|
41
|
+
export const getWorkflowSpec = (specs: readonly WorkflowSpec[], name: string) =>
|
|
42
|
+
specs.find((spec) => spec.name === name) ?? null;
|
|
43
|
+
|
|
44
|
+
export const compactWorkflowInputs = (inputs: WorkflowPlanInputs) =>
|
|
45
|
+
Object.fromEntries(Object.entries(inputs).filter(([, value]) => value !== null && value !== ""));
|
|
46
|
+
|
|
47
|
+
const addFieldComponentForType = (typeName: string) => {
|
|
48
|
+
const normalizedType = normalizeFieldType(typeName);
|
|
49
|
+
if (normalizedType === "Boolean") return "Field.ToggleSelect";
|
|
50
|
+
if (normalizedType === "Date") return "Field.Date";
|
|
51
|
+
if (normalizedType === "Int" || normalizedType === "Float") return "manual numeric input review";
|
|
52
|
+
if (typeName.toLowerCase() === "enum") return "Field.ToggleSelect";
|
|
53
|
+
return "Field.Text";
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
const createAddFieldRecommendations = (inputs: Record<string, WorkflowInputValue>): WorkflowRecommendation[] => {
|
|
57
|
+
const app = typeof inputs.app === "string" ? inputs.app : "<app-or-lib>";
|
|
58
|
+
const module = typeof inputs.module === "string" ? inputs.module : "<module>";
|
|
59
|
+
const field = typeof inputs.field === "string" ? inputs.field : "<field>";
|
|
60
|
+
const typeName = typeof inputs.type === "string" ? inputs.type : null;
|
|
61
|
+
if (!typeName) return [];
|
|
62
|
+
|
|
63
|
+
const normalizedType = typeName.toLowerCase() === "enum" ? "enum" : normalizeFieldType(typeName);
|
|
64
|
+
const constantPath = `*/lib/${module}/${module}.constant.ts`;
|
|
65
|
+
const dictionaryPath = `*/lib/${module}/${module}.dictionary.ts`;
|
|
66
|
+
const templatePath = `*/lib/${module}/${capitalize(module)}.Template.tsx`;
|
|
67
|
+
return [
|
|
68
|
+
...(normalizedType === "Int" || normalizedType === "Float"
|
|
69
|
+
? [
|
|
70
|
+
{
|
|
71
|
+
code: "add-field-import",
|
|
72
|
+
kind: "import" as const,
|
|
73
|
+
target: constantPath,
|
|
74
|
+
confidence: "high" as const,
|
|
75
|
+
message: `Import ${normalizedType} from "akanjs/base" before writing field(${normalizedType}).`,
|
|
76
|
+
},
|
|
77
|
+
]
|
|
78
|
+
: []),
|
|
79
|
+
{
|
|
80
|
+
code: "add-field-placement-constant",
|
|
81
|
+
kind: "placement",
|
|
82
|
+
target: constantPath,
|
|
83
|
+
confidence: "high",
|
|
84
|
+
message: `Insert ${field}: field(${normalizedType}) in ${capitalize(module)}Input.`,
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
code: "add-field-placement-dictionary",
|
|
88
|
+
kind: "placement",
|
|
89
|
+
target: dictionaryPath,
|
|
90
|
+
confidence: "high",
|
|
91
|
+
message: `Add dictionary labels for ${module}.${field}.`,
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
code: "add-field-component",
|
|
95
|
+
kind: "ui-component",
|
|
96
|
+
target: templatePath,
|
|
97
|
+
confidence: normalizedType === "Int" || normalizedType === "Float" ? "low" : "high",
|
|
98
|
+
action:
|
|
99
|
+
normalizedType === "Int" || normalizedType === "Float"
|
|
100
|
+
? "Do not auto-edit UI. Check local Template/Unit/View patterns for Field.Text parsing, formatting, validation rules, and dictionary labels before adding a numeric input."
|
|
101
|
+
: undefined,
|
|
102
|
+
message:
|
|
103
|
+
normalizedType === "Int" || normalizedType === "Float"
|
|
104
|
+
? `Numeric UI for ${field} (${normalizedType}) requires manual review; a safe numeric input component pattern is not yet detected.`
|
|
105
|
+
: `Recommended UI component for ${field} (${normalizedType}): ${addFieldComponentForType(typeName)}.`,
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
code: "add-field-ui-manual-review",
|
|
109
|
+
kind: "manual-action",
|
|
110
|
+
target: templatePath,
|
|
111
|
+
action: `Review ${app}:${module} Template/Unit/View/Store surfaces and add ${field} only where the existing pattern is clear. For numeric fields, confirm parser/formatter behavior and validation before using Field.Text.`,
|
|
112
|
+
confidence: "medium",
|
|
113
|
+
message:
|
|
114
|
+
normalizedType === "Int" || normalizedType === "Float"
|
|
115
|
+
? "UI surface edits stay manual because a safe numeric input component pattern is not yet detected."
|
|
116
|
+
: "UI surface edits are planned as manual-review unless a safe existing field-list pattern is detected.",
|
|
117
|
+
},
|
|
118
|
+
];
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
const createWorkflowPlanRecommendations = (
|
|
122
|
+
spec: WorkflowSpec,
|
|
123
|
+
inputs: Record<string, WorkflowInputValue>,
|
|
124
|
+
): WorkflowRecommendation[] => [
|
|
125
|
+
{
|
|
126
|
+
code: "workflow-apply-first",
|
|
127
|
+
kind: "auto-apply",
|
|
128
|
+
confidence: "high",
|
|
129
|
+
action: "Call apply_workflow with the MCP planPath before editing source files directly.",
|
|
130
|
+
message: `Apply the ${spec.name} plan through apply_workflow when MCP returns planPath or next.tool=apply_workflow.`,
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
code: "workflow-validate-apply-report",
|
|
134
|
+
kind: "validation",
|
|
135
|
+
confidence: "high",
|
|
136
|
+
action:
|
|
137
|
+
"After apply_workflow, call run_validation with validationTarget when present; otherwise use applyReportPath.",
|
|
138
|
+
message: "Validate the apply report artifact so diagnostics and recommendations follow the actual apply result.",
|
|
139
|
+
},
|
|
140
|
+
...(spec.name === "add-field" ? createAddFieldRecommendations(inputs) : []),
|
|
141
|
+
];
|
|
142
|
+
|
|
143
|
+
export const createWorkflowPlan = (spec: WorkflowSpec, rawInputs: Record<string, unknown>): WorkflowPlan => {
|
|
144
|
+
const inputs: Record<string, WorkflowInputValue> = {};
|
|
145
|
+
const diagnostics: WorkflowDiagnostic[] = [];
|
|
146
|
+
|
|
147
|
+
for (const [name, inputSpec] of Object.entries(spec.inputs)) {
|
|
148
|
+
const rawValue = rawInputs[name];
|
|
149
|
+
if (rawValue === undefined || rawValue === null || rawValue === "") {
|
|
150
|
+
if (inputSpec.required) {
|
|
151
|
+
diagnostics.push({
|
|
152
|
+
severity: "error",
|
|
153
|
+
code: "workflow-input-missing",
|
|
154
|
+
input: name,
|
|
155
|
+
message: `Workflow ${spec.name} requires input "${name}".`,
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
continue;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const value = normalizeInputValue(name, inputSpec, rawValue);
|
|
162
|
+
if (value === null) {
|
|
163
|
+
diagnostics.push({
|
|
164
|
+
severity: "error",
|
|
165
|
+
code: "workflow-input-invalid",
|
|
166
|
+
input: name,
|
|
167
|
+
message: `Workflow ${spec.name} input "${name}" must be ${inputSpec.type}.`,
|
|
168
|
+
});
|
|
169
|
+
continue;
|
|
170
|
+
}
|
|
171
|
+
if (inputSpec.allowedValues && typeof value === "string" && !inputSpec.allowedValues.includes(value)) {
|
|
172
|
+
diagnostics.push({
|
|
173
|
+
severity: "error",
|
|
174
|
+
code: "workflow-input-not-allowed",
|
|
175
|
+
input: name,
|
|
176
|
+
message: `Workflow ${spec.name} input "${name}" must be one of: ${inputSpec.allowedValues.join(", ")}.`,
|
|
177
|
+
});
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
180
|
+
inputs[name] = value;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const fieldType = inputs.type;
|
|
184
|
+
if (
|
|
185
|
+
spec.name === "add-field" &&
|
|
186
|
+
typeof fieldType === "string" &&
|
|
187
|
+
(fieldType.toLowerCase() === "number" || fieldType.toLowerCase() === "numeric")
|
|
188
|
+
) {
|
|
189
|
+
diagnostics.push({
|
|
190
|
+
severity: "error",
|
|
191
|
+
code: "primitive-field-type-unsupported",
|
|
192
|
+
input: "type",
|
|
193
|
+
message: `Field type "${fieldType}" is ambiguous in Akan. Use Int for integer fields or Float for decimal fields.`,
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
return {
|
|
198
|
+
schemaVersion: 1,
|
|
199
|
+
workflow: spec.name,
|
|
200
|
+
mode: "plan",
|
|
201
|
+
inputs,
|
|
202
|
+
optionalSurfaces: spec.optionalSurfaces ?? {},
|
|
203
|
+
steps: spec.steps,
|
|
204
|
+
predictedChanges: spec.predictedChanges,
|
|
205
|
+
validation: spec.validation,
|
|
206
|
+
diagnostics,
|
|
207
|
+
recommendations: createWorkflowPlanRecommendations(spec, inputs),
|
|
208
|
+
requiresApproval: true,
|
|
209
|
+
};
|
|
210
|
+
};
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import type { PrimitiveFormat, PrimitiveWriteReport } from "./types";
|
|
2
|
+
import { jsonText } from "./utils";
|
|
3
|
+
|
|
4
|
+
export const createPrimitiveWriteReport = ({
|
|
5
|
+
command,
|
|
6
|
+
status,
|
|
7
|
+
changedFiles = [],
|
|
8
|
+
generatedFiles = [],
|
|
9
|
+
validationCommands = [],
|
|
10
|
+
diagnostics = [],
|
|
11
|
+
nextActions = [],
|
|
12
|
+
}: Omit<PrimitiveWriteReport, "schemaVersion" | "status"> & {
|
|
13
|
+
status?: PrimitiveWriteReport["status"];
|
|
14
|
+
}): PrimitiveWriteReport => ({
|
|
15
|
+
schemaVersion: 1,
|
|
16
|
+
command,
|
|
17
|
+
status: status ?? (diagnostics.some((diagnostic) => diagnostic.severity === "error") ? "failed" : "passed"),
|
|
18
|
+
changedFiles,
|
|
19
|
+
generatedFiles,
|
|
20
|
+
validationCommands,
|
|
21
|
+
diagnostics,
|
|
22
|
+
nextActions,
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
export const renderPrimitiveWriteReport = (report: PrimitiveWriteReport) =>
|
|
26
|
+
[
|
|
27
|
+
`# Primitive Write: ${report.command}`,
|
|
28
|
+
"",
|
|
29
|
+
`- Status: ${report.status}`,
|
|
30
|
+
"",
|
|
31
|
+
"## Changed Files",
|
|
32
|
+
...(report.changedFiles.length
|
|
33
|
+
? report.changedFiles.map((file) => `- \`${file.action}\` ${file.path}: ${file.reason}`)
|
|
34
|
+
: ["- none"]),
|
|
35
|
+
"",
|
|
36
|
+
"## Generated Files",
|
|
37
|
+
...(report.generatedFiles.length
|
|
38
|
+
? report.generatedFiles.map((file) => `- \`${file.action}\` ${file.path}: ${file.reason}`)
|
|
39
|
+
: ["- none"]),
|
|
40
|
+
"",
|
|
41
|
+
"## Validation Commands",
|
|
42
|
+
...(report.validationCommands.length
|
|
43
|
+
? report.validationCommands.map((validation) => `- \`${validation.command}\`: ${validation.reason}`)
|
|
44
|
+
: ["- none"]),
|
|
45
|
+
"",
|
|
46
|
+
"## Diagnostics",
|
|
47
|
+
...(report.diagnostics.length
|
|
48
|
+
? report.diagnostics.map((diagnostic) => `- [${diagnostic.severity}] ${diagnostic.code}: ${diagnostic.message}`)
|
|
49
|
+
: ["- none"]),
|
|
50
|
+
"",
|
|
51
|
+
"## Next Actions",
|
|
52
|
+
...(report.nextActions.length
|
|
53
|
+
? report.nextActions.map((action) => `- \`${action.command}\`: ${action.reason}`)
|
|
54
|
+
: ["- none"]),
|
|
55
|
+
"",
|
|
56
|
+
].join("\n");
|
|
57
|
+
|
|
58
|
+
export const renderPrimitiveReport = (report: PrimitiveWriteReport, format: PrimitiveFormat = "markdown") =>
|
|
59
|
+
format === "json" ? jsonText(report) : renderPrimitiveWriteReport(report);
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
RepairReport,
|
|
3
|
+
WorkflowApplyReport,
|
|
4
|
+
WorkflowFormat,
|
|
5
|
+
WorkflowPlan,
|
|
6
|
+
WorkflowRunArtifact,
|
|
7
|
+
WorkflowSpec,
|
|
8
|
+
WorkflowValidationRunReport,
|
|
9
|
+
} from "./types";
|
|
10
|
+
import { jsonText } from "./utils";
|
|
11
|
+
|
|
12
|
+
export const renderWorkflowList = (specs: readonly WorkflowSpec[]) =>
|
|
13
|
+
[
|
|
14
|
+
"# Akan Workflows",
|
|
15
|
+
"",
|
|
16
|
+
...specs.flatMap((spec) => [`- \`${spec.name}\`: ${spec.description}`, ` - When: ${spec.whenToUse}`]),
|
|
17
|
+
"",
|
|
18
|
+
].join("\n");
|
|
19
|
+
|
|
20
|
+
export const renderWorkflowExplain = (spec: WorkflowSpec) =>
|
|
21
|
+
[
|
|
22
|
+
`# Workflow: ${spec.name}`,
|
|
23
|
+
"",
|
|
24
|
+
spec.description,
|
|
25
|
+
"",
|
|
26
|
+
"## When To Use",
|
|
27
|
+
spec.whenToUse,
|
|
28
|
+
"",
|
|
29
|
+
"## Inputs",
|
|
30
|
+
...Object.entries(spec.inputs).map(
|
|
31
|
+
([name, input]) =>
|
|
32
|
+
`- \`${name}\`${input.required ? " (required)" : ""}: ${input.description}${
|
|
33
|
+
input.allowedValues ? ` Allowed: ${input.allowedValues.join(", ")}.` : ""
|
|
34
|
+
}`,
|
|
35
|
+
),
|
|
36
|
+
"",
|
|
37
|
+
"## Optional Surfaces",
|
|
38
|
+
...Object.entries(spec.optionalSurfaces ?? {}).map(([name, mode]) => `- \`${name}\`: ${mode}`),
|
|
39
|
+
"",
|
|
40
|
+
"## Steps",
|
|
41
|
+
...spec.steps.map((step, index) => `${index + 1}. \`${step.id}\` (${step.tool}): ${step.description}`),
|
|
42
|
+
"",
|
|
43
|
+
"## Validation",
|
|
44
|
+
...spec.validation.map((validation) => `- \`${validation.command}\`: ${validation.reason}`),
|
|
45
|
+
"",
|
|
46
|
+
].join("\n");
|
|
47
|
+
|
|
48
|
+
export const renderWorkflowPlan = (plan: WorkflowPlan) =>
|
|
49
|
+
[
|
|
50
|
+
`# Workflow Plan: ${plan.workflow}`,
|
|
51
|
+
"",
|
|
52
|
+
`- Mode: ${plan.mode}`,
|
|
53
|
+
`- Requires approval: ${plan.requiresApproval}`,
|
|
54
|
+
"",
|
|
55
|
+
"## Inputs",
|
|
56
|
+
...Object.entries(plan.inputs).map(
|
|
57
|
+
([name, value]) => `- \`${name}\`: ${Array.isArray(value) ? value.join(", ") : value}`,
|
|
58
|
+
),
|
|
59
|
+
"",
|
|
60
|
+
"## Optional Surfaces",
|
|
61
|
+
...Object.entries(plan.optionalSurfaces).map(([name, mode]) => `- \`${name}\`: ${mode}`),
|
|
62
|
+
"",
|
|
63
|
+
"## Steps",
|
|
64
|
+
...plan.steps.map((step, index) => `${index + 1}. \`${step.id}\` (${step.tool}): ${step.description}`),
|
|
65
|
+
"",
|
|
66
|
+
"## Predicted Changes",
|
|
67
|
+
...plan.predictedChanges.map((change) => {
|
|
68
|
+
const scope = change.applyScope ? ` (${change.applyScope})` : "";
|
|
69
|
+
return `- \`${change.action}\`${scope} ${change.target}: ${change.reason}`;
|
|
70
|
+
}),
|
|
71
|
+
"",
|
|
72
|
+
"## Validation",
|
|
73
|
+
...plan.validation.map((validation) => `- \`${validation.command}\`: ${validation.reason}`),
|
|
74
|
+
"",
|
|
75
|
+
"## Diagnostics",
|
|
76
|
+
...(plan.diagnostics.length
|
|
77
|
+
? plan.diagnostics.map((diagnostic) => `- [${diagnostic.severity}] ${diagnostic.code}: ${diagnostic.message}`)
|
|
78
|
+
: ["- none"]),
|
|
79
|
+
"",
|
|
80
|
+
"## Recommendations",
|
|
81
|
+
...(plan.recommendations.length
|
|
82
|
+
? plan.recommendations.map((recommendation) => `- [${recommendation.kind}] ${recommendation.message}`)
|
|
83
|
+
: ["- none"]),
|
|
84
|
+
"",
|
|
85
|
+
].join("\n");
|
|
86
|
+
|
|
87
|
+
export const renderWorkflowApplyReport = (report: WorkflowApplyReport) =>
|
|
88
|
+
[
|
|
89
|
+
`# Workflow Apply: ${report.workflow}`,
|
|
90
|
+
"",
|
|
91
|
+
`- Mode: ${report.mode}`,
|
|
92
|
+
`- Status: ${report.status}`,
|
|
93
|
+
"",
|
|
94
|
+
"## Changed Files",
|
|
95
|
+
...(report.changedFiles.length
|
|
96
|
+
? report.changedFiles.map((file) => `- \`${file.action}\` ${file.path}: ${file.reason}`)
|
|
97
|
+
: ["- none"]),
|
|
98
|
+
"",
|
|
99
|
+
"## Generated Files",
|
|
100
|
+
...(report.generatedFiles.length
|
|
101
|
+
? report.generatedFiles.map((file) => `- \`${file.action}\` ${file.path}: ${file.reason}`)
|
|
102
|
+
: ["- none"]),
|
|
103
|
+
"",
|
|
104
|
+
"## Applied Commands",
|
|
105
|
+
...(report.appliedCommands.length
|
|
106
|
+
? report.appliedCommands.map((command) => `- \`${command.command}\`: ${command.reason}`)
|
|
107
|
+
: ["- none"]),
|
|
108
|
+
"",
|
|
109
|
+
"## Recommended Validation Commands",
|
|
110
|
+
...(report.recommendedValidationCommands.length
|
|
111
|
+
? report.recommendedValidationCommands.map((command) => `- \`${command.command}\`: ${command.reason}`)
|
|
112
|
+
: ["- none"]),
|
|
113
|
+
"",
|
|
114
|
+
"## Diagnostics",
|
|
115
|
+
...(report.diagnostics.length
|
|
116
|
+
? report.diagnostics.map((diagnostic) => `- [${diagnostic.severity}] ${diagnostic.code}: ${diagnostic.message}`)
|
|
117
|
+
: ["- none"]),
|
|
118
|
+
"",
|
|
119
|
+
"## Recommendations",
|
|
120
|
+
...(report.recommendations.length
|
|
121
|
+
? report.recommendations.map((recommendation) => `- [${recommendation.kind}] ${recommendation.message}`)
|
|
122
|
+
: ["- none"]),
|
|
123
|
+
"",
|
|
124
|
+
"## Next Actions",
|
|
125
|
+
...(report.nextActions.length
|
|
126
|
+
? report.nextActions.map((action) => `- \`${action.command}\`: ${action.reason}`)
|
|
127
|
+
: ["- none"]),
|
|
128
|
+
"",
|
|
129
|
+
].join("\n");
|
|
130
|
+
|
|
131
|
+
export const renderWorkflowApply = (report: WorkflowApplyReport, format: WorkflowFormat = "markdown") =>
|
|
132
|
+
format === "json" ? jsonText(report) : renderWorkflowApplyReport(report);
|
|
133
|
+
|
|
134
|
+
export const renderWorkflowValidationRunReport = (report: WorkflowValidationRunReport) =>
|
|
135
|
+
[
|
|
136
|
+
`# Workflow Validation: ${report.workflow}`,
|
|
137
|
+
"",
|
|
138
|
+
`- Run: ${report.runId}`,
|
|
139
|
+
`- Status: ${report.status}`,
|
|
140
|
+
`- Source status: ${report.sourceStatus}`,
|
|
141
|
+
`- Workspace status: ${report.workspaceStatus}`,
|
|
142
|
+
`- Overall status: ${report.overallStatus}`,
|
|
143
|
+
"",
|
|
144
|
+
"## Known Blockers",
|
|
145
|
+
...(report.knownBlockers.length
|
|
146
|
+
? report.knownBlockers.map((blocker) => {
|
|
147
|
+
const command = blocker.command ? ` \`${blocker.command}\`` : "";
|
|
148
|
+
const count = blocker.count > 1 ? ` (${blocker.count}x)` : "";
|
|
149
|
+
return `- [${blocker.failureScope}]${command}${count}: ${blocker.message}`;
|
|
150
|
+
})
|
|
151
|
+
: ["- none"]),
|
|
152
|
+
"",
|
|
153
|
+
"## Commands",
|
|
154
|
+
...(report.commands.length
|
|
155
|
+
? report.commands.map((command) => {
|
|
156
|
+
const scope = command.failureScope ? ` (${command.failureScope})` : "";
|
|
157
|
+
return `- [${command.status}] \`${command.command}\`${scope}: ${command.reason}`;
|
|
158
|
+
})
|
|
159
|
+
: ["- none"]),
|
|
160
|
+
"",
|
|
161
|
+
"## Diagnostics",
|
|
162
|
+
...(report.diagnostics.length
|
|
163
|
+
? report.diagnostics.map((diagnostic) => `- [${diagnostic.severity}] ${diagnostic.code}: ${diagnostic.message}`)
|
|
164
|
+
: ["- none"]),
|
|
165
|
+
"",
|
|
166
|
+
"## Repair Actions",
|
|
167
|
+
...(report.repairActions.length
|
|
168
|
+
? report.repairActions.map((action) => `- \`${action.command}\`: ${action.reason}`)
|
|
169
|
+
: ["- none"]),
|
|
170
|
+
"",
|
|
171
|
+
"## Next Actions",
|
|
172
|
+
...(report.nextActions.length
|
|
173
|
+
? report.nextActions.map((action) => `- \`${action.command}\`: ${action.reason}`)
|
|
174
|
+
: ["- none"]),
|
|
175
|
+
"",
|
|
176
|
+
].join("\n");
|
|
177
|
+
|
|
178
|
+
export const renderWorkflowValidation = (report: WorkflowValidationRunReport, format: WorkflowFormat = "markdown") =>
|
|
179
|
+
format === "json" ? jsonText(report) : renderWorkflowValidationRunReport(report);
|
|
180
|
+
|
|
181
|
+
export const renderRepairReportMarkdown = (report: RepairReport) =>
|
|
182
|
+
[
|
|
183
|
+
`# Akan Repair: ${report.kind}`,
|
|
184
|
+
"",
|
|
185
|
+
`- Status: ${report.status}`,
|
|
186
|
+
`- Target: ${report.target ?? "none"}`,
|
|
187
|
+
"",
|
|
188
|
+
"## Commands",
|
|
189
|
+
...(report.commands.length
|
|
190
|
+
? report.commands.map((command) => `- [${command.status}] \`${command.command}\`: ${command.reason}`)
|
|
191
|
+
: ["- none"]),
|
|
192
|
+
"",
|
|
193
|
+
"## Diagnostics",
|
|
194
|
+
...(report.diagnostics.length
|
|
195
|
+
? report.diagnostics.map((diagnostic) => `- [${diagnostic.severity}] ${diagnostic.code}: ${diagnostic.message}`)
|
|
196
|
+
: ["- none"]),
|
|
197
|
+
"",
|
|
198
|
+
"## Next Actions",
|
|
199
|
+
...(report.nextActions.length
|
|
200
|
+
? report.nextActions.map((action) => `- \`${action.command}\`: ${action.reason}`)
|
|
201
|
+
: ["- none"]),
|
|
202
|
+
"",
|
|
203
|
+
].join("\n");
|
|
204
|
+
|
|
205
|
+
export const renderRepairReport = (report: RepairReport, format: WorkflowFormat = "markdown") =>
|
|
206
|
+
format === "json" ? jsonText(report) : renderRepairReportMarkdown(report);
|
|
207
|
+
|
|
208
|
+
export const renderWorkflowRunArtifact = (artifact: WorkflowRunArtifact, format: WorkflowFormat = "markdown") => {
|
|
209
|
+
if ("kind" in artifact) return renderRepairReport(artifact, format);
|
|
210
|
+
if ("mode" in artifact && artifact.mode === "validate") return renderWorkflowValidation(artifact, format);
|
|
211
|
+
if ("mode" in artifact && (artifact.mode === "apply" || artifact.mode === "dry-run")) {
|
|
212
|
+
return renderWorkflowApply(artifact, format);
|
|
213
|
+
}
|
|
214
|
+
return jsonText(artifact);
|
|
215
|
+
};
|