@design-ai/cli 4.55.0 → 4.57.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 (301) hide show
  1. package/.claude-plugin/plugin.json +109 -25
  2. package/CHANGELOG.md +75 -0
  3. package/README.ko.md +15 -32
  4. package/README.md +18 -31
  5. package/cli/bin/design-ai.mjs +1 -0
  6. package/cli/commands/help.mjs +7 -4
  7. package/cli/commands/index.mjs +292 -0
  8. package/cli/commands/learn-help.mjs +149 -0
  9. package/cli/commands/learn-print-profile.mjs +373 -0
  10. package/cli/commands/learn-print-restore.mjs +349 -0
  11. package/cli/commands/learn-print-signals.mjs +453 -0
  12. package/cli/commands/learn.mjs +60 -1281
  13. package/cli/commands/pack.mjs +7 -3
  14. package/cli/commands/prompt.mjs +7 -3
  15. package/cli/commands/search.mjs +94 -1
  16. package/cli/lib/dispatch.mjs +3 -0
  17. package/cli/lib/embedding-index.mjs +199 -0
  18. package/cli/lib/embedding-provider.mjs +121 -0
  19. package/cli/lib/embedding-rerank.mjs +52 -0
  20. package/cli/lib/learn-args.mjs +490 -0
  21. package/cli/lib/learn-backup.mjs +748 -0
  22. package/cli/lib/learn-curation.mjs +612 -0
  23. package/cli/lib/learn-eval.mjs +459 -0
  24. package/cli/lib/learn-profile.mjs +763 -0
  25. package/cli/lib/learn-select.mjs +223 -0
  26. package/cli/lib/learn-shared.mjs +158 -0
  27. package/cli/lib/learn-test-support.mjs +218 -0
  28. package/cli/lib/learn-usage.mjs +360 -0
  29. package/cli/lib/learn.mjs +83 -3694
  30. package/cli/lib/lexical.mjs +137 -0
  31. package/cli/lib/local-config.mjs +110 -0
  32. package/cli/lib/mcp-server.mjs +221 -13
  33. package/cli/lib/pack.mjs +31 -4
  34. package/cli/lib/prompt.mjs +42 -4
  35. package/cli/lib/recall.mjs +193 -0
  36. package/cli/lib/retrieval-index.mjs +0 -0
  37. package/cli/lib/search-ranked.mjs +170 -0
  38. package/cli/lib/search.mjs +11 -1
  39. package/cli/lib/signals-backlog-commands.mjs +673 -0
  40. package/cli/lib/signals-backlog.mjs +361 -0
  41. package/cli/lib/signals-eval.mjs +176 -0
  42. package/cli/lib/signals-readiness.mjs +247 -0
  43. package/cli/lib/signals-registry.mjs +377 -0
  44. package/cli/lib/signals-render.mjs +478 -0
  45. package/cli/lib/signals-shared.mjs +75 -0
  46. package/cli/lib/signals.mjs +16 -2337
  47. package/cli/lib/site-analysis.mjs +297 -0
  48. package/cli/lib/site-args.mjs +433 -0
  49. package/cli/lib/site-bundle-build.mjs +127 -0
  50. package/cli/lib/site-bundle-check.mjs +454 -0
  51. package/cli/lib/site-bundle-commands.mjs +95 -0
  52. package/cli/lib/site-bundle-compare.mjs +157 -0
  53. package/cli/lib/site-bundle-contract.mjs +79 -0
  54. package/cli/lib/site-bundle-files.mjs +87 -0
  55. package/cli/lib/site-bundle-handoff-expected.mjs +173 -0
  56. package/cli/lib/site-bundle-handoff-runbook-action-summary.mjs +332 -0
  57. package/cli/lib/site-bundle-handoff-runbook-actions.mjs +113 -0
  58. package/cli/lib/site-bundle-handoff-runbook-evidence-fields.mjs +164 -0
  59. package/cli/lib/site-bundle-handoff-runbook-evidence.mjs +334 -0
  60. package/cli/lib/site-bundle-handoff-runbook-format.mjs +31 -0
  61. package/cli/lib/site-bundle-handoff-runbook-human-lines.mjs +167 -0
  62. package/cli/lib/site-bundle-handoff-runbook-maps.mjs +238 -0
  63. package/cli/lib/site-bundle-handoff-runbook-next-step.mjs +278 -0
  64. package/cli/lib/site-bundle-handoff-runbook-stage-metadata.mjs +84 -0
  65. package/cli/lib/site-bundle-handoff-runbook.mjs +444 -0
  66. package/cli/lib/site-bundle-handoff-summary.mjs +183 -0
  67. package/cli/lib/site-bundle-handoff.mjs +271 -0
  68. package/cli/lib/site-bundle-readme.mjs +98 -0
  69. package/cli/lib/site-bundle-repair-report.mjs +143 -0
  70. package/cli/lib/site-bundle-repair.mjs +68 -0
  71. package/cli/lib/site-content.mjs +399 -0
  72. package/cli/lib/site-evidence.mjs +35 -0
  73. package/cli/lib/site-mcp-commands.mjs +28 -0
  74. package/cli/lib/site-mcp-probes.mjs +159 -0
  75. package/cli/lib/site-mcp-readiness.mjs +157 -0
  76. package/cli/lib/site-mcp-report.mjs +324 -0
  77. package/cli/lib/site-next-actions.mjs +333 -0
  78. package/cli/lib/site-options.mjs +104 -0
  79. package/cli/lib/site-prompts.mjs +332 -0
  80. package/cli/lib/site-starter.mjs +153 -0
  81. package/cli/lib/site-strings.mjs +23 -0
  82. package/cli/lib/site-tasks.mjs +93 -0
  83. package/cli/lib/site-test-support.mjs +68 -0
  84. package/cli/lib/site-workflow-graph.mjs +309 -0
  85. package/cli/lib/site-workspace.mjs +492 -0
  86. package/cli/lib/site.mjs +108 -6617
  87. package/cli/lib/skill-proposals-apply-commands.mjs +135 -0
  88. package/cli/lib/skill-proposals-apply-contract.mjs +750 -0
  89. package/cli/lib/skill-proposals-apply-plan.mjs +118 -0
  90. package/cli/lib/skill-proposals-generate.mjs +298 -0
  91. package/cli/lib/skill-proposals-render.mjs +532 -0
  92. package/cli/lib/skill-proposals-review.mjs +262 -0
  93. package/cli/lib/skill-proposals.mjs +15 -2046
  94. package/cli/lib/workspace-args.mjs +83 -0
  95. package/cli/lib/workspace-git.mjs +169 -0
  96. package/cli/lib/workspace-learning.mjs +483 -0
  97. package/cli/lib/workspace-repo.mjs +139 -0
  98. package/cli/lib/workspace-report.mjs +283 -0
  99. package/cli/lib/workspace-test-support.mjs +99 -0
  100. package/cli/lib/workspace.mjs +30 -1056
  101. package/docs/AI-LEARNING-PHASE2.md +215 -0
  102. package/docs/DISTRIBUTION.ko.md +35 -6
  103. package/docs/DISTRIBUTION.md +35 -8
  104. package/docs/NEXT-SURFACE-DECISION.md +125 -0
  105. package/docs/PRODUCT-READINESS.md +3 -3
  106. package/docs/RELEASE-CHECKLIST.md +20 -3
  107. package/docs/RELEASE-GATES.ko.md +35 -0
  108. package/docs/RELEASE-GATES.md +234 -0
  109. package/docs/ROADMAP.md +2232 -8
  110. package/docs/external-status.md +20 -7
  111. package/docs/inspection-20260630.md +169 -0
  112. package/docs/integrations/design-ai-mcp-server.md +35 -3
  113. package/docs/integrations/vscode-walkthrough.ko.md +3 -3
  114. package/docs/integrations/vscode-walkthrough.md +3 -3
  115. package/docs/reference/ant-design.md +413 -0
  116. package/docs/reference/awesome-design-md.md +439 -0
  117. package/docs/reference/mui.md +783 -0
  118. package/docs/reference/shadcn-ui.md +298 -0
  119. package/docs/site-overrides/main.html +1 -1
  120. package/examples/component-accordion-actions.md +5 -5
  121. package/examples/component-accordion-details.md +5 -5
  122. package/examples/component-accordion-summary.md +5 -5
  123. package/examples/component-accordion.md +3 -3
  124. package/examples/component-affix.md +1 -1
  125. package/examples/component-alert-dialog.md +1 -1
  126. package/examples/component-alert-title.md +1 -1
  127. package/examples/component-alert.md +3 -3
  128. package/examples/component-anchor.md +1 -1
  129. package/examples/component-app-bar.md +1 -1
  130. package/examples/component-aspect-ratio.md +1 -1
  131. package/examples/component-auto-complete.md +2 -2
  132. package/examples/component-avatar-group.md +5 -5
  133. package/examples/component-avatar.md +3 -3
  134. package/examples/component-back-top.md +1 -1
  135. package/examples/component-backdrop.md +1 -1
  136. package/examples/component-badge.md +3 -3
  137. package/examples/component-border-beam.md +3 -3
  138. package/examples/component-bottom-navigation.md +1 -1
  139. package/examples/component-box.md +1 -1
  140. package/examples/component-breadcrumb.md +3 -3
  141. package/examples/component-button-base.md +3 -3
  142. package/examples/component-button-group.md +2 -2
  143. package/examples/component-button.md +3 -3
  144. package/examples/component-calendar.md +2 -2
  145. package/examples/component-card-actions.md +1 -1
  146. package/examples/component-card-content.md +1 -1
  147. package/examples/component-card-header.md +1 -1
  148. package/examples/component-card-media.md +1 -1
  149. package/examples/component-card.md +3 -3
  150. package/examples/component-carousel.md +2 -2
  151. package/examples/component-cascader.md +1 -1
  152. package/examples/component-chart.md +1 -1
  153. package/examples/component-checkbox.md +3 -3
  154. package/examples/component-click-away-listener.md +1 -1
  155. package/examples/component-code.md +1 -1
  156. package/examples/component-collapsible.md +1 -1
  157. package/examples/component-color-picker.md +1 -1
  158. package/examples/component-combobox.md +1 -1
  159. package/examples/component-command.md +1 -1
  160. package/examples/component-config-provider.md +3 -3
  161. package/examples/component-context-menu.md +1 -1
  162. package/examples/component-css-baseline.md +3 -3
  163. package/examples/component-date-picker.md +2 -2
  164. package/examples/component-descriptions.md +1 -1
  165. package/examples/component-dialog-actions.md +1 -1
  166. package/examples/component-dialog-content-text.md +1 -1
  167. package/examples/component-dialog-content.md +1 -1
  168. package/examples/component-dialog-title.md +1 -1
  169. package/examples/component-dialog.md +2 -2
  170. package/examples/component-divider.md +3 -3
  171. package/examples/component-drawer.md +3 -3
  172. package/examples/component-dropdown.md +3 -3
  173. package/examples/component-empty.md +2 -2
  174. package/examples/component-fade.md +1 -1
  175. package/examples/component-field.md +1 -1
  176. package/examples/component-filled-input.md +1 -1
  177. package/examples/component-flex.md +1 -1
  178. package/examples/component-float-button.md +2 -2
  179. package/examples/component-form-control-label.md +1 -1
  180. package/examples/component-form-control.md +1 -1
  181. package/examples/component-form-controls.md +1 -1
  182. package/examples/component-form-group.md +1 -1
  183. package/examples/component-form-helper-text.md +1 -1
  184. package/examples/component-form-label.md +1 -1
  185. package/examples/component-form.md +2 -2
  186. package/examples/component-grid.md +2 -2
  187. package/examples/component-grow.md +1 -1
  188. package/examples/component-hover-card.md +1 -1
  189. package/examples/component-icon-button.md +1 -1
  190. package/examples/component-icon.md +1 -1
  191. package/examples/component-image-list.md +1 -1
  192. package/examples/component-image.md +1 -1
  193. package/examples/component-input-adornment.md +1 -1
  194. package/examples/component-input-base.md +1 -1
  195. package/examples/component-input-number.md +1 -1
  196. package/examples/component-input-otp.md +2 -2
  197. package/examples/component-input.md +3 -3
  198. package/examples/component-item.md +1 -1
  199. package/examples/component-label.md +1 -1
  200. package/examples/component-layout.md +1 -1
  201. package/examples/component-link.md +1 -1
  202. package/examples/component-list-item-avatar.md +1 -1
  203. package/examples/component-list-item-button.md +1 -1
  204. package/examples/component-list-item-icon.md +1 -1
  205. package/examples/component-list-item-text.md +1 -1
  206. package/examples/component-list-item.md +1 -1
  207. package/examples/component-list-subheader.md +1 -1
  208. package/examples/component-list.md +2 -2
  209. package/examples/component-masonry.md +1 -1
  210. package/examples/component-mentions.md +1 -1
  211. package/examples/component-menu-item.md +1 -1
  212. package/examples/component-menu-list.md +1 -1
  213. package/examples/component-menu.md +1 -1
  214. package/examples/component-menubar.md +1 -1
  215. package/examples/component-message.md +1 -1
  216. package/examples/component-mobile-stepper.md +1 -1
  217. package/examples/component-modal.md +5 -5
  218. package/examples/component-navigation-menu.md +1 -1
  219. package/examples/component-notification.md +1 -1
  220. package/examples/component-outlined-input.md +1 -1
  221. package/examples/component-pagination.md +3 -3
  222. package/examples/component-paper.md +1 -1
  223. package/examples/component-popconfirm.md +1 -1
  224. package/examples/component-popover.md +3 -3
  225. package/examples/component-popper.md +1 -1
  226. package/examples/component-progress.md +2 -2
  227. package/examples/component-qr-code.md +1 -1
  228. package/examples/component-radio.md +3 -3
  229. package/examples/component-rate.md +2 -2
  230. package/examples/component-resizable.md +1 -1
  231. package/examples/component-result.md +1 -1
  232. package/examples/component-scroll-area.md +1 -1
  233. package/examples/component-segmented.md +3 -3
  234. package/examples/component-select.md +3 -3
  235. package/examples/component-separator.md +1 -1
  236. package/examples/component-sheet.md +1 -1
  237. package/examples/component-sidebar.md +1 -1
  238. package/examples/component-skeleton.md +3 -3
  239. package/examples/component-slide.md +1 -1
  240. package/examples/component-slider.md +3 -3
  241. package/examples/component-snackbar-content.md +1 -1
  242. package/examples/component-snackbar.md +1 -1
  243. package/examples/component-sonner.md +1 -1
  244. package/examples/component-space.md +1 -1
  245. package/examples/component-speed-dial-action.md +1 -1
  246. package/examples/component-speed-dial.md +1 -1
  247. package/examples/component-spin.md +2 -2
  248. package/examples/component-spinner.md +1 -1
  249. package/examples/component-splitter.md +2 -2
  250. package/examples/component-stack.md +1 -1
  251. package/examples/component-statistic.md +1 -1
  252. package/examples/component-step-button.md +4 -4
  253. package/examples/component-step-connector.md +4 -4
  254. package/examples/component-step-content.md +1 -1
  255. package/examples/component-step-icon.md +1 -1
  256. package/examples/component-step-label.md +1 -1
  257. package/examples/component-step.md +2 -2
  258. package/examples/component-steps.md +2 -2
  259. package/examples/component-swipeable-drawer.md +1 -1
  260. package/examples/component-switch.md +3 -3
  261. package/examples/component-tab-scroll-button.md +5 -5
  262. package/examples/component-tab.md +1 -1
  263. package/examples/component-table-body.md +1 -1
  264. package/examples/component-table-cell.md +1 -1
  265. package/examples/component-table-container.md +1 -1
  266. package/examples/component-table-footer.md +1 -1
  267. package/examples/component-table-head.md +1 -1
  268. package/examples/component-table-pagination.md +1 -1
  269. package/examples/component-table-row.md +1 -1
  270. package/examples/component-table-sort-label.md +1 -1
  271. package/examples/component-table.md +2 -2
  272. package/examples/component-tabs.md +3 -3
  273. package/examples/component-tag-badge.md +2 -2
  274. package/examples/component-tag.md +1 -1
  275. package/examples/component-textarea-autosize.md +1 -1
  276. package/examples/component-textarea.md +1 -1
  277. package/examples/component-time-picker.md +1 -1
  278. package/examples/component-timeline.md +1 -1
  279. package/examples/component-toast.md +4 -4
  280. package/examples/component-toggle-button.md +1 -1
  281. package/examples/component-toggle.md +1 -1
  282. package/examples/component-toolbar.md +1 -1
  283. package/examples/component-tooltip.md +3 -3
  284. package/examples/component-tour.md +1 -1
  285. package/examples/component-transfer.md +1 -1
  286. package/examples/component-tree-select.md +1 -1
  287. package/examples/component-tree.md +1 -1
  288. package/examples/component-typography.md +2 -2
  289. package/examples/component-upload.md +1 -1
  290. package/examples/component-watermark.md +1 -1
  291. package/examples/component-zoom.md +1 -1
  292. package/knowledge/COVERAGE.md +4 -3
  293. package/knowledge/components/INDEX.md +202 -202
  294. package/knowledge/patterns/brand-references.md +72 -72
  295. package/package.json +1 -1
  296. package/tools/audit/local-ci.py +16 -1
  297. package/tools/audit/package-smoke.py +717 -9
  298. package/tools/audit/registry-smoke.py +912 -10
  299. package/tools/audit/release-metadata.py +47 -1
  300. package/tools/audit/smoke_assertions.py +998 -35
  301. package/tools/migrations/refs-links-to-reference-pages.py +179 -0
