@design-ai/cli 4.55.0 → 4.56.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/CHANGELOG.md +39 -0
  3. package/README.ko.md +7 -7
  4. package/README.md +9 -9
  5. package/cli/lib/mcp-server.mjs +208 -10
  6. package/cli/lib/site-analysis.mjs +297 -0
  7. package/cli/lib/site-args.mjs +433 -0
  8. package/cli/lib/site-bundle-build.mjs +127 -0
  9. package/cli/lib/site-bundle-check.mjs +454 -0
  10. package/cli/lib/site-bundle-commands.mjs +95 -0
  11. package/cli/lib/site-bundle-compare.mjs +157 -0
  12. package/cli/lib/site-bundle-contract.mjs +79 -0
  13. package/cli/lib/site-bundle-files.mjs +87 -0
  14. package/cli/lib/site-bundle-handoff-runbook-actions.mjs +113 -0
  15. package/cli/lib/site-bundle-handoff-runbook-evidence-fields.mjs +164 -0
  16. package/cli/lib/site-bundle-handoff-runbook-evidence.mjs +334 -0
  17. package/cli/lib/site-bundle-handoff-runbook-format.mjs +31 -0
  18. package/cli/lib/site-bundle-handoff-runbook-stage-metadata.mjs +84 -0
  19. package/cli/lib/site-bundle-handoff-runbook.mjs +1331 -0
  20. package/cli/lib/site-bundle-handoff-summary.mjs +183 -0
  21. package/cli/lib/site-bundle-handoff.mjs +271 -0
  22. package/cli/lib/site-bundle-readme.mjs +98 -0
  23. package/cli/lib/site-bundle-repair-report.mjs +143 -0
  24. package/cli/lib/site-bundle-repair.mjs +68 -0
  25. package/cli/lib/site-content.mjs +399 -0
  26. package/cli/lib/site-evidence.mjs +35 -0
  27. package/cli/lib/site-mcp-commands.mjs +28 -0
  28. package/cli/lib/site-mcp-probes.mjs +159 -0
  29. package/cli/lib/site-mcp-readiness.mjs +157 -0
  30. package/cli/lib/site-mcp-report.mjs +324 -0
  31. package/cli/lib/site-next-actions.mjs +333 -0
  32. package/cli/lib/site-options.mjs +104 -0
  33. package/cli/lib/site-prompts.mjs +332 -0
  34. package/cli/lib/site-starter.mjs +153 -0
  35. package/cli/lib/site-strings.mjs +23 -0
  36. package/cli/lib/site-tasks.mjs +93 -0
  37. package/cli/lib/site-workflow-graph.mjs +309 -0
  38. package/cli/lib/site-workspace.mjs +492 -0
  39. package/cli/lib/site.mjs +108 -6617
  40. package/docs/DISTRIBUTION.ko.md +35 -6
  41. package/docs/DISTRIBUTION.md +35 -8
  42. package/docs/RELEASE-CHECKLIST.md +20 -3
  43. package/docs/ROADMAP.md +2179 -0
  44. package/docs/external-status.md +22 -7
  45. package/docs/integrations/design-ai-mcp-server.md +32 -0
  46. package/docs/integrations/vscode-walkthrough.ko.md +3 -3
  47. package/docs/integrations/vscode-walkthrough.md +3 -3
  48. package/docs/site-overrides/main.html +1 -1
  49. package/package.json +1 -1
  50. package/tools/audit/package-smoke.py +106 -0
  51. package/tools/audit/registry-smoke.py +378 -10
  52. package/tools/audit/smoke_assertions.py +83 -22
