@akanjs/cli 2.4.0 → 2.4.1-rc.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.
- package/agent.command-h4afc69n.js +26 -0
- package/application.command-47mj9qsy.js +165 -0
- package/applicationBuildRunner-yz144508.js +284 -0
- package/applicationReleasePackager-brvth6rs.js +245 -0
- package/capacitorApp-y0h6cgft.js +58 -0
- package/cloud.command-qgjxja3n.js +94 -0
- package/commandManifest.json +1 -0
- package/context.command-nqbtak4f.js +82 -0
- package/dependencyScanner-m4x5maek.js +9 -0
- package/guideline.command-ya0dh44f.js +356 -0
- package/incrementalBuilder.proc.js +89 -17394
- package/index-1xdrsbry.js +1447 -0
- package/index-45aj5ry0.js +990 -0
- package/index-5vvwc0cz.js +559 -0
- package/index-61keag0s.js +40 -0
- package/index-6pz1j0zj.js +62 -0
- package/index-73pr2cmy.js +534 -0
- package/index-76rn3g2c.js +76 -0
- package/index-77crfweb.js +1884 -0
- package/index-85msc0wg.js +161 -0
- package/index-8pkbzj26.js +840 -0
- package/index-8rc0bm04.js +514 -0
- package/index-a5rmdgy4.js +4359 -0
- package/index-a6sbyy0b.js +2769 -0
- package/index-fgc8r6dj.js +33 -0
- package/index-h6ca6qg0.js +2777 -0
- package/index-hdqztm58.js +758 -0
- package/index-hwzpw9c1.js +202 -0
- package/index-pmm9e2jf.js +120 -0
- package/index-qaq13qk3.js +80 -0
- package/index-qhtr07v8.js +1072 -0
- package/index-r24hmh0q.js +4 -0
- package/index-sgmas1fc.js +462 -0
- package/index-ss469dec.js +11 -0
- package/index-swf4bmbg.js +25 -0
- package/index-wnp7hwq7.js +193 -0
- package/index-wq8jwx8z.js +224 -0
- package/index-x53a5nya.js +301 -0
- package/index-y3hdhy4p.js +229 -0
- package/index-z9gvz7b0.js +83 -0
- package/index.js +54 -23340
- package/library.command-r15zdqvp.js +33 -0
- package/localRegistry.command-p1pgxw78.js +178 -0
- package/module.command-qrj3kmyz.js +54 -0
- package/package.command-r8sq5kzp.js +43 -0
- package/package.json +2 -2
- package/page.command-c6xdx0xm.js +24 -0
- package/primitive.command-pv9ssmtf.js +62 -0
- package/quality.command-es67wvdp.js +809 -0
- package/repair.command-677675vw.js +67 -0
- package/routeSourceValidator-wbhmbwpj.js +132 -0
- package/scalar.command-kabkd6wd.js +35 -0
- package/templates/facetIndex/index.ts +1 -1
- package/typeChecker-kravn7ns.js +8 -0
- package/typecheck.proc.js +4 -194
- package/workflow.command-64r6cw0w.js +108 -0
- package/workspace.command-xk68sd6c.js +435 -0
|
@@ -0,0 +1,990 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
import {
|
|
3
|
+
AkanContextAnalyzer
|
|
4
|
+
} from "./index-8rc0bm04.js";
|
|
5
|
+
import {
|
|
6
|
+
WorkflowExecutor,
|
|
7
|
+
compactWorkflowInputs,
|
|
8
|
+
createDryRunWorkflowApplyReport,
|
|
9
|
+
createWorkflowApplyReport,
|
|
10
|
+
createWorkflowBaselineSummary,
|
|
11
|
+
createWorkflowPlan,
|
|
12
|
+
createWorkflowValidationRunReport,
|
|
13
|
+
getWorkflowSpec,
|
|
14
|
+
jsonText,
|
|
15
|
+
listWorkflowSpecs,
|
|
16
|
+
readWorkflowRunArtifact,
|
|
17
|
+
renderWorkflowApply,
|
|
18
|
+
renderWorkflowExplain,
|
|
19
|
+
renderWorkflowList,
|
|
20
|
+
renderWorkflowPlan,
|
|
21
|
+
renderWorkflowRunArtifact,
|
|
22
|
+
renderWorkflowValidation,
|
|
23
|
+
workflowCommandsForPlan,
|
|
24
|
+
workflowPlanApproval,
|
|
25
|
+
writeWorkflowRunArtifact
|
|
26
|
+
} from "./index-h6ca6qg0.js";
|
|
27
|
+
import {
|
|
28
|
+
runner
|
|
29
|
+
} from "./index-hdqztm58.js";
|
|
30
|
+
|
|
31
|
+
// pkgs/@akanjs/cli/workflow/workflow.runner.ts
|
|
32
|
+
import { mkdir, readFile, writeFile } from "fs/promises";
|
|
33
|
+
import path from "path";
|
|
34
|
+
import { capitalize } from "akanjs/common";
|
|
35
|
+
|
|
36
|
+
// pkgs/@akanjs/cli/workflows/shared.ts
|
|
37
|
+
var sysInputs = {
|
|
38
|
+
app: {
|
|
39
|
+
type: "string",
|
|
40
|
+
required: true,
|
|
41
|
+
description: "Target app or library name."
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
var moduleInput = {
|
|
45
|
+
module: {
|
|
46
|
+
type: "string",
|
|
47
|
+
required: true,
|
|
48
|
+
description: "Target domain, service, or scalar module name."
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
var baseValidation = [
|
|
52
|
+
{ command: "akan sync <app-or-lib>", reason: "Refresh generated Akan files from source conventions.", kind: "sync" },
|
|
53
|
+
{
|
|
54
|
+
command: "akan lint <app-or-lib-or-pkg>",
|
|
55
|
+
reason: "Validate formatting, imports, and static lint rules.",
|
|
56
|
+
kind: "lint"
|
|
57
|
+
}
|
|
58
|
+
];
|
|
59
|
+
|
|
60
|
+
// pkgs/@akanjs/cli/workflows/addEnumField.ts
|
|
61
|
+
var addEnumFieldWorkflowSpec = {
|
|
62
|
+
schemaVersion: 1,
|
|
63
|
+
name: "add-enum-field",
|
|
64
|
+
description: "Plan an enum field addition with option and dictionary coverage.",
|
|
65
|
+
whenToUse: "Use when the user asks to add a field with a closed set of values.",
|
|
66
|
+
inputs: {
|
|
67
|
+
...sysInputs,
|
|
68
|
+
...moduleInput,
|
|
69
|
+
field: { type: "string", required: true, description: "Name of the enum field to add." },
|
|
70
|
+
values: { type: "string-list", required: true, description: "Comma-separated enum values." },
|
|
71
|
+
default: { type: "string", description: "Optional default enum value." }
|
|
72
|
+
},
|
|
73
|
+
optionalSurfaces: {
|
|
74
|
+
dictionary: "include",
|
|
75
|
+
option: "include",
|
|
76
|
+
template: "infer",
|
|
77
|
+
unit: "infer",
|
|
78
|
+
view: "infer",
|
|
79
|
+
store: "infer"
|
|
80
|
+
},
|
|
81
|
+
steps: [
|
|
82
|
+
{
|
|
83
|
+
id: "inspect-module",
|
|
84
|
+
title: "Inspect module",
|
|
85
|
+
tool: "inspectModule",
|
|
86
|
+
description: "Read module files and existing enum conventions."
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
id: "update-constant",
|
|
90
|
+
title: "Update enum field",
|
|
91
|
+
tool: "updateConstantEnumField",
|
|
92
|
+
description: "Plan enum type and field shape changes."
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
id: "update-dictionary",
|
|
96
|
+
title: "Update dictionary",
|
|
97
|
+
tool: "updateDictionaryEnum",
|
|
98
|
+
description: "Plan enum labels and field labels."
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
id: "update-option",
|
|
102
|
+
title: "Update options",
|
|
103
|
+
tool: "updateOptionEnum",
|
|
104
|
+
description: "Plan option entries used by UI controls."
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
id: "sync-generated",
|
|
108
|
+
title: "Sync generated files",
|
|
109
|
+
tool: "syncTarget",
|
|
110
|
+
description: "Refresh generated files after enum source changes."
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
id: "validate-target",
|
|
114
|
+
title: "Validate target",
|
|
115
|
+
tool: "lintTarget",
|
|
116
|
+
description: "Run validation commands for the target."
|
|
117
|
+
}
|
|
118
|
+
],
|
|
119
|
+
predictedChanges: [
|
|
120
|
+
{ target: "*/lib/<module>/<module>.constant.ts", action: "modify", reason: "Enum field shape is added." },
|
|
121
|
+
{ target: "*/lib/<module>/<module>.dictionary.ts", action: "modify", reason: "Enum labels are added." },
|
|
122
|
+
{ target: "*/lib/<module>/<module>.option.ts", action: "modify", reason: "Enum options may be added." }
|
|
123
|
+
],
|
|
124
|
+
validation: [
|
|
125
|
+
...baseValidation,
|
|
126
|
+
{ command: "akan typecheck <app-name>", reason: "Validate enum usage across module surfaces.", kind: "typecheck" }
|
|
127
|
+
],
|
|
128
|
+
completionCriteria: ["Enum values are represented.", "Labels and options exist.", "Sync, lint, and typecheck pass."]
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
// pkgs/@akanjs/cli/workflows/addField.ts
|
|
132
|
+
var addFieldWorkflowSpec = {
|
|
133
|
+
schemaVersion: 1,
|
|
134
|
+
name: "add-field",
|
|
135
|
+
description: "Plan a field addition across domain and selected UI surfaces.",
|
|
136
|
+
whenToUse: "Use when the user asks to add a new field to an existing domain module.",
|
|
137
|
+
inputs: {
|
|
138
|
+
...sysInputs,
|
|
139
|
+
...moduleInput,
|
|
140
|
+
field: { type: "string", required: true, description: "Name of the field to add." },
|
|
141
|
+
type: {
|
|
142
|
+
type: "string",
|
|
143
|
+
required: true,
|
|
144
|
+
description: "Field type or scalar name. Use Int for integer fields or Float for decimal fields; do not use Number."
|
|
145
|
+
},
|
|
146
|
+
values: { type: "string-list", description: "Comma-separated enum values when type is enum." },
|
|
147
|
+
default: {
|
|
148
|
+
type: "string",
|
|
149
|
+
description: "Optional default value. plan/apply coerce by field type: Int/Float to numeric literals, Boolean to true/false, Date to new Date(...), String/scalar to string literals, and enum only when the value is in values."
|
|
150
|
+
},
|
|
151
|
+
surfaces: {
|
|
152
|
+
type: "string-list",
|
|
153
|
+
description: "Optional UI surfaces to update, comma-separated. Use template to auto-update simple generated Template forms; View/Unit stay planned as manual review."
|
|
154
|
+
},
|
|
155
|
+
includeInLight: {
|
|
156
|
+
type: "boolean",
|
|
157
|
+
description: "Whether to add the field to the Light<Model> projection used by list/card displays."
|
|
158
|
+
}
|
|
159
|
+
},
|
|
160
|
+
optionalSurfaces: {
|
|
161
|
+
dictionary: "include",
|
|
162
|
+
template: "infer",
|
|
163
|
+
unit: "infer",
|
|
164
|
+
view: "infer",
|
|
165
|
+
store: "infer"
|
|
166
|
+
},
|
|
167
|
+
steps: [
|
|
168
|
+
{
|
|
169
|
+
id: "inspect-module",
|
|
170
|
+
title: "Inspect module",
|
|
171
|
+
tool: "inspectModule",
|
|
172
|
+
description: "Read module files and detect existing field/UI patterns."
|
|
173
|
+
},
|
|
174
|
+
{
|
|
175
|
+
id: "update-constant",
|
|
176
|
+
title: "Update constant",
|
|
177
|
+
tool: "updateConstantField",
|
|
178
|
+
description: "Plan the source-of-truth field shape change."
|
|
179
|
+
},
|
|
180
|
+
{
|
|
181
|
+
id: "update-dictionary",
|
|
182
|
+
title: "Update dictionary",
|
|
183
|
+
tool: "updateDictionaryField",
|
|
184
|
+
description: "Plan field labels and user-facing text."
|
|
185
|
+
},
|
|
186
|
+
{
|
|
187
|
+
id: "update-ui-surfaces",
|
|
188
|
+
title: "Update UI surfaces",
|
|
189
|
+
tool: "updateUiSurfaces",
|
|
190
|
+
description: "Plan optional Template, Unit, View, and Store updates according to inferred surfaces.",
|
|
191
|
+
when: "optionalSurfaces.* is include or infer"
|
|
192
|
+
},
|
|
193
|
+
{
|
|
194
|
+
id: "sync-generated",
|
|
195
|
+
title: "Sync generated files",
|
|
196
|
+
tool: "syncTarget",
|
|
197
|
+
description: "Refresh generated files after source changes."
|
|
198
|
+
},
|
|
199
|
+
{
|
|
200
|
+
id: "validate-target",
|
|
201
|
+
title: "Validate target",
|
|
202
|
+
tool: "lintTarget",
|
|
203
|
+
description: "Run validation commands for the target."
|
|
204
|
+
}
|
|
205
|
+
],
|
|
206
|
+
predictedChanges: [
|
|
207
|
+
{
|
|
208
|
+
target: "*/lib/<module>/<module>.constant.ts",
|
|
209
|
+
action: "modify",
|
|
210
|
+
applyScope: "auto",
|
|
211
|
+
reason: "Field shape is added."
|
|
212
|
+
},
|
|
213
|
+
{
|
|
214
|
+
target: "*/lib/<module>/<module>.dictionary.ts",
|
|
215
|
+
action: "modify",
|
|
216
|
+
applyScope: "auto",
|
|
217
|
+
reason: "Field label is added."
|
|
218
|
+
},
|
|
219
|
+
{
|
|
220
|
+
target: "*/lib/<module>/<Module>.Template.tsx",
|
|
221
|
+
action: "modify",
|
|
222
|
+
applyScope: "manual-review",
|
|
223
|
+
reason: "Form surface may include the field."
|
|
224
|
+
},
|
|
225
|
+
{
|
|
226
|
+
target: "*/lib/cnst.ts",
|
|
227
|
+
action: "sync",
|
|
228
|
+
applyScope: "generated-sync",
|
|
229
|
+
reason: "Generated constants may change after sync."
|
|
230
|
+
},
|
|
231
|
+
{
|
|
232
|
+
target: "*/lib/dict.ts",
|
|
233
|
+
action: "sync",
|
|
234
|
+
applyScope: "generated-sync",
|
|
235
|
+
reason: "Generated dictionary barrel may change after sync."
|
|
236
|
+
}
|
|
237
|
+
],
|
|
238
|
+
validation: [
|
|
239
|
+
...baseValidation,
|
|
240
|
+
{
|
|
241
|
+
command: "akan typecheck <app-name>",
|
|
242
|
+
reason: "Validate cross-surface TypeScript contracts after field changes.",
|
|
243
|
+
kind: "typecheck"
|
|
244
|
+
}
|
|
245
|
+
],
|
|
246
|
+
completionCriteria: [
|
|
247
|
+
"Field exists in source-of-truth module shape.",
|
|
248
|
+
"Dictionary labels cover the field.",
|
|
249
|
+
"Generated files are refreshed.",
|
|
250
|
+
"Lint and typecheck pass."
|
|
251
|
+
]
|
|
252
|
+
};
|
|
253
|
+
|
|
254
|
+
// pkgs/@akanjs/cli/workflows/addMutation.ts
|
|
255
|
+
var addMutationWorkflowSpec = {
|
|
256
|
+
schemaVersion: 1,
|
|
257
|
+
name: "add-mutation",
|
|
258
|
+
description: "Plan a server mutation and matching signal/store/UI action surface.",
|
|
259
|
+
whenToUse: "Use when the user asks for a new state-changing module action.",
|
|
260
|
+
inputs: {
|
|
261
|
+
...sysInputs,
|
|
262
|
+
...moduleInput,
|
|
263
|
+
mutation: { type: "string", required: true, description: "Mutation/action name." }
|
|
264
|
+
},
|
|
265
|
+
optionalSurfaces: {
|
|
266
|
+
service: "include",
|
|
267
|
+
signal: "include",
|
|
268
|
+
store: "infer",
|
|
269
|
+
util: "infer",
|
|
270
|
+
unit: "infer",
|
|
271
|
+
view: "infer",
|
|
272
|
+
dictionary: "include"
|
|
273
|
+
},
|
|
274
|
+
steps: [
|
|
275
|
+
{
|
|
276
|
+
id: "inspect-module",
|
|
277
|
+
title: "Inspect module",
|
|
278
|
+
tool: "inspectModule",
|
|
279
|
+
description: "Read service, signal, store, and UI action patterns."
|
|
280
|
+
},
|
|
281
|
+
{
|
|
282
|
+
id: "update-service",
|
|
283
|
+
title: "Update service",
|
|
284
|
+
tool: "updateServiceMutation",
|
|
285
|
+
description: "Plan the server-side mutation method."
|
|
286
|
+
},
|
|
287
|
+
{
|
|
288
|
+
id: "update-signal",
|
|
289
|
+
title: "Update signal",
|
|
290
|
+
tool: "updateSignalMutation",
|
|
291
|
+
description: "Plan the client/server signal mutation binding."
|
|
292
|
+
},
|
|
293
|
+
{
|
|
294
|
+
id: "update-action-surfaces",
|
|
295
|
+
title: "Update action surfaces",
|
|
296
|
+
tool: "updateActionSurfaces",
|
|
297
|
+
description: "Plan optional store and UI action changes."
|
|
298
|
+
},
|
|
299
|
+
{
|
|
300
|
+
id: "sync-generated",
|
|
301
|
+
title: "Sync generated files",
|
|
302
|
+
tool: "syncTarget",
|
|
303
|
+
description: "Refresh generated service and signal barrels."
|
|
304
|
+
},
|
|
305
|
+
{
|
|
306
|
+
id: "validate-target",
|
|
307
|
+
title: "Validate target",
|
|
308
|
+
tool: "lintTarget",
|
|
309
|
+
description: "Run validation commands for the target."
|
|
310
|
+
}
|
|
311
|
+
],
|
|
312
|
+
predictedChanges: [
|
|
313
|
+
{
|
|
314
|
+
target: "*/lib/<module>/<module>.service.ts",
|
|
315
|
+
action: "modify",
|
|
316
|
+
applyScope: "auto",
|
|
317
|
+
reason: "Service mutation method stub is added."
|
|
318
|
+
},
|
|
319
|
+
{
|
|
320
|
+
target: "*/lib/<module>/<module>.signal.ts",
|
|
321
|
+
action: "modify",
|
|
322
|
+
applyScope: "auto",
|
|
323
|
+
reason: "Signal endpoint mutation is added."
|
|
324
|
+
},
|
|
325
|
+
{ target: "*/lib/srv.ts", action: "sync", reason: "Generated service barrel may change after sync." },
|
|
326
|
+
{ target: "*/lib/sig.ts", action: "sync", reason: "Generated signal barrel may change after sync." }
|
|
327
|
+
],
|
|
328
|
+
validation: [
|
|
329
|
+
...baseValidation,
|
|
330
|
+
{
|
|
331
|
+
command: "akan typecheck <app-name>",
|
|
332
|
+
reason: "Validate mutation contracts across service and signal.",
|
|
333
|
+
kind: "typecheck"
|
|
334
|
+
}
|
|
335
|
+
],
|
|
336
|
+
completionCriteria: ["Service and signal contracts align.", "Generated files are refreshed.", "Validation passes."]
|
|
337
|
+
};
|
|
338
|
+
|
|
339
|
+
// pkgs/@akanjs/cli/workflows/addSlice.ts
|
|
340
|
+
var addSliceWorkflowSpec = {
|
|
341
|
+
schemaVersion: 1,
|
|
342
|
+
name: "add-slice",
|
|
343
|
+
description: "Plan a list/query slice across data access, signal, and view surfaces.",
|
|
344
|
+
whenToUse: "Use when the user asks for a filtered list, tab, segment, or reusable query slice.",
|
|
345
|
+
inputs: {
|
|
346
|
+
...sysInputs,
|
|
347
|
+
...moduleInput,
|
|
348
|
+
slice: { type: "string", required: true, description: "Slice or query name." }
|
|
349
|
+
},
|
|
350
|
+
optionalSurfaces: {
|
|
351
|
+
service: "include",
|
|
352
|
+
signal: "include",
|
|
353
|
+
zone: "infer",
|
|
354
|
+
page: "infer",
|
|
355
|
+
dictionary: "include"
|
|
356
|
+
},
|
|
357
|
+
steps: [
|
|
358
|
+
{
|
|
359
|
+
id: "inspect-module",
|
|
360
|
+
title: "Inspect module",
|
|
361
|
+
tool: "inspectModule",
|
|
362
|
+
description: "Read existing query, slice, zone, and page loading patterns."
|
|
363
|
+
},
|
|
364
|
+
{
|
|
365
|
+
id: "update-service-query",
|
|
366
|
+
title: "Update service query",
|
|
367
|
+
tool: "updateServiceSlice",
|
|
368
|
+
description: "Plan the query helper or filter handling."
|
|
369
|
+
},
|
|
370
|
+
{
|
|
371
|
+
id: "update-signal-slice",
|
|
372
|
+
title: "Update signal slice",
|
|
373
|
+
tool: "updateSignalSlice",
|
|
374
|
+
description: "Plan the signal slice contract."
|
|
375
|
+
},
|
|
376
|
+
{
|
|
377
|
+
id: "connect-view-surfaces",
|
|
378
|
+
title: "Connect view surfaces",
|
|
379
|
+
tool: "connectSliceSurfaces",
|
|
380
|
+
description: "Plan optional Zone and page loader changes."
|
|
381
|
+
},
|
|
382
|
+
{
|
|
383
|
+
id: "sync-generated",
|
|
384
|
+
title: "Sync generated files",
|
|
385
|
+
tool: "syncTarget",
|
|
386
|
+
description: "Refresh generated files after slice changes."
|
|
387
|
+
},
|
|
388
|
+
{
|
|
389
|
+
id: "validate-target",
|
|
390
|
+
title: "Validate target",
|
|
391
|
+
tool: "lintTarget",
|
|
392
|
+
description: "Run validation commands for the target."
|
|
393
|
+
}
|
|
394
|
+
],
|
|
395
|
+
predictedChanges: [
|
|
396
|
+
{
|
|
397
|
+
target: "*/lib/<module>/<module>.service.ts",
|
|
398
|
+
action: "modify",
|
|
399
|
+
applyScope: "auto",
|
|
400
|
+
reason: "Query helper method stub is added."
|
|
401
|
+
},
|
|
402
|
+
{
|
|
403
|
+
target: "*/lib/<module>/<module>.signal.ts",
|
|
404
|
+
action: "modify",
|
|
405
|
+
applyScope: "auto",
|
|
406
|
+
reason: "Signal slice entry is added."
|
|
407
|
+
},
|
|
408
|
+
{
|
|
409
|
+
target: "*/lib/<module>/<Module>.Zone.tsx",
|
|
410
|
+
action: "modify",
|
|
411
|
+
applyScope: "manual-review",
|
|
412
|
+
reason: "Zone may connect slice state."
|
|
413
|
+
},
|
|
414
|
+
{ target: "*/lib/sig.ts", action: "sync", reason: "Generated signal barrel may change after sync." }
|
|
415
|
+
],
|
|
416
|
+
validation: [
|
|
417
|
+
...baseValidation,
|
|
418
|
+
{
|
|
419
|
+
command: "akan build <app-name>",
|
|
420
|
+
reason: "Validate page and list surfaces when a slice changes app behavior.",
|
|
421
|
+
kind: "custom"
|
|
422
|
+
}
|
|
423
|
+
],
|
|
424
|
+
completionCriteria: [
|
|
425
|
+
"Slice query and signal shape align.",
|
|
426
|
+
"Optional view surfaces are planned.",
|
|
427
|
+
"Validation passes."
|
|
428
|
+
]
|
|
429
|
+
};
|
|
430
|
+
|
|
431
|
+
// pkgs/@akanjs/cli/workflows/createModule.ts
|
|
432
|
+
var createModuleWorkflowSpec = {
|
|
433
|
+
schemaVersion: 1,
|
|
434
|
+
name: "create-module",
|
|
435
|
+
description: "Plan a new database-backed domain module using Akan module conventions.",
|
|
436
|
+
whenToUse: "Use when the user asks for a new business entity with constant, service, signal, store, and UI surfaces.",
|
|
437
|
+
inputs: {
|
|
438
|
+
...sysInputs,
|
|
439
|
+
module: { type: "string", required: true, description: "Name of the module to create." }
|
|
440
|
+
},
|
|
441
|
+
optionalSurfaces: {
|
|
442
|
+
page: "infer",
|
|
443
|
+
template: "include",
|
|
444
|
+
unit: "include",
|
|
445
|
+
view: "include"
|
|
446
|
+
},
|
|
447
|
+
steps: [
|
|
448
|
+
{
|
|
449
|
+
id: "inspect-system",
|
|
450
|
+
title: "Inspect target system",
|
|
451
|
+
tool: "inspectSystem",
|
|
452
|
+
description: "Check the target app or library before planning new module files."
|
|
453
|
+
},
|
|
454
|
+
{
|
|
455
|
+
id: "create-module",
|
|
456
|
+
title: "Create module",
|
|
457
|
+
tool: "createModule",
|
|
458
|
+
description: "Use the existing create-module primitive to scaffold conventional module files."
|
|
459
|
+
},
|
|
460
|
+
{
|
|
461
|
+
id: "sync-generated",
|
|
462
|
+
title: "Sync generated files",
|
|
463
|
+
tool: "syncTarget",
|
|
464
|
+
description: "Refresh generated barrels after adding a module."
|
|
465
|
+
},
|
|
466
|
+
{
|
|
467
|
+
id: "validate-target",
|
|
468
|
+
title: "Validate target",
|
|
469
|
+
tool: "lintTarget",
|
|
470
|
+
description: "Run validation commands that prove the scaffold matches Akan conventions."
|
|
471
|
+
}
|
|
472
|
+
],
|
|
473
|
+
predictedChanges: [
|
|
474
|
+
{ target: "*/lib/<module>/*", action: "create", reason: "New module source files are scaffolded." },
|
|
475
|
+
{ target: "*/lib/cnst.ts", action: "sync", reason: "Generated constant barrel may include the new module." },
|
|
476
|
+
{ target: "*/lib/srv.ts", action: "sync", reason: "Generated service barrel may include the new module." },
|
|
477
|
+
{ target: "*/lib/sig.ts", action: "sync", reason: "Generated signal barrel may include the new module." }
|
|
478
|
+
],
|
|
479
|
+
validation: baseValidation,
|
|
480
|
+
completionCriteria: [
|
|
481
|
+
"Module abstract and conventional files exist.",
|
|
482
|
+
"Generated files are refreshed with sync.",
|
|
483
|
+
"Lint passes for the target."
|
|
484
|
+
]
|
|
485
|
+
};
|
|
486
|
+
|
|
487
|
+
// pkgs/@akanjs/cli/workflows/createScalar.ts
|
|
488
|
+
var createScalarWorkflowSpec = {
|
|
489
|
+
schemaVersion: 1,
|
|
490
|
+
name: "create-scalar",
|
|
491
|
+
description: "Plan a reusable scalar value module without database ownership.",
|
|
492
|
+
whenToUse: "Use when the user asks for a reusable value object, primitive domain type, or shared scalar.",
|
|
493
|
+
inputs: {
|
|
494
|
+
...sysInputs,
|
|
495
|
+
scalar: { type: "string", required: true, description: "Name of the scalar to create." }
|
|
496
|
+
},
|
|
497
|
+
optionalSurfaces: {
|
|
498
|
+
dictionary: "include"
|
|
499
|
+
},
|
|
500
|
+
steps: [
|
|
501
|
+
{
|
|
502
|
+
id: "inspect-system",
|
|
503
|
+
title: "Inspect target system",
|
|
504
|
+
tool: "inspectSystem",
|
|
505
|
+
description: "Check the target app or library before planning a scalar."
|
|
506
|
+
},
|
|
507
|
+
{
|
|
508
|
+
id: "create-scalar",
|
|
509
|
+
title: "Create scalar",
|
|
510
|
+
tool: "createScalar",
|
|
511
|
+
description: "Use the existing create-scalar primitive to scaffold scalar files."
|
|
512
|
+
},
|
|
513
|
+
{
|
|
514
|
+
id: "sync-generated",
|
|
515
|
+
title: "Sync generated files",
|
|
516
|
+
tool: "syncTarget",
|
|
517
|
+
description: "Refresh generated barrels after adding a scalar."
|
|
518
|
+
},
|
|
519
|
+
{
|
|
520
|
+
id: "validate-target",
|
|
521
|
+
title: "Validate target",
|
|
522
|
+
tool: "lintTarget",
|
|
523
|
+
description: "Run validation commands for the scalar target."
|
|
524
|
+
}
|
|
525
|
+
],
|
|
526
|
+
predictedChanges: [
|
|
527
|
+
{ target: "*/lib/__scalar/<scalar>/*", action: "create", reason: "New scalar source files are scaffolded." }
|
|
528
|
+
],
|
|
529
|
+
validation: baseValidation,
|
|
530
|
+
completionCriteria: ["Scalar files exist under __scalar.", "Generated files are refreshed.", "Lint passes."]
|
|
531
|
+
};
|
|
532
|
+
|
|
533
|
+
// pkgs/@akanjs/cli/workflows/createUi.ts
|
|
534
|
+
var createUiWorkflowSpec = {
|
|
535
|
+
schemaVersion: 1,
|
|
536
|
+
name: "create-ui",
|
|
537
|
+
description: "Plan creation of a conventional module UI surface.",
|
|
538
|
+
whenToUse: "Use when the user asks for a View, Unit, Template, Zone, or Util component for an existing module.",
|
|
539
|
+
inputs: {
|
|
540
|
+
...sysInputs,
|
|
541
|
+
...moduleInput,
|
|
542
|
+
surface: {
|
|
543
|
+
type: "string",
|
|
544
|
+
required: true,
|
|
545
|
+
description: "UI surface to create.",
|
|
546
|
+
allowedValues: ["view", "unit", "template", "zone", "util"]
|
|
547
|
+
}
|
|
548
|
+
},
|
|
549
|
+
optionalSurfaces: {
|
|
550
|
+
dictionary: "infer",
|
|
551
|
+
store: "infer"
|
|
552
|
+
},
|
|
553
|
+
steps: [
|
|
554
|
+
{
|
|
555
|
+
id: "inspect-module",
|
|
556
|
+
title: "Inspect module",
|
|
557
|
+
tool: "inspectModule",
|
|
558
|
+
description: "Read existing module context and component files."
|
|
559
|
+
},
|
|
560
|
+
{
|
|
561
|
+
id: "create-ui",
|
|
562
|
+
title: "Create UI surface",
|
|
563
|
+
tool: "createUi",
|
|
564
|
+
description: "Create the requested UI surface through an Akan primitive."
|
|
565
|
+
},
|
|
566
|
+
{
|
|
567
|
+
id: "sync-generated",
|
|
568
|
+
title: "Sync generated files",
|
|
569
|
+
tool: "syncTarget",
|
|
570
|
+
description: "Refresh UI barrels after adding a component."
|
|
571
|
+
},
|
|
572
|
+
{
|
|
573
|
+
id: "validate-target",
|
|
574
|
+
title: "Validate target",
|
|
575
|
+
tool: "lintTarget",
|
|
576
|
+
description: "Run validation commands for the target."
|
|
577
|
+
}
|
|
578
|
+
],
|
|
579
|
+
predictedChanges: [
|
|
580
|
+
{ target: "*/lib/<module>/<Module>.<Surface>.tsx", action: "create", reason: "New UI component is created." },
|
|
581
|
+
{ target: "*/ui/index.ts", action: "sync", reason: "Generated UI barrel may include the new component." }
|
|
582
|
+
],
|
|
583
|
+
validation: baseValidation,
|
|
584
|
+
completionCriteria: ["Requested UI surface exists.", "UI barrels are refreshed.", "Lint passes."]
|
|
585
|
+
};
|
|
586
|
+
|
|
587
|
+
// pkgs/@akanjs/cli/workflows/index.ts
|
|
588
|
+
var workflowSpecs = [
|
|
589
|
+
createModuleWorkflowSpec,
|
|
590
|
+
createScalarWorkflowSpec,
|
|
591
|
+
createUiWorkflowSpec,
|
|
592
|
+
addFieldWorkflowSpec,
|
|
593
|
+
addEnumFieldWorkflowSpec,
|
|
594
|
+
addMutationWorkflowSpec,
|
|
595
|
+
addSliceWorkflowSpec
|
|
596
|
+
];
|
|
597
|
+
|
|
598
|
+
// pkgs/@akanjs/cli/workflow/workflow.runner.ts
|
|
599
|
+
var resolvePath = (filePath) => path.isAbsolute(filePath) ? filePath : path.join(process.cwd(), filePath);
|
|
600
|
+
var isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
601
|
+
var isWorkflowPlan = (value) => {
|
|
602
|
+
if (!isRecord(value))
|
|
603
|
+
return false;
|
|
604
|
+
return value.schemaVersion === 1 && value.mode === "plan" && typeof value.workflow === "string";
|
|
605
|
+
};
|
|
606
|
+
var isWorkflowApplyReport = (value) => {
|
|
607
|
+
if (!isRecord(value))
|
|
608
|
+
return false;
|
|
609
|
+
return value.schemaVersion === 1 && (value.mode === "apply" || value.mode === "dry-run") && isWorkflowPlan(value.plan);
|
|
610
|
+
};
|
|
611
|
+
var isWorkflowRunArtifact = (value) => {
|
|
612
|
+
if (!isRecord(value))
|
|
613
|
+
return false;
|
|
614
|
+
return value.schemaVersion === 1 && (typeof value.mode === "string" || typeof value.command === "string");
|
|
615
|
+
};
|
|
616
|
+
var failedPlan = (workflow, diagnostics) => ({
|
|
617
|
+
schemaVersion: 1,
|
|
618
|
+
workflow,
|
|
619
|
+
mode: "plan",
|
|
620
|
+
inputs: {},
|
|
621
|
+
optionalSurfaces: {},
|
|
622
|
+
steps: [],
|
|
623
|
+
predictedChanges: [],
|
|
624
|
+
validation: [],
|
|
625
|
+
diagnostics,
|
|
626
|
+
recommendations: [],
|
|
627
|
+
requiresApproval: true,
|
|
628
|
+
approval: workflowPlanApproval
|
|
629
|
+
});
|
|
630
|
+
var failedApplyReport = (workflow, diagnostics, plan) => createWorkflowApplyReport({
|
|
631
|
+
workflow,
|
|
632
|
+
mode: "apply",
|
|
633
|
+
changedFiles: [],
|
|
634
|
+
generatedFiles: [],
|
|
635
|
+
commands: [],
|
|
636
|
+
diagnostics,
|
|
637
|
+
nextActions: [],
|
|
638
|
+
plan: plan ?? failedPlan(workflow, diagnostics)
|
|
639
|
+
});
|
|
640
|
+
var commandForShell = (command) => command.startsWith("akan ") ? `bun run ${command}` : command;
|
|
641
|
+
var inferValidationKind = (command) => {
|
|
642
|
+
if (command.kind)
|
|
643
|
+
return command.kind;
|
|
644
|
+
if (/\bakan\s+sync\b/.test(command.command))
|
|
645
|
+
return "sync";
|
|
646
|
+
if (/\bakan\s+lint\b/.test(command.command))
|
|
647
|
+
return "lint";
|
|
648
|
+
if (/\bakan\s+typecheck\b/.test(command.command))
|
|
649
|
+
return "typecheck";
|
|
650
|
+
if (/\bakan\s+doctor\b/.test(command.command))
|
|
651
|
+
return "doctor";
|
|
652
|
+
return "custom";
|
|
653
|
+
};
|
|
654
|
+
var classifyValidationFailure = (command, error) => {
|
|
655
|
+
const output = `${error.stdout ?? ""}
|
|
656
|
+
${error.stderr ?? ""}
|
|
657
|
+
${error.message ?? ""}`.toLowerCase();
|
|
658
|
+
if (error.code === 127 || output.includes("command not found") || output.includes("bun: command not found")) {
|
|
659
|
+
return "environment";
|
|
660
|
+
}
|
|
661
|
+
if (output.includes("biome.json") || output.includes("biome configuration") || output.includes("configuration file") || output.includes("invalid configuration") || output.includes("failed to load")) {
|
|
662
|
+
return "workspace-config";
|
|
663
|
+
}
|
|
664
|
+
const kind = inferValidationKind(command);
|
|
665
|
+
if (kind === "lint" || kind === "typecheck" || kind === "sync")
|
|
666
|
+
return "source-change";
|
|
667
|
+
return "unknown";
|
|
668
|
+
};
|
|
669
|
+
var defaultValidationExecutor = (workspace) => async (command) => {
|
|
670
|
+
const kind = inferValidationKind(command);
|
|
671
|
+
try {
|
|
672
|
+
const stdout = await workspace.spawn("bash", ["-lc", commandForShell(command.command)], {
|
|
673
|
+
cwd: workspace.workspaceRoot
|
|
674
|
+
});
|
|
675
|
+
return {
|
|
676
|
+
command: command.command,
|
|
677
|
+
reason: command.reason,
|
|
678
|
+
kind,
|
|
679
|
+
status: "passed",
|
|
680
|
+
exitCode: 0,
|
|
681
|
+
stdout
|
|
682
|
+
};
|
|
683
|
+
} catch (error) {
|
|
684
|
+
const commandError = error;
|
|
685
|
+
return {
|
|
686
|
+
command: command.command,
|
|
687
|
+
reason: command.reason,
|
|
688
|
+
kind,
|
|
689
|
+
status: "failed",
|
|
690
|
+
exitCode: commandError.code ?? 1,
|
|
691
|
+
failureScope: classifyValidationFailure(command, commandError),
|
|
692
|
+
stdout: commandError.stdout,
|
|
693
|
+
stderr: commandError.stderr ?? commandError.message
|
|
694
|
+
};
|
|
695
|
+
}
|
|
696
|
+
};
|
|
697
|
+
var readJsonFile = async (filePath) => JSON.parse(await readFile(resolvePath(filePath), "utf8"));
|
|
698
|
+
var planInputString = (plan, key) => {
|
|
699
|
+
const value = plan.inputs[key];
|
|
700
|
+
return typeof value === "string" ? value : "";
|
|
701
|
+
};
|
|
702
|
+
var workflowPathsForPlanLike = (plan) => {
|
|
703
|
+
const app = planInputString(plan, "app");
|
|
704
|
+
const module = planInputString(plan, "module");
|
|
705
|
+
const moduleClass = module ? capitalize(module) : "<Module>";
|
|
706
|
+
return plan.predictedChanges.map((change) => change.target.replace(/^\*\//, app ? `apps/${app}/` : "").replaceAll("<module>", module || "<module>").replaceAll("<Module>", moduleClass));
|
|
707
|
+
};
|
|
708
|
+
var workflowDiagnosticFromDoctor = (diagnostic, fallbackScope) => ({
|
|
709
|
+
severity: diagnostic.severity,
|
|
710
|
+
code: diagnostic.code,
|
|
711
|
+
message: diagnostic.message,
|
|
712
|
+
scope: diagnostic.scope ?? fallbackScope,
|
|
713
|
+
failureScope: (diagnostic.scope ?? fallbackScope) === "baseline" ? "workspace-config" : (diagnostic.scope ?? fallbackScope) === "workflow" ? "source-change" : "unknown",
|
|
714
|
+
context: diagnostic.context
|
|
715
|
+
});
|
|
716
|
+
var baselineBlockerCachePath = (workflow) => `.akan/workflows/baseline/${workflow}.json`;
|
|
717
|
+
var blockerFingerprint = (blocker) => [blocker.failureScope, blocker.code, blocker.command ?? "", blocker.kind ?? "", blocker.message].join("|");
|
|
718
|
+
var applyBaselineBlockerCache = async (workspace, report) => {
|
|
719
|
+
const cacheableBlockers = report.knownBlockers.filter((blocker) => blocker.failureScope === "workspace-config" || blocker.failureScope === "environment");
|
|
720
|
+
if (cacheableBlockers.length === 0)
|
|
721
|
+
return report;
|
|
722
|
+
const cachePath = baselineBlockerCachePath(report.workflow);
|
|
723
|
+
const cached = (await workspace.exists(cachePath) ? await workspace.readJson(cachePath) : null) ?? { schemaVersion: 1, workflow: report.workflow, blockers: [] };
|
|
724
|
+
const knownFingerprints = new Set(cached.blockers.map((blocker) => blocker.fingerprint));
|
|
725
|
+
const now = new Date().toISOString();
|
|
726
|
+
const blockers = new Map(cached.blockers.map((blocker) => [blocker.fingerprint, blocker]));
|
|
727
|
+
for (const blocker of cacheableBlockers) {
|
|
728
|
+
const fingerprint = blockerFingerprint(blocker);
|
|
729
|
+
blockers.set(fingerprint, {
|
|
730
|
+
fingerprint,
|
|
731
|
+
code: blocker.code,
|
|
732
|
+
message: blocker.message,
|
|
733
|
+
failureScope: blocker.failureScope,
|
|
734
|
+
command: blocker.command,
|
|
735
|
+
kind: blocker.kind,
|
|
736
|
+
lastSeenAt: now
|
|
737
|
+
});
|
|
738
|
+
}
|
|
739
|
+
await workspace.writeFile(cachePath, jsonText({ schemaVersion: 1, workflow: report.workflow, blockers: [...blockers.values()] }), { silent: true });
|
|
740
|
+
const knownBlockers = report.knownBlockers.map((blocker) => {
|
|
741
|
+
if (!knownFingerprints.has(blockerFingerprint(blocker)))
|
|
742
|
+
return blocker;
|
|
743
|
+
return {
|
|
744
|
+
...blocker,
|
|
745
|
+
known: true,
|
|
746
|
+
message: `Known baseline blocker, unrelated to this source change: ${blocker.message}`
|
|
747
|
+
};
|
|
748
|
+
});
|
|
749
|
+
return {
|
|
750
|
+
...report,
|
|
751
|
+
knownBlockers,
|
|
752
|
+
baselineSummary: {
|
|
753
|
+
...report.baselineSummary,
|
|
754
|
+
knownBlockerCount: knownBlockers.filter((blocker) => blocker.known).length
|
|
755
|
+
}
|
|
756
|
+
};
|
|
757
|
+
};
|
|
758
|
+
var withBaselineDetailsPolicy = (report, includeBaselineDetails) => {
|
|
759
|
+
const baselineDiagnostics = report.baselineDiagnostics ?? [];
|
|
760
|
+
return {
|
|
761
|
+
...report,
|
|
762
|
+
baselineSummary: createWorkflowBaselineSummary(baselineDiagnostics, {
|
|
763
|
+
detailsIncluded: includeBaselineDetails,
|
|
764
|
+
knownBlockerCount: report.knownBlockers.filter((blocker) => blocker.known).length
|
|
765
|
+
}),
|
|
766
|
+
baselineDiagnostics: includeBaselineDetails ? baselineDiagnostics : []
|
|
767
|
+
};
|
|
768
|
+
};
|
|
769
|
+
|
|
770
|
+
class WorkflowRunner extends runner("workflow") {
|
|
771
|
+
list({ format = "markdown" } = {}) {
|
|
772
|
+
const workflows = listWorkflowSpecs(workflowSpecs);
|
|
773
|
+
if (format === "json")
|
|
774
|
+
return jsonText({
|
|
775
|
+
schemaVersion: 1,
|
|
776
|
+
workflows: workflows.map(({ name, description, whenToUse }) => ({ name, description, whenToUse }))
|
|
777
|
+
});
|
|
778
|
+
return renderWorkflowList(workflows);
|
|
779
|
+
}
|
|
780
|
+
explain(workflow, { format = "markdown" } = {}) {
|
|
781
|
+
const spec = getWorkflowSpec(workflowSpecs, workflow);
|
|
782
|
+
if (!spec)
|
|
783
|
+
throw new Error(`Unknown workflow: ${workflow}. Run \`akan workflow list\` to see available workflows.`);
|
|
784
|
+
return format === "json" ? jsonText(spec) : renderWorkflowExplain(spec);
|
|
785
|
+
}
|
|
786
|
+
async plan(workflow, inputs, { format = "markdown", out = null } = {}) {
|
|
787
|
+
const spec = getWorkflowSpec(workflowSpecs, workflow);
|
|
788
|
+
if (!spec)
|
|
789
|
+
throw new Error(`Unknown workflow: ${workflow}. Run \`akan workflow list\` to see available workflows.`);
|
|
790
|
+
const plan = createWorkflowPlan(spec, compactWorkflowInputs(inputs));
|
|
791
|
+
if (out) {
|
|
792
|
+
const outPath = resolvePath(out);
|
|
793
|
+
await mkdir(path.dirname(outPath), { recursive: true });
|
|
794
|
+
await writeFile(outPath, jsonText(plan));
|
|
795
|
+
}
|
|
796
|
+
return format === "json" ? jsonText(plan) : renderWorkflowPlan(plan);
|
|
797
|
+
}
|
|
798
|
+
async apply(planPath, {
|
|
799
|
+
dryRun = false,
|
|
800
|
+
format = "markdown",
|
|
801
|
+
registry,
|
|
802
|
+
workspace
|
|
803
|
+
} = {}) {
|
|
804
|
+
const renderApplyReport = async (report) => {
|
|
805
|
+
if (!workspace)
|
|
806
|
+
return renderWorkflowApply(report, format);
|
|
807
|
+
const { artifact } = await writeWorkflowRunArtifact(workspace, report);
|
|
808
|
+
return renderWorkflowApply(artifact, format);
|
|
809
|
+
};
|
|
810
|
+
let plan;
|
|
811
|
+
try {
|
|
812
|
+
const parsed = JSON.parse(await readFile(resolvePath(planPath), "utf8"));
|
|
813
|
+
if (!isWorkflowPlan(parsed)) {
|
|
814
|
+
const report = failedApplyReport("unknown", [
|
|
815
|
+
{
|
|
816
|
+
severity: "error",
|
|
817
|
+
code: "workflow-plan-invalid",
|
|
818
|
+
message: `Workflow plan file is invalid: ${planPath}.`
|
|
819
|
+
}
|
|
820
|
+
]);
|
|
821
|
+
return await renderApplyReport(report);
|
|
822
|
+
}
|
|
823
|
+
plan = parsed;
|
|
824
|
+
} catch (error) {
|
|
825
|
+
const report = failedApplyReport("unknown", [
|
|
826
|
+
{
|
|
827
|
+
severity: "error",
|
|
828
|
+
code: "workflow-plan-read-failed",
|
|
829
|
+
message: `Could not read workflow plan file: ${planPath}. ${error instanceof Error ? error.message : ""}`.trim()
|
|
830
|
+
}
|
|
831
|
+
]);
|
|
832
|
+
return await renderApplyReport(report);
|
|
833
|
+
}
|
|
834
|
+
const spec = getWorkflowSpec(workflowSpecs, plan.workflow);
|
|
835
|
+
if (!spec) {
|
|
836
|
+
const report = failedApplyReport(plan.workflow, [
|
|
837
|
+
{
|
|
838
|
+
severity: "error",
|
|
839
|
+
code: "workflow-unknown",
|
|
840
|
+
message: `Unknown workflow in plan: ${plan.workflow}.`
|
|
841
|
+
}
|
|
842
|
+
], plan);
|
|
843
|
+
return await renderApplyReport(report);
|
|
844
|
+
}
|
|
845
|
+
if (dryRun)
|
|
846
|
+
return await renderApplyReport(createDryRunWorkflowApplyReport(plan));
|
|
847
|
+
if (!registry) {
|
|
848
|
+
const report = failedApplyReport(plan.workflow, [
|
|
849
|
+
{
|
|
850
|
+
severity: "error",
|
|
851
|
+
code: "workflow-registry-missing",
|
|
852
|
+
message: "Workflow apply requires a step runner registry."
|
|
853
|
+
}
|
|
854
|
+
], plan);
|
|
855
|
+
return await renderApplyReport(report);
|
|
856
|
+
}
|
|
857
|
+
return await renderApplyReport(await new WorkflowExecutor(registry, workspace).apply(plan));
|
|
858
|
+
}
|
|
859
|
+
async validate(runIdOrPlan, {
|
|
860
|
+
format = "markdown",
|
|
861
|
+
workspace,
|
|
862
|
+
execute,
|
|
863
|
+
includeBaselineDetails = false
|
|
864
|
+
}) {
|
|
865
|
+
const loaded = await this.loadValidationTarget(runIdOrPlan, workspace);
|
|
866
|
+
const doctor = await AkanContextAnalyzer.doctor(workspace, {
|
|
867
|
+
strict: true,
|
|
868
|
+
runIdOrPlan,
|
|
869
|
+
changedFiles: loaded.changedFiles
|
|
870
|
+
});
|
|
871
|
+
const report = withBaselineDetailsPolicy(await applyBaselineBlockerCache(workspace, await createWorkflowValidationRunReport({
|
|
872
|
+
workflow: loaded.plan?.workflow ?? loaded.workflow,
|
|
873
|
+
source: loaded.source,
|
|
874
|
+
plan: loaded.plan,
|
|
875
|
+
commands: loaded.commands,
|
|
876
|
+
execute: execute ?? defaultValidationExecutor(workspace),
|
|
877
|
+
diagnostics: loaded.diagnostics,
|
|
878
|
+
baselineDiagnostics: (doctor.baselineDiagnostics ?? []).map((diagnostic) => workflowDiagnosticFromDoctor(diagnostic, "baseline")),
|
|
879
|
+
workflowDiagnostics: (doctor.workflowDiagnostics ?? []).map((diagnostic) => workflowDiagnosticFromDoctor(diagnostic, "workflow")),
|
|
880
|
+
repairActions: loaded.repairActions
|
|
881
|
+
})), includeBaselineDetails);
|
|
882
|
+
await writeWorkflowRunArtifact(workspace, report);
|
|
883
|
+
return renderWorkflowValidation(report, format);
|
|
884
|
+
}
|
|
885
|
+
async report(runId, { format = "markdown", workspace }) {
|
|
886
|
+
try {
|
|
887
|
+
return renderWorkflowRunArtifact(await readWorkflowRunArtifact(workspace, runId), format);
|
|
888
|
+
} catch (error) {
|
|
889
|
+
const report = await createWorkflowValidationRunReport({
|
|
890
|
+
runId,
|
|
891
|
+
workflow: "unknown",
|
|
892
|
+
source: { type: "run-report", runId },
|
|
893
|
+
commands: [],
|
|
894
|
+
execute: async (command) => ({
|
|
895
|
+
command: command.command,
|
|
896
|
+
reason: command.reason,
|
|
897
|
+
status: "failed",
|
|
898
|
+
exitCode: 1
|
|
899
|
+
}),
|
|
900
|
+
diagnostics: [
|
|
901
|
+
{
|
|
902
|
+
severity: "error",
|
|
903
|
+
code: "workflow-run-read-failed",
|
|
904
|
+
message: `Could not read workflow run report: ${runId}. ${error instanceof Error ? error.message : ""}`.trim()
|
|
905
|
+
}
|
|
906
|
+
]
|
|
907
|
+
});
|
|
908
|
+
return renderWorkflowValidation(report, format);
|
|
909
|
+
}
|
|
910
|
+
}
|
|
911
|
+
async loadValidationTarget(runIdOrPlan, workspace) {
|
|
912
|
+
const loadFromArtifact = (artifact, sourcePath) => {
|
|
913
|
+
if (isWorkflowApplyReport(artifact)) {
|
|
914
|
+
return {
|
|
915
|
+
workflow: artifact.workflow,
|
|
916
|
+
source: { type: "apply-report", path: sourcePath, runId: artifact.runId },
|
|
917
|
+
plan: artifact.plan,
|
|
918
|
+
commands: artifact.recommendedValidationCommands.length ? artifact.recommendedValidationCommands : workflowCommandsForPlan(artifact.plan),
|
|
919
|
+
diagnostics: artifact.diagnostics,
|
|
920
|
+
changedFiles: artifact.changedFiles.map((file) => file.path),
|
|
921
|
+
repairActions: []
|
|
922
|
+
};
|
|
923
|
+
}
|
|
924
|
+
if ("mode" in artifact && artifact.mode === "validate") {
|
|
925
|
+
return {
|
|
926
|
+
workflow: artifact.workflow,
|
|
927
|
+
source: { type: "run-report", runId: artifact.runId },
|
|
928
|
+
plan: artifact.plan,
|
|
929
|
+
commands: artifact.plan ? workflowCommandsForPlan(artifact.plan) : [],
|
|
930
|
+
diagnostics: artifact.diagnostics,
|
|
931
|
+
changedFiles: [],
|
|
932
|
+
repairActions: artifact.repairActions
|
|
933
|
+
};
|
|
934
|
+
}
|
|
935
|
+
return {
|
|
936
|
+
workflow: "unknown",
|
|
937
|
+
source: { type: "run-report", runId: runIdOrPlan },
|
|
938
|
+
plan: undefined,
|
|
939
|
+
commands: [],
|
|
940
|
+
diagnostics: [
|
|
941
|
+
{
|
|
942
|
+
severity: "error",
|
|
943
|
+
code: "workflow-validation-source-unsupported",
|
|
944
|
+
message: `Workflow validation source is not supported: ${runIdOrPlan}.`
|
|
945
|
+
}
|
|
946
|
+
],
|
|
947
|
+
changedFiles: [],
|
|
948
|
+
repairActions: []
|
|
949
|
+
};
|
|
950
|
+
};
|
|
951
|
+
try {
|
|
952
|
+
const parsed = await readJsonFile(runIdOrPlan);
|
|
953
|
+
if (isWorkflowPlan(parsed)) {
|
|
954
|
+
return {
|
|
955
|
+
workflow: parsed.workflow,
|
|
956
|
+
source: { type: "plan", path: resolvePath(runIdOrPlan) },
|
|
957
|
+
plan: parsed,
|
|
958
|
+
commands: workflowCommandsForPlan(parsed),
|
|
959
|
+
diagnostics: parsed.diagnostics,
|
|
960
|
+
changedFiles: workflowPathsForPlanLike(parsed),
|
|
961
|
+
repairActions: []
|
|
962
|
+
};
|
|
963
|
+
}
|
|
964
|
+
if (isWorkflowRunArtifact(parsed))
|
|
965
|
+
return loadFromArtifact(parsed, resolvePath(runIdOrPlan));
|
|
966
|
+
} catch {}
|
|
967
|
+
try {
|
|
968
|
+
const artifact = await readWorkflowRunArtifact(workspace, runIdOrPlan);
|
|
969
|
+
return loadFromArtifact(artifact, runIdOrPlan);
|
|
970
|
+
} catch (error) {
|
|
971
|
+
return {
|
|
972
|
+
workflow: "unknown",
|
|
973
|
+
source: { type: "run-report", runId: runIdOrPlan },
|
|
974
|
+
plan: undefined,
|
|
975
|
+
commands: [],
|
|
976
|
+
diagnostics: [
|
|
977
|
+
{
|
|
978
|
+
severity: "error",
|
|
979
|
+
code: "workflow-validation-source-read-failed",
|
|
980
|
+
message: `Could not read workflow validation source: ${runIdOrPlan}. ${error instanceof Error ? error.message : ""}`.trim()
|
|
981
|
+
}
|
|
982
|
+
],
|
|
983
|
+
changedFiles: [],
|
|
984
|
+
repairActions: []
|
|
985
|
+
};
|
|
986
|
+
}
|
|
987
|
+
}
|
|
988
|
+
}
|
|
989
|
+
|
|
990
|
+
export { WorkflowRunner };
|