@@ -0,0 +1,157 @@
1
+ import { SITE_BUNDLE_CHECKSUM_FILES } from "./site-content.mjs";
2
+ import { IMPLEMENTATION_EVIDENCE_KEYS } from "./site-evidence.mjs";
3
+ import {
4
+ addIssue,
5
+ statusFromIssues,
6
+ } from "./site-analysis.mjs";
7
+ import { buildSiteBundleCheckReport } from "./site-bundle-check.mjs";
8
+
9
+ export function summarizeBundleForCompare(report) {
10
+ return {
11
+ directory: report.directory,
12
+ status: report.status,
13
+ valid: report.valid,
14
+ siteName: report.summary.siteName || "",
15
+ source: report.summary.source || "",
16
+ workspaceStatus: report.workspaceStatus || "unknown",
17
+ mcpStatus: report.mcpStatus || "unknown",
18
+ mcpProbeStatus: report.mcpProbeStatus || "unknown",
19
+ mcpProbeCounts: { ...report.mcpProbeCounts },
20
+ totalTasks: report.summary.totalTasks || 0,
21
+ implementationEvidence: { ...report.summary.implementationEvidence },
22
+ checksumAlgorithm: report.summary.checksumAlgorithm || "",
23
+ checksumBundleDigest: report.summary.checksumBundleDigest || "",
24
+ checksumFailures: report.counts.checksumFailures,
25
+ generatedFailures: report.counts.generatedFailures,
26
+ verifiedGeneratedFiles: report.counts.verifiedGeneratedFiles,
27
+ generatedDriftFiles: [...report.generatedContract.driftFiles],
28
+ issueCount: report.issues.length,
29
+ };
30
+ }
31
+
32
+ export function buildBundleMetadataChanges(left, right) {
33
+ const pairs = [
34
+ ["siteName", "Site name", left.summary.siteName || "", right.summary.siteName || ""],
35
+ ["source", "Source", left.summary.source || "", right.summary.source || ""],
36
+ ["workspaceStatus", "Workspace status", left.workspaceStatus || "unknown", right.workspaceStatus || "unknown"],
37
+ ["mcpStatus", "MCP status", left.mcpStatus || "unknown", right.mcpStatus || "unknown"],
38
+ ["mcpProbeStatus", "MCP probe status", left.mcpProbeStatus || "unknown", right.mcpProbeStatus || "unknown"],
39
+ ...["count", "pass", "warn", "fail"].map((key) => [
40
+ `mcpProbeCounts.${key}`,
41
+ `MCP probe ${key}`,
42
+ String(left.summary.mcpProbeCounts[key] || 0),
43
+ String(right.summary.mcpProbeCounts[key] || 0),
44
+ ]),
45
+ ["totalTasks", "Task count", String(left.summary.totalTasks || 0), String(right.summary.totalTasks || 0)],
46
+ ...IMPLEMENTATION_EVIDENCE_KEYS.map((key) => [
47
+ `implementationEvidence.${key}`,
48
+ `Evidence ${key}`,
49
+ String(left.summary.implementationEvidence[key] || 0),
50
+ String(right.summary.implementationEvidence[key] || 0),
51
+ ]),
52
+ ];
53
+ return pairs
54
+ .filter(([, , leftValue, rightValue]) => leftValue !== rightValue)
55
+ .map(([key, label, leftValue, rightValue]) => ({ key, label, leftValue, rightValue }));
56
+ }
57
+
58
+ export function buildBundleFileChanges(left, right) {
59
+ return SITE_BUNDLE_CHECKSUM_FILES
60
+ .map((filePath) => ({
61
+ path: filePath,
62
+ leftChecksum: String(left.summary.checksumFiles[filePath] || ""),
63
+ rightChecksum: String(right.summary.checksumFiles[filePath] || ""),
64
+ }))
65
+ .filter((file) => file.leftChecksum !== file.rightChecksum);
66
+ }
67
+
68
+ export function buildSiteBundleCompareReport({ target, compareTarget }) {
69
+ const left = buildSiteBundleCheckReport({ target });
70
+ const right = buildSiteBundleCheckReport({ target: compareTarget });
71
+ const issues = [];
72
+
73
+ if (left.status === "fail") {
74
+ addIssue(issues, "fail", "bundle-compare-left-invalid", "Primary bundle must pass bundle-check before comparison can be trusted");
75
+ } else if (left.status === "warn") {
76
+ addIssue(issues, "warn", "bundle-compare-left-warn", "Primary bundle has bundle-check warnings; review them before target-repo handoff");
77
+ }
78
+ if (right.status === "fail") {
79
+ addIssue(issues, "fail", "bundle-compare-right-invalid", "Comparison bundle must pass bundle-check before comparison can be trusted");
80
+ } else if (right.status === "warn") {
81
+ addIssue(issues, "warn", "bundle-compare-right-warn", "Comparison bundle has bundle-check warnings; review them before target-repo handoff");
82
+ }
83
+
84
+ const leftDigest = left.summary.checksumBundleDigest || "";
85
+ const rightDigest = right.summary.checksumBundleDigest || "";
86
+ const digestMatch = Boolean(leftDigest && rightDigest && leftDigest === rightDigest);
87
+ const changedFiles = buildBundleFileChanges(left, right);
88
+ const metadataChanges = buildBundleMetadataChanges(left, right);
89
+ const hasDifferences = !digestMatch || changedFiles.length > 0 || metadataChanges.length > 0;
90
+ const hasFailures = issues.some((issue) => issue.level === "fail");
91
+
92
+ if (issues.length === 0 && !hasDifferences) {
93
+ addIssue(issues, "pass", "bundle-compare-identical", "Handoff bundles have the same bundle digest and checksum manifest");
94
+ } else if (!hasFailures && hasDifferences) {
95
+ addIssue(issues, "warn", "bundle-compare-different", "Handoff bundles differ; review changed files before target-repo handoff");
96
+ }
97
+
98
+ const status = statusFromIssues(issues);
99
+ return {
100
+ status,
101
+ valid: left.valid && right.valid,
102
+ sameBundle: !hasDifferences,
103
+ digestMatch,
104
+ left: summarizeBundleForCompare(left),
105
+ right: summarizeBundleForCompare(right),
106
+ counts: {
107
+ changedFiles: changedFiles.length,
108
+ metadataChanges: metadataChanges.length,
109
+ leftIssues: left.issues.length,
110
+ rightIssues: right.issues.length,
111
+ issues: issues.length,
112
+ warnings: issues.filter((issue) => issue.level === "warn").length,
113
+ failures: issues.filter((issue) => issue.level === "fail").length,
114
+ },
115
+ changedFiles,
116
+ metadataChanges,
117
+ issues,
118
+ };
119
+ }
120
+
121
+ export function formatSiteBundleCompareJson(report) {
122
+ return JSON.stringify(report, null, 2);
123
+ }
124
+
125
+ function formatMcpProbeCounts(counts = {}) {
126
+ return `${counts.pass || 0}/${counts.count || 0} passing, ${counts.warn || 0} warning, ${counts.fail || 0} failing`;
127
+ }
128
+
129
+ export function formatSiteBundleCompareHuman(report) {
130
+ return [
131
+ `Website Improvement handoff bundle compare: ${report.left.directory} -> ${report.right.directory}`,
132
+ "",
133
+ `Status: ${report.status}`,
134
+ `Same bundle: ${report.sameBundle ? "yes" : "no"}`,
135
+ `Digest match: ${report.digestMatch ? "yes" : "no"}`,
136
+ `Left digest: ${report.left.checksumBundleDigest || "not recorded"}`,
137
+ `Right digest: ${report.right.checksumBundleDigest || "not recorded"}`,
138
+ `Changed files: ${report.counts.changedFiles}`,
139
+ `Metadata changes: ${report.counts.metadataChanges}`,
140
+ `Generated contract: left ${report.left.verifiedGeneratedFiles}/${SITE_BUNDLE_CHECKSUM_FILES.length}, right ${report.right.verifiedGeneratedFiles}/${SITE_BUNDLE_CHECKSUM_FILES.length}`,
141
+ `Generated drift files: left ${report.left.generatedDriftFiles.length ? report.left.generatedDriftFiles.join(", ") : "none"}, right ${report.right.generatedDriftFiles.length ? report.right.generatedDriftFiles.join(", ") : "none"}`,
142
+ `MCP probes: left ${formatMcpProbeCounts(report.left.mcpProbeCounts)}, right ${formatMcpProbeCounts(report.right.mcpProbeCounts)}`,
143
+ "",
144
+ "Changed files:",
145
+ ...(report.changedFiles.length
146
+ ? report.changedFiles.map((file) => `- ${file.path}`)
147
+ : ["- none"]),
148
+ "",
149
+ "Metadata changes:",
150
+ ...(report.metadataChanges.length
151
+ ? report.metadataChanges.map((item) => `- ${item.label}: ${item.leftValue || "not recorded"} -> ${item.rightValue || "not recorded"}`)
152
+ : ["- none"]),
153
+ "",
154
+ "Issues:",
155
+ ...report.issues.map((issue) => `- [${issue.level}] ${issue.id}: ${issue.message}`),
156
+ ].join("\n");
157
+ }
@@ -0,0 +1,79 @@
1
+ // Generated bundle contract helpers for Website Improvement handoff bundles.
2
+
3
+ import {
4
+ existsSync,
5
+ readFileSync,
6
+ statSync,
7
+ } from "node:fs";
8
+ import path from "node:path";
9
+
10
+ import { addIssue } from "./site-analysis.mjs";
11
+ import { buildSiteHandoffBundle } from "./site-bundle-build.mjs";
12
+ import { SITE_BUNDLE_CHECKSUM_FILES } from "./site-content.mjs";
13
+ import {
14
+ sha256Hex,
15
+ shortDigest,
16
+ } from "./site-bundle-files.mjs";
17
+
18
+ export function emptyBundleGeneratedContract(source = "") {
19
+ return {
20
+ available: false,
21
+ source: source || "",
22
+ expectedFiles: SITE_BUNDLE_CHECKSUM_FILES.length,
23
+ verifiedFiles: 0,
24
+ driftFiles: [],
25
+ files: [],
26
+ };
27
+ }
28
+
29
+ export function buildBundleGeneratedContract(directory, workspace, source) {
30
+ const contractSource = source || "website-workspace.tasks.json";
31
+ const expectedBundle = buildSiteHandoffBundle(workspace, { filePath: contractSource });
32
+ const expectedFiles = new Map(expectedBundle.files.map((file) => [file.path, file.content]));
33
+ const files = SITE_BUNDLE_CHECKSUM_FILES.map((filePath) => {
34
+ const expectedContent = expectedFiles.get(filePath);
35
+ const expectedDigest = typeof expectedContent === "string" ? sha256Hex(expectedContent) : "";
36
+ const targetPath = path.join(directory, filePath);
37
+ const present = existsSync(targetPath) && statSync(targetPath).isFile();
38
+ const actualDigest = present ? sha256Hex(readFileSync(targetPath, "utf8")) : "";
39
+ return {
40
+ path: filePath,
41
+ present,
42
+ matches: Boolean(present && expectedDigest && actualDigest === expectedDigest),
43
+ expectedDigest,
44
+ actualDigest,
45
+ };
46
+ });
47
+ return {
48
+ available: true,
49
+ source: contractSource,
50
+ expectedFiles: SITE_BUNDLE_CHECKSUM_FILES.length,
51
+ verifiedFiles: files.filter((file) => file.matches).length,
52
+ driftFiles: files.filter((file) => file.present && !file.matches).map((file) => file.path),
53
+ files,
54
+ };
55
+ }
56
+
57
+ export function addBundleGeneratedContractIssues(generatedContract, issues) {
58
+ if (!generatedContract.available) return;
59
+ for (const file of generatedContract.files) {
60
+ if (!file.present || file.matches) continue;
61
+ addIssue(
62
+ issues,
63
+ "fail",
64
+ `bundle-generated-${file.path}`,
65
+ `${file.path} does not match the current CLI-generated bundle contract (expected ${shortDigest(file.expectedDigest)}, actual ${shortDigest(file.actualDigest)})`,
66
+ );
67
+ }
68
+ }
69
+
70
+ export function formatGeneratedContractDriftLines(generatedContract) {
71
+ const driftFiles = generatedContract.files.filter((file) => file.present && !file.matches);
72
+ if (driftFiles.length === 0) return ["- none"];
73
+ return driftFiles.map((file) => `- ${file.path}: expected ${shortDigest(file.expectedDigest)}, actual ${shortDigest(file.actualDigest)}`);
74
+ }
75
+
76
+ export function formatGeneratedContractDriftSummary(generatedContract) {
77
+ if (!generatedContract.driftFiles.length) return "none";
78
+ return generatedContract.driftFiles.join(", ");
79
+ }
@@ -0,0 +1,87 @@
1
+ // File, digest, and validation helpers for Website Improvement handoff bundles.
2
+
3
+ import {
4
+ existsSync,
5
+ readFileSync,
6
+ statSync,
7
+ } from "node:fs";
8
+ import { createHash } from "node:crypto";
9
+ import path from "node:path";
10
+
11
+ import { SITE_BUNDLE_CHECKSUM_FILES } from "./site-content.mjs";
12
+
13
+ function addIssue(issues, level, id, message) {
14
+ issues.push({ level, id, message });
15
+ }
16
+
17
+ export function sha256Hex(content) {
18
+ return createHash("sha256").update(content, "utf8").digest("hex");
19
+ }
20
+
21
+ export function shortDigest(digest) {
22
+ return digest ? String(digest).slice(0, 12) : "missing";
23
+ }
24
+
25
+ export function buildBundleDigest(checksumFiles) {
26
+ const manifest = SITE_BUNDLE_CHECKSUM_FILES.map((filePath) => `${filePath}\t${checksumFiles[filePath] || ""}`).join("\n");
27
+ return sha256Hex(`${manifest}\n`);
28
+ }
29
+
30
+ export function buildBundleChecksums(files) {
31
+ const checksumFiles = Object.fromEntries(
32
+ files
33
+ .filter((file) => file.path !== "summary.json")
34
+ .map((file) => [file.path, sha256Hex(file.content)]),
35
+ );
36
+ return {
37
+ algorithm: "sha256",
38
+ bundleDigest: buildBundleDigest(checksumFiles),
39
+ files: checksumFiles,
40
+ };
41
+ }
42
+
43
+ export function readBundleFile(directory, relativePath, issues) {
44
+ const target = path.join(directory, relativePath);
45
+ if (!existsSync(target)) {
46
+ addIssue(issues, "fail", `bundle-missing-${relativePath}`, `Bundle file is missing: ${relativePath}`);
47
+ return null;
48
+ }
49
+ if (!statSync(target).isFile()) {
50
+ addIssue(issues, "fail", `bundle-file-${relativePath}`, `Bundle path must be a file: ${relativePath}`);
51
+ return null;
52
+ }
53
+ return readFileSync(target, "utf8");
54
+ }
55
+
56
+ export function parseBundleJson(directory, relativePath, issues) {
57
+ const text = readBundleFile(directory, relativePath, issues);
58
+ if (text === null) return null;
59
+ try {
60
+ return JSON.parse(text);
61
+ } catch (error) {
62
+ addIssue(issues, "fail", `bundle-json-${relativePath}`, `Bundle JSON is invalid in ${relativePath}: ${error.message}`);
63
+ return null;
64
+ }
65
+ }
66
+
67
+ export function arraysEqual(left, right) {
68
+ if (!Array.isArray(left) || !Array.isArray(right)) return false;
69
+ if (left.length !== right.length) return false;
70
+ return left.every((item, index) => item === right[index]);
71
+ }
72
+
73
+ export function addBundleMarkdownIssue(directory, relativePath, fragments, issues) {
74
+ const text = readBundleFile(directory, relativePath, issues);
75
+ if (text === null) return;
76
+ for (const fragment of fragments) {
77
+ if (!text.includes(fragment)) {
78
+ addIssue(issues, "fail", `bundle-markdown-${relativePath}`, `${relativePath} is missing required text: ${fragment}`);
79
+ }
80
+ }
81
+ }
82
+
83
+ export function readBundleTextIfPresent(directory, relativePath) {
84
+ const targetPath = path.join(directory, relativePath);
85
+ if (!existsSync(targetPath) || !statSync(targetPath).isFile()) return "";
86
+ return readFileSync(targetPath, "utf8").trim();
87
+ }
@@ -0,0 +1,173 @@
1
+ // Expected next-action initial-validation fixtures for bundle-handoff contract tests.
2
+
3
+ export const buildExpectedInitialValidationChecklistSummary = (checklist) => {
4
+ const checkedItems = checklist.filter((item) => item.checkedInitially);
5
+ const uncheckedItems = checklist.filter((item) => !item.checkedInitially);
6
+ const blockingItems = checklist.filter((item) => item.completionBlocking);
7
+ const blockingUncheckedItems = checklist.filter((item) => item.completionBlocking && !item.checkedInitially);
8
+ const firstUncheckedItem = uncheckedItems[0] || {};
9
+ const status = blockingUncheckedItems.length > 0 ? "blocked" : "ready";
10
+ return {
11
+ status,
12
+ statusLabel: status === "blocked" ? "Checklist blocked" : "Checklist ready",
13
+ statusTone: status === "blocked" ? "danger" : "success",
14
+ iconName: status === "blocked" ? "list-x" : "list-checks",
15
+ actionLabel: status === "blocked" ? "Complete required evidence" : "Continue",
16
+ helperText: status === "blocked"
17
+ ? `${blockingUncheckedItems.length} required checklist item(s) need evidence before completion.`
18
+ : "No required checklist items are unchecked on first render.",
19
+ itemCount: checklist.length,
20
+ checkedCount: checkedItems.length,
21
+ uncheckedCount: uncheckedItems.length,
22
+ requiredCount: checklist.filter((item) => item.required).length,
23
+ optionalCount: checklist.filter((item) => !item.required).length,
24
+ blockingCount: blockingItems.length,
25
+ blockingUncheckedCount: blockingUncheckedItems.length,
26
+ nonBlockingCount: checklist.filter((item) => !item.completionBlocking).length,
27
+ completionPercent: checklist.length > 0 ? Math.round((checkedItems.length / checklist.length) * 100) : 100,
28
+ progressLabel: `${checkedItems.length}/${checklist.length} complete`,
29
+ allCheckedInitially: uncheckedItems.length === 0,
30
+ hasUncheckedItems: uncheckedItems.length > 0,
31
+ hasBlockingUncheckedItems: blockingUncheckedItems.length > 0,
32
+ canCompleteInitially: blockingUncheckedItems.length === 0,
33
+ firstUncheckedItemKey: firstUncheckedItem.key || "",
34
+ firstUncheckedItemLabel: firstUncheckedItem.label || "",
35
+ firstUncheckedItemMessage: firstUncheckedItem.message || "",
36
+ };
37
+ };
38
+
39
+ export function buildExpectedNextInitialValidation(bundle) {
40
+ const expectedNextInitialValidationStates = [
41
+ {
42
+ key: "strictBundleCheckOutput",
43
+ label: "Strict bundle-check output",
44
+ rule: "non-empty-text",
45
+ status: "missing-required",
46
+ statusLabel: "Missing required",
47
+ statusTone: "danger",
48
+ iconName: "alert-circle",
49
+ actionLabel: "Provide evidence",
50
+ helperText: "Required before completion",
51
+ valid: false,
52
+ blocking: true,
53
+ severity: "error",
54
+ required: true,
55
+ allowsEmpty: false,
56
+ touched: false,
57
+ dirty: false,
58
+ valuePresent: false,
59
+ valueLength: 0,
60
+ minLength: 20,
61
+ valueShape: "long-text",
62
+ acceptsMultiple: false,
63
+ emptyValue: "",
64
+ payloadPath: "sourceBundle.verification.strictBundleCheckOutput",
65
+ message: "Provide strict bundle-check output before marking this action complete.",
66
+ },
67
+ {
68
+ key: "bundleDigest",
69
+ label: "Bundle digest",
70
+ rule: "checksum-or-digest-text",
71
+ status: "missing-required",
72
+ statusLabel: "Missing required",
73
+ statusTone: "danger",
74
+ iconName: "alert-circle",
75
+ actionLabel: "Provide evidence",
76
+ helperText: "Required before completion",
77
+ valid: false,
78
+ blocking: true,
79
+ severity: "error",
80
+ required: true,
81
+ allowsEmpty: false,
82
+ touched: false,
83
+ dirty: false,
84
+ valuePresent: false,
85
+ valueLength: 0,
86
+ minLength: 8,
87
+ valueShape: "short-text",
88
+ acceptsMultiple: false,
89
+ emptyValue: "",
90
+ payloadPath: "sourceBundle.verification.bundleDigest",
91
+ message: "Provide bundle digest before marking this action complete.",
92
+ },
93
+ ];
94
+ const expectedNextInitialValidationDisplayMetadata = [
95
+ {
96
+ key: "strictBundleCheckOutput",
97
+ label: "Strict bundle-check output",
98
+ status: "missing-required",
99
+ statusLabel: "Missing required",
100
+ statusTone: "danger",
101
+ iconName: "alert-circle",
102
+ actionLabel: "Provide evidence",
103
+ helperText: "Required before completion",
104
+ blocking: true,
105
+ required: true,
106
+ message: "Provide strict bundle-check output before marking this action complete.",
107
+ },
108
+ {
109
+ key: "bundleDigest",
110
+ label: "Bundle digest",
111
+ status: "missing-required",
112
+ statusLabel: "Missing required",
113
+ statusTone: "danger",
114
+ iconName: "alert-circle",
115
+ actionLabel: "Provide evidence",
116
+ helperText: "Required before completion",
117
+ blocking: true,
118
+ required: true,
119
+ message: "Provide bundle digest before marking this action complete.",
120
+ },
121
+ ];
122
+ const expectedNextInitialValidationChecklist = expectedNextInitialValidationStates.map((state) => ({
123
+ key: state.key,
124
+ label: state.label,
125
+ status: state.status,
126
+ statusLabel: state.statusLabel,
127
+ statusTone: state.statusTone,
128
+ iconName: state.iconName,
129
+ actionLabel: state.actionLabel,
130
+ helperText: state.helperText,
131
+ required: state.required,
132
+ blocking: state.blocking,
133
+ completionBlocking: state.blocking,
134
+ checkedInitially: state.valid,
135
+ disabled: false,
136
+ message: state.message,
137
+ payloadPath: state.payloadPath,
138
+ }));
139
+ const expectedNextInitialValidationChecklistSummary = buildExpectedInitialValidationChecklistSummary(
140
+ expectedNextInitialValidationChecklist,
141
+ );
142
+ const expectedNextInitialValidationSummary = {
143
+ status: "blocked",
144
+ statusLabel: "Blocked by required evidence",
145
+ statusTone: "danger",
146
+ iconName: "alert-circle",
147
+ actionLabel: "Provide required evidence",
148
+ helperText: "2 required evidence field(s) need input before completion.",
149
+ fieldCount: 2,
150
+ requiredCount: 2,
151
+ optionalCount: 0,
152
+ validCount: 0,
153
+ invalidCount: 2,
154
+ blockingCount: 2,
155
+ nonBlockingCount: 0,
156
+ missingRequiredCount: 2,
157
+ optionalEmptyCount: 0,
158
+ dangerDisplayCount: 2,
159
+ infoDisplayCount: 0,
160
+ allFieldsPristine: true,
161
+ canCompleteInitially: false,
162
+ firstBlockingFieldKey: "strictBundleCheckOutput",
163
+ firstBlockingFieldLabel: "Strict bundle-check output",
164
+ firstBlockingMessage: "Provide strict bundle-check output before marking this action complete.",
165
+ };
166
+ return {
167
+ expectedNextInitialValidationChecklist,
168
+ expectedNextInitialValidationChecklistSummary,
169
+ expectedNextInitialValidationDisplayMetadata,
170
+ expectedNextInitialValidationStates,
171
+ expectedNextInitialValidationSummary,
172
+ };
173
+ }