@@ -0,0 +1,334 @@
1
+ // Evidence capture helpers for Website Improvement bundle handoff runbooks.
2
+
3
+ export function getEvidenceCaptureFieldValueShape(field) {
4
+ return {
5
+ textarea: "long-text",
6
+ text: "short-text",
7
+ "file-path": "file-path",
8
+ list: "string-list",
9
+ }[field.inputType] || "text";
10
+ }
11
+
12
+ export function getEvidenceCaptureFieldEmptyValue(field) {
13
+ return field.inputType === "list" ? [] : "";
14
+ }
15
+
16
+ export function getEvidenceCaptureFieldRequirementLabel(field) {
17
+ return field.required ? "Required" : "Optional";
18
+ }
19
+
20
+ export function getEvidenceCaptureFieldAriaLabel(field) {
21
+ return `${field.label} evidence (${field.required ? "required" : "optional"})`;
22
+ }
23
+
24
+ export function getEvidenceCaptureFieldHelpText(field) {
25
+ return field.validationHint || field.placeholder || "";
26
+ }
27
+
28
+ export function getEvidenceCaptureFieldSectionKey(field) {
29
+ return {
30
+ strictBundleCheckOutput: "source-bundle-verification",
31
+ bundleDigest: "source-bundle-verification",
32
+ handoffJsonSnapshot: "handoff-snapshot",
33
+ promptOutputFile: "handoff-prompt-output",
34
+ selectedTaskId: "handoff-prompt-output",
35
+ targetRepoChangedFiles: "target-repo-changes",
36
+ targetRepoVerificationResults: "target-repo-verification",
37
+ viewportAccessibilityNotes: "viewport-accessibility-qa",
38
+ finalEvidenceRecord: "final-handoff-evidence",
39
+ remainingRisks: "risk-record",
40
+ }[field.key] || "general-evidence";
41
+ }
42
+
43
+ export function getEvidenceCaptureFieldSectionLabel(field) {
44
+ return {
45
+ "source-bundle-verification": "Source bundle verification",
46
+ "handoff-snapshot": "Handoff snapshot",
47
+ "handoff-prompt-output": "Handoff prompt output",
48
+ "target-repo-changes": "Target repo changes",
49
+ "target-repo-verification": "Target repo verification",
50
+ "viewport-accessibility-qa": "Viewport and accessibility QA",
51
+ "final-handoff-evidence": "Final handoff evidence",
52
+ "risk-record": "Risk record",
53
+ "general-evidence": "General evidence",
54
+ }[getEvidenceCaptureFieldSectionKey(field)];
55
+ }
56
+
57
+ export function getEvidenceCaptureFieldPayloadNamespace(field) {
58
+ return {
59
+ strictBundleCheckOutput: "sourceBundle",
60
+ bundleDigest: "sourceBundle",
61
+ handoffJsonSnapshot: "handoffSnapshot",
62
+ promptOutputFile: "handoffPrompt",
63
+ selectedTaskId: "handoffPrompt",
64
+ targetRepoChangedFiles: "targetRepo",
65
+ targetRepoVerificationResults: "targetRepo",
66
+ viewportAccessibilityNotes: "targetRepo",
67
+ finalEvidenceRecord: "handoffEvidence",
68
+ remainingRisks: "handoffEvidence",
69
+ }[field.key] || "evidence";
70
+ }
71
+
72
+ export function getEvidenceCaptureFieldPayloadPath(field) {
73
+ return {
74
+ strictBundleCheckOutput: "sourceBundle.verification.strictBundleCheckOutput",
75
+ bundleDigest: "sourceBundle.verification.bundleDigest",
76
+ handoffJsonSnapshot: "handoffSnapshot.strictJson",
77
+ promptOutputFile: "handoffPrompt.outputFile",
78
+ selectedTaskId: "handoffPrompt.selectedTaskId",
79
+ targetRepoChangedFiles: "targetRepo.changedFiles",
80
+ targetRepoVerificationResults: "targetRepo.verificationResults",
81
+ viewportAccessibilityNotes: "targetRepo.viewportAccessibilityNotes",
82
+ finalEvidenceRecord: "handoffEvidence.finalRecord",
83
+ remainingRisks: "handoffEvidence.remainingRisks",
84
+ }[field.key] || `evidence.${field.key || "unknown"}`;
85
+ }
86
+
87
+ export function uniqueValues(values) {
88
+ return Array.from(new Set(values));
89
+ }
90
+
91
+ export function cloneEvidenceCaptureValue(value) {
92
+ return Array.isArray(value) ? [...value] : value;
93
+ }
94
+
95
+ function setPayloadTemplateValue(target, payloadPath, value) {
96
+ const pathParts = String(payloadPath || "").split(".").filter(Boolean);
97
+ if (pathParts.length === 0) {
98
+ return target;
99
+ }
100
+ let cursor = target;
101
+ pathParts.slice(0, -1).forEach((part) => {
102
+ if (!cursor[part] || typeof cursor[part] !== "object" || Array.isArray(cursor[part])) {
103
+ cursor[part] = {};
104
+ }
105
+ cursor = cursor[part];
106
+ });
107
+ cursor[pathParts[pathParts.length - 1]] = cloneEvidenceCaptureValue(value);
108
+ return target;
109
+ }
110
+
111
+ export function buildEvidenceCapturePayloadTemplate(fields) {
112
+ return fields.reduce(
113
+ (template, field) => setPayloadTemplateValue(template, field.payloadPath, field.emptyValue),
114
+ {},
115
+ );
116
+ }
117
+
118
+ export function buildEvidenceCapturePayloadFlatTemplate(fields) {
119
+ return Object.fromEntries(
120
+ fields.map((field) => [field.payloadPath, cloneEvidenceCaptureValue(field.emptyValue)]),
121
+ );
122
+ }
123
+
124
+ export function buildEvidenceCapturePayloadBindings(fields) {
125
+ return fields.map((field) => ({
126
+ key: field.key,
127
+ label: field.label,
128
+ payloadNamespace: field.payloadNamespace,
129
+ payloadPath: field.payloadPath,
130
+ inputType: field.inputType,
131
+ valueShape: field.valueShape,
132
+ acceptsMultiple: field.acceptsMultiple,
133
+ required: field.required,
134
+ requirementLabel: field.requirementLabel,
135
+ emptyValue: cloneEvidenceCaptureValue(field.emptyValue),
136
+ validationRule: field.validationRule,
137
+ minLength: field.minLength,
138
+ sectionKey: field.sectionKey,
139
+ sectionLabel: field.sectionLabel,
140
+ ariaLabel: field.ariaLabel,
141
+ }));
142
+ }
143
+
144
+ function getEvidenceCaptureValidationFailureMessage(field) {
145
+ return field.required
146
+ ? `Provide ${field.label.toLowerCase()} before marking this action complete.`
147
+ : `Optional: provide ${field.label.toLowerCase()} when available.`;
148
+ }
149
+
150
+ export function buildEvidenceCaptureValidationSpecs(fields) {
151
+ return fields.map((field) => ({
152
+ key: field.key,
153
+ label: field.label,
154
+ rule: field.validationRule,
155
+ severity: field.required ? "error" : "info",
156
+ required: field.required,
157
+ allowsEmpty: !field.required,
158
+ minLength: field.minLength,
159
+ valueShape: field.valueShape,
160
+ acceptsMultiple: field.acceptsMultiple,
161
+ emptyValue: cloneEvidenceCaptureValue(field.emptyValue),
162
+ message: field.validationHint,
163
+ failureMessage: getEvidenceCaptureValidationFailureMessage(field),
164
+ }));
165
+ }
166
+
167
+ function getEvidenceCaptureInitialValidationDisplay(status) {
168
+ return {
169
+ "missing-required": {
170
+ statusLabel: "Missing required",
171
+ statusTone: "danger",
172
+ iconName: "alert-circle",
173
+ actionLabel: "Provide evidence",
174
+ helperText: "Required before completion",
175
+ },
176
+ "optional-empty": {
177
+ statusLabel: "Optional empty",
178
+ statusTone: "info",
179
+ iconName: "info",
180
+ actionLabel: "Add optional evidence",
181
+ helperText: "Can remain empty",
182
+ },
183
+ }[status] || {
184
+ statusLabel: "Unknown",
185
+ statusTone: "neutral",
186
+ iconName: "circle",
187
+ actionLabel: "Review",
188
+ helperText: "Review this field",
189
+ };
190
+ }
191
+
192
+ export function buildEvidenceCaptureInitialValidationStates(fields) {
193
+ return fields.map((field) => {
194
+ const status = field.required ? "missing-required" : "optional-empty";
195
+ const display = getEvidenceCaptureInitialValidationDisplay(status);
196
+ return {
197
+ key: field.key,
198
+ label: field.label,
199
+ rule: field.validationRule,
200
+ status,
201
+ statusLabel: display.statusLabel,
202
+ statusTone: display.statusTone,
203
+ iconName: display.iconName,
204
+ actionLabel: display.actionLabel,
205
+ helperText: display.helperText,
206
+ valid: !field.required,
207
+ blocking: field.required,
208
+ severity: field.required ? "error" : "info",
209
+ required: field.required,
210
+ allowsEmpty: !field.required,
211
+ touched: false,
212
+ dirty: false,
213
+ valuePresent: false,
214
+ valueLength: 0,
215
+ minLength: field.minLength,
216
+ valueShape: field.valueShape,
217
+ acceptsMultiple: field.acceptsMultiple,
218
+ emptyValue: cloneEvidenceCaptureValue(field.emptyValue),
219
+ payloadPath: field.payloadPath,
220
+ message: field.required
221
+ ? getEvidenceCaptureValidationFailureMessage(field)
222
+ : field.validationHint,
223
+ };
224
+ });
225
+ }
226
+
227
+ export function buildEvidenceCaptureInitialValidationDisplayMetadata(fields) {
228
+ return buildEvidenceCaptureInitialValidationStates(fields).map((state) => ({
229
+ key: state.key,
230
+ label: state.label,
231
+ status: state.status,
232
+ statusLabel: state.statusLabel,
233
+ statusTone: state.statusTone,
234
+ iconName: state.iconName,
235
+ actionLabel: state.actionLabel,
236
+ helperText: state.helperText,
237
+ blocking: state.blocking,
238
+ required: state.required,
239
+ message: state.message,
240
+ }));
241
+ }
242
+
243
+ export function buildEvidenceCaptureInitialValidationChecklist(fields) {
244
+ return buildEvidenceCaptureInitialValidationStates(fields).map((state) => ({
245
+ key: state.key,
246
+ label: state.label,
247
+ status: state.status,
248
+ statusLabel: state.statusLabel,
249
+ statusTone: state.statusTone,
250
+ iconName: state.iconName,
251
+ actionLabel: state.actionLabel,
252
+ helperText: state.helperText,
253
+ required: state.required,
254
+ blocking: state.blocking,
255
+ completionBlocking: state.blocking,
256
+ checkedInitially: state.valid,
257
+ disabled: false,
258
+ message: state.message,
259
+ payloadPath: state.payloadPath,
260
+ }));
261
+ }
262
+
263
+ export function buildEvidenceCaptureInitialValidationChecklistSummary(fields) {
264
+ const checklist = buildEvidenceCaptureInitialValidationChecklist(fields);
265
+ const checkedItems = checklist.filter((item) => item.checkedInitially);
266
+ const uncheckedItems = checklist.filter((item) => !item.checkedInitially);
267
+ const blockingItems = checklist.filter((item) => item.completionBlocking);
268
+ const blockingUncheckedItems = checklist.filter((item) => item.completionBlocking && !item.checkedInitially);
269
+ const firstUncheckedItem = uncheckedItems[0];
270
+ const status = blockingUncheckedItems.length > 0 ? "blocked" : "ready";
271
+ const completionPercent = checklist.length > 0
272
+ ? Math.round((checkedItems.length / checklist.length) * 100)
273
+ : 100;
274
+ return {
275
+ status,
276
+ statusLabel: status === "blocked" ? "Checklist blocked" : "Checklist ready",
277
+ statusTone: status === "blocked" ? "danger" : "success",
278
+ iconName: status === "blocked" ? "list-x" : "list-checks",
279
+ actionLabel: status === "blocked" ? "Complete required evidence" : "Continue",
280
+ helperText: status === "blocked"
281
+ ? `${blockingUncheckedItems.length} required checklist item(s) need evidence before completion.`
282
+ : "No required checklist items are unchecked on first render.",
283
+ itemCount: checklist.length,
284
+ checkedCount: checkedItems.length,
285
+ uncheckedCount: uncheckedItems.length,
286
+ requiredCount: checklist.filter((item) => item.required).length,
287
+ optionalCount: checklist.filter((item) => !item.required).length,
288
+ blockingCount: blockingItems.length,
289
+ blockingUncheckedCount: blockingUncheckedItems.length,
290
+ nonBlockingCount: checklist.filter((item) => !item.completionBlocking).length,
291
+ completionPercent,
292
+ progressLabel: `${checkedItems.length}/${checklist.length} complete`,
293
+ allCheckedInitially: uncheckedItems.length === 0,
294
+ hasUncheckedItems: uncheckedItems.length > 0,
295
+ hasBlockingUncheckedItems: blockingUncheckedItems.length > 0,
296
+ canCompleteInitially: blockingUncheckedItems.length === 0,
297
+ firstUncheckedItemKey: firstUncheckedItem?.key || "",
298
+ firstUncheckedItemLabel: firstUncheckedItem?.label || "",
299
+ firstUncheckedItemMessage: firstUncheckedItem?.message || "",
300
+ };
301
+ }
302
+
303
+ export function buildEvidenceCaptureInitialValidationSummary(fields) {
304
+ const states = buildEvidenceCaptureInitialValidationStates(fields);
305
+ const blockingStates = states.filter((state) => state.blocking);
306
+ const firstBlockingState = blockingStates[0];
307
+ const status = blockingStates.length > 0 ? "blocked" : "ready";
308
+ return {
309
+ status,
310
+ statusLabel: status === "blocked" ? "Blocked by required evidence" : "Ready for completion",
311
+ statusTone: status === "blocked" ? "danger" : "success",
312
+ iconName: status === "blocked" ? "alert-circle" : "check-circle",
313
+ actionLabel: status === "blocked" ? "Provide required evidence" : "Continue",
314
+ helperText: status === "blocked"
315
+ ? `${blockingStates.length} required evidence field(s) need input before completion.`
316
+ : "No required evidence is missing on first render.",
317
+ fieldCount: states.length,
318
+ requiredCount: states.filter((state) => state.required).length,
319
+ optionalCount: states.filter((state) => !state.required).length,
320
+ validCount: states.filter((state) => state.valid).length,
321
+ invalidCount: states.filter((state) => !state.valid).length,
322
+ blockingCount: blockingStates.length,
323
+ nonBlockingCount: states.filter((state) => !state.blocking).length,
324
+ missingRequiredCount: states.filter((state) => state.status === "missing-required").length,
325
+ optionalEmptyCount: states.filter((state) => state.status === "optional-empty").length,
326
+ dangerDisplayCount: states.filter((state) => state.statusTone === "danger").length,
327
+ infoDisplayCount: states.filter((state) => state.statusTone === "info").length,
328
+ allFieldsPristine: states.every((state) => !state.dirty && !state.touched),
329
+ canCompleteInitially: blockingStates.length === 0,
330
+ firstBlockingFieldKey: firstBlockingState?.key || "",
331
+ firstBlockingFieldLabel: firstBlockingState?.label || "",
332
+ firstBlockingMessage: firstBlockingState?.message || "",
333
+ };
334
+ }
@@ -0,0 +1,31 @@
1
+ // Human-readable formatting for Website Improvement bundle handoff runbooks.
2
+
3
+ export function formatBundleHandoffOperatorRunbookLines(operatorRunbook) {
4
+ if (!operatorRunbook || !Array.isArray(operatorRunbook.stages) || operatorRunbook.stages.length === 0) {
5
+ return ["- No operator runbook is available."];
6
+ }
7
+ if (Array.isArray(operatorRunbook.stageHumanLines) && operatorRunbook.stageHumanLines.length === operatorRunbook.stages.length) {
8
+ return operatorRunbook.stageHumanLines;
9
+ }
10
+ return operatorRunbook.stages.map((stage) => {
11
+ const checklistSummary = stage.actionEvidenceCaptureInitialValidationChecklistSummary
12
+ || operatorRunbook.stageActionEvidenceCaptureInitialValidationChecklistSummaryByKey?.[stage.key];
13
+ return formatBundleHandoffOperatorRunbookStageLine(stage, checklistSummary);
14
+ });
15
+ }
16
+
17
+ export function formatBundleHandoffOperatorRunbookStageLine(stage, checklistSummary = null) {
18
+ const commands = Array.isArray(stage.commands) ? stage.commands : [];
19
+ const outputFiles = Array.isArray(stage.outputFiles) ? stage.outputFiles : [];
20
+ const required = stage.required ? "required" : "optional";
21
+ const commandText = commands.length
22
+ ? ` command: \`${commands[0].command}\``
23
+ : " command: manual";
24
+ const outputText = outputFiles.length ? ` output: ${outputFiles.join(", ")}` : "";
25
+ const evidenceText = checklistSummary?.itemCount > 0
26
+ ? ` evidence: ${checklistSummary.progressLabel}, ${checklistSummary.statusLabel}${
27
+ checklistSummary.firstUncheckedItemLabel ? `; next: ${checklistSummary.firstUncheckedItemLabel}` : ""
28
+ }`
29
+ : "";
30
+ return `- ${stage.step}. ${stage.key} (${required}, ${stage.runPolicy || stage.kind}): ${stage.label}.${commandText}${outputText}${evidenceText}`;
31
+ }
@@ -0,0 +1,84 @@
1
+ // Stage dependency and requirement metadata for Website Improvement bundle handoff runbooks.
2
+
3
+ export function getStageLabel(stages, stageKey) {
4
+ return stages.find((stage) => stage.key === stageKey)?.label || stageKey;
5
+ }
6
+
7
+ export function getStageActionPrerequisiteKeys(stage) {
8
+ return ({
9
+ verifySourceBundle: [],
10
+ refreshHandoffSnapshot: [],
11
+ writeEffectiveTaskPrompt: ["verifySourceBundle"],
12
+ executeInTargetRepo: ["verifySourceBundle", "writeEffectiveTaskPrompt"],
13
+ recordEvidence: ["executeInTargetRepo"],
14
+ }[stage.key] || []);
15
+ }
16
+
17
+ export function getStageActionPrerequisiteLabels(stage, stages) {
18
+ return getStageActionPrerequisiteKeys(stage).map((stageKey) => getStageLabel(stages, stageKey));
19
+ }
20
+
21
+ export function getStageActionBlockedStageKeys(stage, stages) {
22
+ return stages
23
+ .filter((candidate) => getStageActionPrerequisiteKeys(candidate).includes(stage.key))
24
+ .map((candidate) => candidate.key);
25
+ }
26
+
27
+ export function getStageActionDependencyReasonCode(stage) {
28
+ return getStageActionPrerequisiteKeys(stage).length > 0 ? "requires-prerequisite-actions" : "";
29
+ }
30
+
31
+ export function getStageActionDependencyReason(stage) {
32
+ return ({
33
+ writeEffectiveTaskPrompt: "Complete Verify source bundle integrity before writing the selected task prompt.",
34
+ executeInTargetRepo: "Complete Verify source bundle integrity and Write effective task handoff prompt before implementing in the target website repo.",
35
+ recordEvidence: "Complete Execute the task in the target website repo before recording implementation evidence.",
36
+ }[stage.key] || "");
37
+ }
38
+
39
+ export function getStageActionCompletionCriteria(stage) {
40
+ return ({
41
+ verifySourceBundle: [
42
+ "Strict bundle check status is pass.",
43
+ "Checksum and generated-file drift counts are zero.",
44
+ ],
45
+ refreshHandoffSnapshot: [
46
+ "Strict handoff JSON can be regenerated without target-repo mutation.",
47
+ ],
48
+ writeEffectiveTaskPrompt: [
49
+ "Selected task handoff prompt is written to the expected local Markdown output file.",
50
+ "Output command remains local-output-file only.",
51
+ ],
52
+ executeInTargetRepo: [
53
+ "Target website repo has scoped implementation changes for the selected task.",
54
+ "Target repo lint/typecheck/build or equivalent verification has been run.",
55
+ ],
56
+ recordEvidence: [
57
+ "Changed files, verification commands, viewport checks, accessibility checks, remaining risks, and bundle digest are recorded.",
58
+ ],
59
+ }[stage.key] || []);
60
+ }
61
+
62
+ export function getStageActionEvidenceRequirements(stage) {
63
+ return ({
64
+ verifySourceBundle: [
65
+ "Strict bundle-check command output or JSON status.",
66
+ "Bundle digest and zero drift counts.",
67
+ ],
68
+ refreshHandoffSnapshot: [
69
+ "Refreshed strict handoff JSON snapshot when a wrapper consumes the latest contract.",
70
+ ],
71
+ writeEffectiveTaskPrompt: [
72
+ "Generated prompt output file path.",
73
+ "Selected task id and output filename.",
74
+ ],
75
+ executeInTargetRepo: [
76
+ "Target repo changed file list.",
77
+ "Target repo verification command results.",
78
+ "Viewport and accessibility check notes for affected pages.",
79
+ ],
80
+ recordEvidence: [
81
+ "Final evidence record includes changed files, verification, viewport/accessibility checks, risks, and bundle digest.",
82
+ ],
83
+ }[stage.key] || []);
84
+ }