@lumenflow/cli 3.15.0 → 3.16.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/dist/docs-sync.js +97 -46
- package/dist/docs-sync.js.map +1 -1
- package/dist/lumenflow-upgrade.js +19 -13
- package/dist/lumenflow-upgrade.js.map +1 -1
- package/package.json +8 -8
- package/packs/sidekick/.turbo/turbo-build.log +1 -1
- package/packs/sidekick/package.json +1 -1
- package/packs/software-delivery/.turbo/turbo-build.log +1 -1
- package/packs/software-delivery/package.json +1 -1
- package/dist/chunk-2D2VOCA4.js +0 -37
- package/dist/chunk-2D5KFYGX.js +0 -284
- package/dist/chunk-2GXVIN57.js +0 -14072
- package/dist/chunk-2MQ7HZWZ.js +0 -26
- package/dist/chunk-2UFQ3A3C.js +0 -643
- package/dist/chunk-3RG5ZIWI.js +0 -10
- package/dist/chunk-4N74J3UT.js +0 -15
- package/dist/chunk-5GTOXFYR.js +0 -392
- package/dist/chunk-5VY6MQMC.js +0 -240
- package/dist/chunk-67XVPMRY.js +0 -1297
- package/dist/chunk-6HO4GWJE.js +0 -164
- package/dist/chunk-6W5XHWYV.js +0 -1890
- package/dist/chunk-6X4EMYJQ.js +0 -64
- package/dist/chunk-6XYXI2NQ.js +0 -772
- package/dist/chunk-7ANSOV6Q.js +0 -285
- package/dist/chunk-A624LFLB.js +0 -1380
- package/dist/chunk-ADN5NHG4.js +0 -126
- package/dist/chunk-B7YJYJKG.js +0 -33
- package/dist/chunk-CCLHCPKG.js +0 -210
- package/dist/chunk-CK36VROC.js +0 -1584
- package/dist/chunk-D3UOFRSB.js +0 -81
- package/dist/chunk-DFR4DJBM.js +0 -230
- package/dist/chunk-DSYBDHYH.js +0 -79
- package/dist/chunk-DWMLTXKQ.js +0 -1176
- package/dist/chunk-E3REJTAJ.js +0 -28
- package/dist/chunk-EA3IVO64.js +0 -633
- package/dist/chunk-EK2AKZKD.js +0 -55
- package/dist/chunk-ELD7JTTT.js +0 -343
- package/dist/chunk-EX6TT2XI.js +0 -195
- package/dist/chunk-EXINSFZE.js +0 -82
- package/dist/chunk-EZ6ZBYBM.js +0 -510
- package/dist/chunk-FBKAPTJ2.js +0 -16
- package/dist/chunk-FVLV5RYH.js +0 -1118
- package/dist/chunk-GDNSBQVK.js +0 -2485
- package/dist/chunk-GPQHMBNN.js +0 -278
- package/dist/chunk-GTFJB67L.js +0 -68
- package/dist/chunk-HANJXVKW.js +0 -1127
- package/dist/chunk-HEVS5YLD.js +0 -269
- package/dist/chunk-HMEVZKPQ.js +0 -9
- package/dist/chunk-HRGSYNLM.js +0 -3511
- package/dist/chunk-ISZR5N4K.js +0 -60
- package/dist/chunk-J6SUPR2C.js +0 -226
- package/dist/chunk-JERYVEIZ.js +0 -244
- package/dist/chunk-JHHWGL2N.js +0 -87
- package/dist/chunk-JONWQUB5.js +0 -775
- package/dist/chunk-K2DIWWDM.js +0 -1766
- package/dist/chunk-KY4PGL5V.js +0 -969
- package/dist/chunk-L737LQ4C.js +0 -1285
- package/dist/chunk-LFTWYIB2.js +0 -497
- package/dist/chunk-LV47RFNJ.js +0 -41
- package/dist/chunk-MKSAITI7.js +0 -15
- package/dist/chunk-MZ7RKIX4.js +0 -212
- package/dist/chunk-NAP6CFSO.js +0 -84
- package/dist/chunk-ND6MY37M.js +0 -16
- package/dist/chunk-NMG736UR.js +0 -683
- package/dist/chunk-NRAXROED.js +0 -32
- package/dist/chunk-NRIZR3A7.js +0 -690
- package/dist/chunk-NX43BG3M.js +0 -233
- package/dist/chunk-O645XLSI.js +0 -297
- package/dist/chunk-OMJD6A3S.js +0 -235
- package/dist/chunk-QB6SJD4T.js +0 -430
- package/dist/chunk-QFSTL4J3.js +0 -276
- package/dist/chunk-QLGDFMFX.js +0 -212
- package/dist/chunk-RIAAGL2E.js +0 -13
- package/dist/chunk-RWO5XMZ6.js +0 -86
- package/dist/chunk-RXRKBBSM.js +0 -149
- package/dist/chunk-RZOZMML6.js +0 -363
- package/dist/chunk-U7I7FS7T.js +0 -113
- package/dist/chunk-UI42RODY.js +0 -717
- package/dist/chunk-UTVMVSCO.js +0 -519
- package/dist/chunk-V6OJGLBA.js +0 -1746
- package/dist/chunk-W2JHVH7D.js +0 -152
- package/dist/chunk-WD3Y7VQN.js +0 -280
- package/dist/chunk-WOCTQ5MS.js +0 -303
- package/dist/chunk-WZR3ZUNN.js +0 -696
- package/dist/chunk-XGI665H7.js +0 -150
- package/dist/chunk-XKY65P2T.js +0 -304
- package/dist/chunk-Y4CQZY65.js +0 -57
- package/dist/chunk-YFEXKLVE.js +0 -194
- package/dist/chunk-YHO3HS5X.js +0 -287
- package/dist/chunk-YLS7AZSX.js +0 -738
- package/dist/chunk-ZE473AO6.js +0 -49
- package/dist/chunk-ZF747T3O.js +0 -644
- package/dist/chunk-ZHCZHZH3.js +0 -43
- package/dist/chunk-ZZNZX2XY.js +0 -87
- package/dist/constants-7QAP3VQ4.js +0 -23
- package/dist/dist-IY3UUMWK.js +0 -33
- package/dist/invariants-runner-W5RGHCSU.js +0 -27
- package/dist/lane-lock-6J36HD5O.js +0 -35
- package/dist/mem-checkpoint-core-EANG2GVN.js +0 -14
- package/dist/mem-signal-core-2LZ2WYHW.js +0 -19
- package/dist/memory-store-OLB5FO7K.js +0 -18
- package/dist/service-6BYCOCO5.js +0 -13
- package/dist/spawn-policy-resolver-NTSZYQ6R.js +0 -17
- package/dist/spawn-task-builder-R4E2BHSW.js +0 -22
- package/dist/wu-done-pr-WLFFFEPJ.js +0 -25
- package/dist/wu-done-validation-3J5E36FE.js +0 -30
- package/dist/wu-duplicate-id-detector-5S7JHELK.js +0 -232
- package/packs/sidekick/.turbo/turbo-typecheck.log +0 -4
- package/packs/software-delivery/.turbo/turbo-typecheck.log +0 -4
package/dist/chunk-NRIZR3A7.js
DELETED
|
@@ -1,690 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
createWuPaths
|
|
3
|
-
} from "./chunk-6HO4GWJE.js";
|
|
4
|
-
import {
|
|
5
|
-
STRING_LITERALS,
|
|
6
|
-
WU_DEFAULTS
|
|
7
|
-
} from "./chunk-DWMLTXKQ.js";
|
|
8
|
-
import {
|
|
9
|
-
WU_EXPOSURE_VALUES,
|
|
10
|
-
WU_STATUS_GROUPS
|
|
11
|
-
} from "./chunk-V6OJGLBA.js";
|
|
12
|
-
import {
|
|
13
|
-
ErrorCodes,
|
|
14
|
-
createError,
|
|
15
|
-
getErrorMessage
|
|
16
|
-
} from "./chunk-RXRKBBSM.js";
|
|
17
|
-
|
|
18
|
-
// ../core/dist/date-utils.js
|
|
19
|
-
import { format } from "date-fns";
|
|
20
|
-
function todayISO() {
|
|
21
|
-
return format(/* @__PURE__ */ new Date(), "yyyy-MM-dd");
|
|
22
|
-
}
|
|
23
|
-
var DATE_FORMAT_ISO = "yyyy-MM-dd";
|
|
24
|
-
function normalizeToDateString(value) {
|
|
25
|
-
if (value == null) {
|
|
26
|
-
return void 0;
|
|
27
|
-
}
|
|
28
|
-
if (typeof value === "string" && /^\d{4}-\d{2}-\d{2}$/.test(value)) {
|
|
29
|
-
return value;
|
|
30
|
-
}
|
|
31
|
-
if (value instanceof Date) {
|
|
32
|
-
return format(value, DATE_FORMAT_ISO);
|
|
33
|
-
}
|
|
34
|
-
if (typeof value === "string" && /^\d{4}-\d{2}-\d{2}T/.test(value)) {
|
|
35
|
-
return format(new Date(value), DATE_FORMAT_ISO);
|
|
36
|
-
}
|
|
37
|
-
try {
|
|
38
|
-
const date = new Date(String(value));
|
|
39
|
-
if (!isNaN(date.getTime())) {
|
|
40
|
-
return format(date, DATE_FORMAT_ISO);
|
|
41
|
-
}
|
|
42
|
-
} catch {
|
|
43
|
-
}
|
|
44
|
-
return void 0;
|
|
45
|
-
}
|
|
46
|
-
function normalizeISODateTime(value) {
|
|
47
|
-
if (value == null) {
|
|
48
|
-
return void 0;
|
|
49
|
-
}
|
|
50
|
-
if (typeof value === "string" && /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/.test(value)) {
|
|
51
|
-
return value;
|
|
52
|
-
}
|
|
53
|
-
let dateInput = value;
|
|
54
|
-
if (typeof value === "string" && /^\d+$/.test(value)) {
|
|
55
|
-
dateInput = Number(value);
|
|
56
|
-
}
|
|
57
|
-
const date = new Date(dateInput);
|
|
58
|
-
if (isNaN(date.getTime())) {
|
|
59
|
-
return void 0;
|
|
60
|
-
}
|
|
61
|
-
return date.toISOString();
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
// ../core/dist/wu-schema.js
|
|
65
|
-
import { z } from "zod";
|
|
66
|
-
var VALID_STATUSES = [
|
|
67
|
-
"todo",
|
|
68
|
-
"ready",
|
|
69
|
-
"backlog",
|
|
70
|
-
"in_progress",
|
|
71
|
-
"blocked",
|
|
72
|
-
"done",
|
|
73
|
-
"completed",
|
|
74
|
-
"cancelled",
|
|
75
|
-
"abandoned",
|
|
76
|
-
"deferred",
|
|
77
|
-
"closed",
|
|
78
|
-
"superseded"
|
|
79
|
-
];
|
|
80
|
-
var PLACEHOLDER_SENTINEL = "[PLACEHOLDER]";
|
|
81
|
-
var MIN_DESCRIPTION_LENGTH = 50;
|
|
82
|
-
var WU_ID_FORMAT_MESSAGE = "Must be WU-XXX format";
|
|
83
|
-
var ACCEPTANCE_REQUIRED_MSG = "At least one acceptance criterion required";
|
|
84
|
-
var NEWLINE_PATTERN = /\\n|\n/;
|
|
85
|
-
var normalizedStringArray = z.array(z.string()).transform((arr) => arr.flatMap((s) => s.split(NEWLINE_PATTERN)).map((s) => s.trim()).filter(Boolean));
|
|
86
|
-
var _normalizedMultilineString = z.string().transform((s) => s.replace(/\\n/g, "\n"));
|
|
87
|
-
var filePathItem = z.string().refine((s) => !s.includes("\n") && !s.includes("\\n"), {
|
|
88
|
-
message: "File path cannot contain newlines - split into separate array items"
|
|
89
|
-
});
|
|
90
|
-
var normalizedCodePaths = normalizedStringArray.pipe(z.array(filePathItem)).default([]);
|
|
91
|
-
var normalizedTestPaths = z.object({
|
|
92
|
-
manual: normalizedStringArray.optional(),
|
|
93
|
-
unit: normalizedStringArray.optional(),
|
|
94
|
-
integration: normalizedStringArray.optional(),
|
|
95
|
-
e2e: normalizedStringArray.optional()
|
|
96
|
-
}).optional();
|
|
97
|
-
var baseDescriptionField = z.string().min(1, "Description is required").transform((s) => s.replace(/\\n/g, "\n")).refine((val) => val.trim().length >= MIN_DESCRIPTION_LENGTH, {
|
|
98
|
-
// WU-1539 fix: Use function message for dynamic interpolation
|
|
99
|
-
message: `Description must be at least ${MIN_DESCRIPTION_LENGTH} characters`
|
|
100
|
-
});
|
|
101
|
-
var strictDescriptionField = z.string().min(1, "Description is required").transform((s) => s.replace(/\\n/g, "\n")).refine((val) => !val.includes(PLACEHOLDER_SENTINEL), {
|
|
102
|
-
message: `Description cannot contain ${PLACEHOLDER_SENTINEL} marker`
|
|
103
|
-
}).refine((val) => val.trim().length >= MIN_DESCRIPTION_LENGTH, {
|
|
104
|
-
// WU-1539 fix: Use function message for dynamic interpolation
|
|
105
|
-
message: `Description must be at least ${MIN_DESCRIPTION_LENGTH} characters`
|
|
106
|
-
});
|
|
107
|
-
var hasItems = (value) => {
|
|
108
|
-
if (Array.isArray(value)) {
|
|
109
|
-
return value.length > 0;
|
|
110
|
-
}
|
|
111
|
-
if (typeof value === "object" && value !== null) {
|
|
112
|
-
return Object.values(value).some(hasItems);
|
|
113
|
-
}
|
|
114
|
-
return false;
|
|
115
|
-
};
|
|
116
|
-
var checkStringsForPlaceholder = (value) => {
|
|
117
|
-
if (typeof value === "string") {
|
|
118
|
-
return !value.includes(PLACEHOLDER_SENTINEL);
|
|
119
|
-
}
|
|
120
|
-
if (Array.isArray(value)) {
|
|
121
|
-
return value.every(checkStringsForPlaceholder);
|
|
122
|
-
}
|
|
123
|
-
if (typeof value === "object" && value !== null) {
|
|
124
|
-
return Object.values(value).every(checkStringsForPlaceholder);
|
|
125
|
-
}
|
|
126
|
-
return true;
|
|
127
|
-
};
|
|
128
|
-
var baseAcceptanceField = z.union([
|
|
129
|
-
// Flat array format (legacy): acceptance: ["item1", "item2"]
|
|
130
|
-
// WU-1750: Normalize embedded newlines: ["1. a\n2. b"] → ["1. a", "2. b"]
|
|
131
|
-
normalizedStringArray.pipe(z.array(z.string()).min(1, ACCEPTANCE_REQUIRED_MSG)),
|
|
132
|
-
// Nested object format (structured): acceptance: { category1: ["item1"], category2: ["item2"] }
|
|
133
|
-
z.record(z.string(), normalizedStringArray).refine((obj) => Object.values(obj).some(hasItems), {
|
|
134
|
-
message: ACCEPTANCE_REQUIRED_MSG
|
|
135
|
-
})
|
|
136
|
-
]);
|
|
137
|
-
var strictAcceptanceField = z.union([
|
|
138
|
-
// Flat array format (legacy): acceptance: ["item1", "item2"]
|
|
139
|
-
// WU-1750: Normalize embedded newlines: ["1. a\n2. b"] → ["1. a", "2. b"]
|
|
140
|
-
normalizedStringArray.pipe(z.array(z.string()).min(1, ACCEPTANCE_REQUIRED_MSG)).refine((arr) => !arr.some((item) => item.includes(PLACEHOLDER_SENTINEL)), {
|
|
141
|
-
message: `Acceptance criteria cannot contain ${PLACEHOLDER_SENTINEL} markers`
|
|
142
|
-
}),
|
|
143
|
-
// Nested object format (structured): acceptance: { category1: ["item1"], category2: ["item2"] }
|
|
144
|
-
z.record(z.string(), normalizedStringArray).refine((obj) => Object.values(obj).some(hasItems), {
|
|
145
|
-
message: ACCEPTANCE_REQUIRED_MSG
|
|
146
|
-
}).refine((obj) => checkStringsForPlaceholder(obj), {
|
|
147
|
-
message: `Acceptance criteria cannot contain ${PLACEHOLDER_SENTINEL} markers`
|
|
148
|
-
})
|
|
149
|
-
]);
|
|
150
|
-
var sharedFields = {
|
|
151
|
-
/** WU identifier (e.g., WU-1162) */
|
|
152
|
-
id: z.string().regex(/^WU-\d+$/, "ID must match pattern WU-XXX"),
|
|
153
|
-
/** Short title describing the work */
|
|
154
|
-
title: z.string().min(1, "Title is required"),
|
|
155
|
-
/** Lane assignment (parent or sub-lane) */
|
|
156
|
-
lane: z.string().min(1, "Lane is required"),
|
|
157
|
-
/** Work type classification */
|
|
158
|
-
type: z.enum(["feature", "bug", "documentation", "process", "tooling", "chore", "refactor"], {
|
|
159
|
-
error: "Invalid type"
|
|
160
|
-
}).default(WU_DEFAULTS.type),
|
|
161
|
-
/** Current status in workflow */
|
|
162
|
-
status: z.enum(VALID_STATUSES, {
|
|
163
|
-
error: `Invalid status. Valid values: ${VALID_STATUSES.join(", ")}`
|
|
164
|
-
}).default(WU_DEFAULTS.status),
|
|
165
|
-
/** Priority level */
|
|
166
|
-
priority: z.enum(["P0", "P1", "P2", "P3"], {
|
|
167
|
-
error: "Invalid priority"
|
|
168
|
-
}).default(WU_DEFAULTS.priority),
|
|
169
|
-
/** Creation date (YYYY-MM-DD) */
|
|
170
|
-
created: z.string().regex(/^\d{4}-\d{2}-\d{2}$/, "Created must be YYYY-MM-DD"),
|
|
171
|
-
/** Files modified by this WU - WU-1750: Normalized to split embedded newlines */
|
|
172
|
-
code_paths: normalizedCodePaths,
|
|
173
|
-
/** Test specifications - WU-1750: All test arrays normalized */
|
|
174
|
-
tests: normalizedTestPaths.default(WU_DEFAULTS.tests),
|
|
175
|
-
/** Output artifacts (stamps, docs, etc.) - WU-1750: Normalized */
|
|
176
|
-
artifacts: normalizedStringArray.optional().default(WU_DEFAULTS.artifacts),
|
|
177
|
-
/** Upstream WU dependencies (informational, legacy field) - WU-1750: Normalized */
|
|
178
|
-
dependencies: normalizedStringArray.optional().default(WU_DEFAULTS.dependencies),
|
|
179
|
-
// === Initiative System Fields (WU-1246) ===
|
|
180
|
-
/** Parent initiative reference (format: INIT-{number} or slug) */
|
|
181
|
-
initiative: z.string().optional(),
|
|
182
|
-
/** Phase number within parent initiative */
|
|
183
|
-
phase: z.number().int().positive().optional(),
|
|
184
|
-
/** WU IDs that this WU blocks (downstream dependencies) - WU-1750: Normalized + validated */
|
|
185
|
-
blocks: normalizedStringArray.pipe(z.array(z.string().regex(/^WU-\d+$/, WU_ID_FORMAT_MESSAGE))).optional(),
|
|
186
|
-
/** WU IDs that block this WU (upstream dependencies) - WU-1750: Normalized + validated */
|
|
187
|
-
blocked_by: normalizedStringArray.pipe(z.array(z.string().regex(/^WU-\d+$/, WU_ID_FORMAT_MESSAGE))).optional(),
|
|
188
|
-
/** Cross-cutting tags (orthogonal to initiative) - WU-1750: Normalized */
|
|
189
|
-
labels: normalizedStringArray.optional(),
|
|
190
|
-
// === End Initiative System Fields ===
|
|
191
|
-
/**
|
|
192
|
-
* WU-1683: First-class plan field, symmetric with initiative `related_plan`.
|
|
193
|
-
* Set via wu:create --plan, wu:edit --plan, or plan:link --id WU-XXX.
|
|
194
|
-
*/
|
|
195
|
-
plan: z.string().optional(),
|
|
196
|
-
/**
|
|
197
|
-
* WU-1833: References to plans, design docs, external specifications
|
|
198
|
-
* WU-1834: Supports both flat string array AND nested object format for backwards compatibility
|
|
199
|
-
*
|
|
200
|
-
* Flat format (WU-1833+): ['docs/plans/WU-XXX-plan.md']
|
|
201
|
-
* Nested format (legacy): [{file: 'docs/path.md', section: 'heading'}]
|
|
202
|
-
* Mixed format allowed: ['path.md', {section: 'heading'}]
|
|
203
|
-
* Bare object (WU-428): {file: 'docs/path.md', section: 'heading'}
|
|
204
|
-
*/
|
|
205
|
-
spec_refs: z.union([
|
|
206
|
-
// Single object format (WU-428 style): {file: '...', section: '...'}
|
|
207
|
-
z.object({
|
|
208
|
-
file: z.string().optional(),
|
|
209
|
-
section: z.string()
|
|
210
|
-
}),
|
|
211
|
-
// Array format (WU-1833+): strings, objects, or mixed
|
|
212
|
-
z.array(z.union([
|
|
213
|
-
z.string(),
|
|
214
|
-
// Flat format: 'docs/path.md'
|
|
215
|
-
z.object({
|
|
216
|
-
// Nested format: {file: 'path', section: 'heading'}
|
|
217
|
-
file: z.string().optional(),
|
|
218
|
-
section: z.string()
|
|
219
|
-
})
|
|
220
|
-
]))
|
|
221
|
-
]).optional(),
|
|
222
|
-
/** Known risks or constraints - WU-1750: Normalized */
|
|
223
|
-
risks: normalizedStringArray.optional().default(WU_DEFAULTS.risks),
|
|
224
|
-
/**
|
|
225
|
-
* Free-form notes - supports string or array (auto-converted to string)
|
|
226
|
-
* WU-1750: Normalizes escaped newlines (\\n → actual newlines)
|
|
227
|
-
*/
|
|
228
|
-
notes: z.union([
|
|
229
|
-
z.string(),
|
|
230
|
-
z.array(z.string())
|
|
231
|
-
// Legacy array format - will be converted
|
|
232
|
-
]).optional().transform((val) => {
|
|
233
|
-
if (Array.isArray(val)) {
|
|
234
|
-
return val.filter((s) => s.trim().length > 0).join(STRING_LITERALS.NEWLINE);
|
|
235
|
-
}
|
|
236
|
-
if (typeof val === "string") {
|
|
237
|
-
return val.replace(/\\n/g, "\n");
|
|
238
|
-
}
|
|
239
|
-
return val ?? WU_DEFAULTS.notes;
|
|
240
|
-
}),
|
|
241
|
-
/** Requires human review before merge */
|
|
242
|
-
requires_review: z.boolean().optional().default(WU_DEFAULTS.requires_review),
|
|
243
|
-
/** Locked state (done WUs only) */
|
|
244
|
-
locked: z.boolean().optional(),
|
|
245
|
-
/** Completion date (done WUs only) - auto-normalized to ISO datetime */
|
|
246
|
-
completed_at: z.string().optional().transform((val) => normalizeISODateTime(val)),
|
|
247
|
-
/** Claimed mode (worktree/branch-only/worktree-pr/branch-pr) */
|
|
248
|
-
claimed_mode: z.enum(["worktree", "branch-only", "worktree-pr", "branch-pr"]).optional(),
|
|
249
|
-
/**
|
|
250
|
-
* WU-1589: Canonical branch name for this WU claim.
|
|
251
|
-
*
|
|
252
|
-
* Set at claim time to record the actual branch used.
|
|
253
|
-
* Used by defaultBranchFrom() as highest-priority source for branch resolution.
|
|
254
|
-
* Essential for branch-pr mode where cloud agents may use non-lane-derived branch names.
|
|
255
|
-
* Cleared on rollback/release/recover when resetting to ready.
|
|
256
|
-
*/
|
|
257
|
-
claimed_branch: z.string().optional(),
|
|
258
|
-
/** Assigned agent email */
|
|
259
|
-
assigned_to: z.string().email().optional(),
|
|
260
|
-
/** Claim timestamp - auto-normalized to ISO datetime */
|
|
261
|
-
claimed_at: z.string().optional().transform((val) => normalizeISODateTime(val)),
|
|
262
|
-
/** Block reason (blocked WUs only) */
|
|
263
|
-
blocked_reason: z.string().optional(),
|
|
264
|
-
/** Worktree path (claimed WUs only) */
|
|
265
|
-
worktree_path: z.string().optional(),
|
|
266
|
-
/** Current active session ID (WU-1438: auto-set on claim, cleared on done) */
|
|
267
|
-
session_id: z.string().uuid().optional(),
|
|
268
|
-
/** Agent sessions (issue logging metadata, WU-1231) */
|
|
269
|
-
agent_sessions: z.array(z.object({
|
|
270
|
-
session_id: z.string().uuid(),
|
|
271
|
-
started: z.string().datetime(),
|
|
272
|
-
completed: z.string().datetime().optional(),
|
|
273
|
-
agent_type: z.enum([
|
|
274
|
-
"claude-code",
|
|
275
|
-
"codex-cli",
|
|
276
|
-
"cursor",
|
|
277
|
-
"gemini-cli",
|
|
278
|
-
"windsurf",
|
|
279
|
-
"copilot",
|
|
280
|
-
"other"
|
|
281
|
-
]),
|
|
282
|
-
context_tier: z.union([z.literal(1), z.literal(2), z.literal(3)]),
|
|
283
|
-
incidents_logged: z.number().int().min(0).default(0),
|
|
284
|
-
incidents_major: z.number().int().min(0).default(0),
|
|
285
|
-
artifacts: z.array(z.string()).optional()
|
|
286
|
-
})).optional(),
|
|
287
|
-
// === Exposure System Fields (WU-1998) ===
|
|
288
|
-
/**
|
|
289
|
-
* WU-1998: Exposure level - defines how the WU exposes functionality to users
|
|
290
|
-
*
|
|
291
|
-
* Valid values:
|
|
292
|
-
* - 'ui': User-facing UI changes (pages, components, widgets)
|
|
293
|
-
* - 'api': API endpoints called by UI or external clients
|
|
294
|
-
* - 'backend-only': Backend-only changes (no user visibility)
|
|
295
|
-
* - 'documentation': Documentation changes only
|
|
296
|
-
*
|
|
297
|
-
* Optional during transition period, will become required after backlog update.
|
|
298
|
-
*/
|
|
299
|
-
exposure: z.enum(WU_EXPOSURE_VALUES, {
|
|
300
|
-
error: `Invalid exposure value. Valid values: ${WU_EXPOSURE_VALUES.join(", ")}`
|
|
301
|
-
}).optional(),
|
|
302
|
-
/**
|
|
303
|
-
* WU-1998: User journey description for user-facing WUs
|
|
304
|
-
*
|
|
305
|
-
* Recommended for exposure: 'ui' and 'api'.
|
|
306
|
-
* Describes the end-user interaction flow affected by this WU.
|
|
307
|
-
*/
|
|
308
|
-
user_journey: z.string().optional(),
|
|
309
|
-
/**
|
|
310
|
-
* WU-1998: Related UI WUs for backend/API changes
|
|
311
|
-
*
|
|
312
|
-
* For WUs with exposure: 'api', this field lists UI WUs that consume the API.
|
|
313
|
-
* Ensures backend features have corresponding UI coverage.
|
|
314
|
-
* Each entry must match WU-XXX format.
|
|
315
|
-
*/
|
|
316
|
-
ui_pairing_wus: normalizedStringArray.pipe(z.array(z.string().regex(/^WU-\d+$/, WU_ID_FORMAT_MESSAGE))).optional(),
|
|
317
|
-
/**
|
|
318
|
-
* WU-2022: Navigation path for UI-exposed features
|
|
319
|
-
*
|
|
320
|
-
* For WUs with exposure: 'ui', specifies the route where the feature is accessible.
|
|
321
|
-
* Used by wu:done to verify that UI features are actually navigable.
|
|
322
|
-
* Prevents "orphaned code" where features exist but users cannot access them.
|
|
323
|
-
*
|
|
324
|
-
* Example: '/dashboard', '/settings/preferences', '/space'
|
|
325
|
-
*/
|
|
326
|
-
navigation_path: z.string().optional(),
|
|
327
|
-
// === End Exposure System Fields ===
|
|
328
|
-
// === Sizing Estimate Fields (WU-2141) ===
|
|
329
|
-
/**
|
|
330
|
-
* WU-2141: Optional sizing estimate metadata.
|
|
331
|
-
*
|
|
332
|
-
* Records expected WU complexity for tooling-backed sizing enforcement.
|
|
333
|
-
* Absent for historical WUs (backward compatible).
|
|
334
|
-
*
|
|
335
|
-
* Fields:
|
|
336
|
-
* - estimated_files: Expected number of files to modify
|
|
337
|
-
* - estimated_tool_calls: Expected tool call count
|
|
338
|
-
* - strategy: Execution strategy from wu-sizing-guide.md
|
|
339
|
-
* - exception_type: Override type when thresholds intentionally exceeded
|
|
340
|
-
* - exception_reason: Justification for the exception (required with exception_type)
|
|
341
|
-
*/
|
|
342
|
-
sizing_estimate: z.object({
|
|
343
|
-
estimated_files: z.number().int().min(0),
|
|
344
|
-
estimated_tool_calls: z.number().int().min(0),
|
|
345
|
-
strategy: z.enum([
|
|
346
|
-
"single-session",
|
|
347
|
-
"checkpoint-resume",
|
|
348
|
-
"orchestrator-worker",
|
|
349
|
-
"decomposition"
|
|
350
|
-
]),
|
|
351
|
-
exception_type: z.enum(["docs-only", "shallow-multi-file"]).optional(),
|
|
352
|
-
exception_reason: z.string().optional()
|
|
353
|
-
}).refine((data) => {
|
|
354
|
-
if (data.exception_type !== void 0) {
|
|
355
|
-
return data.exception_reason !== void 0 && data.exception_reason.trim().length > 0;
|
|
356
|
-
}
|
|
357
|
-
return true;
|
|
358
|
-
}, {
|
|
359
|
-
message: "sizing_estimate.exception_reason is required and must be non-empty when exception_type is set",
|
|
360
|
-
path: ["exception_reason"]
|
|
361
|
-
}).optional(),
|
|
362
|
-
// === End Sizing Estimate Fields ===
|
|
363
|
-
// === Agent-First Approval Fields (WU-2079 → WU-2080) ===
|
|
364
|
-
/**
|
|
365
|
-
* WU-2080: Escalation triggers detected for this WU
|
|
366
|
-
*
|
|
367
|
-
* Agent-first model: agents auto-approve by default.
|
|
368
|
-
* Human escalation only when these triggers are detected:
|
|
369
|
-
* - sensitive_data: Changes to sensitive data handling
|
|
370
|
-
* - security_p0: P0 security incident or vulnerability
|
|
371
|
-
* - budget: Budget/resource allocation above threshold
|
|
372
|
-
* - cross_lane_arch: Cross-lane architectural decision
|
|
373
|
-
*
|
|
374
|
-
* Empty array = no escalation needed, agent proceeds autonomously.
|
|
375
|
-
*/
|
|
376
|
-
escalation_triggers: z.array(z.enum(["sensitive_data", "security_p0", "budget", "cross_lane_arch"])).optional().default([]),
|
|
377
|
-
/**
|
|
378
|
-
* WU-2080: Human escalation required flag
|
|
379
|
-
*
|
|
380
|
-
* Auto-set to true when escalation_triggers is non-empty.
|
|
381
|
-
* When true, wu:done requires human confirmation before completion.
|
|
382
|
-
*/
|
|
383
|
-
requires_human_escalation: z.boolean().optional().default(false),
|
|
384
|
-
/**
|
|
385
|
-
* WU-2080: Email(s) of approvers who signed off
|
|
386
|
-
*
|
|
387
|
-
* Auto-populated with claiming agent at wu:claim.
|
|
388
|
-
* Additional human approvers added when escalation is resolved.
|
|
389
|
-
*/
|
|
390
|
-
approved_by: z.array(z.string().email()).optional(),
|
|
391
|
-
/**
|
|
392
|
-
* WU-2080: Timestamp when approval was granted
|
|
393
|
-
*
|
|
394
|
-
* Auto-set at wu:claim for agent auto-approval.
|
|
395
|
-
* Updated when human escalation is resolved.
|
|
396
|
-
*/
|
|
397
|
-
approved_at: z.string().optional().transform((val) => normalizeISODateTime(val)),
|
|
398
|
-
/**
|
|
399
|
-
* WU-2080: Human who resolved escalation (if UnsafeAny)
|
|
400
|
-
*
|
|
401
|
-
* Only set when requires_human_escalation was true and resolved.
|
|
402
|
-
*/
|
|
403
|
-
escalation_resolved_by: z.string().email().optional(),
|
|
404
|
-
/**
|
|
405
|
-
* WU-2080: Timestamp when human resolved escalation
|
|
406
|
-
*/
|
|
407
|
-
escalation_resolved_at: z.string().optional().transform((val) => normalizeISODateTime(val)),
|
|
408
|
-
// Legacy fields (deprecated, kept for backwards compatibility)
|
|
409
|
-
/** @deprecated Use escalation_triggers instead */
|
|
410
|
-
requires_cso_approval: z.boolean().optional().default(false),
|
|
411
|
-
/** @deprecated Use escalation_triggers instead */
|
|
412
|
-
requires_cto_approval: z.boolean().optional().default(false),
|
|
413
|
-
/** @deprecated Use escalation_triggers instead */
|
|
414
|
-
requires_design_approval: z.boolean().optional().default(false)
|
|
415
|
-
// === End Agent-First Approval Fields ===
|
|
416
|
-
};
|
|
417
|
-
var BaseWUSchema = z.object({
|
|
418
|
-
...sharedFields,
|
|
419
|
-
description: baseDescriptionField,
|
|
420
|
-
acceptance: baseAcceptanceField
|
|
421
|
-
});
|
|
422
|
-
var ReadyWUSchema = BaseWUSchema;
|
|
423
|
-
var WUSchema = z.object({
|
|
424
|
-
...sharedFields,
|
|
425
|
-
description: strictDescriptionField,
|
|
426
|
-
acceptance: strictAcceptanceField
|
|
427
|
-
});
|
|
428
|
-
function validateWU(data) {
|
|
429
|
-
return WUSchema.safeParse(data);
|
|
430
|
-
}
|
|
431
|
-
function validateReadyWU(data) {
|
|
432
|
-
return ReadyWUSchema.safeParse(data);
|
|
433
|
-
}
|
|
434
|
-
function validateDoneWU(wu) {
|
|
435
|
-
const errors = [];
|
|
436
|
-
if (wu.type !== "documentation" && wu.type !== "process") {
|
|
437
|
-
if (!wu.code_paths || wu.code_paths.length === 0) {
|
|
438
|
-
errors.push("Code paths required for non-documentation WUs");
|
|
439
|
-
}
|
|
440
|
-
}
|
|
441
|
-
return {
|
|
442
|
-
valid: errors.length === 0,
|
|
443
|
-
errors
|
|
444
|
-
};
|
|
445
|
-
}
|
|
446
|
-
function validateApprovalGates(wu) {
|
|
447
|
-
const errors = [];
|
|
448
|
-
const warnings = [];
|
|
449
|
-
const triggers = wu.escalation_triggers || [];
|
|
450
|
-
const requiresEscalation = wu.requires_human_escalation || triggers.length > 0;
|
|
451
|
-
if (requiresEscalation) {
|
|
452
|
-
const resolved = wu.escalation_resolved_by && wu.escalation_resolved_at;
|
|
453
|
-
if (!resolved) {
|
|
454
|
-
errors.push(`Human escalation required for: ${triggers.join(", ")}
|
|
455
|
-
To resolve: pnpm wu:escalate --resolve --id ${wu.id}`);
|
|
456
|
-
}
|
|
457
|
-
}
|
|
458
|
-
if (wu.requires_cso_approval || wu.requires_cto_approval || wu.requires_design_approval) {
|
|
459
|
-
warnings.push("Using deprecated requires_X_approval fields. Migrate to escalation_triggers model.");
|
|
460
|
-
}
|
|
461
|
-
return {
|
|
462
|
-
valid: errors.length === 0,
|
|
463
|
-
errors,
|
|
464
|
-
warnings
|
|
465
|
-
};
|
|
466
|
-
}
|
|
467
|
-
function detectEscalationTriggers(wu) {
|
|
468
|
-
const triggers = [];
|
|
469
|
-
const lane = (wu.lane || "").toLowerCase();
|
|
470
|
-
const codePaths = wu.code_paths || [];
|
|
471
|
-
const sensitivePatterns = ["pii", "user-data", "auth", "credentials"];
|
|
472
|
-
const touchesSensitive = codePaths.some((p) => sensitivePatterns.some((pat) => p.toLowerCase().includes(pat)));
|
|
473
|
-
if (touchesSensitive || lane.includes("pii")) {
|
|
474
|
-
triggers.push("sensitive_data");
|
|
475
|
-
}
|
|
476
|
-
if (wu.priority === "P0" && lane.includes("security")) {
|
|
477
|
-
triggers.push("security_p0");
|
|
478
|
-
}
|
|
479
|
-
return triggers;
|
|
480
|
-
}
|
|
481
|
-
function generateAutoApproval(wu, agentEmail) {
|
|
482
|
-
const triggers = detectEscalationTriggers(wu);
|
|
483
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
484
|
-
return {
|
|
485
|
-
approved_by: [agentEmail],
|
|
486
|
-
approved_at: now,
|
|
487
|
-
escalation_triggers: triggers,
|
|
488
|
-
requires_human_escalation: triggers.length > 0
|
|
489
|
-
};
|
|
490
|
-
}
|
|
491
|
-
function validateAndNormalizeWUYAML(data) {
|
|
492
|
-
const result = WUSchema.safeParse(data);
|
|
493
|
-
if (!result.success) {
|
|
494
|
-
const errors = result.error.issues.map((issue) => `${issue.path.join(".")}: ${issue.message}`);
|
|
495
|
-
return {
|
|
496
|
-
valid: false,
|
|
497
|
-
normalized: null,
|
|
498
|
-
errors,
|
|
499
|
-
wasNormalized: false
|
|
500
|
-
};
|
|
501
|
-
}
|
|
502
|
-
const normalized = result.data;
|
|
503
|
-
const original = typeof data === "object" && data !== null ? data : {};
|
|
504
|
-
const wasNormalized = detectNormalizationChanges(original, normalized);
|
|
505
|
-
return {
|
|
506
|
-
valid: true,
|
|
507
|
-
normalized,
|
|
508
|
-
errors: [],
|
|
509
|
-
wasNormalized
|
|
510
|
-
};
|
|
511
|
-
}
|
|
512
|
-
function validateWUCompleteness(wu) {
|
|
513
|
-
const warnings = [];
|
|
514
|
-
const status = wu.status ?? "";
|
|
515
|
-
const isTerminal = WU_STATUS_GROUPS.TERMINAL.includes(status);
|
|
516
|
-
if (isTerminal) {
|
|
517
|
-
return { warnings };
|
|
518
|
-
}
|
|
519
|
-
const type = wu.type || "feature";
|
|
520
|
-
const requiresContext = ["feature", "bug", "refactor"].includes(type);
|
|
521
|
-
if (!requiresContext) {
|
|
522
|
-
return { warnings };
|
|
523
|
-
}
|
|
524
|
-
if (!wu.notes || wu.notes.trim().length === 0) {
|
|
525
|
-
warnings.push(`${wu.id}: Missing 'notes' field. Add implementation context, deployment instructions, or plan links.`);
|
|
526
|
-
}
|
|
527
|
-
const hasManualTests = wu.tests?.manual && wu.tests.manual.length > 0;
|
|
528
|
-
if (!hasManualTests) {
|
|
529
|
-
warnings.push(`${wu.id}: Missing 'tests.manual' field. Add manual verification steps for acceptance criteria.`);
|
|
530
|
-
}
|
|
531
|
-
if (type === "feature") {
|
|
532
|
-
const specRefs = wu.spec_refs;
|
|
533
|
-
const hasSpecRefs = !!specRefs && (specRefs.length ?? 0) > 0;
|
|
534
|
-
if (!hasSpecRefs) {
|
|
535
|
-
const plansDirHint = `${createWuPaths().PLANS_DIR().replace(/\/+$/, "")}/`;
|
|
536
|
-
warnings.push(`${wu.id}: Missing 'spec_refs' field. Link to plan file (${plansDirHint}, lumenflow://plans/, or ~/.lumenflow/plans/) for traceability.`);
|
|
537
|
-
}
|
|
538
|
-
}
|
|
539
|
-
return { warnings };
|
|
540
|
-
}
|
|
541
|
-
function detectNormalizationChanges(original, normalized) {
|
|
542
|
-
if (original.description !== normalized.description) {
|
|
543
|
-
return true;
|
|
544
|
-
}
|
|
545
|
-
const origPaths = Array.isArray(original.code_paths) ? original.code_paths : [];
|
|
546
|
-
const normPaths = Array.isArray(normalized.code_paths) ? normalized.code_paths : [];
|
|
547
|
-
if (origPaths.length !== normPaths.length) {
|
|
548
|
-
return true;
|
|
549
|
-
}
|
|
550
|
-
for (let i = 0; i < origPaths.length; i++) {
|
|
551
|
-
if (origPaths[i] !== normPaths[i]) {
|
|
552
|
-
return true;
|
|
553
|
-
}
|
|
554
|
-
}
|
|
555
|
-
if (Array.isArray(original.acceptance) && Array.isArray(normalized.acceptance)) {
|
|
556
|
-
if (original.acceptance.length !== normalized.acceptance.length) {
|
|
557
|
-
return true;
|
|
558
|
-
}
|
|
559
|
-
for (let i = 0; i < original.acceptance.length; i++) {
|
|
560
|
-
if (original.acceptance[i] !== normalized.acceptance[i]) {
|
|
561
|
-
return true;
|
|
562
|
-
}
|
|
563
|
-
}
|
|
564
|
-
}
|
|
565
|
-
return false;
|
|
566
|
-
}
|
|
567
|
-
|
|
568
|
-
// ../core/dist/wu-yaml.js
|
|
569
|
-
import { existsSync, readFileSync, writeFileSync, promises as fs } from "fs";
|
|
570
|
-
import { parse, stringify } from "yaml";
|
|
571
|
-
var YAML_SCALAR_TYPES = Object.freeze({
|
|
572
|
-
/** Unquoted string (when safe) */
|
|
573
|
-
PLAIN: "PLAIN",
|
|
574
|
-
/** Single-quoted string */
|
|
575
|
-
QUOTE_SINGLE: "QUOTE_SINGLE",
|
|
576
|
-
/** Double-quoted string */
|
|
577
|
-
QUOTE_DOUBLE: "QUOTE_DOUBLE",
|
|
578
|
-
/** Block literal (|) */
|
|
579
|
-
BLOCK_LITERAL: "BLOCK_LITERAL",
|
|
580
|
-
/** Block folded (>) */
|
|
581
|
-
BLOCK_FOLDED: "BLOCK_FOLDED"
|
|
582
|
-
});
|
|
583
|
-
var YAML_LINE_WIDTH = 100;
|
|
584
|
-
var YAML_STRINGIFY_OPTIONS = Object.freeze({
|
|
585
|
-
lineWidth: YAML_LINE_WIDTH,
|
|
586
|
-
singleQuote: true,
|
|
587
|
-
defaultKeyType: YAML_SCALAR_TYPES.PLAIN
|
|
588
|
-
});
|
|
589
|
-
function parseWUYaml(text, sourcePath) {
|
|
590
|
-
try {
|
|
591
|
-
return parse(text);
|
|
592
|
-
} catch (e) {
|
|
593
|
-
const msg = getErrorMessage(e);
|
|
594
|
-
throw createError(ErrorCodes.YAML_PARSE_ERROR, `Failed to parse YAML ${sourcePath}: ${msg}`, {
|
|
595
|
-
path: sourcePath,
|
|
596
|
-
originalError: msg
|
|
597
|
-
});
|
|
598
|
-
}
|
|
599
|
-
}
|
|
600
|
-
function validateWUId(doc, expectedId, wuPath) {
|
|
601
|
-
const parsed = doc;
|
|
602
|
-
if (!parsed || parsed.id !== expectedId) {
|
|
603
|
-
throw createError(ErrorCodes.WU_NOT_FOUND, `WU YAML id mismatch. Expected ${expectedId}, found ${parsed && parsed.id}`, { path: wuPath, expectedId, foundId: parsed && parsed.id });
|
|
604
|
-
}
|
|
605
|
-
return parsed;
|
|
606
|
-
}
|
|
607
|
-
function readWU(wuPath, expectedId) {
|
|
608
|
-
if (!existsSync(wuPath)) {
|
|
609
|
-
throw createError(ErrorCodes.FILE_NOT_FOUND, `WU file not found: ${wuPath}`, {
|
|
610
|
-
path: wuPath,
|
|
611
|
-
expectedId
|
|
612
|
-
});
|
|
613
|
-
}
|
|
614
|
-
const text = readFileSync(wuPath, { encoding: "utf-8" });
|
|
615
|
-
const doc = parseWUYaml(text, wuPath);
|
|
616
|
-
return validateWUId(doc, expectedId, wuPath);
|
|
617
|
-
}
|
|
618
|
-
async function readWUAsync(wuPath, expectedId) {
|
|
619
|
-
try {
|
|
620
|
-
const text = await fs.readFile(wuPath, { encoding: "utf-8" });
|
|
621
|
-
const doc = parseWUYaml(text, wuPath);
|
|
622
|
-
return validateWUId(doc, expectedId, wuPath);
|
|
623
|
-
} catch (err) {
|
|
624
|
-
const errObj = err;
|
|
625
|
-
if (errObj.code === "ENOENT") {
|
|
626
|
-
throw createError(ErrorCodes.FILE_NOT_FOUND, `WU file not found: ${wuPath}`, {
|
|
627
|
-
path: wuPath,
|
|
628
|
-
expectedId
|
|
629
|
-
});
|
|
630
|
-
}
|
|
631
|
-
throw err;
|
|
632
|
-
}
|
|
633
|
-
}
|
|
634
|
-
function parseYAML(text) {
|
|
635
|
-
return parse(text);
|
|
636
|
-
}
|
|
637
|
-
function stringifyYAML(doc, options = {}) {
|
|
638
|
-
return stringify(doc, { ...YAML_STRINGIFY_OPTIONS, ...options });
|
|
639
|
-
}
|
|
640
|
-
function readWURaw(yamlPath) {
|
|
641
|
-
if (!existsSync(yamlPath)) {
|
|
642
|
-
throw createError(ErrorCodes.FILE_NOT_FOUND, `YAML file not found: ${yamlPath}`, {
|
|
643
|
-
path: yamlPath
|
|
644
|
-
});
|
|
645
|
-
}
|
|
646
|
-
const text = readFileSync(yamlPath, { encoding: "utf-8" });
|
|
647
|
-
return parseWUYaml(text, yamlPath);
|
|
648
|
-
}
|
|
649
|
-
function writeWU(wuPath, doc) {
|
|
650
|
-
BaseWUSchema.parse(doc);
|
|
651
|
-
const out = stringify(doc, YAML_STRINGIFY_OPTIONS);
|
|
652
|
-
writeFileSync(wuPath, out, { encoding: "utf-8" });
|
|
653
|
-
}
|
|
654
|
-
function appendNote(doc, note) {
|
|
655
|
-
if (!note)
|
|
656
|
-
return;
|
|
657
|
-
const existing = doc.notes;
|
|
658
|
-
if (existing === void 0 || existing === null || existing === "") {
|
|
659
|
-
doc.notes = note;
|
|
660
|
-
} else if (Array.isArray(existing)) {
|
|
661
|
-
const joined = existing.filter(Boolean).join(STRING_LITERALS.NEWLINE).trimEnd();
|
|
662
|
-
doc.notes = joined ? `${joined}${STRING_LITERALS.NEWLINE}${note}` : note;
|
|
663
|
-
} else if (typeof existing === "string") {
|
|
664
|
-
const trimmed = existing.trimEnd();
|
|
665
|
-
doc.notes = trimmed ? `${trimmed}${STRING_LITERALS.NEWLINE}${note}` : note;
|
|
666
|
-
} else {
|
|
667
|
-
doc.notes = note;
|
|
668
|
-
}
|
|
669
|
-
}
|
|
670
|
-
|
|
671
|
-
export {
|
|
672
|
-
todayISO,
|
|
673
|
-
normalizeToDateString,
|
|
674
|
-
PLACEHOLDER_SENTINEL,
|
|
675
|
-
BaseWUSchema,
|
|
676
|
-
validateWU,
|
|
677
|
-
validateReadyWU,
|
|
678
|
-
validateDoneWU,
|
|
679
|
-
validateApprovalGates,
|
|
680
|
-
generateAutoApproval,
|
|
681
|
-
validateAndNormalizeWUYAML,
|
|
682
|
-
validateWUCompleteness,
|
|
683
|
-
readWU,
|
|
684
|
-
readWUAsync,
|
|
685
|
-
parseYAML,
|
|
686
|
-
stringifyYAML,
|
|
687
|
-
readWURaw,
|
|
688
|
-
writeWU,
|
|
689
|
-
appendNote
|
|
690
|
-
};
|